Skip to main content

mindus/block/
distribution.rs

1//! conveyors ( & ducts )
2use crate::block::simple::*;
3use crate::block::*;
4use crate::content;
5use crate::data::autotile::tile;
6use crate::data::dynamic::DynType;
7use crate::item;
8
9make_simple!(
10    ConveyorBlock,
11    |_, name, _, ctx: Option<&RenderingContext>, rot, s| tile(ctx.unwrap(), name, rot, s),
12    |_, buff: &mut DataRead| {
13        // format:
14        // - amount: `i32`
15        // - iterate amount:
16        //  - val: `i32`
17        //  - id = (((val >> 24) as u8) & 0xff) as u16
18        //  - x = (val >> 16) as u8) as f32 / 127.0
19        //  - y = ((val >> 8) as u8 as f32 + 128.0) / 255.0
20        let amount = buff.read_i32()?;
21        for _ in 0..amount {
22            buff.skip(4)?;
23        }
24        Ok(())
25    }
26);
27
28make_simple!(
29    DuctBlock,
30    |_, name, _, ctx: Option<&RenderingContext>, rot, s| tile(ctx.unwrap(), name, rot, s),
31    |_, buff: &mut DataRead| {
32        // format:
33        // - rec_dir: `i8`
34        buff.skip(1)
35    }
36);
37
38make_simple!(JunctionBlock => |_, buff| { read_directional_item_buffer(buff) });
39make_simple!(SimpleDuctBlock, |_, name, _, _, rot: Rotation, s| {
40    let mut base = load!("duct-base", s);
41    let mut top = load!(from name which is ["overflow-duct" "underflow-duct"], s);
42    // SAFETY: any load!() is square
43    unsafe { top.rotate(rot.rotated(false).count()) };
44    // SAFETY: same size
45    unsafe { base.overlay(&top) };
46    base
47});
48
49fn draw_stack(
50    _: &StackConveyor,
51    name: &str,
52    _: Option<&State>,
53    ctx: Option<&RenderingContext>,
54    rot: Rotation,
55    s: Scale,
56) -> ImageHolder<4> {
57    let ctx = ctx.unwrap();
58    let mask = mask(ctx, rot, name);
59    #[rustfmt::skip]
60    let edge = |n: u8| {
61        match n {
62            0 => load!(concat "edge-0" => name which is ["surge-conveyor" | "plastanium-conveyor"], s),
63            1 => load!(concat "edge-1" => name which is ["surge-conveyor" | "plastanium-conveyor"], s),
64            2 => load!(concat "edge-2" => name which is ["surge-conveyor" | "plastanium-conveyor"], s),
65            _ => load!(concat "edge-3" => name which is ["surge-conveyor" | "plastanium-conveyor"], s)
66        }
67    };
68    let edgify = |skip, to: &mut ImageHolder<4>| {
69        for i in 0..4 {
70            if i == skip {
71                continue;
72            }
73            unsafe { to.overlay(&edge(i)) };
74        }
75    };
76    let gimme = |n: u8| match n {
77        0 => load!(concat 0 => name which is ["surge-conveyor" | "plastanium-conveyor"], s),
78        1 => load!(concat 1 => name which is ["surge-conveyor" | "plastanium-conveyor"], s),
79        _ => load!("plastanium-conveyor-2", s),
80    };
81    let empty = ctx.cross[rot.count() as usize].map_or(true, |(v, _)| v.name.get_name() != name);
82    // mindustry says fuck this and just draws the arrow convs in schems but im better than that
83    if rot.mirrored(true, true).mask() == mask && empty && name != "surge-conveyor" {
84        // end
85        let mut base = gimme(2);
86        edgify(rot.mirrored(true, true).rotated(false).count(), &mut base);
87        base
88    } else if mask == B0000 && empty {
89        // single
90        let mut base = gimme(0);
91        unsafe { base.rotate(rot.rotated(false).count()) };
92        edgify(5, &mut base);
93        base
94    } else if mask == B0000 {
95        // input
96        let mut base = gimme(1);
97        edgify(rot.rotated(false).count(), &mut base);
98        base
99    } else {
100        // directional
101        let mut base = gimme(0);
102        let going = rot.rotated(false).count();
103        unsafe { base.rotate(going) };
104        for [r, i] in [[3, 0b1000], [0, 0b0100], [1, 0b0010], [2, 0b0001]] {
105            if (mask.into_u8() & i) == 0 && (going != r || empty) {
106                unsafe { base.overlay(&edge(r)) };
107            }
108        }
109        base
110    }
111}
112
113make_simple!(
114    StackConveyor,
115    draw_stack,
116    // format:
117    // - link: `i32`
118    // - cooldown: `f32`
119    |_, buff: &mut DataRead| buff.skip(8)
120);
121make_simple!(
122    SurgeRouter,
123    |_, _, _, _, r: Rotation, s| {
124        let mut base = load!("surge-router", s);
125        let mut top = load!("top", s);
126        unsafe { top.rotate(r.rotated(false).count()) };
127        unsafe { base.overlay(&top) };
128        base
129    },
130    |_, buff: &mut DataRead| buff.skip(2)
131);
132// format: id: [`i32`]
133make_simple!(UnitCargoLoader => |_, buff: &mut DataRead| buff.skip(4));
134
135pub struct ItemBlock {
136    size: u8,
137    symmetric: bool,
138    build_cost: BuildCost,
139}
140
141impl ItemBlock {
142    #[must_use]
143    pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self {
144        assert!(size != 0, "invalid size");
145        Self {
146            size,
147            symmetric,
148            build_cost,
149        }
150    }
151
152    state_impl!(pub Option<item::Type>);
153}
154
155impl BlockLogic for ItemBlock {
156    impl_block!();
157
158    fn data_from_i32(&self, config: i32, _: GridPos) -> Result<DynData, DataConvertError> {
159        if config < 0 || config > i32::from(u16::MAX) {
160            return Err(DataConvertError::Custom(Box::new(ItemConvertError(config))));
161        }
162        Ok(DynData::Content(content::Type::Item, config as u16))
163    }
164
165    fn deserialize_state(&self, data: DynData) -> Result<Option<State>, DeserializeError> {
166        match data {
167            DynData::Empty => Ok(Some(Self::create_state(None))),
168            DynData::Content(content::Type::Item, id) => Ok(Some(Self::create_state(Some(
169                ItemDeserializeError::forward(item::Type::try_from(id))?,
170            )))),
171            DynData::Content(have, ..) => Err(DeserializeError::Custom(Box::new(
172                ItemDeserializeError::ContentType(have),
173            ))),
174            _ => Err(DeserializeError::InvalidType {
175                have: data.get_type(),
176                expect: DynType::Content,
177            }),
178        }
179    }
180
181    fn mirror_state(&self, _: &mut State, _: bool, _: bool) {}
182
183    fn rotate_state(&self, _: &mut State, _: bool) {}
184
185    fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> {
186        Ok(Self::get_state(state).map_or(DynData::Empty, |item| {
187            DynData::Content(content::Type::Item, item.into())
188        }))
189    }
190
191    fn draw(
192        &self,
193        name: &str,
194        state: Option<&State>,
195        _: Option<&RenderingContext>,
196        rot: Rotation,
197        s: Scale,
198    ) -> ImageHolder<4> {
199        let mut p = load!(from name which is ["sorter" | "inverted-sorter" | "duct-router" | "duct-unloader" | "unit-cargo-unload-point" | "unloader" | "item-source"], s);
200        if let Some(state) = state
201            && let Some(item) = Self::get_state(state)
202        {
203            let mut top = load!(s -> match name {
204                "unit-cargo-unload-point" => "unit-cargo-unload-point-top",
205                "unloader" => "unloader-center",
206                _ => "center",
207            });
208            unsafe { p.overlay(top.tint(item.color())) };
209            if name == "duct-unloader" {
210                unsafe {
211                    let mut x = load!("duct-unloader-arrows", s);
212                    p.overlay(&*x.rotate(rot.rotated(false).count()));
213                }
214            }
215            return p;
216        }
217        if matches!(name, "duct-router" | "duct-unloader") {
218            let mut top = load!(s -> match name {
219                "duct-router" => "top",
220                "duct-unloader" => "duct-unloader-top",
221            });
222            unsafe { top.rotate(rot.rotated(false).count()) };
223            unsafe { p.overlay(&top) };
224        }
225        p
226    }
227
228    /// format:
229    /// (sorter | unloader | duct router | item source)
230    /// - item: `i16` as item
231    /// (duct-unloader/directional):
232    /// - tmp: `i16`
233    /// - if tmp != -1: item = tmp as item
234    /// - offset: `u16`
235    /// (unit-cargo-unload-point)
236    /// - item: `u16` as item
237    /// - stale: `bool`
238    fn read(&self, b: &mut Build, buff: &mut DataRead) -> Result<(), DataReadError> {
239        match b.block.name() {
240            "duct-unloader" => {
241                let n = buff.read_i16()?;
242                if n != -1 {
243                    b.state = Some(Self::create_state(item::Type::try_from(n as u16).ok()));
244                }
245                buff.skip(2)?;
246            }
247            "unit-cargo-unload-point" => {
248                b.state = Some(Self::create_state(
249                    item::Type::try_from(buff.read_u16()?).ok(),
250                ));
251                buff.skip(1)?;
252            }
253            _ => {
254                b.state = Some(Self::create_state(
255                    item::Type::try_from(buff.read_u16()?).ok(),
256                ));
257            }
258        }
259        Ok(())
260    }
261}
262
263#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
264#[error("invalid config ({0}) for item")]
265pub struct ItemConvertError(pub i32);
266
267#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
268pub enum ItemDeserializeError {
269    #[error("expected Item but got {0:?}")]
270    ContentType(content::Type),
271    #[error("target item not found")]
272    NotFound(#[from] item::TryFromU16Error),
273}
274
275impl ItemDeserializeError {
276    pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError> {
277        match result {
278            Ok(v) => Ok(v),
279            Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))),
280        }
281    }
282}
283
284pub struct BridgeBlock {
285    size: u8,
286    symmetric: bool,
287    build_cost: BuildCost,
288    range: u16,
289    ortho: bool,
290}
291
292type Point2 = (i32, i32);
293
294impl BridgeBlock {
295    #[must_use]
296    pub const fn new(
297        size: u8,
298        symmetric: bool,
299        build_cost: BuildCost,
300        range: u16,
301        ortho: bool,
302    ) -> Self {
303        assert!(size != 0, "invalid size");
304        assert!(range != 0, "invalid range");
305        Self {
306            size,
307            symmetric,
308            build_cost,
309            range,
310            ortho,
311        }
312    }
313
314    state_impl!(pub Option<Point2>);
315}
316
317impl BlockLogic for BridgeBlock {
318    impl_block!();
319
320    fn data_from_i32(&self, config: i32, pos: GridPos) -> Result<DynData, DataConvertError> {
321        let (x, y) = ((config >> 16) as i16, config as i16);
322        if x < 0 || y < 0 {
323            return Err(DataConvertError::Custom(Box::new(BridgeConvertError {
324                x,
325                y,
326            })));
327        }
328        let dx = i32::from(x) - pos.0 as i32;
329        let dy = i32::from(y) - pos.1 as i32;
330        Ok(DynData::Point2(dx, dy))
331    }
332
333    fn deserialize_state(&self, data: DynData) -> Result<Option<State>, DeserializeError> {
334        match data {
335            DynData::Empty => Ok(Some(Self::create_state(None))),
336            DynData::Point2(dx, dy) => {
337                if self.ortho {
338                    // the game uses (-worldX, -worldY) to indicate no target
339                    // likely because the absolute target being (0, 0) means it's unlinked
340                    if dx != 0 && dy != 0 {
341                        return Ok(Some(Self::create_state(None)));
342                    }
343                    if dx > i32::from(self.range) || dx < -i32::from(self.range) {
344                        return Ok(Some(Self::create_state(None)));
345                    }
346                }
347                // can't check range otherwise, it depends on the target's size
348                Ok(Some(Self::create_state(Some((dx, dy)))))
349            }
350            _ => Err(DeserializeError::InvalidType {
351                have: data.get_type(),
352                expect: DynType::Point2,
353            }),
354        }
355    }
356
357    fn mirror_state(&self, state: &mut State, horizontally: bool, vertically: bool) {
358        match Self::get_state_mut(state) {
359            None => (),
360            Some((dx, dy)) => {
361                if horizontally {
362                    *dx = -*dx;
363                }
364                if vertically {
365                    *dy = -*dy;
366                }
367            }
368        }
369    }
370
371    fn rotate_state(&self, state: &mut State, clockwise: bool) {
372        match Self::get_state_mut(state) {
373            None => (),
374            Some((dx, dy)) => {
375                let (cdx, cdy) = (*dx, *dy);
376                *dx = if clockwise { cdy } else { -cdy };
377                *dy = if clockwise { -cdx } else { cdx };
378            }
379        }
380    }
381
382    fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> {
383        match Self::get_state(state) {
384            None => Ok(DynData::Point2(-1, -1)),
385            Some((dx, dy)) => Ok(DynData::Point2(*dx, *dy)),
386        }
387    }
388
389    /// format:
390    /// (item bridge)
391    /// - become [`read_buffered_item_bridge`]
392    /// (buffered brige)
393    /// - become [`read_item_buffer`]
394    /// (mass driver) (9b)
395    /// - link: [`i32`]
396    /// - rotation: [`f32`]
397    /// - state: [`i8`]
398    /// (payload mass driver) (19b)
399    /// - call [`read_payload_block`](crate::block::payload::read_payload_block)
400    /// - link: [`i32`]
401    /// - rotation: [`f32`]
402    /// - state: [`i8`]
403    /// - reload: [`f32`]
404    /// - charge: [`f32`]
405    /// - loaded: [`bool`]
406    /// - charging: [`bool`]
407    fn read(
408        &self,
409        t: &mut Build,
410        buff: &mut crate::data::DataRead,
411    ) -> Result<(), crate::data::ReadError> {
412        match t.block.name() {
413            "bridge-conveyor" => read_buffered_item_bridge(buff)?,
414            "phase-conveyor" | "phase-conduit" | "bridge-conduit" => read_item_bridge(buff)?,
415            "mass-driver" => buff.skip(9)?,
416            "payload-mass-driver" | "large-payload-mass-driver" => {
417                crate::block::payload::read_payload_block(buff)?;
418                buff.skip(19)?;
419            }
420            // no state?
421            "duct-bridge" | "reinforced-bridge-conduit" => {}
422            n => unreachable!("{n}"), // surely no forget
423        }
424        Ok(())
425    }
426
427    fn draw(
428        &self,
429        name: &str,
430        _: Option<&State>,
431        _: Option<&RenderingContext>,
432        r: Rotation,
433        s: Scale,
434    ) -> ImageHolder<4> {
435        match name {
436            "mass-driver" => {
437                let mut base = load!("mass-driver-base", s);
438                unsafe { base.overlay(&load!("mass-driver", s)) };
439                base
440            }
441            "duct-bridge" | "reinforced-bridge-conduit" => {
442                let mut base =
443                    load!(from name which is ["duct-bridge" | "reinforced-bridge-conduit"], s);
444                let mut arrow = load!(
445                    s -> match name {
446                        "duct-bridge" => "duct-bridge-dir",
447                        _ => "reinforced-bridge-conduit-dir",
448                    }
449                );
450                unsafe { arrow.rotate(r.rotated(false).count()) };
451                unsafe { base.overlay(&arrow) };
452                base
453            }
454            // "bridge-conveyor" | "phase-conveyor" | "bridge-conduit" | "phase-conduit" | "payload-mass-driver" | "large-payload-mass-driver"
455            _ => unreachable!(),
456        }
457    }
458}
459
460#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
461#[error("invalid coordinates ({x}, {y}) for bridge")]
462pub struct BridgeConvertError {
463    pub x: i16,
464    pub y: i16,
465}
466
467/// format;
468/// - call [`read_item_bridge`]
469/// - become [`read_item_buffer`]
470fn read_buffered_item_bridge(buff: &mut DataRead) -> Result<(), DataReadError> {
471    read_item_bridge(buff)?;
472    read_item_buffer(buff)
473}
474
475/// format:
476/// - index: `u8`
477/// - iter `u8`
478///     l: `i64`
479fn read_item_buffer(buff: &mut DataRead) -> Result<(), DataReadError> {
480    buff.skip(1)?;
481    let n = buff.read_u8()? as usize;
482    buff.skip(n * 8)
483}
484
485/// format:
486/// - link: `i32`
487/// - warmup: `f32`
488/// - iterate `u8`
489///     - incoming: `i32`
490/// - moved: `bool`
491fn read_item_bridge(buff: &mut DataRead) -> Result<(), DataReadError> {
492    buff.skip(8)?;
493    let n = buff.read_u8()? as usize;
494    buff.skip((n * 4) + 1)
495}
496
497/// format:
498/// - iterate 4
499///     - u8
500///     - iterate u8
501///         - i64
502fn read_directional_item_buffer(buff: &mut DataRead) -> Result<(), DataReadError> {
503    for _ in 0..4 {
504        let _ = buff.read_u8()?;
505        let n = buff.read_u8()? as usize;
506        buff.skip(n * 8)?;
507    }
508    Ok(())
509}