factorio_blueprint/factorio-blueprint/src/abstraction.rs

785 lines
23 KiB
Rust

use crate::{BlueprintEntity, BlueprintPosition};
use clap::ValueEnum;
use factorio_core::{
aabb::AABB,
beltoptions::Beltspeed,
misc::PositionMap,
pathfield::PathField,
prelude::*,
quaterdirection::QuaterDirection,
visualize::{Color, Visualization},
};
use std::{
collections::{HashMap, HashSet},
sync::atomic::AtomicUsize,
};
mod power_connection;
mod roboports;
pub mod serde;
mod visualize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UndergroundType {
Input,
Output,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
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, ValueEnum)]
pub enum ChestType {
Wood,
Iron,
Steel,
}
impl ChestType {
fn string(&self) -> String {
match self {
ChestType::Wood => "wooden-chest",
ChestType::Iron => "iron-chest",
ChestType::Steel => "steel-chest",
}
.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, ValueEnum)]
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!(),
}
}
fn get_index(&self) -> u8 {
match self {
DirectionType::Dir(direction) => direction.get_index() * 4,
DirectionType::QuarterDir(quater_direction) => quater_direction.to_int_direction(),
}
}
}
#[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: Beltspeed,
input_priority_left: Option<bool>,
output_priority_left: Option<bool>,
filter: Option<String>,
},
ElectricPole(ElectricPoleType),
Inserter {
inserter_type: InserterType,
override_stack_size: Option<u8>,
},
Production {
name: String,
recipe: Option<String>,
size: Position,
},
Rail {
rail_type: RailType,
},
Roboport,
Chest(ChestType),
Unknown {
name: String,
size: Position,
misc: HashMap<String, serde_json::Value>,
},
}
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_chest(chest_type: ChestType, position: Position) -> Self {
Self::new(EntityType::Chest(chest_type), position, Direction::Up)
}
pub fn new_splitter(beltspeed: Beltspeed, position: Position, direction: Direction) -> Self {
Self::new(
EntityType::Splitter {
beltspeed,
input_priority_left: None,
output_priority_left: None,
filter: None,
},
position,
direction,
)
}
pub fn new_splitter_with_priority(
beltspeed: Beltspeed,
position: Position,
direction: Direction,
input_priority_left: Option<bool>,
output_priority_left: Option<bool>,
filter: Option<String>,
) -> Self {
Self::new(
EntityType::Splitter {
beltspeed,
input_priority_left,
output_priority_left,
filter,
},
position,
direction,
)
}
pub fn new_inserter(
inserter_type: InserterType,
override_stack_size: Option<u8>,
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<str>,
recipe: Option<impl AsRef<str>>,
position: Position,
direction: Direction,
size: Position,
) -> Self {
Self::new(
EntityType::Production {
name: name.as_ref().to_owned(),
recipe: recipe.map(|s| s.as_ref().to_owned()),
size,
},
position,
direction,
)
}
pub fn new_rail(
rail_type: RailType,
position: Position,
direction: impl Into<QuaterDirection>,
) -> 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<str>,
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,
input_priority_left: _,
output_priority_left: _,
filter: _,
} => 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(),
EntityType::Chest(chest_type) => chest_type.string(),
}
}
pub fn get_maybe_underground_type_string(&self) -> Option<String> {
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<u8> {
match &self.entity {
EntityType::Inserter {
inserter_type: _,
override_stack_size,
} => *override_stack_size,
_ => None,
}
}
pub fn get_maybe_recipe(&self) -> Option<String> {
match &self.entity {
EntityType::Production {
name: _,
recipe,
size: _,
} => recipe.clone(),
_ => None,
}
}
pub fn get_maybe_input_priority(&self) -> Option<String> {
match self.entity {
EntityType::Splitter {
beltspeed: _,
input_priority_left,
output_priority_left: _,
filter: _,
} => match input_priority_left {
Some(true) => Some("left".to_string()),
Some(false) => Some("right".to_string()),
None => None,
},
_ => None,
}
}
pub fn get_maybe_output_priority(&self) -> Option<String> {
match self.entity {
EntityType::Splitter {
beltspeed: _,
input_priority_left: _,
output_priority_left,
filter: _,
} => match output_priority_left {
Some(true) => Some("left".to_string()),
Some(false) => Some("right".to_string()),
None => None,
},
_ => None,
}
}
pub fn get_maybe_filter(&self) -> Option<String> {
match &self.entity {
EntityType::Splitter {
beltspeed: _,
input_priority_left: _,
output_priority_left: _,
filter,
} => filter.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 { .. } => (),
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<EntityKey, usize>,
wires: HashSet<(EntityKey, u8, EntityKey, u8)>,
}
impl Blueprint {
pub fn new() -> Self {
Self {
entities: Vec::new(),
keys: HashMap::new(),
wires: HashSet::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<Item = &'a PathField>,
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.insert((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())
.maybe_input_priority(e.get_maybe_input_priority())
.maybe_output_priority(e.get_maybe_output_priority())
.maybe_filter(e.get_maybe_filter())
.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<AABB> {
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<bool>,
}
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()
}
}