mindustry logic execution, map- and schematic- parsing and rendering
conduits
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | assets/blocks/liquid/conduits/conduit-bottom.png | bin | 156 -> 0 bytes | |||
| -rw-r--r-- | assets/blocks/liquid/conduits/reinforced-conduit-cap.png | bin | 179 -> 0 bytes | |||
| -rw-r--r-- | src/block/distribution.rs | 254 | ||||
| -rw-r--r-- | src/block/liquid.rs | 31 | ||||
| -rw-r--r-- | src/block/mod.rs | 12 | ||||
| -rw-r--r-- | src/data/autotile.rs | 455 | ||||
| -rw-r--r-- | src/data/map.rs | 2 | ||||
| -rw-r--r-- | src/data/mod.rs | 1 | ||||
| -rw-r--r-- | src/data/renderer.rs | 222 | ||||
| -rw-r--r-- | src/data/schematic.rs | 2 |
11 files changed, 510 insertions, 471 deletions
@@ -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 Binary files differdeleted file mode 100644 index 04cecf2..0000000 --- a/assets/blocks/liquid/conduits/conduit-bottom.png +++ /dev/null diff --git a/assets/blocks/liquid/conduits/reinforced-conduit-cap.png b/assets/blocks/liquid/conduits/reinforced-conduit-cap.png Binary files differdeleted file mode 100644 index c416e44..0000000 --- a/assets/blocks/liquid/conduits/reinforced-conduit-cap.png +++ /dev/null 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(®); + 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(®); - 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] |