1use 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
10pub type Location2 = (f32, f32);
12pub type Location3 = (f32, f32, f32);
16
17#[derive(Debug, Default, Clone)]
21pub struct ViewLock {
22 pub x: f32,
24 pub y: f32,
26 pub player: PlayerID,
28}
29
30impl ViewLock {
31 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 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#[derive(Debug, Clone)]
54pub enum ObjectsList {
55 SameAsLast,
57 List(Vec<ObjectID>),
59}
60
61impl Default for ObjectsList {
62 fn default() -> Self {
63 ObjectsList::List(vec![])
64 }
65}
66
67impl ObjectsList {
68 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 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 pub fn len(&self) -> usize {
95 match self {
96 ObjectsList::SameAsLast => 0,
97 ObjectsList::List(list) => list.len(),
98 }
99 }
100
101 pub fn is_empty(&self) -> bool {
103 self.len() == 0
104 }
105}
106
107#[derive(Debug, Default, Clone)]
109pub struct OrderCommand {
110 pub player_id: PlayerID,
112 pub target_id: Option<ObjectID>,
114 pub location: Location2,
116 pub objects: ObjectsList,
118}
119
120impl OrderCommand {
121 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 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#[derive(Debug, Default, Clone)]
152pub struct StopCommand {
153 pub objects: ObjectsList,
155}
156
157impl StopCommand {
158 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 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#[derive(Debug, Default, Clone)]
176pub struct WorkCommand {
177 pub target_id: Option<ObjectID>,
179 pub location: Location2,
181 pub objects: ObjectsList,
183}
184
185impl WorkCommand {
186 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 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#[derive(Debug, Default, Clone)]
213pub struct MoveCommand {
214 pub player_id: PlayerID,
216 pub target_id: Option<ObjectID>,
218 pub location: Location2,
220 pub objects: ObjectsList,
222}
223
224impl MoveCommand {
225 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 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#[derive(Debug, Default, Clone)]
254pub struct CreateCommand {
255 pub player_id: PlayerID,
257 pub unit_type_id: UnitTypeID,
259 pub location: Location3,
261}
262
263impl CreateCommand {
264 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 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#[derive(Debug, Default, Clone)]
296pub struct AddResourceCommand {
297 pub player_id: PlayerID,
299 pub resource: u8,
301 pub amount: f32,
303}
304
305impl AddResourceCommand {
306 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 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#[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#[derive(Debug, Default, Clone)]
413pub struct ResignCommand {
414 pub player_id: PlayerID,
416 pub comm_player_id: PlayerID,
418 pub dropped: bool,
420}
421
422impl ResignCommand {
423 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 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#[derive(Debug, Default, Clone)]
476pub struct UnitAIStateCommand {
477 pub state: i8,
479 pub objects: ObjectsList,
481}
482
483impl UnitAIStateCommand {
484 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 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#[derive(Debug, Default, Clone)]
503pub struct GuardCommand {
504 pub target_id: Option<ObjectID>,
506 pub objects: ObjectsList,
508}
509
510impl GuardCommand {
511 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 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#[derive(Debug, Default, Clone)]
537pub struct FollowCommand {
538 pub target_id: Option<ObjectID>,
540 pub objects: ObjectsList,
542}
543
544impl FollowCommand {
545 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 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#[derive(Debug, Default, Clone)]
571pub struct PatrolCommand {
572 pub waypoints: ArrayVec<[Location2; 10]>,
574 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#[derive(Debug, Default, Clone)]
616pub struct FormFormationCommand {
617 pub player_id: PlayerID,
619 pub formation_type: i32,
621 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#[derive(Debug, Default, Clone)]
648pub struct UserPatchAICommand {
649 pub player_id: PlayerID,
650 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#[derive(Debug, Default, Clone)]
743pub struct ResearchCommand {
744 pub player_id: PlayerID,
746 pub building_id: ObjectID,
748 pub tech_id: TechID,
750 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#[derive(Debug, Default, Clone)]
786pub struct BuildCommand {
787 pub player_id: PlayerID,
789 pub unit_type_id: UnitTypeID,
791 pub location: Location2,
793 pub frame: u8,
795 pub builders: ObjectsList,
797 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#[derive(Debug, Clone)]
822pub enum GameCommand {
823 SetGameSpeed {
824 player_id: PlayerID,
825 speed: f32,
826 },
827 Inventory {
829 player_id: PlayerID,
830 attribute_id: i16,
831 amount: f32,
832 },
833 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 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 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 },
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#[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#[derive(Debug, Default, Clone)]
1016pub struct CancelBuildCommand {
1017 pub player_id: PlayerID,
1019 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#[derive(Debug, Default, Clone)]
1044pub struct AttackGroundCommand {
1045 pub location: Location2,
1047 pub objects: ObjectsList,
1049}
1050
1051impl AttackGroundCommand {
1052 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 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#[derive(Debug, Default, Clone)]
1075pub struct RepairCommand {
1076 pub target_id: Option<ObjectID>,
1078 pub repairers: ObjectsList,
1080}
1081
1082impl RepairCommand {
1083 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 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#[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#[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#[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#[derive(Debug, Default, Clone)]
1204pub struct QueueCommand {
1205 pub building_id: ObjectID,
1207 pub unit_type_id: UnitTypeID,
1209 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#[derive(Debug, Default, Clone)]
1234pub struct SetGatherPointCommand {
1235 pub buildings: ObjectsList,
1237 pub target_id: Option<ObjectID>,
1239 pub target_type_id: Option<UnitTypeID>,
1241 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
1266macro_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#[derive(Debug, Default, Clone)]
1293pub struct SellResourceCommand {
1294 pub player_id: PlayerID,
1296 pub resource: u8,
1298 pub amount: i8,
1301 pub market_id: ObjectID,
1303}
1304
1305buy_sell_impl!(SellResourceCommand);
1306
1307#[derive(Debug, Default, Clone)]
1309pub struct BuyResourceCommand {
1310 pub player_id: PlayerID,
1312 pub resource: u8,
1314 pub amount: i8,
1317 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#[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#[derive(Debug, Default, Clone)]
1487pub struct Meta {
1488 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 pub num_chapters: Option<u32>,
1499}
1500
1501impl Meta {
1502 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 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 Ok(meta)
1531 }
1532
1533 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#[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#[derive(Debug, Clone)]
1562pub enum Action {
1563 Command(Command),
1564 Sync(Sync),
1565 ViewLock(ViewLock),
1566 Chat(Chat),
1567}