mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/block/distribution.rs')
-rw-r--r--src/block/distribution.rs355
1 files changed, 287 insertions, 68 deletions
diff --git a/src/block/distribution.rs b/src/block/distribution.rs
index 69db26b..9a80e45 100644
--- a/src/block/distribution.rs
+++ b/src/block/distribution.rs
@@ -1,19 +1,269 @@
//! conveyors ( & ducts )
-use std::error::Error;
-use std::fmt;
+use std::borrow::BorrowMut;
use crate::block::simple::*;
use crate::block::*;
use crate::content;
use crate::data::dynamic::DynType;
-use crate::data::renderer::{load, ImageHolder};
use crate::item;
-use crate::utils::ImageUtils;
+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);
+}
-make_simple!(ConveyorBlock);
+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
+ },
+ true
+);
make_simple!(
JunctionBlock,
- |_, _, _, _| None,
+ |_, _, _, _, _| None,
|_, _, _, _, _, buff: &mut crate::data::DataRead| {
// format:
// - iterate 4
@@ -26,34 +276,37 @@ make_simple!(
buff.skip(n * 8)?;
}
Ok(())
- }
+ },
+ false
);
+make_simple!(ControlBlock);
+
make_register! {
"conveyor" => ConveyorBlock::new(1, false, cost!(Copper: 1));
"titanium-conveyor" => ConveyorBlock::new(1, false, cost!(Copper: 1, Lead: 1, Titanium: 1));
- "plastanium-conveyor" => ConveyorBlock::new(1, false, cost!(Graphite: 1, Silicon: 1, Plastanium: 1));
+ "plastanium-conveyor" => ControlBlock::new(1, false, cost!(Graphite: 1, Silicon: 1, Plastanium: 1));
"armored-conveyor" => ConveyorBlock::new(1, false, cost!(Metaglass: 1, Thorium: 1, Plastanium: 1));
"junction" => JunctionBlock::new(1, true, cost!(Copper: 2));
"bridge-conveyor" => BridgeBlock::new(1, false, cost!(Copper: 6, Lead: 6), 4, true);
"phase-conveyor" => BridgeBlock::new(1, false, cost!(Lead: 10, Graphite: 10, Silicon: 7, PhaseFabric: 5), 12, true);
"sorter" => ItemBlock::new(1, true, cost!(Copper: 2, Lead: 2));
"inverted-sorter" => ItemBlock::new(1, true, cost!(Copper: 2, Lead: 2));
- "router" => ConveyorBlock::new(1, true, cost!(Copper: 3));
- "distributor" => ConveyorBlock::new(2, true, cost!(Copper: 4, Lead: 4));
- "overflow-gate" => ConveyorBlock::new(1, true, cost!(Copper: 4, Lead: 2));
- "underflow-gate" => ConveyorBlock::new(1, true, cost!(Copper: 4, Lead: 2));
+ "router" => ControlBlock::new(1, true, cost!(Copper: 3));
+ "distributor" => ControlBlock::new(2, true, cost!(Copper: 4, Lead: 4));
+ "overflow-gate" => ControlBlock::new(1, true, cost!(Copper: 4, Lead: 2));
+ "underflow-gate" => ControlBlock::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" => ConveyorBlock::new(1, false, cost!(Beryllium: 1));
"armored-duct" => ConveyorBlock::new(1, false, cost!(Beryllium: 2, Tungsten: 1));
"duct-router" => ItemBlock::new(1, true, cost!(Beryllium: 10));
- "overflow-duct" => ConveyorBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8));
- "underflow-duct" => ConveyorBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8));
+ "overflow-duct" => ControlBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8));
+ "underflow-duct" => ControlBlock::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" => ConveyorBlock::new(1, false, cost!(SurgeAlloy: 1, Tungsten: 1));
- "surge-router" => ConveyorBlock::new(1, false, cost!(SurgeAlloy: 5, Tungsten: 1)); // not symmetric
- "unit-cargo-loader" => ConveyorBlock::new(3, true, cost!(Silicon: 80, SurgeAlloy: 50, Oxide: 20));
+ "surge-conveyor" => ControlBlock::new(1, false, cost!(SurgeAlloy: 1, Tungsten: 1));
+ "surge-router" => ControlBlock::new(1, false, cost!(SurgeAlloy: 5, Tungsten: 1)); // not symmetric
+ "unit-cargo-loader" => ControlBlock::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, &[]);
@@ -122,7 +375,13 @@ impl BlockLogic for ItemBlock {
}
}
- fn draw(&self, category: &str, name: &str, state: Option<&State>) -> Option<ImageHolder> {
+ fn draw(
+ &self,
+ category: &str,
+ name: &str,
+ state: Option<&State>,
+ _: Option<&RenderingContext>,
+ ) -> Option<ImageHolder> {
if !matches!(
name,
"unloader" | "item-source" | "sorter" | "inverted-sorter"
@@ -146,21 +405,16 @@ impl BlockLogic for ItemBlock {
}
}
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
+#[error("invalid config ({0}) for item")]
pub struct ItemConvertError(pub i32);
-impl fmt::Display for ItemConvertError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "invalid config ({}) for item", self.0)
- }
-}
-
-impl Error for ItemConvertError {}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum ItemDeserializeError {
+ #[error("expected Item but got {0:?}")]
ContentType(content::Type),
- NotFound(item::TryFromU16Error),
+ #[error("target item not found")]
+ NotFound(#[from] item::TryFromU16Error),
}
impl ItemDeserializeError {
@@ -172,34 +426,6 @@ impl ItemDeserializeError {
}
}
-impl From<item::TryFromU16Error> for ItemDeserializeError {
- fn from(err: item::TryFromU16Error) -> Self {
- Self::NotFound(err)
- }
-}
-
-impl fmt::Display for ItemDeserializeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::ContentType(have) => write!(
- f,
- "expected content {:?} but got {have:?}",
- content::Type::Item
- ),
- Self::NotFound(..) => f.write_str("target item not found"),
- }
- }
-}
-
-impl Error for ItemDeserializeError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- match self {
- Self::NotFound(e) => Some(e),
- _ => None,
- }
- }
-}
-
pub struct BridgeBlock {
size: u8,
symmetric: bool,
@@ -244,8 +470,8 @@ impl BlockLogic for BridgeBlock {
y,
})));
}
- let dx = i32::from(x) - i32::from(pos.0);
- let dy = i32::from(y) - i32::from(pos.1);
+ let dx = i32::from(x) - pos.0 as i32;
+ let dy = i32::from(y) - pos.1 as i32;
Ok(DynData::Point2(dx, dy))
}
@@ -334,16 +560,9 @@ impl BlockLogic for BridgeBlock {
}
}
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
+#[error("invalid coordinates ({x}, {y}) for bridge")]
pub struct BridgeConvertError {
pub x: i16,
pub y: i16,
}
-
-impl fmt::Display for BridgeConvertError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "invalid coordinate ({} / {}) for bridge", self.x, self.y)
- }
-}
-
-impl Error for BridgeConvertError {}