Skip to main content

mindus/
unit.rs

1//! units
2//!
3//! [source](https://github.com/Anuken/Mindustry/blob/master/core/src/mindustry/content/UnitTypes.java)
4
5use crate::Serializable;
6use crate::block::payload::read_payload;
7use crate::content::content_enum;
8use crate::data::command::UnitCommand;
9use crate::data::dynamic::DynData;
10use crate::data::entity_mapping::UnitClass;
11use crate::data::{DataRead, ReadError};
12use crate::item::Type as Item;
13use crate::modifier::Type as Status;
14use crate::team::Team;
15use crate::utils::ImageHolder;
16
17macro_rules! units {
18    ($($unit:literal,)+ $(,)?) => { paste::paste! {
19        content_enum! (pub enum Type / Unit for u16 | TryFromU16Error {
20            $($unit,)+
21        });
22
23        impl Type {
24            fn draw(self, s: crate::data::renderer::Scale) -> ImageHolder<4> {
25                match self {
26                    $(Type::[<$unit:camel>] => units!(@help $unit + s),)+
27                }
28            }
29        }
30    } };
31    (@help "block" + $s:expr) => { crate::data::renderer::load!("empty4", $s) };
32    (@help $v:literal + $s:expr) => { crate::data::renderer::load!($v, $s) };
33}
34
35units! {
36    "dagger",
37    "mace",
38    "fortress",
39    "scepter",
40    "reign",
41    "nova",
42    "pulsar",
43    "quasar",
44    "vela",
45    "corvus",
46    "crawler",
47    "atrax",
48    "spiroct",
49    "arkyid",
50    "toxopid",
51    "flare",
52    "horizon",
53    "zenith",
54    "antumbra",
55    "eclipse",
56    "mono",
57    "poly",
58    "mega",
59    "quad",
60    "oct",
61    "risso",
62    "minke",
63    "bryde",
64    "sei",
65    "omura",
66    "retusa",
67    "oxynoe",
68    "cyerce",
69    "aegires",
70    "navanax",
71    "alpha",
72    "beta",
73    "gamma",
74    "stell",
75    "locus",
76    "precept",
77    "vanquish",
78    "conquer",
79    "merui",
80    "cleroi",
81    "anthicus",
82    "anthicus-missile",
83    "tecta",
84    "collaris",
85    "elude",
86    "avert",
87    "obviate",
88    "quell",
89    "quell-missile",
90    "disrupt",
91    "disrupt-missile",
92    "renale",
93    "latum",
94    "evoke",
95    "incite",
96    "emanate",
97    "block",
98    "manifold",
99    "assembly-drone",
100    "scathe-missile",
101}
102
103#[derive(Default, Debug)]
104pub struct UnitState {
105    pub ammo: f32,
106    pub elevation: f32,
107    pub flag: i64,
108    pub health: f32,
109    pub is_shooting: bool,
110    pub rotation: f32,
111    pub shield: f32,
112    pub stack: (Option<Item>, u32),
113    /// how many status can you realistically be afflicted with
114    pub status: [Status; 3],
115    pub team: Team,
116    pub velocity: (f32, f32),
117    pub position: (f32, f32),
118    pub controller: Controller,
119}
120// pub trait TryThen {
121//     fn try_then<O: Try<Output = Option<T>, Residual = E>, T, E, R: Try<Output = T, Residual = E>>(
122//         self,
123//         then: impl FnMut() -> R,
124//     ) -> O;
125// }
126// impl TryThen for bool {
127//     fn try_then<
128//         O: Try<Output = Option<T>, Residual = E>,
129//         T,
130//         E,
131//         R: Try<Output = T, Residual = E>,
132//     >(
133//         self,
134//         mut then: impl FnMut() -> R,
135//     ) {
136//         match self {
137//             false => O::from_output(None),
138//             true => match then().branch() {
139//                 ControlFlow::Continue(x) => O::from_output(Some(x)),
140//                 ControlFlow::Break(x) => O::from_residual(x),
141//             },
142//         }
143//     }
144// }
145pub trait TryThen {
146    fn try_then<T, E>(self, f: impl FnMut() -> Result<T, E>) -> Result<Option<T>, E>;
147}
148impl TryThen for bool {
149    fn try_then<T, E>(self, mut f: impl FnMut() -> Result<T, E>) -> Result<Option<T>, E> {
150        self.then_some(()).map(|()| f()).transpose()
151    }
152}
153
154#[derive(Default, Debug)]
155pub enum Controller {
156    Player(i32),
157    Logic(i32),
158    Command {
159        target: Option<i32>,
160        pos: Option<(f32, f32)>,
161        command: Option<UnitCommand>,
162    },
163    #[default]
164    Assembler,
165}
166
167impl Controller {
168    /// format:
169    /// - match [`u8`]
170    ///     - (0): player
171    ///     - player: [`i32`]
172    ///
173    ///     - (3): logic ai
174    ///     - position: [`i32`]
175    ///
176    ///     - (6): command
177    ///     - has_attack: [`bool`]
178    ///     - has_pos: [`bool`]
179    ///     - if `has_pos`:
180    ///         - pos: ([`f32`], [`f32`])
181    ///     - if `has_attack`:
182    ///         - ty: [`i8`]
183    ///         - target_id: [`i32`]
184    ///     - command: [`i8`] attempt as [`UnitCommand`]
185    ///     - (_): assembler
186    fn read(buff: &mut DataRead) -> Result<Controller, ReadError> {
187        Ok(match buff.read_u8()? {
188            0 => Controller::Player(buff.read_i32()?),
189            3 => Controller::Logic(buff.read_i32()?),
190            t @ (4 | 6 | 7 | 8 | 9) => {
191                let has_attack = buff.read_bool()?;
192                let pos = buff
193                    .read_bool()?
194                    .try_then::<_, ReadError>(|| try { (buff.read_f32()?, buff.read_f32()?) })?;
195                let target = has_attack.try_then::<_, ReadError>(|| try {
196                    buff.skip(1)?;
197                    buff.read_i32()?
198                })?;
199                let n = buff.read_i8()?;
200                let command = u8::try_from(n)
201                    .ok()
202                    .and_then(|x| UnitCommand::try_from(x).ok());
203                if let 7 | 8 | 9 = t {
204                    for _ in 0..buff.read_u8()? {
205                        match buff.read_u8()? {
206                            0 | 1 => {
207                                buff.read_u32()?;
208                            }
209                            2 => {
210                                buff.read_f32()?;
211                                buff.read_f32()?;
212                            }
213                            _ => {}
214                        }
215                    }
216                }
217                if t == 8 {
218                    // stance
219                    buff.read_u8()?;
220                } else if t == 9 {
221                    let stances = buff.read_u8()?;
222                    buff.skip(stances as _)?;
223                }
224                Controller::Command {
225                    target,
226                    pos,
227                    command,
228                }
229            }
230            5 => Controller::Assembler,
231            _ => Controller::Assembler,
232        })
233    }
234}
235
236#[derive(Debug)]
237pub struct Unit {
238    pub state: UnitState,
239    pub ty: Type,
240}
241
242impl UnitClass {
243    pub fn read(self, buff: &mut DataRead) -> Result<Unit, ReadError> {
244        let _rev = buff.read_u16()?;
245        let mut state = UnitState::default();
246        read_abilities(buff)?;
247        state.ammo = buff.read_f32()?;
248        match self {
249            Self::Block
250            | Self::Legs
251            | Self::Elevated
252            | Self::Crawl
253            | Self::Boat
254            | Self::Tank
255            | Self::Air => {
256                state.controller = Controller::read(buff)?;
257                state.elevation = buff.read_f32()?;
258                state.flag = buff.read_i64()?;
259                state.health = buff.read_f32()?;
260                state.is_shooting = buff.read_bool()?;
261                read_tile(buff)?;
262                read_mounts(buff)?;
263            }
264            Self::Mech => {
265                buff.skip(4)?; // base rotation
266                state.controller = Controller::read(buff)?;
267                state.elevation = buff.read_f32()?;
268                state.flag = buff.read_i64()?;
269                state.health = buff.read_f32()?;
270                state.is_shooting = buff.read_bool()?;
271                read_tile(buff)?;
272                read_mounts(buff)?;
273            }
274            Self::Payload => {
275                state.controller = Controller::read(buff)?;
276                state.elevation = buff.read_f32()?;
277                state.flag = buff.read_i64()?;
278                state.health = buff.read_f32()?;
279                state.is_shooting = buff.read_bool()?;
280                read_tile(buff)?;
281                read_mounts(buff)?;
282                for _ in 0..buff.read_i32()? {
283                    // recursion more!
284                    // this is unreliable, as read_payload may not read the full block.
285                    // if read_plans reports a error, with a payload unit, this is why
286                    let _ = read_payload(buff);
287                }
288            }
289            Self::Bomb => {
290                state.controller = Controller::read(buff)?;
291                state.elevation = buff.read_f32()?;
292                state.flag = buff.read_i64()?;
293                state.health = buff.read_f32()?;
294                state.is_shooting = buff.read_bool()?;
295                buff.skip(4)?; // lifetime
296                read_tile(buff)?;
297                read_mounts(buff)?;
298            }
299            Self::Tethered => {
300                buff.skip(4)?;
301                state.controller = Controller::read(buff)?;
302                state.elevation = buff.read_f32()?;
303                state.flag = buff.read_i64()?;
304                state.health = buff.read_f32()?;
305                state.is_shooting = buff.read_bool()?;
306                read_tile(buff)?;
307                read_mounts(buff)?;
308                for _ in 0..buff.read_i32()? {
309                    // recursion more!
310                    read_payload(buff)?;
311                }
312            }
313        }
314        read_plans(buff)?;
315        state.rotation = buff.read_f32()?;
316        state.shield = buff.read_f32()?;
317        buff.skip(1)?; // spawned_by_core
318        state.stack = read_stack(buff)?;
319        state.status = read_status(buff)?;
320        state.team = Team::of(buff.read_u8()?);
321        if self == Self::Bomb {
322            buff.skip(4)?; // time
323        }
324        let ty = Type::try_from(buff.read_u16()?).unwrap();
325        buff.skip(1)?; // update_building
326        state.velocity = (buff.read_f32()?, buff.read_f32()?);
327        state.position = (
328            (buff.read_f32()? / 8.0).floor(),
329            (buff.read_f32()? / 8.0).floor(),
330        );
331        Ok(Unit { state, ty })
332    }
333}
334
335/// format:
336/// - iterate [`u8`]
337///     - ability: [`f32`]
338fn read_abilities(buff: &mut DataRead) -> Result<(), ReadError> {
339    let n = buff.read_u8()? as usize;
340    buff.skip(n * 4)
341}
342
343/// format:
344/// - tile: [`i32`]
345fn read_tile(buff: &mut DataRead) -> Result<(), ReadError> {
346    buff.skip(4)
347}
348
349/// format:
350/// - iterate [`u8`]
351///     - state: [`u8`]
352///     - x aim: [`f32`]
353///     - y aim: [`f32`]
354fn read_mounts(buff: &mut DataRead) -> Result<(), ReadError> {
355    let n = buff.read_u8()? as usize;
356    buff.skip(n * 9)
357}
358
359/// format:
360/// - plan count: [`i32`]
361/// - plan_count == -1 => return
362/// - iterate `plan_count`
363///     - call [`read_plan`]
364fn read_plans(buff: &mut DataRead) -> Result<(), ReadError> {
365    let used = buff.read_i32()?;
366    if used == -1 {
367        return Ok(());
368    }
369    for _ in 0..used {
370        read_plan(buff)?;
371    }
372    Ok(())
373}
374
375/// format:
376/// - ty: [`u8`]
377/// - position: [`i32`]
378/// - if ty != 1
379///     - block: [`u16`]
380///     - rotation: [`i8`]
381///     - has_config: [`bool`]
382///     - config: [`DynData`](crate::data::dynamic::DynData)
383fn read_plan(buff: &mut DataRead) -> Result<(), ReadError> {
384    let ty = buff.read_u8()?;
385    buff.skip(4)?;
386    if ty != 1 {
387        buff.skip(4)?;
388        let _ = DynData::deserialize(buff).unwrap();
389    }
390    Ok(())
391}
392
393/// format:
394/// - item: [`i16`] attempt into [`Item`]
395/// - count: [`u32`]
396fn read_stack(buff: &mut DataRead) -> Result<(Option<Item>, u32), ReadError> {
397    let n = buff.read_i16()?;
398    Ok((
399        (n != -1).then(|| Item::try_from(n as u16).unwrap()),
400        buff.read_u32()?,
401    ))
402}
403
404/// read the status.
405/// i take only 3
406///
407/// format:
408/// - iterate [`i32`]
409///     - status: [`u16`] attempt into [`Status`]
410///     - duration: [`f32`]
411fn read_status(buff: &mut DataRead) -> Result<[Status; 3], ReadError> {
412    let mut status = [Status::None, Status::None, Status::None];
413    for i in 0..buff.read_i32()? {
414        let this = Status::try_from(buff.read_u16()?);
415        buff.skip(4)?;
416        if let Ok(s) = this
417            && i < 3
418        {
419            status[i as usize] = s;
420        }
421    }
422    Ok(status)
423}
424
425impl Unit {
426    #[inline]
427    pub fn draw(&self, s: crate::data::renderer::Scale) -> ImageHolder<4> {
428        self.ty.draw(s)
429    }
430}