factorio-bitpacker 0.1.2

A crate to pack binary blobs into a blueprint string containing factorio combinators.
Documentation
use std::collections::HashMap;
use std::io;
use std::io::Read;

use factorio_blueprint::objects::{
  Blueprint, ControlBehavior, ControlFilter, Entity, EntityConnections, EntityNumber,
  OneBasedIndex, Position, SignalID, SignalIDType,
};

use itertools::Itertools;
use noisy_float::types::R64;

/// The primary signal dictionary
pub const SIGNALS: [(&str, SignalIDType); 256] = [
  ("signal-1", SignalIDType::Virtual),
  ("signal-2", SignalIDType::Virtual),
  ("signal-3", SignalIDType::Virtual),
  ("signal-4", SignalIDType::Virtual),
  ("signal-5", SignalIDType::Virtual),
  ("signal-6", SignalIDType::Virtual),
  ("signal-7", SignalIDType::Virtual),
  ("signal-8", SignalIDType::Virtual),
  ("signal-9", SignalIDType::Virtual),
  ("signal-0", SignalIDType::Virtual),
  ("signal-A", SignalIDType::Virtual),
  ("signal-B", SignalIDType::Virtual),
  ("signal-C", SignalIDType::Virtual),
  ("signal-D", SignalIDType::Virtual),
  ("signal-E", SignalIDType::Virtual),
  ("signal-F", SignalIDType::Virtual),
  ("signal-G", SignalIDType::Virtual),
  ("signal-H", SignalIDType::Virtual),
  ("signal-I", SignalIDType::Virtual),
  ("signal-J", SignalIDType::Virtual),
  ("signal-K", SignalIDType::Virtual),
  ("signal-L", SignalIDType::Virtual),
  ("signal-M", SignalIDType::Virtual),
  ("signal-N", SignalIDType::Virtual),
  ("signal-O", SignalIDType::Virtual),
  ("signal-P", SignalIDType::Virtual),
  ("signal-Q", SignalIDType::Virtual),
  ("signal-R", SignalIDType::Virtual),
  ("signal-S", SignalIDType::Virtual),
  ("signal-T", SignalIDType::Virtual),
  ("signal-U", SignalIDType::Virtual),
  ("signal-V", SignalIDType::Virtual),
  ("signal-W", SignalIDType::Virtual),
  ("signal-X", SignalIDType::Virtual),
  ("signal-Y", SignalIDType::Virtual),
  ("signal-Z", SignalIDType::Virtual),
  ("signal-red", SignalIDType::Virtual),
  ("signal-green", SignalIDType::Virtual),
  ("signal-blue", SignalIDType::Virtual),
  ("signal-yellow", SignalIDType::Virtual),
  ("signal-pink", SignalIDType::Virtual),
  ("signal-cyan", SignalIDType::Virtual),
  ("signal-check", SignalIDType::Virtual),
  ("signal-info", SignalIDType::Virtual),
  ("signal-dot", SignalIDType::Virtual),
  ("water", SignalIDType::Fluid),
  ("crude-oil", SignalIDType::Fluid),
  ("steam", SignalIDType::Fluid),
  ("heavy-oil", SignalIDType::Fluid),
  ("light-oil", SignalIDType::Fluid),
  ("petroleum-gas", SignalIDType::Fluid),
  ("sulfuric-acid", SignalIDType::Fluid),
  ("lubricant", SignalIDType::Fluid),
  ("wooden-chest", SignalIDType::Item),
  ("iron-chest", SignalIDType::Item),
  ("steel-chest", SignalIDType::Item),
  ("storage-tank", SignalIDType::Item),
  ("transport-belt", SignalIDType::Item),
  ("fast-transport-belt", SignalIDType::Item),
  ("express-transport-belt", SignalIDType::Item),
  ("underground-belt", SignalIDType::Item),
  ("fast-underground-belt", SignalIDType::Item),
  ("express-underground-belt", SignalIDType::Item),
  ("splitter", SignalIDType::Item),
  ("fast-splitter", SignalIDType::Item),
  ("express-splitter", SignalIDType::Item),
  ("burner-inserter", SignalIDType::Item),
  ("inserter", SignalIDType::Item),
  ("long-handed-inserter", SignalIDType::Item),
  ("fast-inserter", SignalIDType::Item),
  ("filter-inserter", SignalIDType::Item),
  ("stack-inserter", SignalIDType::Item),
  ("stack-filter-inserter", SignalIDType::Item),
  ("small-electric-pole", SignalIDType::Item),
  ("medium-electric-pole", SignalIDType::Item),
  ("big-electric-pole", SignalIDType::Item),
  ("substation", SignalIDType::Item),
  ("pipe", SignalIDType::Item),
  ("pipe-to-ground", SignalIDType::Item),
  ("pump", SignalIDType::Item),
  ("rail", SignalIDType::Item),
  ("train-stop", SignalIDType::Item),
  ("rail-signal", SignalIDType::Item),
  ("rail-chain-signal", SignalIDType::Item),
  ("locomotive", SignalIDType::Item),
  ("cargo-wagon", SignalIDType::Item),
  ("fluid-wagon", SignalIDType::Item),
  ("artillery-wagon", SignalIDType::Item),
  ("car", SignalIDType::Item),
  ("tank", SignalIDType::Item),
  ("spidertron", SignalIDType::Item),
  ("spidertron-remote", SignalIDType::Item),
  ("logistic-robot", SignalIDType::Item),
  ("construction-robot", SignalIDType::Item),
  ("logistic-chest-active-provider", SignalIDType::Item),
  ("logistic-chest-passive-provider", SignalIDType::Item),
  ("logistic-chest-storage", SignalIDType::Item),
  ("logistic-chest-buffer", SignalIDType::Item),
  ("logistic-chest-requester", SignalIDType::Item),
  ("roboport", SignalIDType::Item),
  ("small-lamp", SignalIDType::Item),
  ("red-wire", SignalIDType::Item),
  ("green-wire", SignalIDType::Item),
  ("arithmetic-combinator", SignalIDType::Item),
  ("decider-combinator", SignalIDType::Item),
  ("constant-combinator", SignalIDType::Item),
  ("power-switch", SignalIDType::Item),
  ("programmable-speaker", SignalIDType::Item),
  ("stone-brick", SignalIDType::Item),
  ("concrete", SignalIDType::Item),
  ("hazard-concrete", SignalIDType::Item),
  ("refined-concrete", SignalIDType::Item),
  ("refined-hazard-concrete", SignalIDType::Item),
  ("landfill", SignalIDType::Item),
  ("cliff-explosives", SignalIDType::Item),
  ("repair-pack", SignalIDType::Item),
  ("blueprint", SignalIDType::Item),
  ("deconstruction-planner", SignalIDType::Item),
  ("upgrade-planner", SignalIDType::Item),
  ("blueprint-book", SignalIDType::Item),
  ("boiler", SignalIDType::Item),
  ("steam-engine", SignalIDType::Item),
  ("solar-panel", SignalIDType::Item),
  ("accumulator", SignalIDType::Item),
  ("nuclear-reactor", SignalIDType::Item),
  ("heat-pipe", SignalIDType::Item),
  ("heat-exchanger", SignalIDType::Item),
  ("steam-turbine", SignalIDType::Item),
  ("burner-mining-drill", SignalIDType::Item),
  ("electric-mining-drill", SignalIDType::Item),
  ("offshore-pump", SignalIDType::Item),
  ("pumpjack", SignalIDType::Item),
  ("stone-furnace", SignalIDType::Item),
  ("steel-furnace", SignalIDType::Item),
  ("electric-furnace", SignalIDType::Item),
  ("assembling-machine-1", SignalIDType::Item),
  ("assembling-machine-2", SignalIDType::Item),
  ("assembling-machine-3", SignalIDType::Item),
  ("oil-refinery", SignalIDType::Item),
  ("chemical-plant", SignalIDType::Item),
  ("centrifuge", SignalIDType::Item),
  ("lab", SignalIDType::Item),
  ("beacon", SignalIDType::Item),
  ("speed-module", SignalIDType::Item),
  ("speed-module-2", SignalIDType::Item),
  ("speed-module-3", SignalIDType::Item),
  ("effectivity-module", SignalIDType::Item),
  ("effectivity-module-2", SignalIDType::Item),
  ("effectivity-module-3", SignalIDType::Item),
  ("productivity-module", SignalIDType::Item),
  ("productivity-module-2", SignalIDType::Item),
  ("productivity-module-3", SignalIDType::Item),
  ("wood", SignalIDType::Item),
  ("coal", SignalIDType::Item),
  ("stone", SignalIDType::Item),
  ("iron-ore", SignalIDType::Item),
  ("copper-ore", SignalIDType::Item),
  ("uranium-ore", SignalIDType::Item),
  ("raw-fish", SignalIDType::Item),
  ("iron-plate", SignalIDType::Item),
  ("copper-plate", SignalIDType::Item),
  ("solid-fuel", SignalIDType::Item),
  ("steel-plate", SignalIDType::Item),
  ("plastic-bar", SignalIDType::Item),
  ("sulfur", SignalIDType::Item),
  ("battery", SignalIDType::Item),
  ("explosives", SignalIDType::Item),
  ("crude-oil-barrel", SignalIDType::Item),
  ("heavy-oil-barrel", SignalIDType::Item),
  ("light-oil-barrel", SignalIDType::Item),
  ("lubricant-barrel", SignalIDType::Item),
  ("petroleum-gas-barrel", SignalIDType::Item),
  ("sulfuric-acid-barrel", SignalIDType::Item),
  ("water-barrel", SignalIDType::Item),
  ("copper-cable", SignalIDType::Item),
  ("iron-stick", SignalIDType::Item),
  ("iron-gear-wheel", SignalIDType::Item),
  ("empty-barrel", SignalIDType::Item),
  ("electronic-circuit", SignalIDType::Item),
  ("advanced-circuit", SignalIDType::Item),
  ("processing-unit", SignalIDType::Item),
  ("engine-unit", SignalIDType::Item),
  ("electric-engine-unit", SignalIDType::Item),
  ("flying-robot-frame", SignalIDType::Item),
  ("satellite", SignalIDType::Item),
  ("rocket-control-unit", SignalIDType::Item),
  ("low-density-structure", SignalIDType::Item),
  ("rocket-fuel", SignalIDType::Item),
  ("nuclear-fuel", SignalIDType::Item),
  ("uranium-235", SignalIDType::Item),
  ("uranium-238", SignalIDType::Item),
  ("uranium-fuel-cell", SignalIDType::Item),
  ("used-up-uranium-fuel-cell", SignalIDType::Item),
  ("automation-science-pack", SignalIDType::Item),
  ("logistic-science-pack", SignalIDType::Item),
  ("military-science-pack", SignalIDType::Item),
  ("chemical-science-pack", SignalIDType::Item),
  ("production-science-pack", SignalIDType::Item),
  ("utility-science-pack", SignalIDType::Item),
  ("space-science-pack", SignalIDType::Item),
  ("pistol", SignalIDType::Item),
  ("submachine-gun", SignalIDType::Item),
  ("shotgun", SignalIDType::Item),
  ("combat-shotgun", SignalIDType::Item),
  ("rocket-launcher", SignalIDType::Item),
  ("flamethrower", SignalIDType::Item),
  ("land-mine", SignalIDType::Item),
  ("firearm-magazine", SignalIDType::Item),
  ("piercing-rounds-magazine", SignalIDType::Item),
  ("uranium-rounds-magazine", SignalIDType::Item),
  ("shotgun-shell", SignalIDType::Item),
  ("piercing-shotgun-shell", SignalIDType::Item),
  ("cannon-shell", SignalIDType::Item),
  ("explosive-cannon-shell", SignalIDType::Item),
  ("uranium-cannon-shell", SignalIDType::Item),
  ("explosive-uranium-cannon-shell", SignalIDType::Item),
  ("artillery-shell", SignalIDType::Item),
  ("rocket", SignalIDType::Item),
  ("explosive-rocket", SignalIDType::Item),
  ("atomic-bomb", SignalIDType::Item),
  ("flamethrower-ammo", SignalIDType::Item),
  ("grenade", SignalIDType::Item),
  ("cluster-grenade", SignalIDType::Item),
  ("poison-capsule", SignalIDType::Item),
  ("slowdown-capsule", SignalIDType::Item),
  ("defender-capsule", SignalIDType::Item),
  ("distractor-capsule", SignalIDType::Item),
  ("destroyer-capsule", SignalIDType::Item),
  ("light-armor", SignalIDType::Item),
  ("heavy-armor", SignalIDType::Item),
  ("modular-armor", SignalIDType::Item),
  ("power-armor", SignalIDType::Item),
  ("power-armor-mk2", SignalIDType::Item),
  ("solar-panel-equipment", SignalIDType::Item),
  ("fusion-reactor-equipment", SignalIDType::Item),
  ("battery-equipment", SignalIDType::Item),
  ("battery-mk2-equipment", SignalIDType::Item),
  ("belt-immunity-equipment", SignalIDType::Item),
  ("exoskeleton-equipment", SignalIDType::Item),
  ("personal-roboport-equipment", SignalIDType::Item),
  ("personal-roboport-mk2-equipment", SignalIDType::Item),
  ("night-vision-equipment", SignalIDType::Item),
  ("energy-shield-equipment", SignalIDType::Item),
  ("energy-shield-mk2-equipment", SignalIDType::Item),
  ("personal-laser-defense-equipment", SignalIDType::Item),
  ("discharge-defense-equipment", SignalIDType::Item),
  ("discharge-defense-remote", SignalIDType::Item),
  ("stone-wall", SignalIDType::Item),
  ("gate", SignalIDType::Item),
  ("gun-turret", SignalIDType::Item),
  ("laser-turret", SignalIDType::Item),
  ("flamethrower-turret", SignalIDType::Item),
  ("artillery-turret", SignalIDType::Item),
  ("artillery-targeting-remote", SignalIDType::Item),
  ("radar", SignalIDType::Item),
  ("rocket-silo", SignalIDType::Item),
];

