mindustry logic execution, map- and schematic- parsing and rendering
add erekir blocks
bendn 2023-06-23
parent da3a3f6 · commit 94d3890
-rw-r--r--Cargo.toml1
-rw-r--r--items.py23
-rw-r--r--src/block/base.rs13
-rw-r--r--src/block/defense.rs27
-rw-r--r--src/block/extraction.rs6
-rw-r--r--src/block/factory.rs16
-rw-r--r--src/block/fluid.rs12
-rw-r--r--src/block/mod.rs6
-rw-r--r--src/block/payload.rs27
-rw-r--r--src/block/power.rs15
-rw-r--r--src/block/simple.rs2
-rw-r--r--src/block/transport.rs11
-rw-r--r--src/block/turret.rs14
-rw-r--r--src/data/base64.rs336
-rw-r--r--src/data/schematic.rs106
-rw-r--r--src/exe/edit.rs1488
-rw-r--r--src/exe/mod.rs2
-rw-r--r--src/exe/print.rs6
-rw-r--r--src/item/mod.rs3
19 files changed, 216 insertions, 1898 deletions
diff --git a/Cargo.toml b/Cargo.toml
index efb00b2..a34f6cd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,6 +9,7 @@ license = "GPL-3.0"
[dependencies]
flate2 = { version = "1.0", features = ["zlib"], default-features = false }
+base64 = "0.21.2"
paste = "1.0.12"
[[bin]]
diff --git a/items.py b/items.py
new file mode 100644
index 0000000..922cc30
--- /dev/null
+++ b/items.py
@@ -0,0 +1,23 @@
+import re
+from sys import argv
+import pyperclip
+
+string = pyperclip.paste()
+reg = re.compile(r"with\((.+)\)\);")
+match = reg.search(string)[1].replace("Items", "") # .replace(",", ":")
+out: str = ""
+i: int = -1
+j: int = 0
+while i < len(match) - 1:
+ i += 1
+ if match[i] == ".":
+ out += match[i + 1].upper()
+ i += 1
+ continue
+ if match[i] == ",":
+ j += 1
+ if j % 2 != 0:
+ out += ":"
+ continue
+ out += match[i]
+pyperclip.copy(f"cost!({out})")
diff --git a/src/block/base.rs b/src/block/base.rs
index ded5ec4..394415a 100644
--- a/src/block/base.rs
+++ b/src/block/base.rs
@@ -19,11 +19,24 @@ make_register! {
"core-shard" => SimpleBlock::new(3, true, cost!(Copper: 1000, Lead: 800));
"core-foundation" => SimpleBlock::new(4, true, cost!(Copper: 3000, Lead: 3000, Silicon: 2000));
"core-nucleus" => SimpleBlock::new(5, true, cost!(Copper: 8000, Lead: 8000, Thorium: 4000, Silicon: 5000));
+ "core-bastion" => SimpleBlock::new(4, true, cost!(Graphite: 1000, Silicon: 1000, Beryllium: 800));
+ "core-citadel" => SimpleBlock::new(5, true, cost!(Silicon: 4000, Beryllium: 4000, Tungsten: 3000, Oxide: 1000));
+ "core-acropolis" => SimpleBlock::new(6, true, cost!(Beryllium: 6000, Silicon: 5000, Tungsten: 5000, Carbide: 3000, Oxide: 3000));
"container" => SimpleBlock::new(2, true, cost!(Titanium: 100));
"vault" => SimpleBlock::new(3, true, cost!(Titanium: 250, Thorium: 125));
"unloader" => ItemBlock::new(1, true, cost!(Titanium: 25, Silicon: 30));
+ "reinforced-container" => SimpleBlock::new(2, true, cost!(Tungsten: 30, Graphite: 40));
+ "reinforced-vault" => SimpleBlock::new(3, true, cost!(Tungsten: 125, Thorium: 70, Beryllium: 100));
"illuminator" => LampBlock::new(1, true, cost!(Lead: 8, Graphite: 12, Silicon: 8));
"launch-pad" => SimpleBlock::new(3, true, cost!(Copper: 350, Lead: 200, Titanium: 150, Silicon: 140));
+ "radar" => SimpleBlock::new(1, true, cost!(Silicon: 60, Graphite: 50, Beryllium: 10));
+ "build-tower" => SimpleBlock::new(3, true, cost!(Silicon: 150, Oxide: 40, Thorium: 60));
+ "regen-projector" => SimpleBlock::new(3, true, cost!(Silicon: 80, Tungsten: 60, Oxide: 40, Beryllium: 80));
+ // barrier projector
+ // editor only
+ "shockwave-tower" => SimpleBlock::new(3, true, cost!(SurgeAlloy: 50, Silicon: 150, Oxide: 30, Tungsten: 100));
+ "shield-projector" => SimpleBlock::new(3, true, &[]);
+ "large-shield-projector" => SimpleBlock::new(4, true, &[]);
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
diff --git a/src/block/defense.rs b/src/block/defense.rs
index 05f8e98..d136dc6 100644
--- a/src/block/defense.rs
+++ b/src/block/defense.rs
@@ -8,29 +8,36 @@ use crate::data::dynamic::{DynData, DynType};
use crate::data::GridPos;
use crate::item::storage::Storage;
-make_register!
-(
+make_register! {
"copper-wall" => SimpleBlock::new(1, true, cost!(Copper: 6));
- "copper-wall-large" => SimpleBlock::new(2, true, cost!(Copper: 24));
+ "copper-wall-large" => SimpleBlock::new(2, true, cost!(Copper: 6 * 4));
"titanium-wall" => SimpleBlock::new(1, true, cost!(Titanium: 6));
- "titanium-wall-large" => SimpleBlock::new(2, true, cost!(Titanium: 24));
+ "titanium-wall-large" => SimpleBlock::new(2, true, cost!(Titanium: 6 * 4));
"plastanium-wall" => SimpleBlock::new(1, true, cost!(Metaglass: 2, Plastanium: 5));
- "plastanium-wall-large" => SimpleBlock::new(2, true, cost!(Metaglass: 8, Plastanium: 20));
+ "plastanium-wall-large" => SimpleBlock::new(2, true, cost!(Metaglass: 2 * 4, Plastanium: 5 * 4));
"thorium-wall" => SimpleBlock::new(1, true, cost!(Thorium: 6));
- "thorium-wall-large" => SimpleBlock::new(2, true, cost!(Thorium: 24));
+ "thorium-wall-large" => SimpleBlock::new(2, true, cost!(Thorium: 6 * 4));
"phase-wall" => SimpleBlock::new(1, true, cost!(PhaseFabric: 6));
- "phase-wall-large" => SimpleBlock::new(2, true, cost!(PhaseFabric: 24));
+ "phase-wall-large" => SimpleBlock::new(2, true, cost!(PhaseFabric: 6 * 4));
"surge-wall" => SimpleBlock::new(1, true, cost!(SurgeAlloy: 6));
- "surge-wall-large" => SimpleBlock::new(2, true, cost!(SurgeAlloy: 24));
+ "surge-wall-large" => SimpleBlock::new(2, true, cost!(SurgeAlloy: 6 * 4));
"door" => DoorBlock::new(1, true, cost!(Titanium: 6, Silicon: 4));
- "door-large" => DoorBlock::new(2, true, cost!(Titanium: 24, Silicon: 16));
+ "door-large" => DoorBlock::new(2, true, cost!(Titanium: 6 * 4, Silicon: 4 * 4));
+ "tungsten-wall" => SimpleBlock::new(1, true, cost!(Tungsten: 6));
+ "large-tungsten-wall" => SimpleBlock::new(2, true, cost!(Tungsten: 6 * 4));
+ "blast-door" => DoorBlock::new(2, true, cost!(Tungsten: 24, Silicon: 24));
+ "reinforced-surge-wall" => SimpleBlock::new(1, true, cost!(SurgeAlloy: 6, Tungsten: 2));
+ "reinforced-surge-wall-large" => SimpleBlock::new(2, true, cost!(SurgeAlloy: 6 * 4, Tungsten: 2 * 4));
+ "carbide-wall" => SimpleBlock::new(1, true, cost!(Thorium: 6, Carbide: 6));
+ "carbide-wall-large" => SimpleBlock::new(2, true, cost!(Thorium: 6 * 4, Carbide: 6 * 4));
+ "shielded-wall" => SimpleBlock::new(2, true, cost!(PhaseFabric: 20, SurgeAlloy: 12, Beryllium: 12));
// sandbox only
"scrap-wall" => SimpleBlock::new(1, true, cost!(Scrap: 6));
"scrap-wall-large" => SimpleBlock::new(2, true, cost!(Scrap: 24));
"scrap-wall-huge" => SimpleBlock::new(3, true, cost!(Scrap: 54));
"scrap-wall-gigantic" => SimpleBlock::new(4, true, cost!(Scrap: 96));
"thruster" => SimpleBlock::new(4, false, cost!(Scrap: 96));
-);
+}
pub struct DoorBlock {
size: u8,
diff --git a/src/block/extraction.rs b/src/block/extraction.rs
index 237af77..ebea8cc 100644
--- a/src/block/extraction.rs
+++ b/src/block/extraction.rs
@@ -10,4 +10,10 @@ make_register!
"water-extractor" => SimpleBlock::new(2, true, cost!(Copper: 30, Lead: 30, Metaglass: 30, Graphite: 30));
"cultivator" => SimpleBlock::new(2, true, cost!(Copper: 25, Lead: 25, Silicon: 10));
"oil-extractor" => SimpleBlock::new(3, true, cost!(Copper: 150, Lead: 115, Graphite: 175, Thorium: 115, Silicon: 75));
+ "vent-condenser" => SimpleBlock::new(3, true, cost!(Graphite: 20, Beryllium: 60));
+ "cliff-crusher" => SimpleBlock::new(2, false, cost!(Beryllium: 100, Graphite: 40));
+ "plasma-bore" => SimpleBlock::new(2, false, cost!(Beryllium: 40));
+ "large-plasma-bore" => SimpleBlock::new(3, false, cost!(Silicon: 100, Oxide: 25, Beryllium: 100, Tungsten: 70));
+ "impact-drill" => SimpleBlock::new(4, true, cost!(Silicon: 70, Beryllium: 90, Graphite: 60));
+ "eruption-drill" => SimpleBlock::new(5, true, cost!(Silicon: 200, Oxide: 20, Tungsten: 200, Thorium: 120));
);
diff --git a/src/block/factory.rs b/src/block/factory.rs
index add4458..f7cfd13 100644
--- a/src/block/factory.rs
+++ b/src/block/factory.rs
@@ -21,6 +21,22 @@ make_register!
"pulverizer" => SimpleBlock::new(1, true, cost!(Copper: 30, Lead: 25));
"coal-centrifuge" => SimpleBlock::new(2, true, cost!(Lead: 30, Graphite: 40, Titanium: 20));
"incinerator" => SimpleBlock::new(1, true, cost!(Lead: 15, Graphite: 5));
+ "silicon-arc-furnace" => SimpleBlock::new(3, true, cost!(Beryllium: 70, Graphite: 80));
+ "electrolyzer" => SimpleBlock::new(3, true, cost!(Silicon: 50, Graphite: 40, Beryllium: 130, Tungsten: 80));
+ "atmospheric-condenser" => SimpleBlock::new(3, true, cost!(Oxide: 60, Beryllium: 180, Silicon: 150));
+ "oxidation-chamber" => SimpleBlock::new(3, true, cost!(Tungsten: 120, Graphite: 80, Silicon: 100, Beryllium: 120));
+ "electric-heater" => SimpleBlock::new(2, false, cost!(Tungsten: 30, Oxide: 30));
+ "slag-heater" => SimpleBlock::new(3, false, cost!(Tungsten: 50, Oxide: 20, Beryllium: 20));
+ "phase-heater" => SimpleBlock::new(2, false, cost!(Oxide: 30, Carbide: 30, Beryllium: 30));
+ "heat-redirector" => SimpleBlock::new(3, false, cost!(Tungsten: 10, Graphite: 10));
+ "heat-router" => SimpleBlock::new(3, false, cost!(Tungsten: 15, Graphite: 10));
+ "slag-incinerator" => SimpleBlock::new(1, true, cost!(Tungsten: 15));
+ "carbide-crucible" => SimpleBlock::new(3, true, cost!(Tungsten: 110, Thorium: 150, Oxide: 60));
+ // slag centrifuge
+ "surge-crucible" => SimpleBlock::new(3, true, cost!(Silicon: 100, Graphite: 80, Tungsten: 80, Oxide: 80));
+ "cyanogen-synthesizer" => SimpleBlock::new(3, true, cost!(Carbide: 50, Silicon: 80, Beryllium: 90));
+ "phase-synthesizer" => SimpleBlock::new(3, true, cost!(Carbide: 90, Silicon: 100, Thorium: 100, Tungsten: 200));
+ // heat reactor
// sandbox only
"heat-source" => SimpleBlock::new(1, false, &[]);
);
diff --git a/src/block/fluid.rs b/src/block/fluid.rs
index 90289bc..0454461 100644
--- a/src/block/fluid.rs
+++ b/src/block/fluid.rs
@@ -13,8 +13,8 @@ use crate::data::GridPos;
use crate::fluid;
use crate::item::storage::Storage;
-make_register!
-(
+make_register! {
+ "reinforced-pump" => SimpleBlock::new(2, true, cost!(Beryllium: 40, Tungsten: 30, Silicon: 20));
"mechanical-pump" => SimpleBlock::new(1, true, cost!(Copper: 15, Metaglass: 10));
"rotary-pump" => SimpleBlock::new(2, true, cost!(Copper: 70, Metaglass: 50, Titanium: 35, Silicon: 20));
"impulse-pump" => SimpleBlock::new(3, true, cost!(Copper: 80, Metaglass: 90, Titanium: 40, Thorium: 35, Silicon: 30));
@@ -27,10 +27,16 @@ make_register!
"liquid-junction" => SimpleBlock::new(1, true, cost!(Metaglass: 8, Graphite: 4));
"bridge-conduit" => BridgeBlock::new(1, true, cost!(Metaglass: 8, Graphite: 4), 4, true);
"phase-conduit" => BridgeBlock::new(1, true, cost!(Metaglass: 20, Titanium: 10, Silicon: 7, PhaseFabric: 5), 12, true);
+ "reinforced-conduit" => SimpleBlock::new(1, false, cost!(Beryllium: 2));
+ "reinforced-liquid-junction" => SimpleBlock::new(1, true, cost!(Graphite: 4, Beryllium: 8));
+ "reinforced-bridge-conduit" => BridgeBlock::new(1, true, cost!(Graphite: 8, Beryllium: 20), 4, true);
+ "reinforced-liquid-router" => SimpleBlock::new(1, true, cost!(Graphite: 8, Beryllium: 4));
+ "reinforced-liquid-container" => SimpleBlock::new(2, true, cost!(Tungsten: 10, Beryllium: 16));
+ "reinforced-liquid-tank" => SimpleBlock::new(3, true, cost!(Tungsten: 40, Beryllium: 50));
// sandbox only
"liquid-source" => FluidBlock::new(1, true, &[]);
"liquid-void" => SimpleBlock::new(1, true, &[]);
-);
+}
pub struct FluidBlock {
size: u8,
diff --git a/src/block/mod.rs b/src/block/mod.rs
index 74e8c2c..2c2e597 100644
--- a/src/block/mod.rs
+++ b/src/block/mod.rs
@@ -168,6 +168,12 @@ pub struct Block {
logic: BoxAccess<'static, dyn BlockLogic + Sync>,
}
+impl PartialEq for Block {
+ fn eq(&self, rhs: &Block) -> bool {
+ self.name == rhs.name
+ }
+}
+
impl Block {
#[must_use]
pub const fn new(
diff --git a/src/block/payload.rs b/src/block/payload.rs
index 8339df3..39a6534 100644
--- a/src/block/payload.rs
+++ b/src/block/payload.rs
@@ -4,7 +4,8 @@ use std::fmt;
use crate::block::simple::{cost, state_impl, BuildCost, SimpleBlock};
use crate::block::{
- self, impl_block, make_register, BlockLogic, DataConvertError, DeserializeError, SerializeError,
+ self, impl_block, make_register, transport::BridgeBlock, BlockLogic, DataConvertError,
+ DeserializeError, SerializeError,
};
use crate::content;
use crate::data::dynamic::{DynData, DynType};
@@ -28,8 +29,30 @@ make_register! {
cost!(Lead: 4000, Thorium: 1000, Silicon: 3000, Plastanium: 600, PhaseFabric: 600, SurgeAlloy: 800));
"repair-point" => SimpleBlock::new(1, true, cost!(Copper: 30, Lead: 30, Silicon: 20));
"repair-turret" => SimpleBlock::new(2, true, cost!(Thorium: 80, Silicon: 90, Plastanium: 60));
+ "tank-fabricator" => SimpleBlock::new(3, true, cost!(Silicon: 200, Beryllium: 150));
+ "ship-fabricator" => SimpleBlock::new(3, true, cost!(Silicon: 250, Beryllium: 200));
+ "mech-fabricator" => SimpleBlock::new(3, true, cost!(Silicon: 200, Graphite: 300, Tungsten: 60));
+ "tank-refabricator" => SimpleBlock::new(3, true, cost!(Beryllium: 200, Tungsten: 80, Silicon: 100));
+ "mech-refabricator" => SimpleBlock::new(3, true, cost!(Beryllium: 250, Tungsten: 120, Silicon: 150));
+ "ship-refabricator" => SimpleBlock::new(3, true, cost!(Beryllium: 200, Tungsten: 100, Silicon: 150, Oxide: 40));
+ "prime-refabricator" => SimpleBlock::new(5, true, cost!(Thorium: 250, Oxide: 200, Tungsten: 200, Silicon: 400));
+ "tank-assembler" => SimpleBlock::new(5, true, cost!(Thorium: 500, Oxide: 150, Carbide: 80, Silicon: 500));
+ "ship-assembler" => SimpleBlock::new(5, true, cost!(Carbide: 100, Oxide: 200, Tungsten: 500, Silicon: 800, Thorium: 400));
+ "mech-assembler" => SimpleBlock::new(5, true, cost!(Carbide: 200, Thorium: 600, Oxide: 200, Tungsten: 500, Silicon: 900)); // smh collaris
+ "basic-assembler-module" => SimpleBlock::new(5, true, cost!(Carbide: 300, Thorium: 500, Oxide: 200, PhaseFabric: 400)); // the dummy block
+ // payload
"payload-conveyor" => SimpleBlock::new(3, false, cost!(Copper: 10, Graphite: 10));
- "payload-router" => SimpleBlock::new(3, false, cost!(Copper: 10, Graphite: 15));
+ "payload-router" => PayloadBlock::new(3, false, cost!(Copper: 10, Graphite: 15));
+ "reinforced-payload-conveyor" => SimpleBlock::new(3, false, cost!(Tungsten: 10));
+ "reinforced-payload-router" => SimpleBlock::new(3, false, cost!(Tungsten: 15));
+ "payload-mass-driver" => BridgeBlock::new(3, true, cost!(Tungsten: 120, Silicon: 120, Graphite: 50), 700, false);
+ "large-payload-mass-driver" => BridgeBlock::new(5, true, cost!(Thorium: 200, Tungsten: 200, Silicon: 200, Graphite: 100, Oxide: 30), 1100, false);
+ "small-deconstructor" => SimpleBlock::new(3, true, cost!(Beryllium: 100, Silicon: 100, Oxide: 40, Graphite: 80));
+ "deconstructor" => SimpleBlock::new(5, true, cost!(Beryllium: 250, Oxide: 100, Silicon: 250, Carbide: 250));
+ "constructor" => PayloadBlock::new(3, true, cost!(Silicon: 100, Beryllium: 150, Tungsten: 80));
+ "large-constructor" => PayloadBlock::new(3, true, cost!(Silicon: 150, Oxide: 150, Tungsten: 200, PhaseFabric: 40));
+ "payload-loader" => SimpleBlock::new(3, false, cost!(Graphite: 50, Silicon: 50, Tungsten: 80));
+ "payload-unloader" => SimpleBlock::new(3, false, cost!(Graphite: 50, Silicon: 50, Tungsten: 30));
// sandbox only
"payload-source" => PayloadBlock::new(5, false, &[]);
"payload-void" => SimpleBlock::new(5, true, &[]);
diff --git a/src/block/power.rs b/src/block/power.rs
index e21d342..6cc71d5 100644
--- a/src/block/power.rs
+++ b/src/block/power.rs
@@ -10,8 +10,7 @@ use crate::data::dynamic::{DynData, DynType};
use crate::data::GridPos;
use crate::item::storage::Storage;
-make_register!
-(
+make_register! {
"power-node" => ConnectorBlock::new(1, true, cost!(Copper: 1, Lead: 3), 10);
"power-node-large" => ConnectorBlock::new(2, true, cost!(Lead: 10, Titanium: 5, Silicon: 3), 15);
"surge-tower" => ConnectorBlock::new(2, true, cost!(Lead: 10, Titanium: 7, Silicon: 15, SurgeAlloy: 15), 2);
@@ -28,11 +27,19 @@ make_register!
"thorium-reactor" => SimpleBlock::new(3, true, cost!(Lead: 300, Metaglass: 50, Graphite: 150, Thorium: 150, Silicon: 200));
"impact-reactor" => SimpleBlock::new(4, true,
cost!(Lead: 500, Metaglass: 250, Graphite: 400, Thorium: 100, Silicon: 300, SurgeAlloy: 250));
+ "beam-node" => ConnectorBlock::new(1, true, cost!(Beryllium: 8), 4);
+ "beam-tower" => ConnectorBlock::new(3, true, cost!(Beryllium: 30, Oxide: 10, Silicon: 10), 12);
+ "turbine-condenser" => SimpleBlock::new(3, true, cost!(Beryllium: 60));
+ "chemical-combustion-chamber" => SimpleBlock::new(3, true, cost!(Graphite: 40, Tungsten: 40, Oxide: 40, Silicon: 30));
+ "pyrolosis-generator" => SimpleBlock::new(3, true, cost!(Graphite: 50, Carbide: 50, Oxide: 60, Silicon: 50));
+ "flux-reactor" => SimpleBlock::new(5, true, cost!(Graphite: 300, Carbide: 200, Oxide: 100, Silicon: 600, SurgeAlloy: 300));
+ "neoplasia-reactor" => SimpleBlock::new(5, true, cost!(Tungsten: 1000, Carbide: 300, Oxide: 150, Silicon: 500, PhaseFabric: 300, SurgeAlloy: 200));
+ // editor only
+ "beam-link" => ConnectorBlock::new(3, true, &[], 12);
// sandbox only
"power-source" => ConnectorBlock::new(1, true, &[], 100);
"power-void" => SimpleBlock::new(1, true, &[]);
-);
-
+}
pub struct ConnectorBlock {
size: u8,
symmetric: bool,
diff --git a/src/block/simple.rs b/src/block/simple.rs
index e3d8db7..d57f091 100644
--- a/src/block/simple.rs
+++ b/src/block/simple.rs
@@ -80,7 +80,7 @@ impl BlockLogic for SimpleBlock {
}
macro_rules! cost {
- ($($item:ident: $cnt:literal),+) => {
+ ($($item:ident: $cnt:expr),+) => {
&[$((crate::item::Type::$item, $cnt)),*]
};
}
diff --git a/src/block/transport.rs b/src/block/transport.rs
index 1125ec5..4494d86 100644
--- a/src/block/transport.rs
+++ b/src/block/transport.rs
@@ -27,6 +27,17 @@ make_register! {
"overflow-gate" => SimpleBlock::new(1, true, cost!(Copper: 4, Lead: 2));
"underflow-gate" => SimpleBlock::new(1, true, cost!(Copper: 4, Lead: 2));
"mass-driver" => BridgeBlock::new(3, true, cost!(Lead: 125, Titanium: 125, Thorium: 50, Silicon: 75), 55, false);
+ "duct" => SimpleBlock::new(1, false, cost!(Beryllium: 1));
+ "armored-duct" => SimpleBlock::new(1, false, cost!(Beryllium: 2, Tungsten: 1));
+ "duct-router" => ItemBlock::new(1, true, cost!(Beryllium: 10));
+ "overflow-duct" => SimpleBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8));
+ "underflow-duct" => SimpleBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8));
+ "duct-bridge" => BridgeBlock::new(1, true, cost!(Beryllium: 20), 3, true);
+ "duct-unloader" => ItemBlock::new(1, true, cost!(Graphite: 20, Silicon: 20, Tungsten: 10));
+ "surge-conveyor" => SimpleBlock::new(1, false, cost!(SurgeAlloy: 1, Tungsten: 1));
+ "surge-router" => SimpleBlock::new(1, false, cost!(SurgeAlloy: 5, Tungsten: 1)); // not symmetric
+ "unit-cargo-loader" => SimpleBlock::new(3, true, cost!(Silicon: 80, SurgeAlloy: 50, Oxide: 20));
+ "unit-cargo-unload-point" => ItemBlock::new(2, true, cost!(Silicon: 60, Tungsten: 60));
// sandbox only
"item-source" => ItemBlock::new(1, true, &[]);
"item-void" => SimpleBlock::new(1, true, &[]);
diff --git a/src/block/turret.rs b/src/block/turret.rs
index 6c91ce2..2280ac9 100644
--- a/src/block/turret.rs
+++ b/src/block/turret.rs
@@ -1,8 +1,7 @@
use crate::block::make_register;
use crate::block::simple::{cost, SimpleBlock};
-make_register!
-(
+make_register! {
"duo" => SimpleBlock::new(1, true, cost!(Copper: 35));
"scatter" => SimpleBlock::new(2, true, cost!(Copper: 85, Lead: 45));
"scorch" => SimpleBlock::new(1, true, cost!(Copper: 25, Graphite: 22));
@@ -21,4 +20,13 @@ make_register!
"foreshadow" => SimpleBlock::new(4, true, cost!(Copper: 1000, Metaglass: 600, Silicon: 600, Plastanium: 200, SurgeAlloy: 300));
"spectre" => SimpleBlock::new(4, true, cost!(Copper: 900, Graphite: 300, Thorium: 250, Plastanium: 175, SurgeAlloy: 250));
"meltdown" => SimpleBlock::new(4, true, cost!(Copper: 1200, Lead: 350, Graphite: 300, Silicon: 325, SurgeAlloy: 325));
-);
+ "breach" => SimpleBlock::new(3, true, cost!(Beryllium: 150, Silicon: 150, Graphite: 250));
+ "diffuse" => SimpleBlock::new(3, true, cost!(Beryllium: 150, Silicon: 200, Graphite: 200, Tungsten: 50));
+ "sublimate" => SimpleBlock::new(3, true, cost!(Tungsten: 150, Silicon: 200, Oxide: 40, Beryllium: 400));
+ "titan" => SimpleBlock::new(4, true, cost!(Tungsten: 250, Silicon: 300, Thorium: 400));
+ "disperse" => SimpleBlock::new(4, true, cost!(Thorium: 50, Oxide: 150, Silicon: 200, Beryllium: 350));
+ "afflict" => SimpleBlock::new(4, true, cost!(SurgeAlloy: 100, Silicon: 200, Graphite: 250, Oxide: 40));
+ "lustre" => SimpleBlock::new(4, true, cost!(Silicon: 250, Graphite: 200, Oxide: 50, Carbide: 90));
+ "scathe" => SimpleBlock::new(5, true, cost!(Oxide: 200, SurgeAlloy: 400, Silicon: 800, Carbide: 500, PhaseFabric: 300));
+ "malign" => SimpleBlock::new(5, true, cost!(Carbide: 400, Beryllium: 2000, Silicon: 800, Graphite: 800, PhaseFabric: 300));
+}
diff --git a/src/data/base64.rs b/src/data/base64.rs
index 1a39c0b..303e9e0 100644
--- a/src/data/base64.rs
+++ b/src/data/base64.rs
@@ -1,338 +1,12 @@
-use std::error::Error;
-use std::fmt;
+use base64::{engine::general_purpose, engine::Engine as _};
+pub use base64::{DecodeSliceError as DecodeError, EncodeSliceError as EncodeError};
-const CHARS: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-const PADDING: u8 = b'=';
-
-fn decode_char(val: u8) -> Option<usize> {
- match val {
- b'A'..=b'Z' => Some((val - b'A') as usize),
- b'a'..=b'z' => Some(26 + (val - b'a') as usize),
- b'0'..=b'9' => Some(52 + (val - b'0') as usize),
- b'+' => Some(0x3E),
- b'/' => Some(0x3F),
- _ => None,
- }
-}
+const BASE64: general_purpose::GeneralPurpose = general_purpose::STANDARD;
pub fn encode(input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
- let use_pad = input.len() % 3 != 0;
- let expect_len = if use_pad {
- 4 * (input.len() / 3 + 1)
- } else {
- 4 * (input.len() / 3)
- };
- if output.len() < expect_len {
- return Err(EncodeError::Overflow {
- need: expect_len,
- have: output.len(),
- });
- }
- let mut in_pos = 0usize;
- let mut out_pos = 0usize;
- while input.len() - in_pos >= 3 {
- let buff = ((input[in_pos] as usize) << 16)
- | ((input[in_pos + 1] as usize) << 8)
- | (input[in_pos + 2] as usize);
- output[out_pos] = CHARS[buff >> 18];
- output[out_pos + 1] = CHARS[(buff >> 12) & 0x3F];
- output[out_pos + 2] = CHARS[(buff >> 6) & 0x3F];
- output[out_pos + 3] = CHARS[buff & 0x3F];
- out_pos += 4;
- in_pos += 3;
- }
- let remain = input.len() - in_pos;
- if remain > 0 {
- let mut buff = (input[in_pos] as usize) << 16;
- if remain > 1 {
- buff |= (input[in_pos + 1] as usize) << 8;
- }
- in_pos += if remain > 1 { 2 } else { 1 };
- output[out_pos] = CHARS[buff >> 18];
- output[out_pos + 1] = CHARS[(buff >> 12) & 0x3F];
- if remain > 1 {
- output[out_pos + 2] = CHARS[(buff >> 6) & 0x3F];
- } else {
- output[out_pos + 2] = PADDING;
- }
- output[out_pos + 3] = PADDING;
- out_pos += 4;
- }
- assert_eq!(
- in_pos,
- input.len(),
- "missed input ({in_pos}, expected {})",
- input.len()
- );
- assert_eq!(
- out_pos, expect_len,
- "missed output ({out_pos}, expected {expect_len})"
- );
- Ok(out_pos)
-}
-
-macro_rules! do_decode {
- ($input:ident, $in_pos:expr) => {
- match decode_char($input[$in_pos]) {
- None => {
- return Err(DecodeError::Malformed {
- at: $in_pos,
- value: $input[$in_pos],
- })
- }
- Some(v) => v,
- }
- };
+ BASE64.encode_slice(input, output)
}
pub fn decode(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
- if input.len() % 4 != 0 {
- // can't decode, but check for malformed data first
- let mut in_pad = false;
- for (i, &c) in input.iter().enumerate() {
- if c == PADDING {
- if i % 4 < 2 {
- return Err(DecodeError::Malformed { at: i, value: c });
- }
- in_pad = true;
- } else if in_pad && i % 4 == 0 {
- return Err(DecodeError::TrailingData { at: i });
- } else if (in_pad && i % 4 == 3) || decode_char(c).is_none() {
- return Err(DecodeError::Malformed { at: i, value: c });
- }
- }
- return Err(DecodeError::Truncated);
- }
- let pad_len = if input.is_empty() || input[input.len() - 1] != PADDING {
- 0
- } else if input[input.len() - 2] != PADDING {
- 1
- } else {
- 2
- };
- let expect_len = input.len() / 4 * 3 - pad_len;
- if output.len() < expect_len {
- return Err(DecodeError::Overflow {
- need: expect_len,
- have: output.len(),
- });
- }
- if input.is_empty() {
- Ok(0)
- } else {
- let mut in_pos = 0usize;
- let mut out_pos = 0usize;
- while in_pos < input.len() - 4 {
- let c0 = do_decode!(input, in_pos);
- let c1 = do_decode!(input, in_pos + 1);
- let c2 = do_decode!(input, in_pos + 2);
- let c3 = do_decode!(input, in_pos + 3);
- let buff = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3;
- output[out_pos] = (buff >> 16) as u8;
- output[out_pos + 1] = (buff >> 8) as u8;
- output[out_pos + 2] = buff as u8;
- in_pos += 4;
- out_pos += 3;
- }
-
- let c0 = do_decode!(input, in_pos);
- let c1 = do_decode!(input, in_pos + 1);
- let mut buff = (c0 << 18) | (c1 << 12);
- output[out_pos] = (buff >> 16) as u8;
- out_pos += 1;
- if input[in_pos + 2] == PADDING {
- if input[in_pos + 3] != PADDING {
- return Err(DecodeError::Malformed {
- at: in_pos + 3,
- value: input[in_pos + 3],
- });
- }
- } else {
- buff |= do_decode!(input, in_pos + 2) << 6;
- output[out_pos] = (buff >> 8) as u8;
- out_pos += 1;
- if input[in_pos + 3] != PADDING {
- buff |= do_decode!(input, in_pos + 3);
- output[out_pos] = buff as u8;
- out_pos += 1;
- }
- }
- in_pos += 4;
-
- assert_eq!(
- in_pos,
- input.len(),
- "missed input ({in_pos}, expected {})",
- input.len()
- );
- assert_eq!(
- out_pos, expect_len,
- "missed output ({out_pos}, expected {expect_len})"
- );
- Ok(out_pos)
- }
-}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum DecodeError {
- Malformed { at: usize, value: u8 },
- Overflow { need: usize, have: usize },
- Truncated,
- TrailingData { at: usize },
-}
-
-impl fmt::Display for DecodeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Malformed { at, value } => {
- write!(f, "malformed base64 character {value:?} (at {at})")
- }
- Self::Overflow { need, have } => {
- write!(f, "decoder overflow (need {need}, but only have {have})")
- }
- Self::Truncated => f.write_str("truncated base64 input stream"),
- Self::TrailingData { at } => write!(f, "trailing data in base64 stream (at {at})"),
- }
- }
-}
-
-impl Error for DecodeError {}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum EncodeError {
- Overflow { need: usize, have: usize },
-}
-
-impl fmt::Display for EncodeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Overflow { need, have } => {
- write!(f, "encoder overflow (need {need}, but only have {have})")
- }
- }
- }
-}
-
-impl Error for EncodeError {}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn validate_chars() {
- for (i, &c0) in CHARS.iter().enumerate() {
- assert_ne!(c0, PADDING, "padding character in data charset at {i}");
- if i > 0 {
- for (j, &c1) in CHARS[..i].iter().enumerate() {
- assert_ne!(c1, c0, "duplicate data character at {j} and {i}");
- }
- }
- }
- }
-
- #[test]
- fn decode_matches_chars() {
- for (i, &c0) in CHARS.iter().enumerate() {
- assert_eq!(
- decode_char(c0),
- Some(i),
- "data character {c0} (at {i}) isn't decoded properly"
- );
- }
- }
-
- macro_rules! test_codec {
- ($func:ident, $input:expr => $expect:expr) => {{
- const EMPTY: u8 = 0xC9u8; // arbitrary
- let mut output = [EMPTY; 64];
- let input = $input;
- let expect = $expect;
- assert_eq!($func(input, &mut output), Ok(expect.len()));
- assert_eq!(&output[..expect.len()], expect);
- assert!(
- output[expect.len()..].iter().all(|&x| x == EMPTY),
- "output buffer overflow"
- );
- }};
- }
-
- #[test]
- fn encoder_success() {
- test_codec!(encode, b"Hello Wor" => b"SGVsbG8gV29y");
- test_codec!(encode, b"Hello Worl" => b"SGVsbG8gV29ybA==");
- test_codec!(encode, b"Hello World" => b"SGVsbG8gV29ybGQ=");
- test_codec!(encode, b"Hello World!" => b"SGVsbG8gV29ybGQh");
- }
-
- #[test]
- fn decoder_success() {
- test_codec!(decode, b"SGVsbG8gV29y" => b"Hello Wor");
- test_codec!(decode, b"SGVsbG8gV29ybA==" => b"Hello Worl");
- test_codec!(decode, b"SGVsbG8gV29ybGQ=" => b"Hello World");
- test_codec!(decode, b"SGVsbG8gV29ybGQh" => b"Hello World!");
- }
-
- #[test]
- fn encoder_fail() {
- let mut output = [0u8; 64];
- assert_eq!(
- encode(b"Hello Worl", &mut output[..15]),
- Err(EncodeError::Overflow { need: 16, have: 15 })
- );
- assert_eq!(
- encode(b"Hello World!", &mut output[..0]),
- Err(EncodeError::Overflow { need: 16, have: 0 })
- );
- }
-
- #[test]
- fn decoder_fail() {
- let mut output = [0u8; 64];
- assert_eq!(
- decode(b"SGVsbG8gV29ybA==", &mut output[..9]),
- Err(DecodeError::Overflow { need: 10, have: 9 })
- );
- assert_eq!(
- decode(b"SGVsbG8gV29ybGQh", &mut output[..11]),
- Err(DecodeError::Overflow { need: 12, have: 11 })
- );
- assert_eq!(
- decode(b"SGVsbG8gV29ybA", &mut output),
- Err(DecodeError::Truncated)
- );
- assert_eq!(
- decode(b"SGVsbG8gV29yb", &mut output),
- Err(DecodeError::Truncated)
- );
- assert_eq!(
- decode(b"SGVsbG8gV29y\n", &mut output),
- Err(DecodeError::Malformed {
- at: 12,
- value: b'\n'
- })
- );
- assert_eq!(
- decode(b"SGVs_bG8gV29y\n", &mut output),
- Err(DecodeError::Malformed { at: 4, value: b'_' })
- );
- assert_eq!(
- decode(b"SGVsbG8gV29ybA==*", &mut output),
- Err(DecodeError::TrailingData { at: 16 })
- );
- assert_eq!(
- decode(b"SGVsbG8gV29ybA=*", &mut output),
- Err(DecodeError::Malformed {
- at: 15,
- value: b'*'
- })
- );
- assert_eq!(
- decode(b"SGVsbG8gV29ybA=A", &mut output),
- Err(DecodeError::Malformed {
- at: 15,
- value: b'A'
- })
- );
- }
+ BASE64.decode_slice(input, output)
}
diff --git a/src/data/schematic.rs b/src/data/schematic.rs
index 9927ad2..92f8f72 100644
--- a/src/data/schematic.rs
+++ b/src/data/schematic.rs
@@ -28,17 +28,13 @@ pub struct Placement<'l> {
rot: Rotation,
}
-impl<'l> Placement<'l> {
- #[must_use]
- pub fn get_pos(&self) -> GridPos {
- self.pos
- }
-
- #[must_use]
- pub fn get_block(&self) -> &'l Block {
- self.block
+impl PartialEq for Placement<'_> {
+ fn eq(&self, rhs: &Placement<'_>) -> bool {
+ self.pos == rhs.pos && self.block == rhs.block && self.rot == rhs.rot
}
+}
+impl<'l> Placement<'l> {
#[must_use]
pub fn get_state(&self) -> Option<&dyn Any> {
match self.state {
@@ -62,11 +58,6 @@ impl<'l> Placement<'l> {
Ok(std::mem::replace(&mut self.state, state))
}
- #[must_use]
- pub fn get_rotation(&self) -> Rotation {
- self.rot
- }
-
pub fn set_rotation(&mut self, rot: Rotation) -> Rotation {
std::mem::replace(&mut self.rot, rot)
}
@@ -89,13 +80,22 @@ impl<'l> Clone for Placement<'l> {
#[derive(Clone)]
pub struct Schematic<'l> {
- width: u16,
- height: u16,
- tags: HashMap<String, String>,
- blocks: Vec<Placement<'l>>,
+ pub width: u16,
+ pub height: u16,
+ pub tags: HashMap<String, String>,
+ pub blocks: Vec<Placement<'l>>,
lookup: Vec<Option<usize>>,
}
+impl<'l> PartialEq for Schematic<'l> {
+ fn eq(&self, rhs: &Schematic<'l>) -> bool {
+ self.width == rhs.width
+ && self.height == rhs.height
+ && self.blocks == rhs.blocks
+ && self.tags == rhs.tags
+ }
+}
+
impl<'l> Schematic<'l> {
#[must_use]
pub fn new(width: u16, height: u16) -> Self {
@@ -127,25 +127,6 @@ impl<'l> Schematic<'l> {
}
#[must_use]
- pub fn get_width(&self) -> u16 {
- self.width
- }
-
- #[must_use]
- pub fn get_height(&self) -> u16 {
- self.height
- }
-
- #[must_use]
- pub fn get_tags(&self) -> &HashMap<String, String> {
- &self.tags
- }
-
- pub fn get_tags_mut(&mut self) -> &mut HashMap<String, String> {
- &mut self.tags
- }
-
- #[must_use]
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
@@ -754,6 +735,12 @@ impl fmt::Display for ResizeError {
impl Error for ResizeError {}
+impl fmt::Debug for Schematic<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self, f)
+ }
+}
+
impl<'l> fmt::Display for Schematic<'l> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/*
@@ -1407,30 +1394,51 @@ impl FusedIterator for PosIter {}
mod test {
use super::*;
- macro_rules!test_iter
- {
- ($name:ident, $it:expr, $($val:expr),+) =>
- {
+ macro_rules! test_iter {
+ ($name:ident, $it:expr, $($val:expr),+) => {
#[test]
- fn $name()
- {
+ fn $name() {
let mut it = $it;
$(test_iter!(impl it, $val);)+
}
};
- (impl $it:ident, $val:literal) =>
- {
- for _ in 0..$val
- {
+ (impl $it:ident, $val:literal) => {
+ for _ in 0..$val {
assert_ne!($it.next(), None, "iterator returned None too early");
}
};
- (impl $it:ident, $val:expr) =>
- {
+ (impl $it:ident, $val:expr) => {
assert_eq!($it.next(), $val);
};
}
+ macro_rules! test_schem {
+ ($name:ident, $($val:expr),+) => {
+ #[test]
+ fn $name() {
+ let reg = crate::block::build_registry();
+ let mut ser = SchematicSerializer(&reg);
+ $(
+ let parsed = ser.deserialize_base64($val).unwrap();
+ println!("{}", parsed.tags.get("name").unwrap());
+ let unparsed = ser.serialize_base64(&parsed).unwrap();
+ let parsed2 = ser.deserialize_base64(&unparsed).unwrap();
+ assert_eq!(parsed, parsed2);
+ )*
+ }
+ };
+ }
+
+ test_schem! {
+ ser_de,
+ "bXNjaAF4nCVNy07DMBCcvC1c4MBnoNz4G8TBSSxRycSRbVr646iHlmUc2/KOZ3dmFo9QDdrVfFkMb9Gsi5mgFxvncNzS0a8Aemcm6yLq948Bz2eTbBjtTwpmTj7gafs00Y6zX0/2Qt6dzLdLeNj8mbrVLxZ6ciamcQlH59BHH5iAYTKJeOGCF6AisFSoBxF55V+hJm1Lvwca8lpVIuzlS0eGLoMqTGUG6OLRJes3Mw40E5ijc2QedkPuU3DfLX0eHriDsgMapaScu9zkT26o5Uq8EmV/zS5vi4tr/wHvJE7M",
+ "bXNjaAF4nE2MzWrEMAyEJ7bjdOnPobDQvfUF8kSlhyTWFlOv3VWcQvv0lRwoawzSjL4ZHOAtXJ4uhEdi+oz8ek5bDCvuA60Lx68aSwbg0zRTWmHe3j2emWI+F14ojEvJYYsVD5RoqVzSzy8xDjNNlzGXQHi5gVO8SvnIZasCnW4uM8fwQf9tT9+Ua1OUV0GBI9ozHToY6IeDtaIACxkOnaoe1rVrg2RV1cP0CuycLA5+LxuUU+U055W0Yrb4sEcGNQ3u1NTh9iHmH6qaOTI=",
+ "bXNjaAF4nE2R226kQAxEzW1oYC5kopV23/IDfMw+R3ng0klaYehsD6w2+fqtamuiDILCLtvH9MgPaTLJl/5ipR3cy4MN9s2FB//PTVaayV7H4N5X5xcR2c39YOerpI9Pe/kVrFuefRjt1A3BTS+2G/0ybW6V41+7rDGyy9UGjNnGtQt+W78C7ZCcgVSD7S/d4kH8+W3q7P5sbrr1nb85N9DeznZcg58/PlFxx6V77tqNr/1lQOr0anuQ7eQCCn2QQ6Rvy+z7Cb7Ib9xSSJpICsGDV5bxoVJKxpSRLIdUKrVkBQoSkVxYJDuWq5SaNByboUEYJ5LgmFlZRhdejit6oDO5Uw/trDTqgWfgpCqFiiG91MVL7IJfLKck3NooyBDEZM4Gw+9jtJOEXgQZ7TQAJZSaM+POFd5TSWpIoVHEVsqrlUcX8xq+U2pi94wyCHZpICn625jAGdVy4DxGpdom2gXeKu2OIw+6w5E7UABnMgKO9CgxOukiHBGjmGz1dFp+VQO57cA7pUR4+wVvFd5q9x2aQT0r/Ew4k/FfPyvunjhGaPgPoVJdLw==",
+ "bXNjaAF4nGNgZmBmZmDJS8xNZeBOyslPzlYAkwzcKanFyUWZBSWZ+XkMDAxsOYlJqTnFDEzRsYwMfAWJlTn5iSm6RfmlJalFQGlGEGJkZWSYxQAAcBkUPA=="
+ // "bXNjaAF4nD1TDUxTVxS+r6+vr30triCSVjLXiulKoAjMrJRkU8b4qSgLUAlIZ1rah7yt9L31h1LMMCNbRAQhYrKwOnEslhCcdmzJuohL1DjZT4FJtiVsoG5LFtzmGgGngHm790mam7x77ne+c945934HKIAcB2K3vYUGScXmWvM+TQ3jYhysG8idtNfhYTgfAw8ASFz2RtrlBaKG12VA6X1KMjg8fgfT6KLBJi7osfsYH21oYdpoD6A4NkB7DG7WSQOJl/X4IPYM426loeU0bABSv9vF2p3I1cI4PKyB87AO2gu9gGi1+10+kMTCiCYXGzActvtoWEY+ABhcIgzaOBCJ4EZICYDx6xAV86vCdx2IAS5QJJAEIRkQ4XAjAHSIIITBUCCGRwIuESCgheEIkwgYIpEAF4I3wSw9bWccTpvNmVkZy5raWT1p3r+vajJ2odyQb+HAW9HxvV556vfvpNy4oVUfDyq36Kyqe73xsdemprMyv52uAreYwcXzJaPU+aDp8fFM24nuzUvVqYo9yr7CjFT/aDDzUUz8S8W7g+X3VCpVnargblNubl4kI1q6J+cFPH2HS6VSF5xzZWhCyYCKO2FjqAEprB9WRsJbwNFFoLKhITRCQheBbByQCMAQQwow1I8M9oPJ2870npqvvq5RvvfFyYE3hjrLmst3TixrV0XSN08Uax/UrMSeHdmKDdj8uhh3Pef2Wa+qDljrj82pK+aM300sl0eTrC/rL3zzZKZhRWFMq+mLvvTZb0bbweGZL/85ywwnl4RLzR9MBdIGy0LJowOWHxoOY2EiaJ/7s7ZP0Tg2wjWb3y6Lm3IPRNonw/0yT/+lZsdFy/LmUEp2RojHl68B41zDx43WJ/qANkwdVOvGtxjzpgo/keUURn2XK6zerz9Km10w3Vb8Ww/t/UdmHyx7fXwEcPiP0w1Xx9f+/m/X/d13Wiees8yPnk69ePlS9Yuf9sQf1dvVB27mm68U+51Fj7emzS+mzw1jzwuvTKFXHoK30l9EXctVlozIiSPdpk5djrW965BmV1XW4qsp8kNXmtWztdklXXTa0u6lO0d1+GS3TV/Q95O+17+S23Hs5sIfP4e/uqvd9oo+p7u0cYiPb4+9f/L+Qn3PmuXDdDai/ev0ts69I9nuNTOXp9HfOmoy/a5Y9D2cYYsebq+cKgB1V9vXdYFfOz7vWiVCLNnUUVkLOGO9umVN0jl2KoIjYSINEzgUORoDBKAnJwSLTLikQOBSAoC0ABBAbMgDWYIuBBeFRE7CbBCXCAwxFBAJPRgCSAFADBlykokcZCKHFAkPbSRKRaFUUsRGUyZLTJksMWWyjSlDJKhfFALZmFAJdFPo1+gkQVKXw/EW8/zToeZ5fh0t/H+V6k8+"
+ // "bXNjaAF4nGNgZ2BjZmDJS8xNZRByrkzOyc9LdctJLEpVT1F4v3AaA3dKanFyUWZBSWZ+HgMDA1tOYlJqTjEDU3QsFwN/eWJJapFuakVJUWJySX4RA3tSYglQpJKBPRliEgNXQX45UElefkoqA39SUWZKeqpucn5eWWolSDmQlVKaWcIgkpyfm1RaDLJDNz01L7UoEWQaf3JRZX5aTmlmim5uZkUqUCA3M7koX7egKD85tbgYqIIlIzEzB+gqQQYwYGYEEkwgxMjAAuQCKSYOZgam//8ZWP7/+/+HgZGZBSTPDlEGVMEKVssAooAMNqAWBpA0SCdQKTMDMzvEZAZGRhCXBZXLyv7///8cIC4AKgZaCOKGAHEh0DBWBpAKIAW0hQNkAR9Q16+KOXtDbhfNNhBQneu5XNV+o/0FSYFCtbrHC+dm3v/MnK3TnKUYGCv0+X3uygksNwzr3jbr755T/O3NuiOxk+7YX7lSoNb3oW3CUq7vKxq4bn1rUKqJsqldfsLX2KkoICQ679L8bW8fCLaY3K+LfGLIe6hoXlaW3iMvrsUq7Hc9Mq1W/OrydlRf+VK9+Ov1iSmsK1deCPKVPF69dG+I5RQ3qSf2PLmlH2bkLwgT4Q3V5+3qnBDPqcG1dNuqZfETim+6G0UqT9b5bGsUznqqb7GZxoxc5eUMT/JvvC79UdlruPvxJis9XhbeTZXLN+68WFB41+DkNRv7uZEGOr/2rvPPu8ZfyXRLI+zoUnmLXRu3+nz0EnP1Omq39TLX3c23cleZ8F62FSnMVCviO2H6tWXB7z2LpA02vleTOXiJK20BT+ADsencfQd0tlqrfQuoWut5dHaX1OP/KwIY5r66Zp4T9+2p241L0XvPfu5w/Zu3bNX77V89kt42zOLf9jJ9vk+msV1vy/Knlywv7Lh4NEY7fvHay0t3Sxo+2q918+je/O23P+50/qEWe45XqGzaR1vh0h1idRwBYZter2DKPJv559VDhbXSHzgin2x8PeXIKsvmtRIVB5V5b/1zcBP+f7VpfuP1OLcJKiePP7n8paroYrul0uF88dp5619+MN8Z7WT0p7DTUqftYOt3XqN51hGf+IVDH0UwcDKwAZMFMwCWiVNe"
+ }
+
test_iter!(
block_iter,
Schematic::new(3, 4).pos_iter(),
diff --git a/src/exe/edit.rs b/src/exe/edit.rs
deleted file mode 100644
index 80a1506..0000000
--- a/src/exe/edit.rs
+++ /dev/null
@@ -1,1488 +0,0 @@
-use std::borrow::Cow;
-use std::env::Args;
-use std::fs;
-use std::io::{self, Write};
-
-use plandustry::block::{build_registry, BlockRegistry, Rotation};
-use plandustry::data::dynamic::DynData;
-use plandustry::data::schematic::{Placement, ResizeError, Schematic, SchematicSerializer};
-use plandustry::data::{base64, DataRead, DataWrite, GridPos, Serializer};
-use plandustry::registry::RegistryEntry;
-
-use crate::args::{self, ArgCount, ArgOption, OptionHandler};
-use crate::print::print_schematic;
-use crate::print_err;
-
-struct State<'l> {
- reg: &'l BlockRegistry<'l>,
- schematic: Option<Schematic<'l>>,
- unsaved: bool,
- subregion: Option<Schematic<'l>>,
- quit: bool,
-}
-
-pub fn main(mut args: Args, arg_off: usize) {
- let mut handler = OptionHandler::default();
- let opt_file = handler
- .add(ArgOption::new(
- Some('f'),
- Some(Cow::Borrowed("file")),
- ArgCount::Required(1),
- ))
- .unwrap();
- if let Err(e) = args::parse(&mut args, &mut handler, arg_off) {
- print_err!(e, "Command error");
- return;
- }
-
- // try to load a schematic from the file argument or as base64
- let reg = build_registry();
- let mut ss = SchematicSerializer(&reg);
- let mut state = State {
- reg: &reg,
- schematic: None,
- unsaved: false,
- subregion: None,
- quit: false,
- };
- if let Some(path) = handler.get_value(opt_file).get_value() {
- match fs::read(path) {
- Ok(data) => match ss.deserialize(&mut DataRead::new(&data)) {
- Ok(s) => {
- println!("Loaded schematic from {path}");
- state.schematic = Some(s);
- }
- Err(e) => print_err!(e, "Could not read schematic from {path}"),
- },
- Err(e) => print_err!(e, "Could not read file {path:?}"),
- }
- } else if let Some(b64) = handler.get_literals().first() {
- match ss.deserialize_base64(b64) {
- Ok(s) => {
- println!("Loaded schematic from CLI");
- state.schematic = Some(s);
- }
- Err(e) => print_err!(e, "Could not read schematic"),
- }
- }
- if state.schematic.is_none() {
- println!(r#"No active schematic, use "new" or "load" to begin editing."#);
- }
- println!(r#"Type "help" for a list of available commands."#);
-
- // the main command interpreter loop
- let mut line_buff = String::new();
- let stdin = io::stdin();
- while !state.quit {
- line_buff.clear();
- print!("> ");
- if let Err(e) = io::stdout().flush() {
- // what the print & println macros would do
- panic!("failed printing to stdout: {e}");
- }
- match stdin.read_line(&mut line_buff) {
- Ok(..) => interpret(&mut state, line_buff.trim_start()),
- Err(e) => {
- print_err!(e, "Failed to read next command");
- if state.unsaved {
- // special case because we wouldn't be able to read a path from stdin
- match ss.serialize_base64(state.schematic.as_ref().unwrap()) {
- Ok(curr) => println!("Current schematic: {curr}"),
- Err(e) => print_err!(e, "Could not serialize schematic"),
- }
- state.unsaved = false;
- }
- break;
- }
- }
- }
-
- // give the user a chance to save their work
- if state.unsaved {
- let mut data = DataWrite::default();
- match ss.serialize(&mut data, state.schematic.as_ref().unwrap()) {
- Ok(()) => {
- let data = data.get_written();
- // SAFETY: base64 output is always valid ASCII
- let buff = unsafe { line_buff.as_mut_vec() };
- buff.resize(4 * ((data.len() + 2) / 3), 0);
- match base64::encode(data, buff) {
- Ok(len) => println!("Current schematic: {}", &line_buff[..len]),
- Err(e) => print_err!(e, "Could not convert schematic to base-64"),
- }
-
- println!(
- "You have unsaved work. Please type a path to save to or press enter to quit."
- );
- loop {
- line_buff.clear();
- match stdin.read_line(&mut line_buff) {
- Ok(..) => {
- let path = line_buff.trim();
- if path.is_empty() {
- break;
- }
- match fs::write(path, data) {
- Ok(()) => println!("Saved schematic to {path}"),
- Err(e) => print_err!(e, "Could not write file {path:?}"),
- }
- }
- Err(e) => {
- print_err!(e, "Failed to read save path");
- return;
- }
- }
- }
- }
- Err(e) => print_err!(e, "Could not serialize schematic"),
- }
- }
-}
-
-struct Tokenizer<'l>(Option<&'l str>);
-
-impl<'l> Tokenizer<'l> {
- fn skip_ws(&mut self) {
- if let Some(curr) = self.0 {
- let curr = curr.trim_start();
- self.0 = if curr.is_empty() { None } else { Some(curr) };
- }
- }
-
- fn next(&mut self) -> Option<&'l str> {
- self.skip_ws();
- if let Some(curr) = self.0 {
- if curr.len() >= 2 && (curr.as_bytes()[0] == b'"' || curr.as_bytes()[0] == b'\'') {
- match (curr[1..]).find(curr.as_bytes()[0] as char) {
- None => {
- self.0 = None;
- Some(&curr[1..])
- }
- Some(end) => {
- let rest = &curr[(end + 2)..];
- self.0 = if rest.is_empty() { None } else { Some(rest) };
- Some(&curr[1..end])
- }
- }
- } else {
- match curr.find(char::is_whitespace) {
- None => {
- self.0 = None;
- Some(curr)
- }
- Some(end) => {
- let rest = &curr[end..];
- self.0 = if rest.is_empty() { None } else { Some(rest) };
- Some(&curr[..end])
- }
- }
- }
- } else {
- None
- }
- }
-
- fn remainder(&mut self) -> Option<&'l str> {
- self.skip_ws();
- if let Some(curr) = self.0 {
- let bytes = curr.as_bytes();
- if bytes.len() >= 2
- && (bytes[0] == b'"' || bytes[0] == b'\'')
- && bytes[bytes.len() - 1] == bytes[0]
- {
- self.0 = None;
- return Some(&curr[1..(curr.len() - 1)]);
- }
- }
- let curr = self.0;
- self.0 = None;
- curr
- }
-}
-
-enum Command {
- Help,
- New,
- Input,
- Load,
- Place,
- Rotate,
- Mirror,
- Move,
- Resize,
- Remove,
- Sub,
- Print,
- Dump,
- Save,
- Quit,
-}
-
-impl Command {
- fn print_help(&self, indent: usize) {
- match self {
- Self::Help => println!(
- "{:<indent$}Prints a list of available commands",
- "\"help\":"
- ),
- Self::New => println!(
- "{:<indent$}Creates a new schematic, erasing the currently loaded one",
- "\"new\":"
- ),
- Self::Input => println!(
- "{:<indent$}Loads a new schematic from a base-64 encoded string",
- "\"input\":"
- ),
- Self::Load => println!("{:<indent$}Loads a new schematic from a file", "\"load\":"),
- Self::Place => println!(
- "{:<indent$}Places a block if enough space is available",
- "\"place\":"
- ),
- Self::Rotate => println!(
- "{:<indent$}Rotates the schematic (CCW) in increments of 90 degrees",
- "\"rotate\":"
- ),
- Self::Mirror => println!(
- "{:<indent$}Mirrors the schematic horizontally or vertically",
- "\"mirror\":"
- ),
- Self::Move => println!(
- "{:<indent$}Moves all blocks by a certain offset",
- "\"move\":"
- ),
- Self::Resize => println!(
- "{:<indent$}Resizes the schematic and offsets it",
- "\"resize\":"
- ),
- Self::Remove => println!(
- "{:<indent$}Removes blocks at a position or within a region",
- "\"remove\":"
- ),
- Self::Sub => println!(
- "{:<indent$}Various commands for editing subregions",
- "\"sub\":"
- ),
- Self::Print => println!(
- "{:<indent$}Prints the schematic in a visual representation",
- "\"print\":"
- ),
- Self::Dump => println!(
- "{:<indent$}Prints the schematic as a base-64 encoded string",
- "\"dump\":"
- ),
- Self::Save => println!("{:<indent$}Saves the schematic to a file", "\"save\":"),
- Self::Quit => println!(
- "{:<indent$}Offers to save unsaved work and exits the program",
- "\"quit\":"
- ),
- }
- self.print_usage(indent);
- }
-
- fn print_usage(&self, indent: usize) {
- match self {
- Self::Help => (),
- Self::New => println!(r#"{:indent$} Usage: "new" <width> [<height>]"#, ""),
- Self::Input => println!(r#"{:indent$} Usage: "input" <base64>"#, ""),
- Self::Load => println!(r#"{:indent$} Usage: "load" <load path>"#, ""),
- Self::Place => {
- println!(
- r#"{:indent$} Usage: "place" <x> <y> <block name> [<rotation> [<replace>]]"#,
- ""
- );
- println!(
- r#"{:indent$} Rotation is one of right, up, left, down or compass angles"#,
- ""
- )
- }
- Self::Rotate => println!(r#"{:indent$} Usage: "rotate" <angle>"#, ""),
- Self::Mirror => println!(r#"{:indent$} Usage: "mirror" <axis>"#, ""),
- Self::Move => println!(r#"{:indent$} Usage: "move" <dx> <dy>"#, ""),
- Self::Resize => println!(
- r#"{:indent$} Usage: "resize" <width> <height> [<dx> <dy>]"#,
- ""
- ),
- Self::Remove => println!(r#"{:indent$} Usage: "remove" <x0> <y0> [<x1> <y1>]"#, ""),
- Self::Sub => println!(r#"{:indent$} Usage: "sub" ... (see "sub help")"#, ""),
- Self::Print | Self::Dump => (),
- Self::Save => println!(r#"{:indent$} Usage: "save" <save path>"#, ""),
- Self::Quit => (),
- }
- }
-}
-
-macro_rules! parse_num {
- ($cmd:path, $name:expr, <$type:ty>::from($val:expr)) => {
- match $val {
- None => {
- eprintln!("Missing argument: {}", $name);
- $cmd.print_usage(0);
- return;
- }
- Some(s) => match s.parse::<$type>() {
- Ok(v) => v,
- Err(e) => {
- print_err!(e, "Could not parse {}", $name);
- $cmd.print_usage(0);
- return;
- }
- },
- }
- };
- ($cmd:path, $tokens:expr, $name:expr, $type:ty) => {
- parse_num!($cmd, $name, <$type>::from($tokens.next()))
- };
-}
-
-fn interpret(state: &mut State, cmd: &str) {
- let mut tokens = Tokenizer(Some(cmd));
- match tokens.next() {
- None => println!(r#"Empty command, type "quit" to exit"#),
- Some("help") => {
- println!(r#"List of available commands:"#);
- const INDENT: usize = 12;
- Command::Help.print_help(INDENT);
- Command::New.print_help(INDENT);
- Command::Input.print_help(INDENT);
- Command::Load.print_help(INDENT);
- Command::Place.print_help(INDENT);
- Command::Rotate.print_help(INDENT);
- Command::Mirror.print_help(INDENT);
- Command::Remove.print_help(INDENT);
- Command::Sub.print_help(INDENT);
- Command::Print.print_help(INDENT);
- Command::Dump.print_help(INDENT);
- Command::Save.print_help(INDENT);
- Command::Quit.print_help(INDENT);
- println!();
- println!("Legend: \"literal (excluding quotes)\" <required> [<optional>]");
- println!("Arguments are delimited by whitespace, unless surrounded with ' or \"");
- if tokens.remainder().is_some() {
- eprintln!("Extra arguments are considered an error");
- } else {
- println!("Extra arguments are considered an error");
- }
- }
- Some("new") => {
- let width = parse_num!(Command::New, tokens, "width", u16);
- if width == 0 {
- eprintln!("Schematic width must be positive");
- return;
- }
- let height = parse_num!(Command::New, tokens, "height", u16);
- if height == 0 {
- eprintln!("Schematic height must be positive");
- return;
- }
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "new""#);
- Command::New.print_usage(0);
- return;
- }
- state.schematic = Some(Schematic::new(width, height));
- // it's empty, no need to save this
- state.unsaved = false;
- }
- Some("input") => {
- let schematic = match tokens.next() {
- None => {
- eprintln!("Missing argument: base64");
- Command::Input.print_usage(0);
- return;
- }
- Some(b64) => match SchematicSerializer(state.reg).deserialize_base64(b64) {
- Ok(s) => s,
- Err(e) => {
- print_err!(e, "Could not deserialize schematic");
- return;
- }
- },
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "input""#);
- Command::Input.print_usage(0);
- return;
- }
- state.schematic = Some(schematic);
- state.unsaved = false;
- }
- Some("load") => {
- let schematic = match tokens.next() {
- None => {
- eprintln!("Missing argument: load path");
- Command::Load.print_usage(0);
- return;
- }
- Some(path) => {
- let data = match fs::read(path) {
- Ok(d) => d,
- Err(e) => {
- print_err!(e, "Could not load from file");
- return;
- }
- };
- match SchematicSerializer(state.reg).deserialize(&mut DataRead::new(&data)) {
- Ok(s) => s,
- Err(e) => {
- print_err!(e, "Could not deserialize schematic");
- return;
- }
- }
- }
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "load""#);
- Command::Load.print_usage(0);
- return;
- }
- state.schematic = Some(schematic);
- state.unsaved = false;
- }
- Some("place") => {
- let Some(ref mut schematic) = state.schematic
- else
- {
- eprintln!(r#"Command "place" requires an active schematic (see "help")"#);
- return;
- };
- let x = parse_num!(Command::Place, tokens, "x", u16);
- let y = parse_num!(Command::Place, tokens, "y", u16);
- if x >= schematic.get_width() || y >= schematic.get_height() {
- eprintln!(
- "Invalid coordinate ({x} / {y}) out of bounds ({} / {})",
- schematic.get_width(),
- schematic.get_height()
- );
- return;
- }
- let block = match tokens.next() {
- None => {
- eprintln!("Missing argument: block name");
- Command::Place.print_usage(0);
- return;
- }
- Some(name) => match state.reg.get(name) {
- None => {
- eprintln!("No such block {name:?}");
- return;
- }
- Some(b) => b,
- },
- };
- let rot = match tokens.next() {
- None => None,
- Some("right") | Some("east") => Some(Rotation::Right),
- Some("up") | Some("north") => Some(Rotation::Up),
- Some("left") | Some("west") => Some(Rotation::Left),
- Some("down") | Some("south") => Some(Rotation::Down),
- Some(rot) => {
- eprintln!("Invalid rotation {rot:?}");
- return;
- }
- };
- let replace = if rot.is_some() {
- match tokens.next() {
- None => None,
- Some("true") | Some("yes") => Some(true),
- Some("false") | Some("no") => Some(false),
- Some(replace) => {
- eprintln!("Invalid replacement {replace:?}");
- return;
- }
- }
- } else {
- None
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "place""#);
- Command::Place.print_usage(0);
- return;
- }
- let rot = rot.unwrap_or(Rotation::Right);
- let result = if replace.unwrap_or(false) {
- schematic
- .replace(x, y, block, DynData::Empty, rot, false)
- .err()
- } else {
- schematic.set(x, y, block, DynData::Empty, rot).err()
- };
- if let Some(e) = result {
- print_err!(e, "Failed to place block at {x} / {y}");
- }
- }
- Some("rotate") => {
- let Some(ref mut schematic) = state.schematic
- else
- {
- eprintln!(r#"Command "rotate" requires an active schematic (see "help")"#);
- return;
- };
- let angle = parse_num!(Command::Rotate, tokens, "angle", i32);
- if angle % 90 != 0 {
- eprintln!("Rotation angle must be a multiple of 90 degrees");
- return;
- }
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "rotate""#);
- Command::Rotate.print_usage(0);
- return;
- }
- match (angle / 90) % 4 {
- 0 => (),
- 1 | -3 => {
- schematic.rotate(false);
- state.unsaved = true;
- }
- 2 | -2 => {
- schematic.rotate_180();
- state.unsaved = true;
- }
- 3 | -1 => {
- schematic.rotate(true);
- state.unsaved = true;
- }
- a => unreachable!("angle {angle} -> {a}"),
- }
- }
- Some("mirror") => {
- let Some(ref mut schematic) = state.schematic
- else
- {
- eprintln!(r#"Command "mirror" requires an active schematic (see "help")"#);
- return;
- };
- let (x, y) = match tokens.next() {
- None => {
- eprintln!("Missing argument: axis");
- Command::Mirror.print_usage(0);
- return;
- }
- Some("x") | Some("h") | Some("horizontal") | Some("horizontally") => (true, false),
- Some("y") | Some("v") | Some("vertical") | Some("vertically") => (false, true),
- Some("both") | Some("all") => (true, true),
- Some(axis) => {
- eprintln!("Invalid mirroring axis: {axis:?}");
- return;
- }
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "mirror""#);
- Command::Mirror.print_usage(0);
- return;
- }
- schematic.mirror(x, y);
- state.unsaved = true;
- }
- Some("move") => {
- let Some(ref mut schematic) = state.schematic
- else
- {
- eprintln!(r#"Command "move" requires an active schematic (see "help")"#);
- return;
- };
- let dx = parse_num!(Command::Move, tokens, "dx", i16);
- let dy = parse_num!(Command::Move, tokens, "dy", i16);
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "move""#);
- Command::Move.print_usage(0);
- return;
- }
- if dx != 0 && dy != 0 {
- if let Err(e) =
- schematic.resize(dx, dy, schematic.get_width(), schematic.get_height())
- {
- match e {
- ResizeError::XOffset { dx, old_w, new_w } => {
- debug_assert_eq!(old_w, new_w);
- eprintln!("Invalid horizontal move {dx} not in ]-{old_w}, {new_w}[");
- }
- ResizeError::YOffset { dy, old_h, new_h } => {
- debug_assert_eq!(old_h, new_h);
- eprintln!("Invalid vertical move {dy} not in ]-{old_h}, {new_h}[");
- }
- ResizeError::Truncated {
- right,
- top,
- left,
- bottom,
- } => {
- eprint!("Move would truncate schematic: ");
- let mut first = true;
- if right > 0 {
- eprint!("{}right: {right}", if first { "" } else { ", " });
- first = false;
- }
- if top > 0 {
- eprint!("{}top: {top}", if first { "" } else { ", " });
- first = false;
- }
- if left > 0 {
- eprint!("{}left: {left}", if first { "" } else { ", " });
- first = false;
- }
- if bottom > 0 {
- eprint!("{}bottom: {bottom}", if first { "" } else { ", " });
- first = false;
- }
- if first {
- eprintln!("<unknown>");
- } else {
- eprintln!();
- }
- }
- _ => print_err!(e, "Unexpected resize error (for {dx} / {dy})"),
- }
- } else {
- state.unsaved = true;
- }
- }
- }
- Some("resize") => {
- let Some(ref mut schematic) = state.schematic
- else
- {
- eprintln!(r#"Command "resize" requires an active schematic (see "help")"#);
- return;
- };
- let w = parse_num!(Command::Resize, tokens, "width", u16);
- if w == 0 {
- eprintln!("Schematic width must be positive");
- return;
- }
- let h = parse_num!(Command::Resize, tokens, "height", u16);
- if h == 0 {
- eprintln!("Schematic height must be positive");
- return;
- }
- let (dx, dy) = if let arg @ Some(..) = tokens.next() {
- let dx = parse_num!(Command::Resize, "dx", <i16>::from(arg));
- let dy = parse_num!(Command::Resize, tokens, "dy", i16);
- (dx, dy)
- } else {
- (0, 0)
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "resize""#);
- Command::Resize.print_usage(0);
- return;
- }
- if w != schematic.get_width() || h != schematic.get_height() || dx != 0 || dy != 0 {
- if let Err(e) = schematic.resize(dx, dy, w, h) {
- print_err!(e, "Could not resize schematic");
- }
- }
- }
- Some("remove") => {
- let Some(ref mut schematic) = state.schematic
- else
- {
- eprintln!(r#"Command "remove" requires an active schematic (see "help")"#);
- return;
- };
- let x0 = parse_num!(Command::Remove, tokens, "x0", u16);
- let y0 = parse_num!(Command::Remove, tokens, "y0", u16);
- if x0 >= schematic.get_width() || y0 >= schematic.get_height() {
- eprintln!(
- "Invalid coordinate ({x0} / {y0}) out of bounds ({} / {})",
- schematic.get_width(),
- schematic.get_height()
- );
- return;
- }
- let (x0, y0, x1, y1) = if let arg @ Some(..) = tokens.next() {
- let x1 = parse_num!(Command::Remove, "x1", <u16>::from(arg));
- let y1 = parse_num!(Command::Remove, tokens, "y1", u16);
- if x1 >= schematic.get_width() || y1 >= schematic.get_height() {
- eprintln!(
- "Invalid coordinate ({x1} / {y1}) out of bounds ({} / {})",
- schematic.get_width(),
- schematic.get_height()
- );
- return;
- }
- (x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1))
- } else {
- (x0, y0, x0, y0)
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "remove""#);
- Command::Remove.print_usage(0);
- return;
- }
- if x1 > x0 || y1 > y0 {
- let mut cnt = 0u32;
- for y in y0..=y1 {
- for x in x0..=x1 {
- // position was already checked while parsing
- if schematic.take(x, y).unwrap().is_some() {
- cnt += 1;
- }
- }
- }
- println!("Removed {cnt} blocks in {x0} / {y0} to {x1} / {y1}");
- if cnt > 0 {
- state.unsaved = true;
- }
- } else {
- // position was already checked while parsing
- match schematic.take(x0, y0).unwrap() {
- None => (),
- Some(p) => {
- println!(
- "Removed block {} from {x0} / {y0}",
- p.get_block().get_name()
- );
- state.unsaved = true;
- }
- }
- }
- }
- Some("sub") => interpret_sub(state, &mut tokens),
- Some("print") => {
- let Some(ref schematic) = state.schematic
- else
- {
- eprintln!(r#"Command "print" requires an active schematic (see "help")"#);
- return;
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "print""#);
- Command::Print.print_usage(0);
- return;
- }
- print_schematic(schematic);
- }
- Some("dump") => {
- let Some(ref schematic) = state.schematic
- else
- {
- eprintln!(r#"Command "dump" requires an active schematic (see "help")"#);
- return;
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "dump""#);
- Command::Dump.print_usage(0);
- return;
- }
- let b64 = match SchematicSerializer(state.reg).serialize_base64(schematic) {
- Ok(b64) => b64,
- Err(e) => {
- print_err!(e, "Could not serialize schematic");
- return;
- }
- };
- println!("Schematic: {}", b64);
- }
- Some("save") => {
- let Some(ref schematic) = state.schematic
- else
- {
- eprintln!(r#"Command "save" requires an active schematic (see "help")"#);
- return;
- };
- let Some(path) = tokens.next()
- else
- {
- eprintln!("Missing argument: save path");
- Command::Save.print_usage(0);
- return;
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "save""#);
- Command::Save.print_usage(0);
- return;
- }
- let mut serial_buff = DataWrite::default();
- if let Err(e) = SchematicSerializer(state.reg).serialize(&mut serial_buff, schematic) {
- print_err!(e, "Could not serialize schematic");
- return;
- }
- if let Err(e) = fs::write(path, serial_buff.get_written()) {
- print_err!(e, "Could not write to file");
- return;
- }
- state.unsaved = false;
- println!("Saved schematic to {path}.");
- }
- Some("quit") => state.quit = true,
- Some(unknown) => eprintln!("Unknown command {unknown:?}"),
- }
-}
-
-enum SubCommand {
- Help,
- Input,
- Copy,
- Cut,
- Paste,
- Place,
- Rotate,
- Mirror,
- Move,
- Resize,
- Remove,
- Print,
- Dump,
-}
-
-impl SubCommand {
- fn print_help(&self, indent: usize) {
- match self {
- Self::Help => println!(
- "{:<indent$}Prints a list of available commands",
- "\"sub\" \"help\":"
- ),
- Self::Input => println!(
- "{:<indent$}Loads a new subregion from a base-64 encoded string",
- "\"sub\" \"input\":"
- ),
- Self::Copy => println!(
- "{:<indent$}Replaces the current subregion by copying from the schematic",
- "\"sub\" \"copy\":"
- ),
- Self::Cut => println!(
- "{:<indent$}Replaces the current subregion by removing from the schematic",
- "\"sub\" \"cut\":"
- ),
- Self::Paste => println!(
- "{:<indent$}Places a copy of the current subregion into the schematic",
- "\"sub\" \"paste\":"
- ),
- Self::Place => println!(
- "{:<indent$}Places a block if enough space is available",
- "\"sub\" \"place\":"
- ),
- Self::Rotate => println!(
- "{:<indent$}Rotates the current subregion (CCW) in increments of 90 degrees",
- "\"sub\" \"rotate\":"
- ),
- Self::Mirror => println!(
- "{:<indent$}Mirrors the current subregion horizontally or vertically",
- "\"sub\" \"mirror\":"
- ),
- Self::Move => println!(
- "{:<indent$}Moves all blocks by a certain offset",
- "\"sub\" \"move\":"
- ),
- Self::Resize => println!(
- "{:<indent$}Resizes the current subregion and offsets it",
- "\"sub\" \"resize\":"
- ),
- Self::Remove => println!(
- "{:<indent$}Removes blocks at a position or within a region",
- "\"sub\" \"remove\":"
- ),
- Self::Print => println!(
- "{:<indent$}Prints the current subregion in a visual representation",
- "\"sub\" \"print\":"
- ),
- Self::Dump => println!(
- "{:<indent$}Prints the current subregion as a base-64 encoded string",
- "\"sub\" \"dump\":"
- ),
- }
- self.print_usage(indent);
- }
-
- fn print_usage(&self, indent: usize) {
- match self {
- Self::Help => (),
- Self::Input => println!(r#"{:indent$} Usage: "sub" "input" <base64>"#, ""),
- Self::Copy => {
- println!(
- r#"{:indent$} Usage: "sub" "copy" <x0> <y0> <x1> <y1> [<strict>]"#,
- ""
- );
- println!(
- r#"{:indent$} Strictness ignores blocks which are not entirely within the bounds"#,
- ""
- );
- }
- Self::Cut => {
- println!(
- r#"{:indent$} Usage: "sub" "cut" <x0> <y0> <x1> <y1> [<strict>]"#,
- ""
- );
- println!(
- r#"{:indent$} Strictness ignores blocks which are not entirely within the bounds"#,
- ""
- );
- }
- Self::Paste => println!(r#"{:indent$} Usage: "sub" "paste" <x> <y>"#, ""),
- Self::Place => {
- println!(
- r#"{:indent$} Usage: "sub" "place" <x> <y> <block name> [<rotation> [<replace>]]"#,
- ""
- );
- println!(
- r#"{:indent$} Rotation is one of right, up, left, down or compass angles"#,
- ""
- )
- }
- Self::Rotate => println!(r#"{:indent$} Usage: "sub" "rotate" <angle>"#, ""),
- Self::Mirror => println!(r#"{:indent$} Usage: "sub" "mirror" <axis>"#, ""),
- Self::Move => println!(r#"{:indent$} Usage: "sub" "move" <dx> <dy>"#, ""),
- Self::Resize => println!(
- r#"{:indent$} Usage: "sub" "resize" <width> <height> [<dx> <dy>]"#,
- ""
- ),
- Self::Remove => println!(
- r#"{:indent$} Usage: "sub" "remove" <x0> <y0> [<x1> <y1>]"#,
- ""
- ),
- Self::Print | Self::Dump => (),
- }
- }
-}
-
-fn interpret_sub(state: &mut State, tokens: &mut Tokenizer) {
- match tokens.next() {
- None => println!(r#"Empty "sub" command, type "sub help" for more info"#),
- Some("help") => {
- const INDENT: usize = 20;
- println!(r#"List of available commands for \"sub\":"#);
- SubCommand::Help.print_help(INDENT);
- SubCommand::Input.print_help(INDENT);
- SubCommand::Copy.print_help(INDENT);
- SubCommand::Cut.print_help(INDENT);
- SubCommand::Paste.print_help(INDENT);
- SubCommand::Place.print_help(INDENT);
- SubCommand::Rotate.print_help(INDENT);
- SubCommand::Mirror.print_help(INDENT);
- SubCommand::Move.print_help(INDENT);
- SubCommand::Resize.print_help(INDENT);
- SubCommand::Remove.print_help(INDENT);
- SubCommand::Print.print_help(INDENT);
- SubCommand::Dump.print_help(INDENT);
- if tokens.remainder().is_some() {
- eprintln!("Extra arguments are considered an error");
- }
- }
- Some("input") => {
- let subregion = match tokens.next() {
- None => {
- eprintln!("Missing argument: base64");
- SubCommand::Input.print_usage(0);
- return;
- }
- Some(b64) => match SchematicSerializer(state.reg).deserialize_base64(b64) {
- Ok(s) => s,
- Err(e) => {
- print_err!(e, "Could not deserialize schematic");
- return;
- }
- },
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "sub input""#);
- SubCommand::Input.print_usage(0);
- return;
- }
- state.subregion = Some(subregion);
- }
- Some(op @ ("copy" | "cut")) => {
- let modify_original = op == "cut";
- let Some(ref mut schematic) = state.schematic
- else
- {
- eprintln!(r#"Command "sub {op}" requires an active schematic (see "help")"#);
- return;
- };
- let x0 = if modify_original {
- parse_num!(SubCommand::Cut, tokens, "x0", u16)
- } else {
- parse_num!(SubCommand::Copy, tokens, "x0", u16)
- };
- let y0 = if modify_original {
- parse_num!(SubCommand::Cut, tokens, "y0", u16)
- } else {
- parse_num!(SubCommand::Copy, tokens, "y0", u16)
- };
- if x0 >= schematic.get_width() || y0 >= schematic.get_height() {
- eprintln!(
- "Invalid lower coordinate ({x0} / {y0}) out of bounds ({} / {})",
- schematic.get_width(),
- schematic.get_height()
- );
- return;
- }
- let x1 = if modify_original {
- parse_num!(SubCommand::Cut, tokens, "x1", u16)
- } else {
- parse_num!(SubCommand::Copy, tokens, "x1", u16)
- };
- let y1 = if modify_original {
- parse_num!(SubCommand::Cut, tokens, "y1", u16)
- } else {
- parse_num!(SubCommand::Copy, tokens, "y1", u16)
- };
- if x1 < x0 || y1 < y0 {
- eprintln!(
- "Invalid upper coordinate ({x1} / {y1}) too low (lower bound {x0} / {y0})"
- );
- return;
- }
- if x1 >= schematic.get_width() || y1 >= schematic.get_height() {
- eprintln!(
- "Invalid upper coordinate ({x1} / {y1}) out of bounds ({} / {})",
- schematic.get_width(),
- schematic.get_height()
- );
- return;
- }
- let strict = match tokens.next() {
- None => false,
- Some("true") | Some("yes") => true,
- Some("false") | Some("no") => false,
- Some(strict) => {
- eprintln!("Invalid strictness {strict:?}");
- return;
- }
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "sub {op}""#);
- SubCommand::Copy.print_usage(0);
- return;
- }
- let mut true_x0 = x0;
- let mut true_y0 = y0;
- let mut true_x1 = x1;
- let mut true_y1 = y1;
- let mut targets = Vec::new();
- for p in schematic.block_iter() {
- let sz = p.get_block().get_size() as u16;
- let pos0 = (p.get_pos().0 - (sz - 1) / 2, p.get_pos().1 - (sz - 1) / 2);
- let pos1 = (p.get_pos().0 + sz / 2, p.get_pos().1 + sz / 2);
- let include = if strict {
- pos0.0 >= x0 && pos0.1 >= y0 && pos1.0 <= x1 && pos1.1 <= y1
- } else {
- pos0.0 <= x1 && pos0.1 <= y1 && pos1.0 >= x0 && pos1.1 >= y0
- };
- if include {
- targets.push(p.get_pos());
- if !strict {
- if pos0.0 < true_x0 {
- true_x0 = pos0.0;
- }
- if pos0.1 < true_y0 {
- true_y0 = pos0.1;
- }
- if pos1.0 > true_x1 {
- true_x1 = pos1.0;
- }
- if pos1.1 > true_y1 {
- true_y1 = pos1.1;
- }
- }
- }
- }
- let mut subregion = Schematic::new(true_x1 - true_x0 + 1, true_y1 - true_y0 + 1);
- if !targets.is_empty() {
- if modify_original {
- state.unsaved = true;
- }
- for GridPos(x, y) in targets {
- let mut original: Option<Placement> = None;
- let place = if modify_original {
- original = schematic.take(x, y).unwrap();
- original.as_ref().unwrap()
- } else {
- schematic.get(x, y).unwrap().unwrap()
- };
- let data = match place.get_state() {
- None => DynData::Empty,
- Some(d) => place.get_block().serialize_state(d).unwrap(),
- };
- subregion
- .set(
- place.get_pos().0 - true_x0,
- place.get_pos().1 - true_y0,
- place.get_block(),
- data,
- place.get_rotation(),
- )
- .unwrap();
- drop(original);
- }
- }
- state.subregion = Some(subregion);
- }
- Some("paste") => {
- let Some(ref mut schematic) = state.schematic
- else
- {
- eprintln!(r#"Command "sub paste" requires an active schematic (see "help")"#);
- return;
- };
- let Some(ref subregion) = state.subregion
- else
- {
- eprintln!(r#"Command "sub paste" requires an active subregion (see "sub help")"#);
- return;
- };
- let x = parse_num!(SubCommand::Paste, tokens, "x", u16);
- let y = parse_num!(SubCommand::Paste, tokens, "y", u16);
- if subregion.get_width() > schematic.get_width()
- || subregion.get_height() > schematic.get_height()
- {
- eprintln!(
- "Subregion ({} / {}) is larger than schematic ({} / {})",
- subregion.get_width(),
- subregion.get_height(),
- schematic.get_width(),
- schematic.get_height()
- );
- return;
- }
- if x >= schematic.get_width() - subregion.get_width() {
- let x1 = x + subregion.get_width() - 1;
- let y1 = y + subregion.get_height() - 1;
- eprintln!(
- "Invalid coordinate ({x} / {y} to {x1} / {y1}) out of bounds ({} / {})",
- schematic.get_width(),
- schematic.get_height()
- );
- return;
- }
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "sub paste""#);
- SubCommand::Paste.print_usage(0);
- return;
- }
- state.unsaved = true;
- for p in subregion.block_iter() {
- let data = match p.get_state() {
- None => DynData::Empty,
- Some(d) => p.get_block().serialize_state(d).unwrap(),
- };
- schematic
- .replace(
- x + p.get_pos().0,
- y + p.get_pos().1,
- p.get_block(),
- data,
- p.get_rotation(),
- false,
- )
- .unwrap();
- }
- }
- Some("place") => {
- let Some(ref mut subregion) = state.subregion
- else
- {
- eprintln!(r#"Command "sub place" requires an active subregion (see "sub help")"#);
- return;
- };
- let x = parse_num!(SubCommand::Place, tokens, "x", u16);
- let y = parse_num!(SubCommand::Place, tokens, "y", u16);
- if x >= subregion.get_width() || y >= subregion.get_height() {
- eprintln!(
- "Invalid coordinate ({x} / {y}) out of bounds ({} / {})",
- subregion.get_width(),
- subregion.get_height()
- );
- return;
- }
- let block = match tokens.next() {
- None => {
- eprintln!("Missing argument: block name");
- SubCommand::Place.print_usage(0);
- return;
- }
- Some(name) => match state.reg.get(name) {
- None => {
- eprintln!("No such block {name:?}");
- return;
- }
- Some(b) => b,
- },
- };
- let rot = match tokens.next() {
- None => None,
- Some("right") | Some("east") => Some(Rotation::Right),
- Some("up") | Some("north") => Some(Rotation::Up),
- Some("left") | Some("west") => Some(Rotation::Left),
- Some("down") | Some("south") => Some(Rotation::Down),
- Some(rot) => {
- eprintln!("Invalid rotation {rot:?}");
- return;
- }
- };
- let replace = if rot.is_some() {
- match tokens.next() {
- None => None,
- Some("true") | Some("yes") => Some(true),
- Some("false") | Some("no") => Some(false),
- Some(replace) => {
- eprintln!("Invalid replacement {replace:?}");
- return;
- }
- }
- } else {
- None
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "sub place""#);
- SubCommand::Place.print_usage(0);
- return;
- }
- let rot = rot.unwrap_or(Rotation::Right);
- let result = if replace.unwrap_or(false) {
- subregion
- .replace(x, y, block, DynData::Empty, rot, false)
- .err()
- } else {
- subregion.set(x, y, block, DynData::Empty, rot).err()
- };
- if let Some(e) = result {
- print_err!(e, "Failed to place block at {x} / {y}");
- }
- }
- Some("rotate") => {
- let Some(ref mut subregion) = state.subregion
- else
- {
- eprintln!(r#"Command "sub rotate" requires an active subgregion (see "sub help")"#);
- return;
- };
- let angle = parse_num!(SubCommand::Rotate, tokens, "angle", i32);
- if angle % 90 != 0 {
- eprintln!("Rotation angle must be a multiple of 90 degrees");
- return;
- }
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "sub rotate""#);
- SubCommand::Rotate.print_usage(0);
- return;
- }
- match (angle / 90) % 4 {
- 0 => (),
- 1 | -3 => subregion.rotate(false),
- 2 | -2 => subregion.rotate_180(),
- 3 | -1 => subregion.rotate(true),
- a => unreachable!("angle {angle} -> {a}"),
- }
- }
- Some("mirror") => {
- let Some(ref mut subregion) = state.subregion
- else
- {
- eprintln!(r#"Command "sub mirror" requires an active subregion (see "sub help")"#);
- return;
- };
- let (x, y) = match tokens.next() {
- None => {
- eprintln!("Missing argument: axis");
- SubCommand::Mirror.print_usage(0);
- return;
- }
- Some("x") | Some("h") | Some("horizontal") | Some("horizontally") => (true, false),
- Some("y") | Some("v") | Some("vertical") | Some("vertically") => (false, true),
- Some("both") | Some("all") => (true, true),
- Some(axis) => {
- eprintln!("Invalid mirroring axis: {axis:?}");
- return;
- }
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "sub mirror""#);
- SubCommand::Mirror.print_usage(0);
- return;
- }
- subregion.mirror(x, y);
- }
- Some("move") => {
- let Some(ref mut subregion) = state.subregion
- else
- {
- eprintln!(r#"Command "sub move" requires an active subregion (see "sub help")"#);
- return;
- };
- let dx = parse_num!(SubCommand::Move, tokens, "dx", i16);
- let dy = parse_num!(SubCommand::Move, tokens, "dy", i16);
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "sub move""#);
- SubCommand::Move.print_usage(0);
- return;
- }
- if dx != 0 && dy != 0 {
- if let Err(e) =
- subregion.resize(dx, dy, subregion.get_width(), subregion.get_height())
- {
- match e {
- ResizeError::XOffset { dx, old_w, new_w } => {
- debug_assert_eq!(old_w, new_w);
- eprintln!("Invalid horizontal move {dx} not in ]-{old_w}, {new_w}[");
- }
- ResizeError::YOffset { dy, old_h, new_h } => {
- debug_assert_eq!(old_h, new_h);
- eprintln!("Invalid vertical move {dy} not in ]-{old_h}, {new_h}[");
- }
- ResizeError::Truncated {
- right,
- top,
- left,
- bottom,
- } => {
- eprint!("Move would truncate subregion: ");
- let mut first = true;
- if right > 0 {
- eprint!("{}right: {right}", if first { "" } else { ", " });
- first = false;
- }
- if top > 0 {
- eprint!("{}top: {top}", if first { "" } else { ", " });
- first = false;
- }
- if left > 0 {
- eprint!("{}left: {left}", if first { "" } else { ", " });
- first = false;
- }
- if bottom > 0 {
- eprint!("{}bottom: {bottom}", if first { "" } else { ", " });
- first = false;
- }
- if first {
- eprintln!("<unknown>");
- } else {
- eprintln!();
- }
- }
- _ => print_err!(e, "Unexpected resize error (for {dx} / {dy})"),
- }
- }
- }
- }
- Some("resize") => {
- let Some(ref mut subregion) = state.subregion
- else
- {
- eprintln!(r#"Command "sub resize" requires an active subregion (see "sub help")"#);
- return;
- };
- let w = parse_num!(SubCommand::Resize, tokens, "width", u16);
- if w == 0 {
- eprintln!("Subregion width must be positive");
- return;
- }
- let h = parse_num!(SubCommand::Resize, tokens, "height", u16);
- if h == 0 {
- eprintln!("Subregion height must be positive");
- return;
- }
- let (dx, dy) = if let arg @ Some(..) = tokens.next() {
- let dx = parse_num!(SubCommand::Resize, "dx", <i16>::from(arg));
- let dy = parse_num!(SubCommand::Resize, tokens, "dy", i16);
- (dx, dy)
- } else {
- (0, 0)
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "sub resize""#);
- SubCommand::Resize.print_usage(0);
- return;
- }
- if w != subregion.get_width() || h != subregion.get_height() || dx != 0 || dy != 0 {
- if let Err(e) = subregion.resize(dx, dy, w, h) {
- print_err!(e, "Could not resize subregion");
- }
- }
- }
- Some("remove") => {
- let Some(ref mut subregion) = state.subregion
- else
- {
- eprintln!(r#"Command "sub remove" requires an active subregion (see "sub help")"#);
- return;
- };
- let x0 = parse_num!(SubCommand::Remove, tokens, "x0", u16);
- let y0 = parse_num!(SubCommand::Remove, tokens, "y0", u16);
- if x0 >= subregion.get_width() || y0 >= subregion.get_height() {
- eprintln!(
- "Invalid coordinate ({x0} / {y0}) out of bounds ({} / {})",
- subregion.get_width(),
- subregion.get_height()
- );
- return;
- }
- let (x0, y0, x1, y1) = if let arg @ Some(..) = tokens.next() {
- let x1 = parse_num!(SubCommand::Remove, "x1", <u16>::from(arg));
- let y1 = parse_num!(SubCommand::Remove, tokens, "y1", u16);
- if x1 >= subregion.get_width() || y1 >= subregion.get_height() {
- eprintln!(
- "Invalid coordinate ({x1} / {y1}) out of bounds ({} / {})",
- subregion.get_width(),
- subregion.get_height()
- );
- return;
- }
- (x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1))
- } else {
- (x0, y0, x0, y0)
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "sub remove""#);
- SubCommand::Remove.print_usage(0);
- return;
- }
- if x1 > x0 || y1 > y0 {
- let mut cnt = 0u32;
- for y in y0..=y1 {
- for x in x0..=x1 {
- // position was already checked while parsing
- if subregion.take(x, y).unwrap().is_some() {
- cnt += 1;
- }
- }
- }
- println!("Removed {cnt} blocks in {x0} / {y0} to {x1} / {y1}");
- } else {
- // position was already checked while parsing
- match subregion.take(x0, y0).unwrap() {
- None => (),
- Some(p) => println!(
- "Removed block {} from {x0} / {y0}",
- p.get_block().get_name()
- ),
- }
- }
- }
- Some("print") => {
- let Some(ref subregion) = state.subregion
- else
- {
- eprintln!(r#"Command "sub print" requires an active subregion (see "sub help")"#);
- return;
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "sub print""#);
- SubCommand::Print.print_usage(0);
- return;
- }
- print_schematic(subregion);
- }
- Some("dump") => {
- let Some(ref subregion) = state.subregion
- else
- {
- eprintln!(r#"Command "sub dump" requires an active subregion (see "sub help")"#);
- return;
- };
- if tokens.remainder().is_some() {
- eprintln!(r#"Too many parameters for "sub dump""#);
- SubCommand::Dump.print_usage(0);
- return;
- }
- let b64 = match SchematicSerializer(state.reg).serialize_base64(subregion) {
- Ok(b64) => b64,
- Err(e) => {
- print_err!(e, "Could not serialize subregion");
- return;
- }
- };
- println!("Subregion: {}", b64);
- }
- Some(unknown) => eprintln!("Unknown command \"sub\" {unknown:?}"),
- }
-}
diff --git a/src/exe/mod.rs b/src/exe/mod.rs
index 801e122..fcfae12 100644
--- a/src/exe/mod.rs
+++ b/src/exe/mod.rs
@@ -1,5 +1,4 @@
pub mod args;
-pub mod edit;
pub mod print;
macro_rules!print_err
@@ -32,7 +31,6 @@ fn main() {
args.next().unwrap(); // path to executable
match args.next() {
None => eprintln!("Not enough arguments, valid commands are: edit, print"),
- Some(s) if s == "edit" => edit::main(args, 1),
Some(s) if s == "print" => print::main(args, 1),
Some(s) => eprintln!("Unknown argument {s}, valid commands are: edit, print"),
}
diff --git a/src/exe/print.rs b/src/exe/print.rs
index d1b79c5..525bf9a 100644
--- a/src/exe/print.rs
+++ b/src/exe/print.rs
@@ -156,17 +156,17 @@ pub fn main(mut args: Args, arg_off: usize) {
}
pub fn print_schematic(s: &Schematic) {
- if let Some(name) = s.get_tags().get("name") {
+ if let Some(name) = s.tags.get("name") {
if !name.is_empty() {
println!("Name: {name}");
}
}
- if let Some(desc) = s.get_tags().get("description") {
+ if let Some(desc) = s.tags.get("description") {
if !desc.is_empty() {
println!("Desc: {desc}");
}
}
- if let Some(labels) = s.get_tags().get("labels") {
+ if let Some(labels) = s.tags.get("labels") {
if !labels.is_empty() && labels != "[]" {
println!("Tags: {:?}", labels);
}
diff --git a/src/item/mod.rs b/src/item/mod.rs
index 4aae21e..a08c71a 100644
--- a/src/item/mod.rs
+++ b/src/item/mod.rs
@@ -5,8 +5,7 @@ use crate::content::content_enum;
pub mod storage;
content_enum! {
- pub enum Type / Item for u16 | TryFromU16Error
- {
+ pub enum Type / Item for u16 | TryFromU16Error {
"copper",
"lead",
"metaglass",