mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/block/distribution.rs')
-rw-r--r--src/block/distribution.rs542
1 files changed, 542 insertions, 0 deletions
diff --git a/src/block/distribution.rs b/src/block/distribution.rs
new file mode 100644
index 0000000..e185d3a
--- /dev/null
+++ b/src/block/distribution.rs
@@ -0,0 +1,542 @@
+//! conveyors ( & ducts )
+use crate::block::simple::*;
+use crate::block::*;
+use crate::content;
+use crate::data::autotile::tile;
+use crate::data::dynamic::DynType;
+use crate::item;
+
+make_simple!(
+ ConveyorBlock,
+ |_, name, _, ctx: Option<&RenderingContext>, rot, s| tile(ctx.unwrap(), name, rot, s),
+ |_, _, _, buff: &mut DataRead| {
+ // format:
+ // - amount: `i32`
+ // - iterate amount:
+ // - val: `i32`
+ // - id = (((val >> 24) as u8) & 0xff) as u16
+ // - x = (val >> 16) as u8) as f32 / 127.0
+ // - y = ((val >> 8) as u8 as f32 + 128.0) / 255.0
+ let amount = buff.read_i32()?;
+ for _ in 0..amount {
+ buff.skip(4)?;
+ }
+ Ok(())
+ }
+);
+
+make_simple!(
+ DuctBlock,
+ |_, name, _, ctx: Option<&RenderingContext>, rot, s| tile(ctx.unwrap(), name, rot, s),
+ |_, _, _, buff: &mut DataRead| {
+ // format:
+ // - rec_dir: `i8`
+ buff.skip(1)
+ }
+);
+
+make_simple!(JunctionBlock => |_, _, _, buff| { read_directional_item_buffer(buff) });
+make_simple!(SimpleDuctBlock, |_, name, _, _, rot: Rotation, s| {
+ let mut base = load!("duct-base", s);
+ let mut top = load!(from name which is ["overflow-duct" "underflow-duct"], s);
+ unsafe {
+ // SAFETY: any load() is square
+ top.rotate(rot.rotated(false).count());
+ // SAFETY: same size
+ base.overlay(&top);
+ }
+ base
+});
+
+fn draw_stack(
+ _: &StackConveyor,
+ name: &str,
+ _: Option<&State>,
+ ctx: Option<&RenderingContext>,
+ rot: Rotation,
+ s: Scale,
+) -> ImageHolder<4> {
+ let ctx = ctx.unwrap();
+ let mask = mask(ctx, rot, name);
+ #[rustfmt::skip]
+ let edge = |n: u8| {
+ match n {
+ 0 => load!(concat "edge-0" => name which is ["surge-conveyor" | "plastanium-conveyor"], s),
+ 1 => load!(concat "edge-1" => name which is ["surge-conveyor" | "plastanium-conveyor"], s),
+ 2 => load!(concat "edge-2" => name which is ["surge-conveyor" | "plastanium-conveyor"], s),
+ _ => load!(concat "edge-3" => name which is ["surge-conveyor" | "plastanium-conveyor"], s)
+ }
+ };
+ let edgify = |skip, to: &mut ImageHolder<4>| {
+ for i in 0..4 {
+ if i == skip {
+ continue;
+ }
+ unsafe { to.overlay(&edge(i)) };
+ }
+ };
+ let gimme = |n: u8| match n {
+ 0 => load!(concat 0 => name which is ["surge-conveyor" | "plastanium-conveyor"], s),
+ 1 => load!(concat 1 => name which is ["surge-conveyor" | "plastanium-conveyor"], s),
+ _ => load!("plastanium-conveyor-2", s),
+ };
+ let empty = ctx.cross[rot.count() as usize].map_or(true, |(v, _)| v.name != name);
+ // mindustry says fuck this and just draws the arrow convs in schems but im better than that
+ if rot.mirrored(true, true).mask() == mask && empty && name != "surge-conveyor" {
+ // end
+ let mut base = gimme(2);
+ edgify(rot.mirrored(true, true).rotated(false).count(), &mut base);
+ base
+ } else if mask == B0000 && empty {
+ // single
+ let mut base = gimme(0);
+ unsafe { base.rotate(rot.rotated(false).count()) };
+ edgify(5, &mut base);
+ base
+ } else if mask == B0000 {
+ // input
+ let mut base = gimme(1);
+ edgify(rot.rotated(false).count(), &mut base);
+ base
+ } else {
+ // directional
+ let mut base = gimme(0);
+ let going = rot.rotated(false).count();
+ unsafe { base.rotate(going) };
+ for [r, i] in [[3, 0b1000], [0, 0b0100], [1, 0b0010], [2, 0b0001]] {
+ if (mask.into_u8() & i) == 0 && (going != r || empty) {
+ unsafe { base.overlay(&edge(r)) };
+ }
+ }
+ base
+ }
+}
+
+make_simple!(
+ StackConveyor,
+ draw_stack,
+ // format:
+ // - link: `i32`
+ // - cooldown: `f32`
+ |_, _, _, buff: &mut DataRead| buff.skip(8)
+);
+make_simple!(SurgeRouter, |_, _, _, _, r: Rotation, s| {
+ let mut base = load!("surge-router", s);
+ unsafe { base.overlay(load!("top", s).rotate(r.rotated(false).count())) };
+ base
+});
+// format: id: [`i32`]
+make_simple!(UnitCargoLoader => |_, _, _, buff: &mut DataRead| buff.skip(4));
+
+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" => StackConveyor::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));
+ "unloader" -> ItemBlock::new(1, true, cost!(Titanium: 25, Silicon: 30));
+ "router" -> BasicBlock::new(1, true, cost!(Copper: 3));
+ "distributor" -> BasicBlock::new(2, true, cost!(Copper: 4, Lead: 4));
+ "overflow-gate" -> BasicBlock::new(1, true, cost!(Copper: 4, Lead: 2));
+ "underflow-gate" -> BasicBlock::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" => DuctBlock::new(1, false, cost!(Beryllium: 1));
+ "armored-duct" => DuctBlock::new(1, false, cost!(Beryllium: 2, Tungsten: 1));
+ "duct-router" => ItemBlock::new(1, true, cost!(Beryllium: 10));
+ "overflow-duct" => SimpleDuctBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8));
+ "underflow-duct" => SimpleDuctBlock::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" => StackConveyor::new(1, false, cost!(SurgeAlloy: 1, Tungsten: 1));
+ "surge-router" => SurgeRouter::new(1, false, cost!(SurgeAlloy: 5, Tungsten: 1)); // not symmetric
+ "unit-cargo-loader" -> BasicBlock::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" -> BasicBlock::new(1, true, &[]);
+}
+
+pub struct ItemBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+}
+
+impl ItemBlock {
+ #[must_use]
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
+ assert!(size != 0, "invalid size");
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub Option<item::Type>);
+}
+
+impl BlockLogic for ItemBlock {
+ impl_block!();
+
+ fn data_from_i32(&self, config: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ if config < 0 || config > i32::from(u16::MAX) {
+ return Err(DataConvertError::Custom(Box::new(ItemConvertError(config))));
+ }
+ Ok(DynData::Content(content::Type::Item, config as u16))
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<State>, DeserializeError> {
+ match data {
+ DynData::Empty => Ok(Some(Self::create_state(None))),
+ DynData::Content(content::Type::Item, id) => Ok(Some(Self::create_state(Some(
+ ItemDeserializeError::forward(item::Type::try_from(id))?,
+ )))),
+ DynData::Content(have, ..) => Err(DeserializeError::Custom(Box::new(
+ ItemDeserializeError::ContentType(have),
+ ))),
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Content,
+ }),
+ }
+ }
+
+ fn mirror_state(&self, _: &mut State, _: bool, _: bool) {}
+
+ fn rotate_state(&self, _: &mut State, _: bool) {}
+
+ fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> {
+ Ok(Self::get_state(state).map_or(DynData::Empty, |item| {
+ DynData::Content(content::Type::Item, item.into())
+ }))
+ }
+
+ fn draw(
+ &self,
+ name: &str,
+ state: Option<&State>,
+ _: Option<&RenderingContext>,
+ rot: Rotation,
+ s: Scale,
+ ) -> ImageHolder<4> {
+ let mut p = load!(from name which is ["sorter" | "inverted-sorter" | "duct-router" | "duct-unloader" | "unit-cargo-unload-point" | "unloader" | "item-source"], s);
+ if let Some(state) = state && let Some(item) = Self::get_state(state) {
+ let mut top = load!(s -> match name {
+ "unit-cargo-unload-point" => "unit-cargo-unload-point-top",
+ "unloader" => "unloader-center",
+ _ => "center",
+ });
+ unsafe { p.overlay(top.tint(item.color())) };
+ return p;
+ }
+ match name {
+ "duct-router" => {
+ unsafe { p.overlay(&load!("top", s).rotate(rot.rotated(false).count())) };
+ }
+ "duct-unloader" => {
+ unsafe {
+ p.overlay(&load!("duct-unloader-top", s).rotate(rot.rotated(false).count()))
+ };
+ }
+ _ => {}
+ };
+ p
+ }
+
+ /// format:
+ /// (sorter | unloader | duct router | item source)
+ /// - item: `i16` as item
+ /// (duct-unloader/directional):
+ /// - tmp: `i16`
+ /// - if tmp != -1: item = tmp as item
+ /// - offset: `u16`
+ /// (unit-cargo-unload-point)
+ /// - item: `u16` as item
+ /// - stale: `bool`
+ fn read(
+ &self,
+ b: &mut Build,
+ _: &BlockRegistry,
+ _: &EntityMapping,
+ buff: &mut DataRead,
+ ) -> Result<(), DataReadError> {
+ match b.block.name() {
+ "duct-unloader" => {
+ let n = buff.read_i16()?;
+ if n != -1 {
+ b.state = Some(Self::create_state(item::Type::try_from(n as u16).ok()));
+ }
+ buff.skip(2)?;
+ }
+ "unit-cargo-unload-point" => {
+ b.state = Some(Self::create_state(
+ item::Type::try_from(buff.read_u16()?).ok(),
+ ));
+ buff.skip(1)?;
+ }
+ _ => {
+ b.state = Some(Self::create_state(
+ item::Type::try_from(buff.read_u16()?).ok(),
+ ));
+ }
+ }
+ Ok(())
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
+#[error("invalid config ({0}) for item")]
+pub struct ItemConvertError(pub i32);
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
+pub enum ItemDeserializeError {
+ #[error("expected Item but got {0:?}")]
+ ContentType(content::Type),
+ #[error("target item not found")]
+ NotFound(#[from] item::TryFromU16Error),
+}
+
+impl ItemDeserializeError {
+ pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError> {
+ match result {
+ Ok(v) => Ok(v),
+ Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))),
+ }
+ }
+}
+
+pub struct BridgeBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+ range: u16,
+ ortho: bool,
+}
+
+type Point2 = (i32, i32);
+
+impl BridgeBlock {
+ #[must_use]
+ pub const fn new(
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+ range: u16,
+ ortho: bool,
+ ) -> Self {
+ assert!(size != 0, "invalid size");
+ assert!(range != 0, "invalid range");
+ Self {
+ size,
+ symmetric,
+ build_cost,
+ range,
+ ortho,
+ }
+ }
+
+ state_impl!(pub Option<Point2>);
+}
+
+impl BlockLogic for BridgeBlock {
+ impl_block!();
+
+ fn data_from_i32(&self, config: i32, pos: GridPos) -> Result<DynData, DataConvertError> {
+ let (x, y) = ((config >> 16) as i16, config as i16);
+ if x < 0 || y < 0 {
+ return Err(DataConvertError::Custom(Box::new(BridgeConvertError {
+ x,
+ y,
+ })));
+ }
+ let dx = i32::from(x) - pos.0 as i32;
+ let dy = i32::from(y) - pos.1 as i32;
+ Ok(DynData::Point2(dx, dy))
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<State>, DeserializeError> {
+ match data {
+ DynData::Empty => Ok(Some(Self::create_state(None))),
+ DynData::Point2(dx, dy) => {
+ if self.ortho {
+ // the game uses (-worldX, -worldY) to indicate no target
+ // likely because the absolute target being (0, 0) means it's unlinked
+ if dx != 0 && dy != 0 {
+ return Ok(Some(Self::create_state(None)));
+ }
+ if dx > i32::from(self.range) || dx < -i32::from(self.range) {
+ return Ok(Some(Self::create_state(None)));
+ }
+ }
+ // can't check range otherwise, it depends on the target's size
+ Ok(Some(Self::create_state(Some((dx, dy)))))
+ }
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::Point2,
+ }),
+ }
+ }
+
+ fn mirror_state(&self, state: &mut State, horizontally: bool, vertically: bool) {
+ match Self::get_state_mut(state) {
+ None => (),
+ Some((dx, dy)) => {
+ if horizontally {
+ *dx = -*dx;
+ }
+ if vertically {
+ *dy = -*dy;
+ }
+ }
+ }
+ }
+
+ fn rotate_state(&self, state: &mut State, clockwise: bool) {
+ match Self::get_state_mut(state) {
+ None => (),
+ Some((dx, dy)) => {
+ let (cdx, cdy) = (*dx, *dy);
+ *dx = if clockwise { cdy } else { -cdy };
+ *dy = if clockwise { -cdx } else { cdx };
+ }
+ }
+ }
+
+ fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> {
+ match Self::get_state(state) {
+ None => Ok(DynData::Empty),
+ Some((dx, dy)) => Ok(DynData::Point2(*dx, *dy)),
+ }
+ }
+
+ /// format:
+ /// (item bridge)
+ /// - become [`read_buffered_item_bridge`]
+ /// (buffered brige)
+ /// - become [`read_item_buffer`]
+ /// (mass driver) (9b)
+ /// - link: [`i32`]
+ /// - rotation: [`f32`]
+ /// - state: [`i8`]
+ /// (payload mass driver) (19b)
+ /// - call [`read_payload_block`](crate::block::payload::read_payload_block)
+ /// - link: [`i32`]
+ /// - rotation: [`f32`]
+ /// - state: [`i8`]
+ /// - reload: [`f32`]
+ /// - charge: [`f32`]
+ /// - loaded: [`bool`]
+ /// - charging: [`bool`]
+ fn read(
+ &self,
+ t: &mut Build,
+ r: &super::BlockRegistry,
+ e: &crate::data::map::EntityMapping,
+ buff: &mut crate::data::DataRead,
+ ) -> Result<(), crate::data::ReadError> {
+ match t.block.name() {
+ "bridge-conveyor" => read_buffered_item_bridge(buff)?,
+ "phase-conveyor" | "phase-conduit" | "bridge-conduit" => read_item_bridge(buff)?,
+ "mass-driver" => buff.skip(9)?,
+ "payload-mass-driver" | "large-payload-mass-driver" => {
+ crate::block::payload::read_payload_block(r, e, buff)?;
+ buff.skip(19)?;
+ }
+ // no state?
+ "duct-bridge" | "reinforced-bridge-conduit" => {}
+ n => unreachable!("{n}"), // surely no forget
+ }
+ Ok(())
+ }
+
+ fn draw(
+ &self,
+ name: &str,
+ _: Option<&State>,
+ _: Option<&RenderingContext>,
+ r: Rotation,
+ s: Scale,
+ ) -> ImageHolder<4> {
+ match name {
+ "mass-driver" => {
+ let mut base = load!("mass-driver-base", s);
+ unsafe { base.overlay(&load!("mass-driver", s)) };
+ base
+ }
+ "duct-bridge" | "reinforced-bridge-conduit" => {
+ let mut base =
+ load!(from name which is ["duct-bridge" | "reinforced-bridge-conduit"], s);
+ let mut arrow = load!(
+ s -> match name {
+ "duct-bridge" => "duct-bridge-dir",
+ _ => "reinforced-bridge-conduit-dir",
+ }
+ );
+ unsafe {
+ arrow.rotate(r.rotated(false).count());
+ base.overlay(&arrow)
+ };
+ base
+ }
+ // "bridge-conveyor" | "phase-conveyor" | "bridge-conduit" | "phase-conduit" | "payload-mass-driver" | "large-payload-mass-driver"
+ _ => unreachable!(),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
+#[error("invalid coordinates ({x}, {y}) for bridge")]
+pub struct BridgeConvertError {
+ pub x: i16,
+ pub y: i16,
+}
+
+/// format;
+/// - call [`read_item_bridge`]
+/// - become [`read_item_buffer`]
+fn read_buffered_item_bridge(buff: &mut DataRead) -> Result<(), DataReadError> {
+ read_item_bridge(buff)?;
+ read_item_buffer(buff)
+}
+
+/// format:
+/// - index: `u8`
+/// - iter `u8`
+/// l: `i64`
+fn read_item_buffer(buff: &mut DataRead) -> Result<(), DataReadError> {
+ buff.skip(1)?;
+ let n = buff.read_u8()? as usize;
+ buff.skip(n * 8)
+}
+
+/// format:
+/// - link: `i32`
+/// - warmup: `f32`
+/// - iterate `u8`
+/// - incoming: `i32`
+/// - moved: `bool`
+fn read_item_bridge(buff: &mut DataRead) -> Result<(), DataReadError> {
+ buff.skip(8)?;
+ let n = buff.read_u8()? as usize;
+ buff.skip((n * 4) + 1)
+}
+
+/// format:
+/// - iterate 4
+/// - u8
+/// - iterate u8
+/// - i64
+fn read_directional_item_buffer(buff: &mut DataRead) -> Result<(), DataReadError> {
+ for _ in 0..4 {
+ let _ = buff.read_u8()?;
+ let n = buff.read_u8()? as usize;
+ buff.skip(n * 8)?;
+ }
+ Ok(())
+}