use factorio_blueprint::abstraction::{Blueprint, Entity, EntityType, RailType, UndergroundType}; use factorio_core::{beltoptions::Beltspeed, prelude::*}; use crate::{balancer::binary_balancer, station::basic_station}; pub struct StationSpec { pub locomotives: usize, pub wagons: usize, pub load: bool, pub beltspeed: Beltspeed, pub lanes: usize, } fn calculate_station_height( output_height: PositionType, lanes: usize, beltspeed: Beltspeed, ) -> PositionType { let station_height = match beltspeed { Beltspeed::Normal => { if lanes == 1 && output_height % 4 == 1 { 4 + ((3 + output_height) & !0b11) } else { 4 + ((15 + output_height) & !0b11) } } Beltspeed::Fast => { if lanes == 1 || lanes == 2 && output_height % 4 == 3 { 4 + ((3 + output_height) & !0b11) } else { 4 + ((9 + output_height) & !0b11) } } Beltspeed::Express | Beltspeed::Turbo => 4 + ((3 + output_height) & !0b11), }; let station_height = PositionType::max(12, station_height); assert!(station_height % 4 == 0); station_height } pub fn multistation(stations: &[StationSpec], stacker_size: usize) -> Blueprint { let longest_train = stations .iter() .map(|s| s.locomotives + s.wagons) .max() .unwrap(); let mut blueprint = Blueprint::new(); // stacker let stacker_length = (longest_train * 5).div_ceil(2); blueprint.add_entity(Entity::new_rail( RailType::ChainSignal, Position::new(23, 1), 0, )); for i in 0..stacker_size { blueprint.add_entity(Entity::new_rail( RailType::CurvedA, Position::new(26, 4 + 8 * i as PositionType), 10, )); blueprint.add_entity(Entity::new_rail( RailType::CurvedB, Position::new(22, 14 + 8 * i as PositionType), 10, )); blueprint.add_entity(Entity::new_rail( RailType::RailSignal, Position::new(17, 15 + 8 * i as PositionType), 2, )); for j in 0..stacker_length { blueprint.add_entity(Entity::new_rail( RailType::Straight, Position::new( 16 - 4 * j as PositionType, 20 + 4 * j as PositionType + 8 * i as PositionType, ), 2, )); } blueprint.add_entity(Entity::new_rail( RailType::CurvedB, Position::new( 14 - 4 * stacker_length as PositionType, 22 + 4 * stacker_length as PositionType + 8 * i as PositionType, ), 2, )); blueprint.add_entity(Entity::new_rail( RailType::CurvedA, Position::new( 10 - 4 * stacker_length as PositionType, 32 + 4 * stacker_length as PositionType + 8 * i as PositionType, ), 2, )); blueprint.add_entity(Entity::new_rail( RailType::ChainSignal, Position::new( 15 - 4 * stacker_length as PositionType, 17 + 4 * stacker_length as PositionType + 8 * i as PositionType, ), 2, )); if i != 0 { blueprint.add_entity(Entity::new_rail( RailType::Straight, Position::new(26, 2 + 8 * (i - 1) as PositionType), 0, )); blueprint.add_entity(Entity::new_rail( RailType::Straight, Position::new(26, 6 + 8 * (i - 1) as PositionType), 0, )); blueprint.add_entity(Entity::new_rail( RailType::Straight, Position::new( 10 - 4 * stacker_length as PositionType, 30 + 4 * stacker_length as PositionType + 8 * i as PositionType, ), 0, )); blueprint.add_entity(Entity::new_rail( RailType::Straight, Position::new( 10 - 4 * stacker_length as PositionType, 34 + 4 * stacker_length as PositionType + 8 * i as PositionType, ), 0, )); } } let total_stacker_height = (26 + 4 * stacker_length + 8 * stacker_size) as PositionType; let inrail_x = 10 - 4 * stacker_length as PositionType; let outrail_x = inrail_x + 52 + 4 * (7 * longest_train).div_ceil(2) as PositionType; let mut previous_station_heights = 0; // station for (i, station) in stations.iter().enumerate() { // in turn blueprint.add_entity(Entity::new_rail( RailType::CurvedA, Position::new( inrail_x, total_stacker_height + 6 + previous_station_heights, ), 8, )); blueprint.add_entity(Entity::new_rail( RailType::CurvedB, Position::new( inrail_x + 4, total_stacker_height + 16 + previous_station_heights, ), 8, )); blueprint.add_entity(Entity::new_rail( RailType::CurvedB, Position::new( inrail_x + 12, total_stacker_height + 24 + previous_station_heights, ), 14, )); blueprint.add_entity(Entity::new_rail( RailType::CurvedA, Position::new( inrail_x + 22, total_stacker_height + 28 + previous_station_heights, ), 14, )); blueprint.add_entity(Entity::new_rail( RailType::RailSignal, Position::new( inrail_x + 3, total_stacker_height + 17 + previous_station_heights, ), 15, )); // out turn blueprint.add_entity(Entity::new_rail( RailType::CurvedA, Position::new( outrail_x, total_stacker_height + 6 + previous_station_heights, ), 10, )); blueprint.add_entity(Entity::new_rail( RailType::CurvedB, Position::new( outrail_x - 4, total_stacker_height + 16 + previous_station_heights, ), 10, )); blueprint.add_entity(Entity::new_rail( RailType::CurvedB, Position::new( outrail_x - 12, total_stacker_height + 24 + previous_station_heights, ), 4, )); blueprint.add_entity(Entity::new_rail( RailType::CurvedA, Position::new( outrail_x - 22, total_stacker_height + 28 + previous_station_heights, ), 4, )); blueprint.add_entity(Entity::new_rail( RailType::RailSignal, Position::new( outrail_x - 3, total_stacker_height + 17 + previous_station_heights, ), 9, )); for j in 0..((7 * longest_train).div_ceil(2) - (7 * (station.locomotives + station.wagons)).div_ceil(2)) { blueprint.add_entity(Entity::new_rail( RailType::Straight, Position::new( inrail_x + 28 + 4 * j as PositionType, 28 + total_stacker_height + previous_station_heights, ), 4, )); } // station let mut b = basic_station( station.load, station.locomotives, station.wagons, station.lanes, station.beltspeed, factorio_core::beltoptions::Belttype::Full, ); let output_height = -b.bounding_box().min().y; b.transform(Transformation::new( Direction::Down, Position::new( inrail_x + 26 + 4 * (7 * longest_train).div_ceil(2) as PositionType, 30 + total_stacker_height + previous_station_heights, ), )); blueprint.add_blueprint(b); // belt output let station_height = calculate_station_height(output_height, station.lanes, station.beltspeed); // rail crossing let (beltdirection, underground_left, underground_right) = match station.load { true => ( Direction::Left, UndergroundType::Output, UndergroundType::Input, ), false => ( Direction::Right, UndergroundType::Input, UndergroundType::Output, ), }; let mut belt_x = outrail_x - 28 - 14 * station.locomotives as PositionType - 3 + 2 * (station.wagons / station.lanes).ilog2() as PositionType; let (balancer_length, mut b) = binary_balancer(station.lanes, station.beltspeed); if station.load { b.transform(Transformation::new( Direction::Down, Position::new(balancer_length, 2 * station.lanes as PositionType), )); } b.transform(Transformation::new( Direction::Up, Position::new( belt_x - 1, 31 + total_stacker_height + previous_station_heights + output_height - 2 * station.lanes as PositionType, ), )); blueprint.add_blueprint(b); belt_x += balancer_length; for j in 0..station.lanes { let (underground_x, signal) = match station_height - (output_height - 2 * j as PositionType) { 5 => { blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_left, Position::new( outrail_x - 23, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_right, Position::new( outrail_x - 9, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_left, Position::new( outrail_x - 7, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); (outrail_x - 23, false) } 7 => { blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_left, Position::new( outrail_x - 17, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); if station.beltspeed != Beltspeed::Turbo { blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_right, Position::new( outrail_x - 7, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_left, Position::new( outrail_x - 5, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); } (outrail_x - 17, false) } 9 => { blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_left, Position::new( outrail_x - 15, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); (outrail_x - 15, false) } 11 => { blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_left, Position::new( outrail_x - 11, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); (outrail_x - 11, false) } 13 => { blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_left, Position::new( outrail_x - 9, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); (outrail_x - 9, false) } 15 => { blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_left, Position::new( outrail_x - 9, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); (outrail_x - 9, true) } 17 => { blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_left, Position::new( outrail_x - 7, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); (outrail_x - 7, false) } 19 | 21 | 23 => { blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_left, Position::new( outrail_x - 5, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); (outrail_x - 5, false) } _ => { blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_left, Position::new( outrail_x - 3, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); (outrail_x - 3, false) } }; if signal { blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_right, Position::new( outrail_x + 5, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); } else { blueprint.add_entity(Entity::new_underground_belt( station.beltspeed, underground_right, Position::new( outrail_x + 3, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); blueprint.add_entity(Entity::new_belt( station.beltspeed, Position::new( outrail_x + 5, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); } for l in 0..((underground_x - belt_x) / 2) { blueprint.add_entity(Entity::new_belt( station.beltspeed, Position::new( belt_x + 2 * l, 30 + total_stacker_height + previous_station_heights + output_height - 2 * j as PositionType, ), beltdirection, )); } } // rail connection if i != stations.len() - 1 { for j in 0..(station_height / 4) { blueprint.add_entity(Entity::new_rail( RailType::Straight, Position::new( inrail_x, total_stacker_height + 4 + previous_station_heights + 4 * j as PositionType, ), 0, )); blueprint.add_entity(Entity::new_rail( RailType::Straight, Position::new( outrail_x, total_stacker_height + 4 + previous_station_heights + 4 * j as PositionType, ), 0, )); } blueprint.add_entity(Entity::new_rail( RailType::ChainSignal, Position::new( inrail_x - 3, total_stacker_height + 15 + previous_station_heights, ), 0, )); blueprint.add_entity(Entity::new_rail( RailType::RailSignal, Position::new( outrail_x + 3, total_stacker_height + 15 + previous_station_heights, ), 8, )); } previous_station_heights += station_height; } // output rails blueprint.add_entity(Entity::new_rail( RailType::CurvedA, Position::new(outrail_x, total_stacker_height - 2), 0, )); blueprint.add_entity(Entity::new_rail( RailType::CurvedB, Position::new(outrail_x - 4, total_stacker_height - 12), 0, )); let out_diagonal_length = (outrail_x - 54) / 4; for i in 0..out_diagonal_length { blueprint.add_entity(Entity::new_rail( RailType::Straight, Position::new( outrail_x - 10 - 4 * i as PositionType, total_stacker_height - 18 - 4 * i as PositionType, ), 6, )); if i % 4 == 0 { blueprint.add_entity(Entity::new_rail( RailType::RailSignal, Position::new( outrail_x - 7 - 4 * i as PositionType, total_stacker_height - 19 - 4 * i as PositionType, ), 6, )); } } let remaining_offset = total_stacker_height - 34 - out_diagonal_length * 4; blueprint.add_entity(Entity::new_rail( RailType::CurvedB, Position::new(42, remaining_offset + 14), 8, )); blueprint.add_entity(Entity::new_rail( RailType::CurvedA, Position::new(38, remaining_offset + 4), 8, )); blueprint.add_entity(Entity::new_rail( RailType::RailSignal, Position::new(41, remaining_offset + 7), 7, )); for i in 0..(remaining_offset / 4) { blueprint.add_entity(Entity::new_rail( RailType::Straight, Position::new(38, 2 + 4 * i as PositionType), 0, )); if i % 8 == 7 { blueprint.add_entity(Entity::new_rail( RailType::RailSignal, Position::new(41, 3 + 4 * i as PositionType), 8, )); } } blueprint.transform(Transformation::new(Direction::Right, Position::new(0, 0))); blueprint }