mindustry logic execution, map- and schematic- parsing and rendering
conduits
bendn 2023-07-21
parent 1fdf0e1 · commit cc00f75
-rw-r--r--Cargo.toml2
-rw-r--r--assets/blocks/liquid/conduits/conduit-bottom.pngbin156 -> 0 bytes
-rw-r--r--assets/blocks/liquid/conduits/reinforced-conduit-cap.pngbin179 -> 0 bytes
-rw-r--r--src/block/distribution.rs254
-rw-r--r--src/block/liquid.rs31
-rw-r--r--src/block/mod.rs12
-rw-r--r--src/data/autotile.rs455
-rw-r--r--src/data/map.rs2
-rw-r--r--src/data/mod.rs1
-rw-r--r--src/data/renderer.rs222
-rw-r--r--src/data/schematic.rs2
11 files changed, 510 insertions, 471 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 07e3523..4ccb351 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "mindus"
-version = "1.1.6"
+version = "1.2.0"
edition = "2021"
description = "A library for working with mindustry data formats (eg schematics) (fork of plandustry)"
authors = [
diff --git a/assets/blocks/liquid/conduits/conduit-bottom.png b/assets/blocks/liquid/conduits/conduit-bottom.png
deleted file mode 100644
index 04cecf2..0000000
--- a/assets/blocks/liquid/conduits/conduit-bottom.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/liquid/conduits/reinforced-conduit-cap.png b/assets/blocks/liquid/conduits/reinforced-conduit-cap.png
deleted file mode 100644
index c416e44..0000000
--- a/assets/blocks/liquid/conduits/reinforced-conduit-cap.png
+++ /dev/null
Binary files differ
diff --git a/src/block/distribution.rs b/src/block/distribution.rs
index a8e1c5d..03b9295 100644
--- a/src/block/distribution.rs
+++ b/src/block/distribution.rs
@@ -1,266 +1,20 @@
//! conveyors ( & ducts )
-use std::borrow::BorrowMut;
-
use crate::block::simple::*;
use crate::block::*;
use crate::content;
+use crate::data::autotile::tile;
use crate::data::dynamic::DynType;
use crate::item;
-use bobbin_bits::U4;
-use image::imageops::{flip_horizontal_in_place as flip_h, flip_vertical_in_place as flip_v};
-#[cfg(test)]
-macro_rules! dir {
- (^) => {
- crate::block::Rotation::Up
- };
- (v) => {
- crate::block::Rotation::Down
- };
- (<) => {
- crate::block::Rotation::Left
- };
- (>) => {
- crate::block::Rotation::Right
- };
-}
-#[cfg(test)]
-macro_rules! conv {
- (_) => {
- None
- };
- ($dir:tt) => {
- Some((
- &crate::block::distribution::CONVEYOR,
- crate::block::distribution::dir!($dir),
- ))
- };
-}
-#[cfg(test)]
-macro_rules! define {
- ($a:tt,$b:tt,$c:tt,$d:tt) => {
- [
- crate::block::distribution::conv!($a),
- crate::block::distribution::conv!($b),
- crate::block::distribution::conv!($c),
- crate::block::distribution::conv!($d),
- ]
- };
-}
-#[cfg(test)]
-pub(crate) use conv;
-#[cfg(test)]
-pub(crate) use define;
-#[cfg(test)]
-pub(crate) use dir;
-
-#[test]
-fn test_mask() {
- macro_rules! assert {
- ($a:tt,$b:tt,$c:tt,$d:tt => $rot: tt => $expect: expr) => {
- assert_eq!(mask!(define!($a, $b, $c, $d), $rot), $expect)
- };
- }
- macro_rules! mask {
- ($cross:expr, $rot: tt) => {
- mask(
- &RenderingContext {
- position: PositionContext {
- position: GridPos(5, 5),
- width: 10,
- height: 10,
- },
- cross: $cross,
- rotation: dir!($rot),
- },
- "conveyor",
- )
- };
- }
- assert!(_,_,_,_ => ^ => U4::B0000);
- assert!(v,_,_,_ => > => U4::B1000);
- assert!(v,v,_,_ => v => U4::B1000);
- assert!(_,v,>,_ => > => U4::B0000);
- assert!(v,>,<,> => ^ => U4::B0001);
- assert!(v,>,>,_ => > => U4::B1000);
-}
-
-fn mask(ctx: &RenderingContext, n: &str) -> U4 {
- macro_rules! c {
- ($in: expr, $srot: expr, $name: expr, $at: expr) => {{
- if let Some((b, rot)) = $in {
- if b.name() == $name {
- // if they go down, we must not go up
- (rot == $at && rot.mirrored(true, true) != $srot) as u8
- } else {
- 0
- }
- } else {
- 0
- }
- }};
- }
- use Rotation::*;
- let mut x = 0b0000;
-
- // println!("{:?}, {ctx}", ctx.cross);
- x |= 8 * c!(ctx.cross[0], ctx.rotation, n, Down);
- x |= 4 * c!(ctx.cross[1], ctx.rotation, n, Left);
- x |= 2 * c!(ctx.cross[2], ctx.rotation, n, Up);
- x |= c!(ctx.cross[3], ctx.rotation, n, Right);
- U4::from(x)
-}
-
-fn tile(ctx: &RenderingContext<'_>, name: &str, rot: Rotation) -> ImageHolder {
- mask2tile(mask(ctx, name), rot, name)
-}
-
-const FLIP_X: u8 = 1;
-const FLIP_Y: u8 = 2;
-
-/// TODO figure out if a flip is cheaper than a rotate_270
-fn mask2tile(mask: U4, rot: Rotation, name: &str) -> ImageHolder {
- use U4::*;
- // let lo = |index: u8| {
- // load("distribution/conveyors", &format!("{name}-{index}"))
- // .unwrap()
- // .value()
- // };
- // r == 5 => flip_v + r - 1
- macro_rules! p {
- ($image:literal, $rotation:literal) => {
- ($image, $rotation, None)
- };
- ($image:literal, $rotation:literal, $flipping:expr) => {
- ($image, $rotation, Some($flipping))
- };
- }
-
- let (index, r, flip) = match mask {
- // from left
- B0001 => match rot {
- Rotation::Down => p!(1, 1, FLIP_Y), // ┐
- Rotation::Right => p!(0, 0), // ─
- Rotation::Up => p!(1, 3), // ┘
- _ => unreachable!(),
- },
- // from below
- B0010 => match rot {
- Rotation::Left => p!(1, 2), // ┐
- Rotation::Right => p!(1, 1), // ┌
- Rotation::Up => p!(0, 3), // │
- _ => unreachable!(),
- },
- // from bottom + left
- B0011 => match rot {
- Rotation::Right => p!(2, 0), // ┬
- Rotation::Up => p!(2, 3, FLIP_Y | FLIP_X), // ┤
- _ => unreachable!(),
- },
- // from right
- B0100 => match rot {
- Rotation::Left => p!(0, 2), // ─
- Rotation::Down => p!(1, 1), // ┌
- Rotation::Up => p!(1, 1, FLIP_X), // └
- _ => unreachable!(),
- },
- // from sides
- B0101 => match rot {
- Rotation::Up => p!(4, 3), // ┴
- Rotation::Down => p!(4, 1), // ┬
- _ => unreachable!(),
- },
- // from right + down
- B0110 => match rot {
- Rotation::Up => p!(2, 3), // ├,
- Rotation::Left => p!(2, 0, FLIP_X), // ┬
- _ => unreachable!(),
- },
- // from right + down + left
- B0111 => match rot {
- Rotation::Up => p!(3, 3), // ┼
- _ => unreachable!(),
- },
- // from above
- B1000 => match rot {
- Rotation::Down => p!(0, 1), // │
- Rotation::Left => p!(1, 0, FLIP_X), // ┘
- Rotation::Right => p!(1, 0), // └
- _ => unreachable!(),
- },
- // from top and left
- B1001 => match rot {
- Rotation::Right => p!(2, 0, FLIP_Y), // ┴
- Rotation::Down => p!(2, 1), // ┤
- _ => unreachable!(),
- },
- // from top sides
- B1010 => match rot {
- Rotation::Right => p!(4, 0), // ├
- Rotation::Left => p!(4, 3), // ┤
- _ => unreachable!(),
- },
- // from top, left, bottom
- B1011 => match rot {
- Rotation::Right => p!(3, 0), // ┼
- _ => unreachable!(),
- },
- // from top and right
- B1100 => match rot {
- Rotation::Down => p!(2, 3, FLIP_X), // ├
- Rotation::Left => p!(2, 2), // ┴
- _ => unreachable!(),
- },
- // from top, left, right
- B1101 => match rot {
- Rotation::Down => p!(3, 1), // ┼
- _ => unreachable!(),
- },
- // from top, right, bottom
- B1110 => match rot {
- Rotation::Left => p!(3, 0, FLIP_X), // ┼
- _ => unreachable!(),
- },
- B0000 => (
- 0,
- match rot {
- Rotation::Left => 2,
- Rotation::Right => 0,
- Rotation::Down => 1,
- Rotation::Up => 3,
- },
- None,
- ),
- // B0000 => (0, wrap(rot.count() as i8 - 1, 0, 3) as u8, None),
- B1111 => unreachable!(),
- };
- let mut p = ImageHolder::from(load("distribution/conveyors", &format!("{name}-{index}")));
- if let Some(op) = flip {
- let re: &mut RgbaImage = p.borrow_mut();
- if (op & FLIP_X) != 0 {
- flip_h(re);
- }
- if (op & FLIP_Y) != 0 {
- flip_v(re);
- }
- }
- if r == 0 {
- return p;
- }
- let mut p = p.own();
- p.rotate(r);
- ImageHolder::from(p)
-}
make_simple!(
ConveyorBlock,
|_, _, name, _, ctx: Option<&RenderingContext>| {
- if let Some(ctx) = ctx {
- return Some(tile(ctx, name, ctx.rotation));
- }
- None
+ let ctx = ctx.unwrap(); // we specified want_context to true
+ Some(tile(ctx, "distribution", "conveyors", name, ctx.rotation))
},
true
);
+
make_simple!(
JunctionBlock,
|_, _, _, _, _| None,
diff --git a/src/block/liquid.rs b/src/block/liquid.rs
index d74fc9d..5141025 100644
--- a/src/block/liquid.rs
+++ b/src/block/liquid.rs
@@ -11,22 +11,45 @@ use crate::fluid;
use crate::utils::ImageUtils;
make_simple!(LiquidBlock);
+make_simple!(
+ ConduitBlock,
+ |_, _, name, _, ctx: Option<&RenderingContext>| {
+ let ctx = ctx.unwrap();
+ let mask = mask(ctx, name);
+ let (index, rot, flip) = mask2rotations(mask, ctx.rotation);
+ let tile = rotations2tile(
+ (index, rot, flip),
+ "liquid",
+ "conduits",
+ &format!("{name}-top"),
+ );
+ let mut bottom = load("liquid", &format!("conduits/conduit-bottom-{index}"))
+ .unwrap()
+ .to_owned();
+ flrot(flip, rot, &mut bottom);
+ bottom.tint(image::Rgb([74, 75, 83]));
+ bottom.overlay(tile.borrow(), 0, 0);
+ // TODO caps. stopped trying bcz too complex
+ Some(ImageHolder::from(bottom))
+ },
+ true
+);
make_register! {
"reinforced-pump" => LiquidBlock::new(2, true, cost!(Beryllium: 40, Tungsten: 30, Silicon: 20));
"mechanical-pump" => LiquidBlock::new(1, true, cost!(Copper: 15, Metaglass: 10));
"rotary-pump" => LiquidBlock::new(2, true, cost!(Copper: 70, Metaglass: 50, Titanium: 35, Silicon: 20));
"impulse-pump" => LiquidBlock::new(3, true, cost!(Copper: 80, Metaglass: 90, Titanium: 40, Thorium: 35, Silicon: 30));
- "conduit" => LiquidBlock::new(1, false, cost!(Metaglass: 1));
- "pulse-conduit" => LiquidBlock::new(1, false, cost!(Metaglass: 1, Titanium: 2));
- "plated-conduit" => LiquidBlock::new(1, false, cost!(Metaglass: 1, Thorium: 2, Plastanium: 1));
+ "conduit" => ConduitBlock::new(1, false, cost!(Metaglass: 1));
+ "pulse-conduit" => ConduitBlock::new(1, false, cost!(Metaglass: 1, Titanium: 2));
+ "plated-conduit" => ConduitBlock::new(1, false, cost!(Metaglass: 1, Thorium: 2, Plastanium: 1));
"liquid-router" => LiquidBlock::new(1, true, cost!(Metaglass: 2, Graphite: 4));
"liquid-container" => LiquidBlock::new(2, true, cost!(Metaglass: 15, Titanium: 10));
"liquid-tank" => LiquidBlock::new(3, true, cost!(Metaglass: 40, Titanium: 30));
"liquid-junction" => LiquidBlock::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" => LiquidBlock::new(1, false, cost!(Beryllium: 2));
+ "reinforced-conduit" => ConduitBlock::new(1, false, cost!(Beryllium: 2));
"reinforced-liquid-junction" => LiquidBlock::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" => LiquidBlock::new(1, true, cost!(Graphite: 8, Beryllium: 4));
diff --git a/src/block/mod.rs b/src/block/mod.rs
index 0afe8f6..ddfa58b 100644
--- a/src/block/mod.rs
+++ b/src/block/mod.rs
@@ -291,23 +291,19 @@ impl RegistryEntry for Block {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
/// the possible rotation states of a object
+#[repr(u8)]
pub enum Rotation {
- Right,
Up,
- Left,
+ Right,
Down,
+ Left,
}
impl Rotation {
#[must_use]
/// count rotations
pub fn count(self) -> u8 {
- match self {
- Self::Up => 0,
- Self::Right => 1,
- Self::Down => 2,
- Self::Left => 3,
- }
+ self as u8
}
#[must_use]
diff --git a/src/data/autotile.rs b/src/data/autotile.rs
new file mode 100644
index 0000000..8413359
--- /dev/null
+++ b/src/data/autotile.rs
@@ -0,0 +1,455 @@
+use super::renderer::*;
+use super::GridPos;
+use crate::block::{Block, Rotation};
+use bobbin_bits::U4;
+use image::imageops::{flip_horizontal_in_place as flip_h, flip_vertical_in_place as flip_v};
+
+#[cfg(test)]
+macro_rules! dir {
+ (^) => {
+ crate::block::Rotation::Up
+ };
+ (v) => {
+ crate::block::Rotation::Down
+ };
+ (<) => {
+ crate::block::Rotation::Left
+ };
+ (>) => {
+ crate::block::Rotation::Right
+ };
+}
+#[cfg(test)]
+macro_rules! conv {
+ (_) => {
+ None
+ };
+ ($dir:tt) => {
+ Some((
+ &crate::block::distribution::CONVEYOR,
+ crate::data::autotile::dir!($dir),
+ ))
+ };
+}
+#[cfg(test)]
+macro_rules! define {
+ ($a:tt,$b:tt,$c:tt,$d:tt) => {
+ [
+ crate::data::autotile::conv!($a),
+ crate::data::autotile::conv!($b),
+ crate::data::autotile::conv!($c),
+ crate::data::autotile::conv!($d),
+ ]
+ };
+}
+
+#[cfg(test)]
+pub(crate) use conv;
+#[cfg(test)]
+pub(crate) use define;
+#[cfg(test)]
+pub(crate) use dir;
+
+pub type Cross<'l> = [Option<(&'l Block, Rotation)>; 4];
+/// holds the 4 bordering blocks
+#[derive(Copy, Clone)]
+pub struct RenderingContext<'l> {
+ pub cross: Cross<'l>,
+ pub rotation: Rotation,
+ pub position: PositionContext,
+}
+
+/// holds positions
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct PositionContext {
+ pub position: GridPos,
+ pub width: usize,
+ pub height: usize,
+}
+
+impl std::fmt::Debug for PositionContext {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "PC<{:?} ({}/{})>",
+ self.position, self.width, self.height
+ )
+ }
+}
+
+impl std::fmt::Debug for RenderingContext<'_> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Display::fmt(self, f)
+ }
+}
+
+impl std::fmt::Display for RenderingContext<'_> {
+ /// this display impl shows RC<$directions=+own rotation>
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "RC<")?;
+ macro_rules! f {
+ ($f:expr, $z:expr, $x:literal, $at: expr, $srot: expr) => {
+ if let Some((_, rot)) = $z {
+ if (rot == $at && rot.mirrored(true, true) != $srot) {
+ $f.write_str($x)?;
+ }
+ }
+ };
+ }
+ f!(f, self.cross[0], "N = ", Rotation::Down, self.rotation);
+ f!(f, self.cross[1], "E = ", Rotation::Left, self.rotation);
+ f!(f, self.cross[2], "S = ", Rotation::Up, self.rotation);
+ f!(f, self.cross[3], "W = ", Rotation::Right, self.rotation);
+
+ write!(f, "{:?}>", self.rotation)
+ }
+}
+
+#[cfg(test)]
+fn print_crosses(v: Vec<Cross<'_>>, height: usize) -> String {
+ let mut s = String::new();
+ for c in v.chunks(height) {
+ for c in c {
+ s.push(c[0].map_or('_', |(_, r)| r.ch()));
+ for c in c[1..].iter() {
+ s.push(',');
+ s.push(c.map_or('_', |(_, r)| r.ch()));
+ }
+ s.push(' ');
+ }
+ s.push('\n');
+ }
+ s
+}
+
+pub fn tile(
+ ctx: &RenderingContext<'_>,
+ category: &str,
+ subcategory: &str,
+ name: &str,
+ rot: Rotation,
+) -> ImageHolder {
+ rotations2tile(
+ mask2rotations(mask(ctx, name), rot),
+ category,
+ subcategory,
+ name,
+ )
+}
+
+pub fn mask2rotations(mask: U4, rot: Rotation) -> (u8, u8, u8) {
+ use U4::*;
+ macro_rules! p {
+ ($image:literal, $rotation:literal) => {
+ ($image, $rotation, 0)
+ };
+ ($image:literal, $rotation:literal, $flipping:expr) => {
+ ($image, $rotation, $flipping)
+ };
+ }
+
+ match mask {
+ // from left
+ B0001 => match rot {
+ Rotation::Down => p!(1, 1, FLIP_Y), // ┐
+ Rotation::Right => p!(0, 0), // ─
+ Rotation::Up => p!(1, 3), // ┘
+ _ => unreachable!(),
+ },
+ // from below
+ B0010 => match rot {
+ Rotation::Left => p!(1, 2), // ┐
+ Rotation::Right => p!(1, 1), // ┌
+ Rotation::Up => p!(0, 3), // │
+ _ => unreachable!(),
+ },
+ // from bottom + left
+ B0011 => match rot {
+ Rotation::Right => p!(2, 0), // ┬
+ Rotation::Up => p!(2, 3, FLIP_Y | FLIP_X), // ┤
+ _ => unreachable!(),
+ },
+ // from right
+ B0100 => match rot {
+ Rotation::Left => p!(0, 2), // ─
+ Rotation::Down => p!(1, 1), // ┌
+ Rotation::Up => p!(1, 1, FLIP_X), // └
+ _ => unreachable!(),
+ },
+ // from sides
+ B0101 => match rot {
+ Rotation::Up => p!(4, 3), // ┴
+ Rotation::Down => p!(4, 1), // ┬
+ _ => unreachable!(),
+ },
+ // from right + down
+ B0110 => match rot {
+ Rotation::Up => p!(2, 3), // ├,
+ Rotation::Left => p!(2, 0, FLIP_X), // ┬
+ _ => unreachable!(),
+ },
+ // from right + down + left
+ B0111 => match rot {
+ Rotation::Up => p!(3, 3), // ┼
+ _ => unreachable!(),
+ },
+ // from above
+ B1000 => match rot {
+ Rotation::Down => p!(0, 1), // │
+ Rotation::Left => p!(1, 0, FLIP_X), // ┘
+ Rotation::Right => p!(1, 0), // └
+ _ => unreachable!(),
+ },
+ // from top and left
+ B1001 => match rot {
+ Rotation::Right => p!(2, 0, FLIP_Y), // ┴
+ Rotation::Down => p!(2, 1), // ┤
+ _ => unreachable!(),
+ },
+ // from top sides
+ B1010 => match rot {
+ Rotation::Right => p!(4, 0), // ├
+ Rotation::Left => p!(4, 3), // ┤
+ _ => unreachable!(),
+ },
+ // from top, left, bottom
+ B1011 => match rot {
+ Rotation::Right => p!(3, 0), // ┼
+ _ => unreachable!(),
+ },
+ // from top and right
+ B1100 => match rot {
+ Rotation::Down => p!(2, 3, FLIP_X), // ├
+ Rotation::Left => p!(2, 2), // ┴
+ _ => unreachable!(),
+ },
+ // from top, left, right
+ B1101 => match rot {
+ Rotation::Down => p!(3, 1), // ┼
+ _ => unreachable!(),
+ },
+ // from top, right, bottom
+ B1110 => match rot {
+ Rotation::Left => p!(3, 0, FLIP_X), // ┼
+ _ => unreachable!(),
+ },
+ B0000 => (
+ 0,
+ match rot {
+ Rotation::Left => 2,
+ Rotation::Right => 0,
+ Rotation::Down => 1,
+ Rotation::Up => 3,
+ },
+ 0,
+ ),
+ B1111 => unreachable!(),
+ }
+}
+
+pub const FLIP_X: u8 = 1;
+pub const FLIP_Y: u8 = 2;
+
+pub fn flrot(flip: u8, rot: u8, with: &mut RgbaImage) {
+ if (flip & FLIP_X) != 0 {
+ flip_h(with);
+ }
+ if (flip & FLIP_Y) != 0 {
+ flip_v(with);
+ }
+ with.rotate(rot);
+ // let mut from = from.own();
+ // from.rotate(rot);
+ // ImageHolder::from(from)
+}
+
+/// TODO figure out if a flip is cheaper than a rotate_270
+pub fn rotations2tile(
+ (index, rot, flip): (u8, u8, u8),
+ category: &str,
+ subcategory: &str,
+ name: &str,
+) -> ImageHolder {
+ let mut p = ImageHolder::from(load(category, &format!("{subcategory}/{name}-{index}")));
+ flrot(flip, rot, p.borrow_mut());
+ ImageHolder::from(p)
+}
+
+pub fn mask(ctx: &RenderingContext, n: &str) -> U4 {
+ macro_rules! c {
+ ($in: expr, $srot: expr, $name: expr, $at: expr) => {{
+ if let Some((b, rot)) = $in {
+ if b.name() == $name {
+ // if they go down, we must not go up
+ (rot == $at && rot.mirrored(true, true) != $srot) as u8
+ } else {
+ 0
+ }
+ } else {
+ 0
+ }
+ }};
+ }
+ use Rotation::*;
+ let mut x = 0b0000;
+
+ x |= 8 * c!(ctx.cross[0], ctx.rotation, n, Down);
+ x |= 4 * c!(ctx.cross[1], ctx.rotation, n, Left);
+ x |= 2 * c!(ctx.cross[2], ctx.rotation, n, Up);
+ x |= c!(ctx.cross[3], ctx.rotation, n, Right);
+ U4::from(x)
+}
+
+pub trait RotationState {
+ fn get_rotation(&self) -> Option<Rotation>;
+}
+pub trait BlockState<'l> {
+ fn get_block(&'l self) -> Option<&'l Block>;
+}
+pub(crate) trait Crossable {
+ fn cross(&self, j: usize, c: &PositionContext) -> Cross;
+}
+
+#[test]
+fn test_cross() {
+ let mut reg = crate::block::BlockRegistry::default();
+ crate::block::distribution::register(&mut reg);
+ let mut ss = super::schematic::SchematicSerializer(&reg);
+ macro_rules! test {
+ ($schem: literal => $($a:tt,$b:tt,$c:tt,$d:tt)*) => {
+ let s = ss.deserialize_base64($schem).unwrap();
+ let mut c = vec![];
+ println!("{:#?}", s.blocks);
+
+ for (position, _) in s.block_iter() {
+ let pctx = PositionContext {
+ position,
+ width: s.width,
+ height: s.height,
+ };
+ c.push(s.cross(&pctx));
+ }
+ let n = s.tags.get("name").map_or("<unknown>", |x| &x);
+ let cc: Vec<Cross> = vec![
+ $(define!($a,$b,$c,$d),)*
+ ];
+ if cc != c {
+ let a = print_crosses(cc, s.height as usize);
+ let b = print_crosses(c, s.height as usize);
+ for diff in diff::lines(&a, &b) {
+ match diff {
+ diff::Result::Left(l) => println!("\x1b[38;5;1m{}", l),
+ diff::Result::Right(r) => println!("\x1b[38;5;2m{}", r),
+ diff::Result::Both(l, _) => println!("\x1b[0m{}", l),
+ }
+ }
+ print!("\x1b[0m");
+ /*
+ for diff in diff::slice(&c.into_iter().enumerate().collect::<Vec<_>>(), &cc.into_iter().enumerate().collect::<Vec<_>>()) {
+ match diff {
+ diff::Result::Left((i, l)) => println!("\x1b[38;5;1m- {l:?} at {i}"),
+ diff::Result::Right((i, r)) => println!("\x1b[38;5;2m+ {r:?} at {i}"),
+ diff::Result::Both((i, l), _) => println!("\x1b[0m {l:?} at {i}"),
+ }
+ }
+ */
+ panic!("test {n} \x1b[38;5;1mfailed\x1b[0m")
+ }
+ println!("test {n} \x1b[38;5;2mpassed\x1b[0m");
+ };
+ }
+ // crosses go from bottom left -> top left -> bottom left + 1 -> top left + 1...
+ // the symbols are directions (> => Right...), which mean the neighbors pointing direction
+ // _ = no block
+
+ // the basic test
+ // ─┐
+ // ─┤
+ test!("bXNjaAF4nGNgYmBiZmDJS8xNZWBNSizOTGbgTkktTi7KLCjJzM9jYGBgy0lMSs0pZmCNfr9gTSwjA0dyfl5ZamV+EVCOhQEBGGEEM4hiZGAGAOb+EWA=" =>
+ // (0, 0) (0, 1)
+ // n e s w borders (west void for first row)
+ >,v,_,_ _,v,>,_
+ // (1, 0) (1, 1)
+ v,_,_,> _,_,v,>
+ );
+ // the loop test
+ // ─│─
+ // ─┼┐
+ // ─└┘
+ test!("bXNjaAF4nDWK4QqAIBCDd6dE0SNGP8zuh2CeaAS9fZk0xvjGBgNjYJM7BDaqy5h3qb6EfAZNAIboNokVvKyE0Wu65NbyDhM+cQv6mTtTM/WFYfqLm6m3lx9MAg7n" =>
+ >,^,_,_ <,>,<,_ _,v,>,_
+ >,<,_,< v,v,^,> _,>,>,<
+ v,_,_,^ >,_,<,> _,_,v,v
+ );
+ // the snek test
+ // └┐
+ // ─┘
+ test!("bXNjaAF4nGNgYmBiZmDJS8xNZWApzkvNZuBOSS1OLsosKMnMz2NgYGDLSUxKzSlmYIqOZWTgSM7PK0utzC8CSrAwIAAjEIIQhGJkYAIARA0Ozg==" =>
+ ^,^,_,_ _,<,>,_
+ <,_,_,> _,_,^,^
+ );
+
+ // the notile test
+ test!("bXNjaAF4nCWJQQqAIBREx69E0Lq994oWph8STEMj6fZpzcDjDQMCSahoDsZsdN1TYB25aucz28uniMlxsdmf3wCGYDYOBbSsAqNN8eYn5XYofJEdAtSB31tfaoIVGw==" =>
+ <,>,_,_ _,^,v,_
+ ^,_,_,v _,_,>,<
+ );
+ // the asymmetrical test
+ // <───
+ // ───>
+ test!("bXNjaAF4nEXJwQqAIBAE0HGVCPrE6GC2B0HdcCPw78MKnMMwj4EFWbjiM8N5bRnLwRpqPK8oBcCU/M5JQetmMAcpNzep/cCIAfX69yv6RF0PFy0O4Q==" =>
+ <,>,_,_ _,<,>,_
+ <,>,_,> _,<,>,<
+ // <,_,_,> _,_,>,<
+ <,_,_,> _,_,>,<
+ );
+
+ // the complex test
+ // ─┬─│││─
+ // ─┤─┘─┘─
+ // ─┤┌─│─┐
+ // ─┼┘─┴─│
+ test!("bXNjaAF4nEWOUQ7CIBBEh2VZTbyCx/A2xg9a+WiC0LTGxNvb7Wjk5wEzb7M4QCO05UdBqj3PF5zuZR2XaX5OvQGwmodSV8j1FnAce3uVd1+24Iz/CYQQ8fcVHYEQIjqEXWEm9LwgX9kR+PLSbm2BMlN6Sk/3LhJnJu6S6CVmxl2MntEzv38AchUPug==" =>
+ >,v,_,_ >,v,>,_ >,v,>,_ _,v,>,_
+ v,<,_,> v,v,v,> v,>,v,> _,<,v,>
+ v,>,_,v >,<,<,v <,^,v,v _,v,>,v
+ <,>,_,< ^,v,>,v v,>,<,> _,^,^,<
+ v,<,_,> >,>,>,< ^,^,v,^ _,^,>,v
+ >,v,_,> ^,v,<,v ^,>,>,> _,>,^,^
+ // <,_,_,< ^,_,>,v v,_,<,> _,_,^,<
+ // v,_,_,> >,_,>,< ^,_,v,^ _,_,>,v
+ // >,_,_,> ^,_,<,v ^,_,>,> _,_,^,^
+ v,_,_,< >,_,v,> >,_,v,^ _,_,>,^
+ );
+}
+
+#[test]
+fn test_mask() {
+ macro_rules! assert {
+ ($a:tt,$b:tt,$c:tt,$d:tt => $rot: tt => $expect: expr) => {
+ assert_eq!(mask!(define!($a, $b, $c, $d), $rot), $expect)
+ };
+ }
+ macro_rules! mask {
+ ($cross:expr, $rot: tt) => {
+ mask(
+ &RenderingContext {
+ position: PositionContext {
+ position: GridPos(5, 5),
+ width: 10,
+ height: 10,
+ },
+ cross: $cross,
+ rotation: dir!($rot),
+ },
+ "conveyor",
+ )
+ };
+ }
+ assert!(_,_,_,_ => ^ => U4::B0000);
+ assert!(v,_,_,_ => > => U4::B1000);
+ assert!(v,v,_,_ => v => U4::B1000);
+ assert!(_,v,>,_ => > => U4::B0000);
+ assert!(v,>,<,> => ^ => U4::B0001);
+ assert!(v,>,>,_ => > => U4::B1000);
+}
diff --git a/src/data/map.rs b/src/data/map.rs
index 5431d01..9c633ea 100644
--- a/src/data/map.rs
+++ b/src/data/map.rs
@@ -81,7 +81,7 @@ use crate::data::DataRead;
use crate::fluid::Type as Fluid;
use crate::item::{storage::Storage, Type as Item};
use crate::team::Team;
-#[allow(unused_imports)]
+#[cfg(doc)]
use crate::{block::content, data::*, fluid, item, modifier, unit};
use super::Serializer;
diff --git a/src/data/mod.rs b/src/data/mod.rs
index c658210..60380df 100644
--- a/src/data/mod.rs
+++ b/src/data/mod.rs
@@ -9,6 +9,7 @@ use std::fmt;
use std::str::Utf8Error;
use thiserror::Error;
+pub(crate) mod autotile;
mod base64;
mod command;
pub mod dynamic;
diff --git a/src/data/renderer.rs b/src/data/renderer.rs
index cb6d88d..ecbbf85 100644
--- a/src/data/renderer.rs
+++ b/src/data/renderer.rs
@@ -4,12 +4,13 @@ use dashmap::DashMap;
use image::codecs::png::PngDecoder;
pub(crate) use image::{DynamicImage, RgbaImage};
use std::io::{BufReader, Cursor};
+use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use zip::ZipArchive;
+pub(crate) use super::autotile::*;
use crate::block::environment::METAL_FLOOR;
-use crate::block::{Block, Rotation};
use crate::team::SHARDED;
pub(crate) use crate::utils::ImageUtils;
use crate::Map;
@@ -58,6 +59,19 @@ impl BorrowMut<RgbaImage> for ImageHolder {
}
}
+impl Deref for ImageHolder {
+ type Target = RgbaImage;
+ fn deref(&self) -> &Self::Target {
+ self.borrow()
+ }
+}
+
+impl DerefMut for ImageHolder {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.borrow_mut()
+ }
+}
+
impl From<Option<Ref<'static, PathBuf, RgbaImage>>> for ImageHolder {
fn from(value: Option<Ref<'static, PathBuf, RgbaImage>>) -> Self {
Self::Borrow(value.unwrap())
@@ -76,61 +90,6 @@ impl From<RgbaImage> for ImageHolder {
}
}
-pub type Cross<'l> = [Option<(&'l Block, Rotation)>; 4];
-/// holds the 4 bordering blocks
-#[derive(Copy, Clone)]
-pub struct RenderingContext<'l> {
- pub cross: Cross<'l>,
- pub rotation: Rotation,
- pub position: PositionContext,
-}
-
-/// holds positions
-#[derive(Copy, Clone, Eq, PartialEq)]
-pub struct PositionContext {
- pub position: GridPos,
- pub width: usize,
- pub height: usize,
-}
-
-impl std::fmt::Debug for PositionContext {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(
- f,
- "PC<{:?} ({}/{})>",
- self.position, self.width, self.height
- )
- }
-}
-
-impl std::fmt::Debug for RenderingContext<'_> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- std::fmt::Display::fmt(self, f)
- }
-}
-
-impl std::fmt::Display for RenderingContext<'_> {
- /// this display impl shows RC<$directions=+own rotation>
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "RC<")?;
- macro_rules! f {
- ($f:expr, $z:expr, $x:literal, $at: expr, $srot: expr) => {
- if let Some((_, rot)) = $z {
- if (rot == $at && rot.mirrored(true, true) != $srot) {
- $f.write_str($x)?;
- }
- }
- };
- }
- f!(f, self.cross[0], "N = ", Rotation::Down, self.rotation);
- f!(f, self.cross[1], "E = ", Rotation::Left, self.rotation);
- f!(f, self.cross[2], "S = ", Rotation::Up, self.rotation);
- f!(f, self.cross[3], "W = ", Rotation::Right, self.rotation);
-
- write!(f, "{:?}>", self.rotation)
- }
-}
-
static CACHE: OnceLock<Cache> = OnceLock::new();
pub(crate) fn load(category: &str, name: &str) -> Option<Ref<'static, PathBuf, RgbaImage>> {
let key = Path::new("blocks").join(category).join(name);
@@ -199,155 +158,6 @@ where
c
}
-pub trait RotationState {
- fn get_rotation(&self) -> Option<Rotation>;
-}
-pub trait BlockState<'l> {
- fn get_block(&'l self) -> Option<&'l Block>;
-}
-pub(crate) trait Crossable {
- fn cross(&self, j: usize, c: &PositionContext) -> Cross;
-}
-
-// pub(crate) trait Darray {
-// type Output;
-// fn n(&self, j: usize, c: &PositionContext) -> Option<&Self::Output>;
-// fn e(&self, j: usize, c: &PositionContext) -> Option<&Self::Output>;
-// fn s(&self, j: usize, c: &PositionContext) -> Option<&Self::Output>;
-// fn w(&self, j: usize, c: &PositionContext) -> Option<&Self::Output>;
-// }
-
-#[cfg(test)]
-fn print_crosses(v: Vec<Cross<'_>>, height: usize) -> String {
- let mut s = String::new();
- for c in v.chunks(height) {
- for c in c {
- s.push(c[0].map_or('_', |(_, r)| r.ch()));
- for c in c[1..].iter() {
- s.push(',');
- s.push(c.map_or('_', |(_, r)| r.ch()));
- }
- s.push(' ');
- }
- s.push('\n');
- }
- s
-}
-
-#[test]
-fn test_cross() {
- use crate::block::distribution::define;
- let mut reg = crate::block::BlockRegistry::default();
- crate::block::distribution::register(&mut reg);
- let mut ss = super::schematic::SchematicSerializer(&reg);
- macro_rules! test {
- ($schem: literal => $($a:tt,$b:tt,$c:tt,$d:tt)*) => {
- let s = ss.deserialize_base64($schem).unwrap();
- let mut c = vec![];
- println!("{:#?}", s.blocks);
-
- for (position, _) in s.block_iter() {
- let pctx = PositionContext {
- position,
- width: s.width,
- height: s.height,
- };
- c.push(s.cross(&pctx));
- }
- let n = s.tags.get("name").map_or("<unknown>", |x| &x);
- let cc: Vec<Cross> = vec![
- $(define!($a,$b,$c,$d),)*
- ];
- if cc != c {
- let a = print_crosses(cc, s.height as usize);
- let b = print_crosses(c, s.height as usize);
- for diff in diff::lines(&a, &b) {
- match diff {
- diff::Result::Left(l) => println!("\x1b[38;5;1m{}", l),
- diff::Result::Right(r) => println!("\x1b[38;5;2m{}", r),
- diff::Result::Both(l, _) => println!("\x1b[0m{}", l),
- }
- }
- print!("\x1b[0m");
- /*
- for diff in diff::slice(&c.into_iter().enumerate().collect::<Vec<_>>(), &cc.into_iter().enumerate().collect::<Vec<_>>()) {
- match diff {
- diff::Result::Left((i, l)) => println!("\x1b[38;5;1m- {l:?} at {i}"),
- diff::Result::Right((i, r)) => println!("\x1b[38;5;2m+ {r:?} at {i}"),
- diff::Result::Both((i, l), _) => println!("\x1b[0m {l:?} at {i}"),
- }
- }
- */
- panic!("test {n} \x1b[38;5;1mfailed\x1b[0m")
- }
- println!("test {n} \x1b[38;5;2mpassed\x1b[0m");
- };
- }
- // crosses go from bottom left -> top left -> bottom left + 1 -> top left + 1...
- // the symbols are directions (> => Right...), which mean the neighbors pointing direction
- // _ = no block
-
- // the basic test
- // ─┐
- // ─┤
- test!("bXNjaAF4nGNgYmBiZmDJS8xNZWBNSizOTGbgTkktTi7KLCjJzM9jYGBgy0lMSs0pZmCNfr9gTSwjA0dyfl5ZamV+EVCOhQEBGGEEM4hiZGAGAOb+EWA=" =>
- // (0, 0) (0, 1)
- // n e s w borders (west void for first row)
- >,v,_,_ _,v,>,_
- // (1, 0) (1, 1)
- v,_,_,> _,_,v,>
- );
- // the loop test
- // ─│─
- // ─┼┐
- // ─└┘
- test!("bXNjaAF4nDWK4QqAIBCDd6dE0SNGP8zuh2CeaAS9fZk0xvjGBgNjYJM7BDaqy5h3qb6EfAZNAIboNokVvKyE0Wu65NbyDhM+cQv6mTtTM/WFYfqLm6m3lx9MAg7n" =>
- >,^,_,_ <,>,<,_ _,v,>,_
- >,<,_,< v,v,^,> _,>,>,<
- v,_,_,^ >,_,<,> _,_,v,v
- );
- // the snek test
- // └┐
- // ─┘
- test!("bXNjaAF4nGNgYmBiZmDJS8xNZWApzkvNZuBOSS1OLsosKMnMz2NgYGDLSUxKzSlmYIqOZWTgSM7PK0utzC8CSrAwIAAjEIIQhGJkYAIARA0Ozg==" =>
- ^,^,_,_ _,<,>,_
- <,_,_,> _,_,^,^
- );
-
- // the notile test
- test!("bXNjaAF4nCWJQQqAIBREx69E0Lq994oWph8STEMj6fZpzcDjDQMCSahoDsZsdN1TYB25aucz28uniMlxsdmf3wCGYDYOBbSsAqNN8eYn5XYofJEdAtSB31tfaoIVGw==" =>
- <,>,_,_ _,^,v,_
- ^,_,_,v _,_,>,<
- );
- // the asymmetrical test
- // <───
- // ───>
- test!("bXNjaAF4nEXJwQqAIBAE0HGVCPrE6GC2B0HdcCPw78MKnMMwj4EFWbjiM8N5bRnLwRpqPK8oBcCU/M5JQetmMAcpNzep/cCIAfX69yv6RF0PFy0O4Q==" =>
- <,>,_,_ _,<,>,_
- <,>,_,> _,<,>,<
- // <,_,_,> _,_,>,<
- <,_,_,> _,_,>,<
- );
-
- // the complex test
- // ─┬─│││─
- // ─┤─┘─┘─
- // ─┤┌─│─┐
- // ─┼┘─┴─│
- test!("bXNjaAF4nEWOUQ7CIBBEh2VZTbyCx/A2xg9a+WiC0LTGxNvb7Wjk5wEzb7M4QCO05UdBqj3PF5zuZR2XaX5OvQGwmodSV8j1FnAce3uVd1+24Iz/CYQQ8fcVHYEQIjqEXWEm9LwgX9kR+PLSbm2BMlN6Sk/3LhJnJu6S6CVmxl2MntEzv38AchUPug==" =>
- >,v,_,_ >,v,>,_ >,v,>,_ _,v,>,_
- v,<,_,> v,v,v,> v,>,v,> _,<,v,>
- v,>,_,v >,<,<,v <,^,v,v _,v,>,v
- <,>,_,< ^,v,>,v v,>,<,> _,^,^,<
- v,<,_,> >,>,>,< ^,^,v,^ _,^,>,v
- >,v,_,> ^,v,<,v ^,>,>,> _,>,^,^
- // <,_,_,< ^,_,>,v v,_,<,> _,_,^,<
- // v,_,_,> >,_,>,< ^,_,v,^ _,_,>,v
- // >,_,_,> ^,_,<,v ^,_,>,> _,_,^,^
- v,_,_,< >,_,v,> >,_,v,^ _,_,>,^
- );
-}
-
/// trait for renderable objects
pub trait Renderable {
/// creates a picture of a schematic. Bridges and node connections are not drawn.
@@ -501,7 +311,7 @@ fn all_blocks() {
None,
Some(&RenderingContext {
cross: [None; 4],
- rotation: Rotation::Up,
+ rotation: crate::block::Rotation::Up,
position: PositionContext {
position: GridPos(0, 0),
width: 5,
diff --git a/src/data/schematic.rs b/src/data/schematic.rs
index 5be24b5..5255c4d 100644
--- a/src/data/schematic.rs
+++ b/src/data/schematic.rs
@@ -729,7 +729,6 @@ mod test {
let parsed2 = unwrap_pretty(ser.deserialize_base64(&unparsed));
println!("\x1b[38;5;2mredeserialized\x1b[0m {}", parsed.tags.get("name").unwrap());
if parsed != parsed2 {
- // TODO serialization currently lossy? missing right side mostly
parsed2.render().save("p2.png").unwrap();
parsed.render().save("p1.png").unwrap();
panic!("DIFFERENT! see `p1.png` != `p2.png`")
@@ -748,6 +747,7 @@ mod test {
"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";
"bXNjaAF4nGNgZGBkZmDJS8xNZeBMrShIzSvOLEtl4E5JLU4uyiwoyczPY2BgYMtJTErNKWZgio5lZODPzUwuytctKMpPTi0uzi8CyjMygAAfA4PQ+Yo5by9u9GxmZGB9GME502nTzKW+Aht/FJq1ez+o8nzYGn5n+wHR70VVf23t9tutu58/Xbm+qr5t/v+PAa93zIn+L1BpFbXfY17fNf1Jyxd/7X7yMuOv0qjQqNCo0KjQqNCo0KjQqNCo0KjQqNCo0KjQqNCo0KjQqNCoEJWFHp987V9uXjv/9y4GAOhu6pc=";
"bXNjaAF4nGNgY2BjZmDJS8xNZWBLTswrSyxm4E5JLU4uyiwoyczPY2BgYMtJTErNKWZgio5lhKthYOBkAAE+IDZjIB8wUWoAC2UGMFHqBSaoF1QYGTycJjFMUFHxVPBkmpQyiYXhpAonQ4OnEAPDJBVWBhXPW0wek7bkTlRhvLXNk4khdzYLQ8M2sAEUeoGFUi+wUBoLLJR5AQDzuCAp";
+ "bXNjaAF4nEWNQRLCIAxFf5O0LhxdewlP5LighQUzCIyl97chVmHx8nmZDyYIQ7J7BUgqruLsw7q8Y22xZABTcnNIK+jxZJyWkv0WGy51S2u4H/Fak2vB/zJww/8MIAVZYh2Gw+jtCx2s+O7pE6nZB0V3bD1sTqtITe8Uc2JOzIm50RpH/U9Bht19AOy5Ge4=";
}
#[test]