use crate::{BlueprintEntity, BlueprintPosition}; use factorio_core::{ aabb::AABB, beltoptions::Beltspeed, misc::PositionMap, pathfield::PathField, prelude::*, quaterdirection::QuaterDirection, visualize::{Color, Visualization}, }; use std::{collections::HashMap, sync::atomic::AtomicUsize}; mod power_connection; mod roboports; mod visualize; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum UndergroundType { Input, Output, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum InserterType { Burner, Normal, Long, Fast, Bulk, Stack, } impl InserterType { fn string(&self) -> String { match self { InserterType::Burner => "burner-inserter", InserterType::Normal => "inserter", InserterType::Long => "long-handed-inserter", InserterType::Fast => "fast-inserter", InserterType::Bulk => "bulk-inserter", InserterType::Stack => "stack-inserter", } .to_owned() } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ElectricPoleType { Small, Medium, Big, Substation, } impl ElectricPoleType { fn string(&self) -> String { match self { ElectricPoleType::Small => "small-electric-pole", ElectricPoleType::Medium => "medium-electric-pole", ElectricPoleType::Big => "big-electric-pole", ElectricPoleType::Substation => "substation", } .to_owned() } fn size(&self) -> Position { match self { ElectricPoleType::Small | ElectricPoleType::Medium => Position::new(2, 2), ElectricPoleType::Big | ElectricPoleType::Substation => Position::new(4, 4), } } fn alignment(&self) -> PositionType { match self { ElectricPoleType::Small | ElectricPoleType::Medium => 1, ElectricPoleType::Big | ElectricPoleType::Substation => 0, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Quality { Normal, Uncommon, Rare, Epic, Legendary, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum RailType { Straight, CurvedA, CurvedB, RailSignal, ChainSignal, } impl RailType { fn string(&self) -> String { match self { RailType::Straight => "straight-rail", RailType::CurvedA => "curved-rail-a", RailType::CurvedB => "curved-rail-b", RailType::RailSignal => "rail-signal", RailType::ChainSignal => "rail-chain-signal", } .to_string() } fn size(&self) -> Position { Position::new(12, 12) } } #[derive(Debug, Clone)] enum DirectionType { Dir(Direction), QuarterDir(QuaterDirection), } impl DirectionType { fn unwrap_dir(&self) -> Direction { match self { DirectionType::Dir(direction) => *direction, DirectionType::QuarterDir(_quater_direction) => panic!(), } } } #[derive(Debug, Clone)] pub struct Entity { entity: EntityType, position: Position, direction: DirectionType, quality: Quality, } #[derive(Debug, Clone)] pub enum EntityType { Belt(Beltspeed), UndergroundBelt(Beltspeed, UndergroundType), Splitter(Beltspeed), ElectricPole(ElectricPoleType), Inserter { inserter_type: InserterType, override_stack_size: Option, }, Production { name: String, recipe: String, size: Position, }, Rail { rail_type: RailType, }, Roboport, Unknown { name: String, size: Position, misc: HashMap, }, } impl Entity { pub fn new(entity: EntityType, position: Position, direction: Direction) -> Self { Self { entity, position, direction: DirectionType::Dir(direction), quality: Quality::Normal, } } pub fn new_quarter_direction( entity: EntityType, position: Position, direction: QuaterDirection, ) -> Self { Self { entity, position, direction: DirectionType::QuarterDir(direction), quality: Quality::Normal, } } pub fn new_belt(beltspeed: Beltspeed, position: Position, direction: Direction) -> Self { Self::new(EntityType::Belt(beltspeed), position, direction) } pub fn new_underground_belt( beltspeed: Beltspeed, underground_type: UndergroundType, position: Position, direction: Direction, ) -> Self { Self::new( EntityType::UndergroundBelt(beltspeed, underground_type), position, direction, ) } pub fn new_splitter(beltspeed: Beltspeed, position: Position, direction: Direction) -> Self { Self::new(EntityType::Splitter(beltspeed), position, direction) } pub fn new_inserter( inserter_type: InserterType, override_stack_size: Option, position: Position, direction: Direction, ) -> Self { Self::new( EntityType::Inserter { inserter_type, override_stack_size, }, position, direction, ) } pub fn new_electric_pole(electric_pole_type: ElectricPoleType, position: Position) -> Self { Self::new( EntityType::ElectricPole(electric_pole_type), position, Direction::Up, ) } pub fn new_production( name: impl AsRef, recipe: impl AsRef, position: Position, direction: Direction, size: Position, ) -> Self { Self::new( EntityType::Production { name: name.as_ref().to_owned(), recipe: recipe.as_ref().to_owned(), size, }, position, direction, ) } pub fn new_rail( rail_type: RailType, position: Position, direction: impl Into, ) -> Self { Self::new_quarter_direction(EntityType::Rail { rail_type }, position, direction.into()) } pub fn new_roboport(position: Position) -> Self { Self::new(EntityType::Roboport, position, Direction::Up) } pub fn new_unknown( name: impl AsRef, position: Position, direction: Direction, size: Position, ) -> Self { Self::new( EntityType::Unknown { name: name.as_ref().to_owned(), size, misc: HashMap::new(), }, position, direction, ) } pub fn quality(mut self, quality: Quality) -> Self { self.quality = quality; self } pub fn get_name(&self) -> String { match &self.entity { EntityType::Belt(beltspeed) => beltspeed.string(), EntityType::UndergroundBelt(beltspeed, _underground_type) => { beltspeed.string_underground() } EntityType::Splitter(beltspeed) => beltspeed.string_splitter(), EntityType::Unknown { name, size: _, misc: _, } => name.clone(), EntityType::ElectricPole(electric_pole_type) => electric_pole_type.string(), EntityType::Inserter { inserter_type, override_stack_size: _, } => inserter_type.string(), EntityType::Production { name, recipe: _, size: _, } => name.clone(), EntityType::Rail { rail_type } => rail_type.string(), EntityType::Roboport => "roboport".to_string(), } } pub fn get_maybe_underground_type_string(&self) -> Option { match &self.entity { EntityType::UndergroundBelt(_, underground_type) => match underground_type { UndergroundType::Input => Some(String::from("input")), UndergroundType::Output => Some(String::from("output")), }, _ => None, } } pub fn get_maybe_override_stack_size(&self) -> Option { match &self.entity { EntityType::Inserter { inserter_type: _, override_stack_size, } => *override_stack_size, _ => None, } } pub fn get_maybe_recipe(&self) -> Option { match &self.entity { EntityType::Production { name: _, recipe, size: _, } => Some(recipe.clone()), _ => None, } } pub fn size(&self) -> Position { match &self.entity { EntityType::Splitter(_) => Position::new(4, 2), EntityType::Unknown { name: _, size, misc: _, } => *size, EntityType::Production { name: _, recipe: _, size, } => *size, EntityType::ElectricPole(electric_pole_type) => electric_pole_type.size(), EntityType::Rail { rail_type } => rail_type.size(), EntityType::Roboport => Position::new(8, 8), _ => Position::new(2, 2), } } pub fn transform(&mut self, transform: Transformation) { self.position = self.position.transform(transform); self.direction = match self.direction { DirectionType::Dir(direction) => DirectionType::Dir(direction.transform(transform)), DirectionType::QuarterDir(quater_direction) => { DirectionType::QuarterDir(quater_direction.transform(transform)) } }; } pub fn get_aabb(&self) -> AABB { let halve_size = self.size() / 2; match self.direction { DirectionType::Dir(direction) => match direction { Direction::Up | Direction::Down => AABB::new( self.position - halve_size, self.position + halve_size - Position::new(1, 1), ), Direction::Right | Direction::Left => AABB::new( self.position - Position::new(halve_size.y, halve_size.x), self.position + Position::new(halve_size.y, halve_size.x) - Position::new(1, 1), ), }, DirectionType::QuarterDir(_) => { AABB::new(self.position - halve_size, self.position + halve_size) } } } fn visualize(&self, v: &mut Visualization, offset: Position) { match &self.entity { EntityType::Belt(_beltspeed) => { v.add_symbol( (self.position - Position::new(1, 1)) / 2 + offset, factorio_core::visualize::Symbol::Arrow(self.direction.unwrap_dir()), Some(factorio_core::visualize::Color::white()), None, ); } EntityType::UndergroundBelt(_beltspeed, underground_type) => match underground_type { UndergroundType::Input => { v.add_symbol( (self.position - Position::new(1, 1)) / 2 + offset, factorio_core::visualize::Symbol::ArrowEnter(self.direction.unwrap_dir()), Some(factorio_core::visualize::Color::white()), None, ); } UndergroundType::Output => { v.add_symbol( (self.position - Position::new(1, 1)) / 2 + offset, factorio_core::visualize::Symbol::ArrowExit(self.direction.unwrap_dir()), Some(factorio_core::visualize::Color::white()), None, ); } }, EntityType::Splitter(_beltspeed) => (), EntityType::ElectricPole(electric_pole_type) => match electric_pole_type { ElectricPoleType::Small => v.add_symbol( (self.position - Position::new(1, 1)) / 2 + offset, factorio_core::visualize::Symbol::Char('s'), Some(Color::cyan()), None, ), ElectricPoleType::Medium => v.add_symbol( (self.position - Position::new(1, 1)) / 2 + offset, factorio_core::visualize::Symbol::Char('m'), Some(Color::cyan()), None, ), ElectricPoleType::Big => { for (dx, dy) in [(-2, -2), (0, -2), (-2, 0), (0, 0)] { v.add_symbol( (self.position + Position::new(dx, dy)) / 2 + offset, factorio_core::visualize::Symbol::Char('l'), Some(Color::cyan()), None, ) } } ElectricPoleType::Substation => { for (dx, dy) in [(-2, -2), (0, -2), (-2, 0), (0, 0)] { v.add_symbol( (self.position + Position::new(dx, dy)) / 2 + offset, factorio_core::visualize::Symbol::Char('S'), Some(Color::cyan()), None, ) } } }, EntityType::Roboport => { for dx in -2..2 { for dy in -2..2 { v.add_symbol( (self.position) / 2 + Position::new(dx, dy) + offset, factorio_core::visualize::Symbol::Char('R'), Some(Color::yellow()), None, ) } } } _ => (), } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct EntityKey(usize); static ENTITY_COUNTER: AtomicUsize = AtomicUsize::new(0); pub struct Blueprint { entities: Vec<(EntityKey, Entity)>, keys: HashMap, wires: Vec<(EntityKey, u8, EntityKey, u8)>, } impl Blueprint { pub fn new() -> Self { Self { entities: Vec::new(), keys: HashMap::new(), wires: Vec::new(), } } pub fn add_entity(&mut self, entity: Entity) -> EntityKey { let id = EntityKey(ENTITY_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); let pos = self.entities.len(); self.keys.insert(id, pos); self.entities.push((id, entity)); id } pub fn add_path<'a>( &mut self, path: impl IntoIterator, beltspeed: Beltspeed, ) { for &p in path { match p { PathField::Belt { pos, dir } => { self.add_entity(Entity::new_belt( beltspeed, 2 * pos + Position::new(1, 1), dir, )); } PathField::Underground { pos, dir, len } => { self.add_entity(Entity::new_underground_belt( beltspeed, UndergroundType::Input, 2 * pos + Position::new(1, 1), dir, )); self.add_entity(Entity::new_underground_belt( beltspeed, UndergroundType::Output, 2 * pos.in_direction(&dir, len as PositionType) + Position::new(1, 1), dir, )); } }; } } pub fn add_wire(&mut self, a: EntityKey, endpoint_a: u8, b: EntityKey, endpoint_b: u8) { self.wires.push((a, endpoint_a, b, endpoint_b)); } pub fn add_blueprint(&mut self, other: Self) { let previous_entities = self.entities.len(); self.entities.extend(other.entities); self.keys.extend( other .keys .into_iter() .map(|(k, v)| (k, v + previous_entities)), ); self.wires.extend(other.wires); } pub fn to_blueprint(&self) -> super::Blueprint { let entities = self .entities .iter() .enumerate() .map(|(i, (_, e))| { BlueprintEntity::builder( e.get_name(), i as u32 + 1, BlueprintPosition::new(0.5 * e.position.x as f64, 0.5 * e.position.y as f64), ) .direction(match e.direction { DirectionType::Dir(direction) => 4 * direction.get_index(), DirectionType::QuarterDir(quater_direction) => { quater_direction.to_int_direction() } }) .maybe_underground_type(e.get_maybe_underground_type_string()) .maybe_override_stack_size(e.get_maybe_override_stack_size()) .maybe_recipe(e.get_maybe_recipe()) .build() }) .collect(); let wires = self .wires .iter() .map(|&(a, endpoint_a, b, endpoint_b)| { [ self.keys[&a] as u32 + 1, endpoint_a as u32, self.keys[&b] as u32 + 1, endpoint_b as u32, ] }) .collect(); super::Blueprint::builder() .label(String::from("test")) .entities(entities) .wires(wires) .build() } pub fn get_aabb(&self) -> Option { self.entities .iter() .map(|(_, e)| e.get_aabb()) .reduce(AABB::combine) } pub fn transform(&mut self, transform: Transformation) { for (_, e) in &mut self.entities { e.transform(transform); } } pub fn bounding_box(&self) -> AABB { self.entities .iter() .map(|(_, e)| AABB::new(e.position, e.position)) .reduce(|a, b| a.combine(b)) .unwrap() } pub fn placeable(&self, pos: Position, size: Position) -> bool { let aabb = AABB::new(pos - size / 2, pos + size / 2 - Position::new(1, 1)); !self .entities .iter() .any(|(_, e)| AABB::collision(e.get_aabb(), aabb)) } pub fn placibility_map(&self) -> Placibility { let mut blocked = PositionMap::new(self.get_aabb().unwrap(), &false); // dbg!(self.get_aabb(), self.get_aabb().unwrap().size()); for (_, e) in self.entities.iter() { let aabb = e.get_aabb(); for y in aabb.min().y..=aabb.max().y { for x in aabb.min().x..=aabb.max().x { blocked[Position::new(x, y)] = true; } } } Placibility { blocked } } } #[derive(Debug)] pub struct Placibility { blocked: PositionMap, } impl Placibility { pub fn placeable(&self, pos: Position, size: Position) -> bool { let aabb = AABB::new(pos - size / 2, pos + size / 2 - Position::new(1, 1)); for y in aabb.min().y..=aabb.max().y { for x in aabb.min().x..=aabb.max().x { if !self.blocked.get_aabb().contains_pos(Position::new(x, y)) || self.blocked[Position::new(x, y)] { return false; } } } true } } impl Default for Blueprint { fn default() -> Self { Self::new() } }