Skip to main content

genie_rec/
actions.rs

1//! Player actions executed during a game.
2
3use crate::{ObjectID, PlayerID, Result};
4use arrayvec::ArrayVec;
5use byteorder::{ReadBytesExt, WriteBytesExt, LE};
6use genie_support::{f32_neq, read_opt_u32, read_str, ReadSkipExt, TechID, UnitTypeID};
7use std::convert::TryInto;
8use std::io::{Read, Write};
9
10/// A location with an X and Y coordinate.
11pub type Location2 = (f32, f32);
12/// A location with an X, Y, and Z coordinate.
13///
14/// The Z coordinate is usually meaningless.
15pub type Location3 = (f32, f32, f32);
16
17/// A viewpoint update, recording where the player is currently looking.
18///
19/// This is used for the View Lock feature when watching a game.
20#[derive(Debug, Default, Clone)]
21pub struct ViewLock {
22    /// The X coordinate the player is looking at.
23    pub x: f32,
24    /// The Y coordinate the player is looking at.
25    pub y: f32,
26    /// The ID of the POV player.
27    pub player: PlayerID,
28}
29
30impl ViewLock {
31    /// Read a view lock action from an input stream.
32    pub fn read_from(mut input: impl Read) -> Result<Self> {
33        let x = input.read_f32::<LE>()?;
34        let y = input.read_f32::<LE>()?;
35        let player = input.read_i32::<LE>()?.try_into().unwrap();
36        Ok(Self { x, y, player })
37    }
38
39    /// Write a view lock action to an output stream.
40    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
41        output.write_f32::<LE>(self.x)?;
42        output.write_f32::<LE>(self.y)?;
43        output.write_i32::<LE>(self.player.try_into().unwrap())?;
44        Ok(())
45    }
46}
47
48/// A list of objects that a command applies to.
49///
50/// The game uses a special value if a command applies to the same objects as the previous command.
51/// That way it does not have to resend 40 object IDs every time a player moves their army. It's
52/// encoded as `ObjectsList::SameAsLast` here.
53#[derive(Debug, Clone)]
54pub enum ObjectsList {
55    /// Use the same objects as the previous command.
56    SameAsLast,
57    /// Use this list of objects.
58    List(Vec<ObjectID>),
59}
60
61impl Default for ObjectsList {
62    fn default() -> Self {
63        ObjectsList::List(vec![])
64    }
65}
66
67impl ObjectsList {
68    /// Read a list of objects from an input stream.
69    pub fn read_from(mut input: impl Read, count: i32) -> Result<Self> {
70        if count < 0xFF {
71            let mut list = vec![];
72            for _ in 0..count {
73                list.push(input.read_i32::<LE>()?.try_into().unwrap());
74            }
75            Ok(ObjectsList::List(list))
76        } else {
77            Ok(ObjectsList::SameAsLast)
78        }
79    }
80
81    /// Write a list of objects to an output stream.
82    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
83        if let ObjectsList::List(list) = self {
84            for entry in list.iter().cloned() {
85                output.write_u32::<LE>(entry.into())?;
86            }
87        }
88        Ok(())
89    }
90
91    /// The amount of objects contained in this list.
92    ///
93    /// For `ObjectsList::SameAsLast` this returns 0.
94    pub fn len(&self) -> usize {
95        match self {
96            ObjectsList::SameAsLast => 0,
97            ObjectsList::List(list) => list.len(),
98        }
99    }
100
101    /// Does this list contain 0 objects?
102    pub fn is_empty(&self) -> bool {
103        self.len() == 0
104    }
105}
106
107/// Task an object to a target object or a target location.
108#[derive(Debug, Default, Clone)]
109pub struct OrderCommand {
110    /// The ID of the player executing this command.
111    pub player_id: PlayerID,
112    /// The target object of this order.
113    pub target_id: Option<ObjectID>,
114    /// The target location of this order.
115    pub location: Location2,
116    /// The objects this command applies to.
117    pub objects: ObjectsList,
118}
119
120impl OrderCommand {
121    /// Read an Order command from an input stream.
122    pub fn read_from(mut input: impl Read) -> Result<Self> {
123        let mut command = Self::default();
124        command.player_id = input.read_u8()?.into();
125        input.skip(2)?;
126        command.target_id = read_opt_u32(&mut input)?;
127        let selected_count = input.read_i32::<LE>()?;
128        command.location = (input.read_f32::<LE>()?, input.read_f32::<LE>()?);
129        command.objects = ObjectsList::read_from(input, selected_count)?;
130        Ok(command)
131    }
132
133    /// Write an Order command to an output stream.
134    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
135        output.write_u8(self.player_id.into())?;
136        output.write_all(&[0, 0])?;
137        output.write_i32::<LE>(
138            self.target_id
139                .map(|id| id.try_into().unwrap())
140                .unwrap_or(-1),
141        )?;
142        output.write_u32::<LE>(self.objects.len().try_into().unwrap())?;
143        output.write_f32::<LE>(self.location.0)?;
144        output.write_f32::<LE>(self.location.1)?;
145        self.objects.write_to(output)?;
146        Ok(())
147    }
148}
149
150/// Task objects to stop.
151#[derive(Debug, Default, Clone)]
152pub struct StopCommand {
153    /// The objects to stop.
154    pub objects: ObjectsList,
155}
156
157impl StopCommand {
158    /// Read a Stop command from an input stream.
159    pub fn read_from(mut input: impl Read) -> Result<Self> {
160        let mut command = Self::default();
161        let selected_count = input.read_i8()?;
162        command.objects = ObjectsList::read_from(input, selected_count as i32)?;
163        Ok(command)
164    }
165
166    /// Write this Stop command to an output stream.
167    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
168        output.write_i8(self.objects.len().try_into().unwrap())?;
169        self.objects.write_to(output)?;
170        Ok(())
171    }
172}
173
174/// Task an object to work.
175#[derive(Debug, Default, Clone)]
176pub struct WorkCommand {
177    /// The target object of this command.
178    pub target_id: Option<ObjectID>,
179    /// The target location of this command.
180    pub location: Location2,
181    /// The objects being tasked.
182    pub objects: ObjectsList,
183}
184
185impl WorkCommand {
186    /// Read a Work command from an input stream.
187    pub fn read_from(mut input: impl Read) -> Result<Self> {
188        let mut command = Self::default();
189        input.skip(3)?;
190        command.target_id = read_opt_u32(&mut input)?;
191        let selected_count = input.read_i8()?;
192        input.skip(3)?;
193        command.location = (input.read_f32::<LE>()?, input.read_f32::<LE>()?);
194        command.objects = ObjectsList::read_from(input, selected_count as i32)?;
195        Ok(command)
196    }
197
198    /// Write this Work command to an output stream.
199    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
200        output.write_all(&[0, 0, 0])?;
201        output.write_i32::<LE>(self.target_id.map(|u| u32::from(u) as i32).unwrap_or(-1))?;
202        output.write_i8(self.objects.len().try_into().unwrap())?;
203        output.write_all(&[0, 0, 0])?;
204        output.write_f32::<LE>(self.location.0)?;
205        output.write_f32::<LE>(self.location.1)?;
206        self.objects.write_to(output)?;
207        Ok(())
208    }
209}
210
211/// Task an object to move.
212#[derive(Debug, Default, Clone)]
213pub struct MoveCommand {
214    /// The ID of the player issuing this command.
215    pub player_id: PlayerID,
216    /// The target object of this command.
217    pub target_id: Option<ObjectID>,
218    /// The target location of this command.
219    pub location: Location2,
220    /// The objects being tasked.
221    pub objects: ObjectsList,
222}
223
224impl MoveCommand {
225    /// Read a Move command from an input stream.
226    pub fn read_from(mut input: impl Read) -> Result<Self> {
227        let mut command = Self::default();
228        command.player_id = input.read_u8()?.into();
229        input.skip(2)?;
230        command.target_id = read_opt_u32(&mut input)?;
231        let selected_count = input.read_i8()?;
232        input.skip(3)?;
233        command.location = (input.read_f32::<LE>()?, input.read_f32::<LE>()?);
234        command.objects = ObjectsList::read_from(input, selected_count as i32)?;
235        Ok(command)
236    }
237
238    /// Write this Move command to an output stream.
239    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
240        output.write_all(&[0, 0, 0])?;
241        output.write_i32::<LE>(self.target_id.map(|u| u32::from(u) as i32).unwrap_or(-1))?;
242        output.write_i8(self.objects.len().try_into().unwrap())?;
243        output.write_all(&[0, 0, 0])?;
244        output.write_f32::<LE>(self.location.0)?;
245        output.write_f32::<LE>(self.location.1)?;
246        self.objects.write_to(output)?;
247        Ok(())
248    }
249}
250/// A command that instantly places a unit type at a given location.
251///
252/// Typically used for cheats and the like.
253#[derive(Debug, Default, Clone)]
254pub struct CreateCommand {
255    /// The ID of the player issuing this command.
256    pub player_id: PlayerID,
257    /// The type of unit to create.
258    pub unit_type_id: UnitTypeID,
259    /// The location.
260    pub location: Location3,
261}
262
263impl CreateCommand {
264    /// Read a Create command from an input stream.
265    pub fn read_from(mut input: impl Read) -> Result<Self> {
266        let mut command = Self::default();
267        let _padding = input.read_u8()?;
268        command.unit_type_id = input.read_u16::<LE>()?.into();
269        command.player_id = input.read_u8()?.into();
270        let _padding = input.read_u8()?;
271        command.location = (
272            input.read_f32::<LE>()?,
273            input.read_f32::<LE>()?,
274            input.read_f32::<LE>()?,
275        );
276        Ok(command)
277    }
278
279    /// Write this Create command to an output stream.
280    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
281        output.write_u8(0)?;
282        output.write_u16::<LE>(self.unit_type_id.into())?;
283        output.write_u8(self.player_id.into())?;
284        output.write_u8(0)?;
285        output.write_f32::<LE>(self.location.0)?;
286        output.write_f32::<LE>(self.location.1)?;
287        output.write_f32::<LE>(self.location.2)?;
288        Ok(())
289    }
290}
291
292/// Add resources to a player's stockpile.
293///
294/// Typically used for cheats.
295#[derive(Debug, Default, Clone)]
296pub struct AddResourceCommand {
297    /// The player this command applies to.
298    pub player_id: PlayerID,
299    /// The resource to add.
300    pub resource: u8,
301    /// The amount to add to this resource. May be negative for subtracting.
302    pub amount: f32,
303}
304
305impl AddResourceCommand {
306    /// Read an AddResource command from an input stream.
307    pub fn read_from(mut input: impl Read) -> Result<Self> {
308        let player_id = input.read_u8()?.into();
309        let resource = input.read_u8()?;
310        let _padding = input.read_u8()?;
311        let amount = input.read_f32::<LE>()?;
312        Ok(Self {
313            player_id,
314            resource,
315            amount,
316        })
317    }
318
319    /// Write this AddResource command to an output stream.
320    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
321        output.write_u8(self.player_id.into())?;
322        output.write_u8(self.resource)?;
323        output.write_u8(0)?;
324        output.write_f32::<LE>(self.amount)?;
325        Ok(())
326    }
327}
328
329///
330#[derive(Debug, Default, Clone)]
331pub struct AIOrderCommand {
332    pub player_id: PlayerID,
333    pub issuer: PlayerID,
334    pub objects: ObjectsList,
335    pub order_type: u16,
336    pub order_priority: i8,
337    pub target_id: Option<ObjectID>,
338    pub target_player_id: Option<PlayerID>,
339    pub target_location: Location3,
340    pub range: f32,
341    pub immediate: bool,
342    pub add_to_front: bool,
343}
344
345impl AIOrderCommand {
346    pub fn read_from(mut input: impl Read) -> Result<Self> {
347        let mut command = Self::default();
348        let selected_count = i32::from(input.read_i8()?);
349        command.player_id = input.read_u8()?.into();
350        command.issuer = input.read_u8()?.into();
351        let object_id = input.read_u32::<LE>()?;
352        command.order_type = input.read_u16::<LE>()?;
353        command.order_priority = input.read_i8()?;
354        let _padding = input.read_u8()?;
355        command.target_id = read_opt_u32(&mut input)?;
356        command.target_player_id = match input.read_i8()? {
357            -1 => None,
358            id => Some(id.try_into().unwrap()),
359        };
360        input.skip(3)?;
361        command.target_location = (
362            input.read_f32::<LE>()?,
363            input.read_f32::<LE>()?,
364            input.read_f32::<LE>()?,
365        );
366        command.range = input.read_f32::<LE>()?;
367        command.immediate = input.read_u8()? != 0;
368        command.add_to_front = input.read_u8()? != 0;
369        let _padding = input.read_u16::<LE>()?;
370        command.objects = if selected_count == 1 {
371            ObjectsList::List(vec![object_id.into()])
372        } else {
373            ObjectsList::read_from(input, selected_count)?
374        };
375        Ok(command)
376    }
377
378    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
379        output.write_i8(self.objects.len().try_into().unwrap())?;
380        output.write_u8(self.player_id.into())?;
381        output.write_u8(self.issuer.into())?;
382        match &self.objects {
383            ObjectsList::List(list) if list.len() == 1 => {
384                output.write_u32::<LE>(list[0].into())?;
385            }
386            _ => output.write_i32::<LE>(-1)?,
387        }
388        output.write_u16::<LE>(self.order_type)?;
389        output.write_i8(self.order_priority)?;
390        output.write_u8(0)?;
391        output.write_i32::<LE>(match self.target_id {
392            Some(id) => id.try_into().unwrap(),
393            None => -1,
394        })?;
395        output.write_u8(self.player_id.into())?;
396        output.write_all(&[0, 0, 0])?;
397        output.write_f32::<LE>(self.target_location.0)?;
398        output.write_f32::<LE>(self.target_location.1)?;
399        output.write_f32::<LE>(self.target_location.2)?;
400        output.write_f32::<LE>(self.range)?;
401        output.write_u8(if self.immediate { 1 } else { 0 })?;
402        output.write_u8(if self.add_to_front { 1 } else { 0 })?;
403        output.write_all(&[0, 0])?;
404        if self.objects.len() > 1 {
405            self.objects.write_to(output)?;
406        }
407        Ok(())
408    }
409}
410
411/// A player resigns or drops from the game.
412#[derive(Debug, Default, Clone)]
413pub struct ResignCommand {
414    /// The ID of the player that is resigning.
415    pub player_id: PlayerID,
416    /// The multiplayer ID of the player that is resigning.
417    pub comm_player_id: PlayerID,
418    /// Is this "resignation" because the player dropped from the game?
419    pub dropped: bool,
420}
421
422impl ResignCommand {
423    /// Read a Resign command from an input stream.
424    pub fn read_from(mut input: impl Read) -> Result<Self> {
425        let player_id = input.read_u8()?.into();
426        let comm_player_id = input.read_u8()?.into();
427        let dropped = input.read_u8()? != 0;
428        Ok(Self {
429            player_id,
430            comm_player_id,
431            dropped,
432        })
433    }
434
435    /// Write this Resign command to an output stream.
436    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
437        output.write_u8(self.player_id.into())?;
438        output.write_u8(self.comm_player_id.into())?;
439        output.write_u8(if self.dropped { 1 } else { 0 })?;
440        Ok(())
441    }
442}
443
444#[derive(Debug, Default, Clone)]
445pub struct GroupWaypointCommand {
446    pub player_id: PlayerID,
447    pub location: (u8, u8),
448    pub objects: ObjectsList,
449}
450
451impl GroupWaypointCommand {
452    pub fn read_from(mut input: impl Read) -> Result<Self> {
453        let player_id = input.read_u8()?.into();
454        let num_units = input.read_u8()?;
455        let x = input.read_u8()?;
456        let y = input.read_u8()?;
457        Ok(Self {
458            player_id,
459            location: (x, y),
460            objects: ObjectsList::read_from(input, i32::from(num_units))?,
461        })
462    }
463
464    pub fn write_to(&self, mut output: impl Write) -> Result<()> {
465        output.write_u8(self.player_id.into())?;
466        output.write_u8(self.objects.len().try_into().unwrap())?;
467        output.write_u8(self.location.0)?;
468        output.write_u8(self.location.1)?;
469        self.objects.write_to(&mut output)?;
470        Ok(())
471    }
472}
473
474/// Set a group of objects's "AI State" (usually known as "stance").
475#[derive(Debug, Default, Clone)]
476pub struct UnitAIStateCommand {
477    /// The new state. Aggressive/Defensive/No Attack/ etc.
478    pub state: i8,
479    /// The objects whose AI state is being changed.
480    pub objects: ObjectsList,
481}
482
483impl UnitAIStateCommand {
484    /// Read a UnitAIState command from an input stream.
485    pub fn read_from(mut input: impl Read) -> Result<Self> {
486        let selected_count = input.read_u8()?;
487        let state = input.read_i8()?;
488        let objects = ObjectsList::read_from(input, i32::from(selected_count))?;
489        Ok(Self { state, objects })
490    }
491
492    /// Write this UnitAIState command to an output stream.
493    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
494        output.write_u8(self.objects.len().try_into().unwrap())?;
495        output.write_i8(self.state)?;
496        self.objects.write_to(output)?;
497        Ok(())
498    }
499}
500
501/// Task units to guard an object.
502#[derive(Debug, Default, Clone)]
503pub struct GuardCommand {
504    /// The target object of this order.
505    pub target_id: Option<ObjectID>,
506    /// The objects this command applies to.
507    pub objects: ObjectsList,
508}
509
510impl GuardCommand {
511    /// Read a Guard command from an input stream.
512    pub fn read_from(mut input: impl Read) -> Result<Self> {
513        let mut command = Self::default();
514        let selected_count = i32::from(input.read_u8()?);
515        input.skip(2)?;
516        command.target_id = read_opt_u32(&mut input)?;
517        command.objects = ObjectsList::read_from(input, selected_count)?;
518        Ok(command)
519    }
520
521    /// Write a Guard command to an output stream.
522    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
523        output.write_u8(self.objects.len().try_into().unwrap())?;
524        output.write_all(&[0, 0])?;
525        output.write_i32::<LE>(
526            self.target_id
527                .map(|id| id.try_into().unwrap())
528                .unwrap_or(-1),
529        )?;
530        self.objects.write_to(output)?;
531        Ok(())
532    }
533}
534
535/// Task units to follow an object.
536#[derive(Debug, Default, Clone)]
537pub struct FollowCommand {
538    /// The target object of this order.
539    pub target_id: Option<ObjectID>,
540    /// The objects this command applies to.
541    pub objects: ObjectsList,
542}
543
544impl FollowCommand {
545    /// Read a Follow command from an input stream.
546    pub fn read_from(mut input: impl Read) -> Result<Self> {
547        let mut command = Self::default();
548        let selected_count = i32::from(input.read_u8()?);
549        input.skip(2)?;
550        command.target_id = read_opt_u32(&mut input)?;
551        command.objects = ObjectsList::read_from(input, selected_count)?;
552        Ok(command)
553    }
554
555    /// Write a Follow command to an output stream.
556    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
557        output.write_u8(self.objects.len().try_into().unwrap())?;
558        output.write_all(&[0, 0])?;
559        output.write_i32::<LE>(
560            self.target_id
561                .map(|id| id.try_into().unwrap())
562                .unwrap_or(-1),
563        )?;
564        self.objects.write_to(output)?;
565        Ok(())
566    }
567}
568
569/// Task a group of objects to patrol along a given path.
570#[derive(Debug, Default, Clone)]
571pub struct PatrolCommand {
572    /// The waypoints that this patrol should pass through.
573    pub waypoints: ArrayVec<[Location2; 10]>,
574    /// The objects to include in this formation.
575    pub objects: ObjectsList,
576}
577
578impl PatrolCommand {
579    pub fn read_from(mut input: impl Read) -> Result<Self> {
580        let mut command = Self::default();
581        let selected_count = input.read_i8()?;
582        let waypoint_count = input.read_u8()?;
583        let _padding = input.read_u8()?;
584        let mut raw_waypoints = [(0.0, 0.0); 10];
585        for w in raw_waypoints.iter_mut() {
586            w.0 = input.read_f32::<LE>()?;
587        }
588        for w in raw_waypoints.iter_mut() {
589            w.1 = input.read_f32::<LE>()?;
590        }
591        command
592            .waypoints
593            .try_extend_from_slice(&raw_waypoints[0..usize::from(waypoint_count)])
594            .unwrap();
595        command.objects = ObjectsList::read_from(input, i32::from(selected_count))?;
596        Ok(command)
597    }
598
599    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
600        output.write_i8(self.objects.len().try_into().unwrap())?;
601        output.write_u8(self.waypoints.len().try_into().unwrap())?;
602        output.write_u8(0)?;
603        for i in 0..10 {
604            output.write_f32::<LE>(self.waypoints.get(i).cloned().unwrap_or_default().0)?;
605        }
606        for i in 0..10 {
607            output.write_f32::<LE>(self.waypoints.get(i).cloned().unwrap_or_default().1)?;
608        }
609        self.objects.write_to(output)?;
610        Ok(())
611    }
612}
613
614/// Task a group of objects to form a formation.
615#[derive(Debug, Default, Clone)]
616pub struct FormFormationCommand {
617    /// The ID of the player issuing this command.
618    pub player_id: PlayerID,
619    /// The type of formation to form.
620    pub formation_type: i32,
621    /// The objects to include in this formation.
622    pub objects: ObjectsList,
623}
624
625impl FormFormationCommand {
626    pub fn read_from(mut input: impl Read) -> Result<Self> {
627        let mut command = Self::default();
628        let selected_count = input.read_i8()?;
629        command.player_id = input.read_u8()?.into();
630        let _padding = input.read_u8()?;
631        command.formation_type = input.read_i32::<LE>()?;
632        command.objects = ObjectsList::read_from(input, i32::from(selected_count))?;
633        Ok(command)
634    }
635
636    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
637        output.write_i8(self.objects.len().try_into().unwrap())?;
638        output.write_u8(self.player_id.into())?;
639        output.write_u8(0)?;
640        output.write_i32::<LE>(self.formation_type)?;
641        self.objects.write_to(output)?;
642        Ok(())
643    }
644}
645
646/// Meta-command for UserPatch's new AI commands.
647#[derive(Debug, Default, Clone)]
648pub struct UserPatchAICommand {
649    pub player_id: PlayerID,
650    /// 0: move to object
651    /// 1: set unit ai state
652    /// 2: ?
653    /// 3: ?
654    /// 4: stop unit group?
655    /// 5: dropoff something?
656    /// 6: dropoff something?
657    /// 7: ?
658    /// 8: set offensive target priority
659    /// 9: reset offensive target priorities?
660    /// 10: nothing?
661    /// 11: stop unit group?
662    /// 12: set gather point to garrison in self
663    /// 13: set ai player name
664    /// 14: unload object
665    /// 15: nothing?
666    pub ai_action: u8,
667    pub params: ArrayVec<[u32; 4]>,
668}
669
670impl UserPatchAICommand {
671    pub fn read_from(mut input: impl Read, size: u32) -> Result<Self> {
672        let num_params = (size - 4) / 4;
673        assert!(
674            num_params < 4,
675            "UserPatchAICommand needs more room for {} params",
676            num_params
677        );
678        let ai_action = input.read_u8()?;
679        let player_id = input.read_u8()?.into();
680        let _padding = input.read_u8()?;
681        let mut params: ArrayVec<[u32; 4]> = Default::default();
682        for _ in 0..num_params {
683            params.push(input.read_u32::<LE>()?);
684        }
685        Ok(Self {
686            player_id,
687            ai_action,
688            params,
689        })
690    }
691
692    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
693        output.write_u8(self.ai_action)?;
694        output.write_u8(self.player_id.into())?;
695        output.write_u8(0)?;
696        for p in &self.params {
697            output.write_u32::<LE>(*p)?;
698        }
699        Ok(())
700    }
701}
702
703#[derive(Debug, Default, Clone)]
704pub struct MakeCommand {
705    pub player_id: PlayerID,
706    pub building_id: ObjectID,
707    pub unit_type_id: UnitTypeID,
708    pub target_id: Option<ObjectID>,
709}
710
711impl MakeCommand {
712    pub fn read_from(mut input: impl Read) -> Result<Self> {
713        input.skip(3)?;
714        let building_id = input.read_u32::<LE>()?.into();
715        let player_id = input.read_u8()?.into();
716        let _padding = input.read_u8()?;
717        let unit_type_id = input.read_u16::<LE>()?.into();
718        let target_id = read_opt_u32(&mut input)?;
719        Ok(Self {
720            player_id,
721            building_id,
722            unit_type_id,
723            target_id,
724        })
725    }
726
727    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
728        output.write_all(&[0, 0, 0])?;
729        output.write_u32::<LE>(self.building_id.into())?;
730        output.write_u8(self.player_id.into())?;
731        output.write_u8(0)?;
732        output.write_u16::<LE>(self.unit_type_id.into())?;
733        output.write_i32::<LE>(match self.target_id {
734            None => -1,
735            Some(id) => id.try_into().unwrap(),
736        })?;
737        Ok(())
738    }
739}
740
741/// Start a research.
742#[derive(Debug, Default, Clone)]
743pub struct ResearchCommand {
744    /// The ID of the player starting the research.
745    pub player_id: PlayerID,
746    /// The building where the research is taking place.
747    pub building_id: ObjectID,
748    /// The tech ID of the research.
749    pub tech_id: TechID,
750    /// TODO
751    pub target_id: Option<ObjectID>,
752}
753
754impl ResearchCommand {
755    pub fn read_from(mut input: impl Read) -> Result<Self> {
756        input.skip(3)?;
757        let building_id = input.read_u32::<LE>()?.into();
758        let player_id = input.read_u8()?.into();
759        let _padding = input.read_u8()?;
760        let tech_id = input.read_u16::<LE>()?.into();
761        let target_id = read_opt_u32(&mut input)?;
762        Ok(Self {
763            player_id,
764            building_id,
765            tech_id,
766            target_id,
767        })
768    }
769
770    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
771        output.write_all(&[0, 0, 0])?;
772        output.write_u32::<LE>(self.building_id.into())?;
773        output.write_u8(self.player_id.into())?;
774        output.write_u8(0)?;
775        output.write_u16::<LE>(self.tech_id.into())?;
776        output.write_i32::<LE>(match self.target_id {
777            None => -1,
778            Some(id) => id.try_into().unwrap(),
779        })?;
780        Ok(())
781    }
782}
783
784/// Place a building foundation and task a group of villagers to start building.
785#[derive(Debug, Default, Clone)]
786pub struct BuildCommand {
787    /// The ID of the player issuing this command.
788    pub player_id: PlayerID,
789    /// The type of building to place.
790    pub unit_type_id: UnitTypeID,
791    /// The location of the new building foundation.
792    pub location: Location2,
793    /// The index of the frame to use, for buildings with multiple graphics like houses.
794    pub frame: u8,
795    /// The IDs of the villagers that are tasked to build this building.
796    pub builders: ObjectsList,
797    /// Unique ID for the _command_ (not building)? Used by AIs?
798    unique_id: Option<u32>,
799}
800
801impl BuildCommand {
802    pub fn read_from(mut input: impl Read) -> Result<Self> {
803        let mut command = Self::default();
804        let selected_count = input.read_i8()?;
805        command.player_id = input.read_u8()?.into();
806        let _padding = input.read_u8()?;
807        let x = input.read_f32::<LE>()?;
808        let y = input.read_f32::<LE>()?;
809        command.location = (x, y);
810        command.unit_type_id = input.read_u16::<LE>()?.into();
811        let _padding = input.read_u16::<LE>()?;
812        command.unique_id = read_opt_u32(&mut input)?;
813        command.frame = input.read_u8()?;
814        input.skip(3)?;
815        command.builders = ObjectsList::read_from(input, i32::from(selected_count))?;
816        Ok(command)
817    }
818}
819
820/// Commands affecting the game.
821#[derive(Debug, Clone)]
822pub enum GameCommand {
823    SetGameSpeed {
824        player_id: PlayerID,
825        speed: f32,
826    },
827    /// Not used in game, but implemented.
828    Inventory {
829        player_id: PlayerID,
830        attribute_id: i16,
831        amount: f32,
832    },
833    /// Not implemented in game.
834    UpgradeTown {
835        player_id: PlayerID,
836    },
837    QuickBuild {
838        player_id: PlayerID,
839    },
840    AlliedVictory {
841        player_id: PlayerID,
842        status: bool,
843    },
844    Cheat {
845        player_id: PlayerID,
846        cheat_id: i16,
847    },
848    /// Not implemented in game.
849    SharedLos {
850        player_id: PlayerID,
851    },
852    Spies {
853        player_id: PlayerID,
854    },
855    SetStrategicNumber {
856        player_id: PlayerID,
857        strategic_number: i16,
858        value: i32,
859    },
860    /// Appears to be unused.
861    Unknown0x0c {
862        player_id: PlayerID,
863    },
864    AddFarmReseedQueue {
865        player_id: PlayerID,
866        amount: i16,
867    },
868    RemoveFarmReseedQueue {
869        player_id: PlayerID,
870        amount: i16,
871    },
872    FarmReseedAutoQueue {
873        player_id: PlayerID,
874        // TODO unknown vars
875    },
876}
877
878#[derive(Debug)]
879struct RawGameCommand {
880    game_command: u8,
881    var1: i16,
882    var2: i16,
883    var3: f32,
884    var4: u32,
885}
886
887impl RawGameCommand {
888    pub fn read_from(mut input: impl Read) -> Result<Self> {
889        let game_command = input.read_u8()?;
890        let var1 = input.read_i16::<LE>()?;
891        let var2 = input.read_i16::<LE>()?;
892        let _padding = input.read_u16::<LE>()?;
893        let var3 = input.read_f32::<LE>()?;
894        let var4 = input.read_u32::<LE>()?;
895        Ok(Self {
896            game_command,
897            var1,
898            var2,
899            var3,
900            var4,
901        })
902    }
903}
904
905impl GameCommand {
906    pub fn read_from(input: impl Read) -> Result<Self> {
907        let RawGameCommand {
908            game_command,
909            var1,
910            var2,
911            var3,
912            var4,
913        } = RawGameCommand::read_from(input)?;
914
915        use GameCommand::*;
916        match game_command {
917            0x01 => Ok(SetGameSpeed {
918                player_id: var1.try_into().unwrap(),
919                speed: var3,
920            }),
921            0x02 => Ok(Inventory {
922                player_id: var1.try_into().unwrap(),
923                attribute_id: var2,
924                amount: var3,
925            }),
926            0x03 => Ok(UpgradeTown {
927                player_id: var1.try_into().unwrap(),
928            }),
929            0x04 => Ok(QuickBuild {
930                player_id: var1.try_into().unwrap(),
931            }),
932            0x05 => Ok(AlliedVictory {
933                player_id: var1.try_into().unwrap(),
934                status: var2 != 0,
935            }),
936            0x06 => Ok(Cheat {
937                player_id: var1.try_into().unwrap(),
938                cheat_id: var2,
939            }),
940            0x07 => Ok(SharedLos {
941                player_id: var1.try_into().unwrap(),
942            }),
943            0x0a => Ok(Spies {
944                player_id: var1.try_into().unwrap(),
945            }),
946            0x0b => Ok(SetStrategicNumber {
947                player_id: var1.try_into().unwrap(),
948                strategic_number: var2,
949                value: var4.try_into().unwrap(),
950            }),
951            0x0c => Ok(Unknown0x0c {
952                player_id: var1.try_into().unwrap(),
953            }),
954            0x0d => Ok(AddFarmReseedQueue {
955                player_id: var1.try_into().unwrap(),
956                amount: var2,
957            }),
958            0x0e => Ok(RemoveFarmReseedQueue {
959                player_id: var1.try_into().unwrap(),
960                amount: var2,
961            }),
962            0x10 => Ok(FarmReseedAutoQueue {
963                player_id: var1.try_into().unwrap(),
964            }),
965            _ => panic!("unimplemented game command {:#x}", game_command),
966        }
967    }
968}
969
970/// Task a group of villagers to build a wall from point A to point B.
971#[derive(Debug, Default, Clone)]
972pub struct BuildWallCommand {
973    pub player_id: PlayerID,
974    pub start: (u8, u8),
975    pub end: (u8, u8),
976    pub unit_type_id: UnitTypeID,
977    pub builders: ObjectsList,
978}
979
980impl BuildWallCommand {
981    fn read_from(mut input: impl Read) -> Result<Self> {
982        let selected_count = input.read_i8()?;
983        let player_id = input.read_u8()?.into();
984        let start = (input.read_u8()?, input.read_u8()?);
985        let end = (input.read_u8()?, input.read_u8()?);
986        let _padding = input.read_u8()?;
987        let unit_type_id = input.read_u16::<LE>()?.into();
988        let _padding = input.read_u16::<LE>()?;
989        assert_eq!(
990            input.read_u32::<LE>()?,
991            0xFFFF_FFFF,
992            "check out what this is for"
993        );
994        let builders = if selected_count == -1 {
995            ObjectsList::SameAsLast
996        } else {
997            let mut list = vec![0; selected_count.try_into().unwrap()];
998            input.read_i32_into::<LE>(&mut list)?;
999            if selected_count == 1 && list[0] == -1 {
1000                list.clear();
1001            }
1002            ObjectsList::List(list.into_iter().map(|id| id.try_into().unwrap()).collect())
1003        };
1004        Ok(Self {
1005            player_id,
1006            start,
1007            end,
1008            unit_type_id,
1009            builders,
1010        })
1011    }
1012}
1013
1014/// Delete a building or cancel a building that's not fully built yet.
1015#[derive(Debug, Default, Clone)]
1016pub struct CancelBuildCommand {
1017    /// The ID of the player issuing this command.
1018    pub player_id: PlayerID,
1019    /// The ID of the building to cancel.
1020    pub building_id: ObjectID,
1021}
1022
1023impl CancelBuildCommand {
1024    pub fn read_from(mut input: impl Read) -> Result<Self> {
1025        input.skip(3)?;
1026        let building_id = input.read_u32::<LE>()?.into();
1027        let player_id = input.read_u32::<LE>()?.try_into().unwrap();
1028        Ok(Self {
1029            player_id,
1030            building_id,
1031        })
1032    }
1033
1034    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
1035        output.write_all(&[0, 0, 0])?;
1036        output.write_u32::<LE>(self.building_id.into())?;
1037        output.write_u32::<LE>(self.player_id.try_into().unwrap())?;
1038        Ok(())
1039    }
1040}
1041
1042/// Task an object to attack ground.
1043#[derive(Debug, Default, Clone)]
1044pub struct AttackGroundCommand {
1045    /// The target location of this command.
1046    pub location: Location2,
1047    /// The objects being tasked.
1048    pub objects: ObjectsList,
1049}
1050
1051impl AttackGroundCommand {
1052    /// Read a AttackGround command from an input stream.
1053    pub fn read_from(mut input: impl Read) -> Result<Self> {
1054        let mut command = Self::default();
1055        let selected_count = i32::from(input.read_i8()?);
1056        input.skip(2)?;
1057        command.location = (input.read_f32::<LE>()?, input.read_f32::<LE>()?);
1058        command.objects = ObjectsList::read_from(input, selected_count as i32)?;
1059        Ok(command)
1060    }
1061
1062    /// Write this AttackGround command to an output stream.
1063    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
1064        output.write_i8(self.objects.len().try_into().unwrap())?;
1065        output.write_all(&[0, 0])?;
1066        output.write_f32::<LE>(self.location.0)?;
1067        output.write_f32::<LE>(self.location.1)?;
1068        self.objects.write_to(output)?;
1069        Ok(())
1070    }
1071}
1072
1073/// Task units to repair an object.
1074#[derive(Debug, Default, Clone)]
1075pub struct RepairCommand {
1076    /// The target object of this order.
1077    pub target_id: Option<ObjectID>,
1078    /// The objects this command applies to.
1079    pub repairers: ObjectsList,
1080}
1081
1082impl RepairCommand {
1083    /// Read a Repair command from an input stream.
1084    pub fn read_from(mut input: impl Read) -> Result<Self> {
1085        let mut command = Self::default();
1086        let selected_count = i32::from(input.read_u8()?);
1087        input.skip(2)?;
1088        command.target_id = read_opt_u32(&mut input)?;
1089        command.repairers = ObjectsList::read_from(input, selected_count)?;
1090        Ok(command)
1091    }
1092
1093    /// Write a Repair command to an output stream.
1094    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
1095        output.write_u8(self.repairers.len().try_into().unwrap())?;
1096        output.write_all(&[0, 0])?;
1097        output.write_i32::<LE>(
1098            self.target_id
1099                .map(|id| id.try_into().unwrap())
1100                .unwrap_or(-1),
1101        )?;
1102        self.repairers.write_to(output)?;
1103        Ok(())
1104    }
1105}
1106
1107/// Ungarrison objects from a given list of objects.
1108#[derive(Debug, Default, Clone)]
1109pub struct UngarrisonCommand {
1110    pub ungarrison_type: i8,
1111    pub unit_type_id: Option<ObjectID>,
1112    pub location: Option<Location2>,
1113    pub objects: ObjectsList,
1114}
1115
1116impl UngarrisonCommand {
1117    fn read_from(mut input: impl Read) -> Result<Self> {
1118        let mut command = Self::default();
1119        let selected_count = input.read_i8()?;
1120        let _padding = input.read_u16::<LE>()?;
1121        let x = input.read_f32::<LE>()?;
1122        let y = input.read_f32::<LE>()?;
1123        command.location = if f32_neq!(x, -1.0) && f32_neq!(y, -1.0) {
1124            Some((x, y))
1125        } else {
1126            None
1127        };
1128        command.ungarrison_type = input.read_i8()?;
1129        input.skip(3)?;
1130        command.unit_type_id = read_opt_u32(&mut input)?;
1131        command.objects = ObjectsList::read_from(input, i32::from(selected_count))?;
1132        Ok(command)
1133    }
1134}
1135
1136/// Send a flare at the given location.
1137#[derive(Debug, Default, Clone)]
1138pub struct FlareCommand {
1139    pub player_id: PlayerID,
1140    pub comm_player_id: PlayerID,
1141    pub recipients: [bool; 9],
1142    pub location: Location2,
1143}
1144
1145impl FlareCommand {
1146    pub fn read_from(mut input: impl Read) -> Result<Self> {
1147        let mut command = Self::default();
1148        input.skip(3)?;
1149        assert_eq!(
1150            input.read_i32::<LE>()?,
1151            -1,
1152            "found flare with unexpected unit id"
1153        );
1154        for receive in command.recipients.iter_mut() {
1155            *receive = input.read_u8()? != 0;
1156        }
1157        input.skip(3)?;
1158        command.location = (input.read_f32::<LE>()?, input.read_f32::<LE>()?);
1159        command.player_id = input.read_u8()?.into();
1160        command.comm_player_id = input.read_u8()?.into();
1161        input.skip(2)?;
1162        Ok(command)
1163    }
1164}
1165
1166///
1167#[derive(Debug, Default, Clone)]
1168pub struct UnitOrderCommand {
1169    pub target_id: Option<ObjectID>,
1170    pub action: i8,
1171    pub param: Option<u8>,
1172    pub location: Option<Location2>,
1173    pub unique_id: Option<u32>,
1174    pub objects: ObjectsList,
1175}
1176
1177impl UnitOrderCommand {
1178    fn read_from(mut input: impl Read) -> Result<Self> {
1179        let mut command = Self::default();
1180        let selected_count = input.read_i8()?;
1181        let _padding = input.read_u16::<LE>()?;
1182        command.target_id = read_opt_u32(&mut input)?;
1183        command.action = input.read_i8()?;
1184        command.param = match input.read_i8()? {
1185            -1 => None,
1186            param => Some(param as u8),
1187        };
1188        let _padding = input.read_u16::<LE>()?;
1189        let x = input.read_f32::<LE>()?;
1190        let y = input.read_f32::<LE>()?;
1191        command.location = if f32_neq!(x, -1.0) && f32_neq!(y, -1.0) {
1192            Some((x, y))
1193        } else {
1194            None
1195        };
1196        command.unique_id = read_opt_u32(&mut input)?;
1197        command.objects = ObjectsList::read_from(input, i32::from(selected_count))?;
1198        Ok(command)
1199    }
1200}
1201
1202///
1203#[derive(Debug, Default, Clone)]
1204pub struct QueueCommand {
1205    /// The ID of the building where this unit is being queued.
1206    pub building_id: ObjectID,
1207    /// The ID of the unit type being queued.
1208    pub unit_type_id: UnitTypeID,
1209    /// The amount of units to queue.
1210    pub amount: u16,
1211}
1212
1213impl QueueCommand {
1214    pub fn read_from(mut input: impl Read) -> Result<Self> {
1215        let mut command = Self::default();
1216        input.skip(3)?;
1217        command.building_id = input.read_u32::<LE>()?.into();
1218        command.unit_type_id = input.read_u16::<LE>()?.into();
1219        command.amount = input.read_u16::<LE>()?;
1220        Ok(command)
1221    }
1222
1223    pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
1224        output.write_all(&[0, 0, 0])?;
1225        output.write_u32::<LE>(self.building_id.into())?;
1226        output.write_u16::<LE>(self.unit_type_id.into())?;
1227        output.write_u16::<LE>(self.amount)?;
1228        Ok(())
1229    }
1230}
1231
1232///
1233#[derive(Debug, Default, Clone)]
1234pub struct SetGatherPointCommand {
1235    /// The IDs of the buildings whose gather points are being set.
1236    pub buildings: ObjectsList,
1237    /// The ID of the object being targeted, if the gather point is set to an object.
1238    pub target_id: Option<ObjectID>,
1239    /// The type ID of the unit being targeted, if the gather point is set to an object.
1240    pub target_type_id: Option<UnitTypeID>,
1241    /// The location of the new gather point, if the gather point is not set to an object.
1242    pub location: Option<Location2>,
1243}
1244
1245impl SetGatherPointCommand {
1246    pub fn read_from(mut input: impl Read) -> Result<Self> {
1247        let mut command = Self::default();
1248        let selected_count = i32::from(input.read_i8()?);
1249        input.skip(2)?;
1250        command.target_id = read_opt_u32(&mut input)?;
1251        command.target_type_id = match input.read_u16::<LE>()? {
1252            0xFFFF => None,
1253            id => Some(id.try_into().unwrap()),
1254        };
1255        input.skip(2)?;
1256        command.location = Some((input.read_f32::<LE>()?, input.read_f32::<LE>()?));
1257        command.buildings = ObjectsList::read_from(input, selected_count)?;
1258        Ok(command)
1259    }
1260
1261    pub fn write_to<W: Write>(&self, _output: &mut W) -> Result<()> {
1262        todo!()
1263    }
1264}
1265
1266/// Read and write impl for market buying/selling commands, which are different commands but have
1267/// the same shape.
1268macro_rules! buy_sell_impl {
1269    ($name:ident) => {
1270        impl $name {
1271            pub fn read_from(mut input: impl Read) -> Result<Self> {
1272                let mut command = Self::default();
1273                command.player_id = input.read_u8()?.into();
1274                command.resource = input.read_u8()?;
1275                command.amount = input.read_i8()?;
1276                command.market_id = input.read_u32::<LE>()?.into();
1277                Ok(command)
1278            }
1279
1280            pub fn write_to<W: Write>(&self, output: &mut W) -> Result<()> {
1281                output.write_u8(self.player_id.into())?;
1282                output.write_u8(self.resource)?;
1283                output.write_i8(self.amount)?;
1284                output.write_u32::<LE>(self.market_id.into())?;
1285                Ok(())
1286            }
1287        }
1288    };
1289}
1290
1291/// Sell a resource at the market.
1292#[derive(Debug, Default, Clone)]
1293pub struct SellResourceCommand {
1294    /// The ID of the player issuing this command.
1295    pub player_id: PlayerID,
1296    /// The resource being sold.
1297    pub resource: u8,
1298    /// The amount being sold, in 100s. Typically this is 1 for selling 100 of a resource, or 5 for
1299    /// selling 500 (with Shift-click).
1300    pub amount: i8,
1301    /// The ID of the building where this resource is being bought.
1302    pub market_id: ObjectID,
1303}
1304
1305buy_sell_impl!(SellResourceCommand);
1306
1307/// Buy a resource at the market.
1308#[derive(Debug, Default, Clone)]
1309pub struct BuyResourceCommand {
1310    /// The ID of the player issuing this command.
1311    pub player_id: PlayerID,
1312    /// The resource being bought.
1313    pub resource: u8,
1314    /// The amount being bought, in 100s. Typically this is 1 for buying 100 of a resource, or 5 for
1315    /// buying 500 (with Shift-click).
1316    pub amount: i8,
1317    /// The ID of the building where this resource is being bought.
1318    pub market_id: ObjectID,
1319}
1320
1321buy_sell_impl!(BuyResourceCommand);
1322
1323#[derive(Debug, Default, Clone)]
1324pub struct Unknown7FCommand {
1325    pub object_id: ObjectID,
1326    pub value: u32,
1327}
1328
1329impl Unknown7FCommand {
1330    pub fn read_from(mut input: impl Read) -> Result<Self> {
1331        input.skip(3)?;
1332        let object_id = input.read_u32::<LE>()?.into();
1333        let value = input.read_u32::<LE>()?;
1334        Ok(Self { object_id, value })
1335    }
1336}
1337
1338/// Send villagers back to work after they've been garrisoned into the Town Center.
1339#[derive(Debug, Default, Clone)]
1340pub struct BackToWorkCommand {
1341    pub building_id: ObjectID,
1342}
1343
1344impl BackToWorkCommand {
1345    pub fn read_from(mut input: impl Read) -> Result<Self> {
1346        input.skip(3)?;
1347        let building_id = input.read_u32::<LE>()?.into();
1348        Ok(Self { building_id })
1349    }
1350}
1351
1352#[derive(Debug, Clone)]
1353pub enum Command {
1354    Order(OrderCommand),
1355    Stop(StopCommand),
1356    Work(WorkCommand),
1357    Move(MoveCommand),
1358    Create(CreateCommand),
1359    AddResource(AddResourceCommand),
1360    AIOrder(AIOrderCommand),
1361    Resign(ResignCommand),
1362    GroupWaypoint(GroupWaypointCommand),
1363    UnitAIState(UnitAIStateCommand),
1364    Guard(GuardCommand),
1365    Follow(FollowCommand),
1366    Patrol(PatrolCommand),
1367    FormFormation(FormFormationCommand),
1368    UserPatchAI(UserPatchAICommand),
1369    Make(MakeCommand),
1370    Research(ResearchCommand),
1371    Build(BuildCommand),
1372    Game(GameCommand),
1373    BuildWall(BuildWallCommand),
1374    CancelBuild(CancelBuildCommand),
1375    AttackGround(AttackGroundCommand),
1376    Repair(RepairCommand),
1377    Ungarrison(UngarrisonCommand),
1378    Flare(FlareCommand),
1379    UnitOrder(UnitOrderCommand),
1380    Queue(QueueCommand),
1381    SetGatherPoint(SetGatherPointCommand),
1382    SellResource(SellResourceCommand),
1383    BuyResource(BuyResourceCommand),
1384    Unknown7F(Unknown7FCommand),
1385    BackToWork(BackToWorkCommand),
1386}
1387
1388impl Command {
1389    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
1390        let len = input.read_u32::<LE>()?;
1391        let mut small_buffer;
1392        let mut big_buffer;
1393        let buffer: &mut [u8] = if len < 512 {
1394            small_buffer = [0; 512];
1395            &mut small_buffer[0..len as usize]
1396        } else {
1397            big_buffer = vec![0; len as usize];
1398            &mut big_buffer
1399        };
1400
1401        input.read_exact(buffer)?;
1402        let mut cursor = std::io::Cursor::new(buffer);
1403        let command = match cursor.read_u8()? {
1404            0x00 => OrderCommand::read_from(cursor).map(Command::Order),
1405            0x01 => StopCommand::read_from(cursor).map(Command::Stop),
1406            0x02 => WorkCommand::read_from(cursor).map(Command::Work),
1407            0x03 => MoveCommand::read_from(cursor).map(Command::Move),
1408            0x04 => CreateCommand::read_from(cursor).map(Command::Create),
1409            0x05 => AddResourceCommand::read_from(cursor).map(Command::AddResource),
1410            0x0a => AIOrderCommand::read_from(cursor).map(Command::AIOrder),
1411            0x0b => ResignCommand::read_from(cursor).map(Command::Resign),
1412            0x10 => GroupWaypointCommand::read_from(cursor).map(Command::GroupWaypoint),
1413            0x12 => UnitAIStateCommand::read_from(cursor).map(Command::UnitAIState),
1414            0x13 => GuardCommand::read_from(cursor).map(Command::Guard),
1415            0x14 => FollowCommand::read_from(cursor).map(Command::Follow),
1416            0x15 => PatrolCommand::read_from(cursor).map(Command::Patrol),
1417            0x17 => FormFormationCommand::read_from(cursor).map(Command::FormFormation),
1418            0x35 => UserPatchAICommand::read_from(cursor, len).map(Command::UserPatchAI),
1419            0x64 => MakeCommand::read_from(cursor).map(Command::Make),
1420            0x65 => ResearchCommand::read_from(cursor).map(Command::Research),
1421            0x66 => BuildCommand::read_from(cursor).map(Command::Build),
1422            0x67 => GameCommand::read_from(cursor).map(Command::Game),
1423            0x69 => BuildWallCommand::read_from(cursor).map(Command::BuildWall),
1424            0x6a => CancelBuildCommand::read_from(cursor).map(Command::CancelBuild),
1425            0x6b => AttackGroundCommand::read_from(cursor).map(Command::AttackGround),
1426            0x6e => RepairCommand::read_from(cursor).map(Command::Repair),
1427            0x6f => UngarrisonCommand::read_from(cursor).map(Command::Ungarrison),
1428            0x73 => FlareCommand::read_from(cursor).map(Command::Flare),
1429            0x75 => UnitOrderCommand::read_from(cursor).map(Command::UnitOrder),
1430            0x77 => QueueCommand::read_from(cursor).map(Command::Queue),
1431            0x78 => SetGatherPointCommand::read_from(cursor).map(Command::SetGatherPoint),
1432            0x7a => SellResourceCommand::read_from(cursor).map(Command::SellResource),
1433            0x7b => BuyResourceCommand::read_from(cursor).map(Command::BuyResource),
1434            0x7f => Unknown7FCommand::read_from(cursor).map(Command::Unknown7F),
1435            0x80 => BackToWorkCommand::read_from(cursor).map(Command::BackToWork),
1436            id => panic!("unsupported command type {:#x}", id),
1437        };
1438
1439        let _world_time = input.read_u32::<LE>()?;
1440        command
1441    }
1442}
1443
1444#[derive(Debug, Default, Clone)]
1445pub struct Sync {
1446    pub sequence: Option<u8>,
1447    pub time: u32,
1448    pub checksums: Option<(u32, u32, u32)>,
1449    pub next_world_time: Option<u32>,
1450}
1451
1452impl Sync {
1453    pub fn read_from<R: Read>(
1454        input: &mut R,
1455        use_sequence_numbers: bool,
1456        includes_checksum: bool,
1457    ) -> Result<Self> {
1458        let mut sync = Self::default();
1459        sync.sequence = if use_sequence_numbers {
1460            Some(input.read_u8()?)
1461        } else {
1462            None
1463        };
1464        sync.time = input.read_u32::<LE>()?;
1465        if false {
1466            let _old_world_time = input.read_u32::<LE>()?;
1467            let _unknown = input.read_u32::<LE>()?;
1468        }
1469        if includes_checksum {
1470            let check_bytes = input.read_u32::<LE>()?;
1471            if check_bytes == 0 {
1472                let _always_zero = input.read_u32::<LE>()?;
1473                let checksum = input.read_u32::<LE>()?;
1474                let position_checksum = input.read_u32::<LE>()?;
1475                let action_checksum = input.read_u32::<LE>()?;
1476                let _always_zero = input.read_u32::<LE>()?;
1477                sync.next_world_time = Some(input.read_u32::<LE>()?);
1478                sync.checksums = Some((checksum, position_checksum, action_checksum));
1479            }
1480        }
1481        Ok(sync)
1482    }
1483}
1484
1485/// Action at the start of the game, contains settings affecting the rec format.
1486#[derive(Debug, Default, Clone)]
1487pub struct Meta {
1488    /// The version of the action log format.
1489    /// `3` for AoC 1.0, `4` for AoC 1.0c and UserPatch.
1490    pub log_version: Option<u32>,
1491    pub checksum_interval: u32,
1492    pub is_multiplayer: bool,
1493    pub use_sequence_numbers: bool,
1494    pub local_player_id: PlayerID,
1495    pub header_position: u32,
1496    /// The amount of saved chapters in this rec / save game. This is only set if the game version
1497    /// that generated the file supports saved chapters (i.e. The Conquerors and up).
1498    pub num_chapters: Option<u32>,
1499}
1500
1501impl Meta {
1502    /// Read the chunk of recorded game body metadata that's the same across all versions.
1503    fn read_from_inner(mut input: impl Read) -> Result<Self> {
1504        let checksum_interval = input.read_u32::<LE>()?;
1505        let is_multiplayer = input.read_u32::<LE>()? != 0;
1506        let local_player_id = input.read_u32::<LE>()?.try_into().unwrap();
1507        let header_position = input.read_u32::<LE>()?;
1508        let use_sequence_numbers = input.read_u32::<LE>()? != 0;
1509        Ok(Self {
1510            checksum_interval,
1511            is_multiplayer,
1512            use_sequence_numbers,
1513            local_player_id,
1514            header_position,
1515            ..Default::default()
1516        })
1517    }
1518
1519    /// Read recorded game body metadata in the `mgl` format used by Age of Empires 2: The
1520    /// Age Of Kings.
1521    pub fn read_from_mgl(mut input: impl Read) -> Result<Self> {
1522        let meta = Self::read_from_inner(&mut input)?;
1523        let _exe_file_size = input.read_u64::<LE>()?;
1524        let _unknown = input.read_f32::<LE>()?;
1525        let _unknown = input.read_f32::<LE>()?;
1526
1527        // TODO if `is_multiplayer` flag contains 2 or 3, the `remaining_syncs_until_checksum`
1528        // value is stored here as u32
1529
1530        Ok(meta)
1531    }
1532
1533    /// Read recorded game body metadata in the `mgx` format used by Age of Empires 2: The
1534    /// Conquerors and all subsequent versions.
1535    pub fn read_from_mgx(mut input: impl Read) -> Result<Self> {
1536        let log_version = input.read_u32::<LE>()?;
1537        assert!(matches!(log_version, 3 | 4));
1538        let mut meta = Self::read_from_inner(&mut input)?;
1539        meta.log_version = Some(log_version);
1540        meta.num_chapters = Some(input.read_u32::<LE>()?);
1541        Ok(meta)
1542    }
1543}
1544
1545/// A chat message sent during the game.
1546#[derive(Debug, Clone)]
1547pub struct Chat {
1548    message: String,
1549}
1550
1551impl Chat {
1552    pub fn read_from<R: Read>(input: &mut R) -> Result<Self> {
1553        assert_eq!(input.read_i32::<LE>()?, -1);
1554        let length = input.read_u32::<LE>()?;
1555        let message = read_str(input, length as usize)?.unwrap_or_default();
1556        Ok(Self { message })
1557    }
1558}
1559
1560/// An action: TODO
1561#[derive(Debug, Clone)]
1562pub enum Action {
1563    Command(Command),
1564    Sync(Sync),
1565    ViewLock(ViewLock),
1566    Chat(Chat),
1567}