pub fn pack_read<R: Read>(mut from: R) -> Result<Blueprint, io::Error> {
  let mut buf = Vec::new();
  from.read_to_end(&mut buf)?;

  let chunks = buf
    .iter()
    .chunks(4);

  let mut words = chunks
    .into_iter()
    .map(|word| {
      let (b1, b2, b3, b4) = word.pad_using(4, |_| &0).cloned().collect_tuple().unwrap();
      u32::from_le_bytes([b1, b2, b3, b4])
    });

  Ok(pack(&mut words))
}

pub fn pack<I: Iterator<Item = u32>>(iter: &mut I) -> Blueprint {
  let entities = iter
    .chunks(SIGNALS.len())
    .into_iter()
    .enumerate()
    .map(move |(cluster_n, words)| {
      words
        .zip(SIGNALS.into_iter())
        .chunks(20)
        .into_iter()
        .map(|signals| {
          let filters = signals
            .enumerate()
            .map(|(signal_n, (word, (signal, signal_type)))| {
              ControlFilter {
                signal: SignalID { name: signal.to_owned(), type_: signal_type },
                index: OneBasedIndex::new(signal_n + 1).unwrap(),
                // safe transmute from u32 to i32
                count: i32::from_le_bytes(word.to_le_bytes()),
              }
            })
            .collect::<Vec<_>>();

          ControlBehavior {
            arithmetic_conditions: None,
            decider_conditions: None,
            filters: Some(filters),
            is_on: Some(true),
          }
        })
        .enumerate()
        .map(move |(ent_n, control)| Entity {
          entity_number: EntityNumber::new((&cluster_n + 1) * (ent_n + 1)).unwrap(),
          name: "constant-combinator".to_owned(),
          position: {
            let n = &ent_n % 7;
            let r = &ent_n / 7 + &cluster_n * 2;

            Position { x: R64::new(r as f64), y: R64::new(n as f64) }
          },
          direction: Some(4), // south
          connections: Some(EntityConnections::NumberIdx(HashMap::new())),
          control_behavior: Some(control),
          orientation: None,
          items: None,
          recipe: None,
          bar: None,
          inventory: None,
          infinity_settings: None,
          type_: None,
          input_priority: None,
          output_priority: None,
          filter: None,
          filters: None,
          filter_mode: None,
          override_stack_size: None,
          drop_position: None,
          pickup_position: None,
          request_filters: None,
          request_from_buffers: None,
          parameters: None,
          alert_parameters: None,
          auto_launch: None,
          variation: None,
          color: None,
          station: None,
        })
        .collect::<Vec<_>>()
    })
    .map(|vec| vec.into_iter())
    .flatten()
    .collect::<Vec<_>>();

  Blueprint {
    item: "blueprint".to_owned(),
    label: "auto generated".to_owned(),
    label_color: None,
    entities,
    tiles: vec![],
    icons: vec![],
    schedules: vec![],
    version: 1u64 << (64 - 16) | 1u64 << (64 - 32) | 46u64 << (64 - 16) | 59110u64,
  }
}