Skip to main content

rpfm_lib/files/group_formations/
mod.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! This module contains the implementation of the Group Formations file format for Total War games.
12//!
13//! Group formations define fixed formation templates that both the AI and player can use to
14//! deploy their units on the battlefield. Each formation is designed for specific tactical
15//! scenarios (attack, defend, naval, etc.) and specifies unit placement, spacing, arrangement
16//! patterns, and which unit types should be placed in each position.
17//!
18//! # File Format
19//!
20//! The group formations file (`groupformations.bin`) is a binary file containing formation
21//! definitions that can be used by both AI and players to deploy armies tactically. Each
22//! formation can specify:
23//!
24//! - AI purpose flags (attack, defend, river crossing, naval, etc.)
25//! - Priority and unit category requirements
26//! - Supported factions and subcultures
27//! - Formation blocks defining unit positions (absolute, relative, or spanning)
28//! - Entity preferences specifying which unit types go where
29//!
30//! # Game Support
31//!
32//! This file format is not versioned in the traditional sense. Instead, different games have
33//! different implementations in the `versions/` subdirectory:
34//!
35//! - Shogun 2: Basic formation system with entity types and arrangements
36//! - Rome 2 (and later): Extended system with entity weights, subcultures, and more AI purposes
37//!
38//! # File Location
39//!
40//! Group formations files are typically found at:
41//! - `groupformations.bin` in the root of a game's pack
42//!
43//! # Usage Example
44//!
45//! ```rust,ignore
46//! use rpfm_lib::files::{Decodeable, group_formations::*};
47//!
48//! // Decode a group formations file
49//! let mut data = std::io::Cursor::new(file_data);
50//! let extra_data = Some(DecodeableExtraData {
51//!     game_info: Some(game_info),
52//!     ..Default::default()
53//! });
54//! let formations = GroupFormations::decode(&mut data, &extra_data)?;
55//!
56//! // Access formation data
57//! for formation in formations.formations() {
58//!     println!("Formation: {}", formation.name());
59//!     println!("Priority: {}", formation.ai_priority());
60//! }
61//! ```
62
63use bitflags::Flags;
64use getset::*;
65use serde_derive::{Serialize, Deserialize};
66
67use std::fmt::Display;
68
69use crate::binary::{ReadBytes, WriteBytes};
70use crate::error::{Result, RLibError};
71use crate::files::{Decodeable, EncodeableExtraData, Encodeable};
72use crate::games::{GameInfo, supported_games::*};
73use crate::utils::*;
74
75use super::DecodeableExtraData;
76
77/// Fixed path to the Group Formations file.
78pub const PATH: &str = "groupformations.bin";
79
80pub mod edit;
81pub mod layout;
82pub mod validation;
83pub mod versions;
84
85#[cfg(test)] mod test_group_formations;
86
87//---------------------------------------------------------------------------//
88//                              Enum & Structs
89//---------------------------------------------------------------------------//
90
91/// Represents an entire Group Formations file decoded in memory.
92///
93/// Contains a list of formation templates that both AI and players can use to
94/// deploy armies on the battlefield for various tactical scenarios.
95#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
96#[getset(get = "pub", get_mut = "pub", set = "pub")]
97pub struct GroupFormations {
98    /// List of all formation definitions in the file.
99    formations: Vec<GroupFormation>,
100}
101
102/// A single formation definition specifying how units should be arranged.
103///
104/// Each formation includes AI usage criteria, unit requirements, and a set of
105/// formation blocks that define where different unit types should be positioned.
106#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
107#[getset(get = "pub", get_mut = "pub", set = "pub")]
108pub struct GroupFormation {
109    /// Name identifier for this formation.
110    name: String,
111
112    /// AI priority value for selecting this formation (higher = more preferred).
113    ai_priority: f32,
114
115    /// Bitflags indicating when this formation should be used (attack, defend, naval, etc.).
116    ai_purpose: AIPurpose,
117
118    /// Unknown field, present in Troy and later.
119    uk_2: u32,
120
121    /// Minimum percentage requirements for unit categories in this formation.
122    min_unit_category_percentage: Vec<MinUnitCategoryPercentage>,
123
124    /// List of supported subcultures (introduced in Rome 2).
125    ///
126    /// If non-empty, this formation is only available to armies from these subcultures.
127    ai_supported_subcultures: Vec<String>,
128
129    /// List of supported factions (introduced in Rome 2).
130    ///
131    /// If non-empty, this formation is only available to armies from these factions.
132    ai_supported_factions: Vec<String>,
133
134    /// Formation blocks defining unit positions and arrangements.
135    group_formation_blocks: Vec<GroupFormationBlock>,
136}
137
138/// Specifies a minimum percentage requirement for a unit category in a formation.
139///
140/// For example, a formation might require at least 30% cavalry units.
141#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
142#[getset(get = "pub", get_mut = "pub", set = "pub")]
143pub struct MinUnitCategoryPercentage {
144    /// The unit category (cavalry, infantry melee, infantry ranged, etc.).
145    category: UnitCategory,
146
147    /// Minimum percentage (0-100) of the army that must belong to this category.
148    percentage: u32,
149}
150
151/// A formation block defining a specific position or region in the formation.
152///
153/// Each block has an ID and contains either absolute positioning, relative positioning
154/// to another block, or spans multiple other blocks.
155#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
156#[getset(get = "pub", get_mut = "pub", set = "pub")]
157pub struct GroupFormationBlock {
158    /// Unique identifier for this block, used for relative positioning references.
159    block_id: u32,
160
161    /// The block type and its associated data.
162    block: Block,
163}
164
165/// Types of formation blocks that can be used to define unit positions.
166///
167/// - `ContainerAbsolute`: Positioned at fixed coordinates.
168/// - `ContainerRelative`: Positioned relative to another block.
169/// - `Spanning`: Encompasses multiple other blocks.
170#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
171pub enum Block {
172    /// A container positioned at absolute coordinates.
173    ContainerAbsolute(ContainerAbsolute),
174
175    /// A container positioned relative to another block.
176    ContainerRelative(ContainerRelative),
177
178    /// A spanning block that encompasses multiple other blocks.
179    Spanning(Spanning)
180}
181
182/// A container block positioned at absolute coordinates on the battlefield.
183///
184/// Defines how units should be arranged at a specific location, including their
185/// spacing, arrangement pattern, and which types of units should occupy this position.
186#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
187#[getset(get = "pub", get_mut = "pub", set = "pub")]
188pub struct ContainerAbsolute {
189    /// Priority for filling this block (higher priority blocks are filled first).
190    block_priority: f32,
191
192    /// How units should be arranged (line, column, crescent, etc.).
193    entity_arrangement: EntityArrangement,
194
195    /// Spacing between units in this block.
196    inter_entity_spacing: f32,
197
198    /// Y-axis offset for crescent formations.
199    crescent_y_offset: f32,
200
201    /// X coordinate of this block's position.
202    position_x: f32,
203
204    /// Y coordinate of this block's position.
205    position_y: f32,
206
207    /// Minimum number of units required to use this block.
208    minimum_entity_threshold: i32,
209
210    /// Maximum number of units that can be placed in this block.
211    maximum_entity_threshold: i32,
212
213    /// Ordered list of preferred unit types for this block.
214    entity_preferences: Vec<EntityPreference>,
215}
216
217/// A container block positioned relative to another block.
218///
219/// Similar to `ContainerAbsolute` but positioned at an offset from a reference block,
220/// allowing formations to be built up from interconnected positioned blocks.
221#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
222#[getset(get = "pub", get_mut = "pub", set = "pub")]
223pub struct ContainerRelative {
224    /// Priority for filling this block (higher priority blocks are filled first).
225    block_priority: f32,
226
227    /// ID of the block this is positioned relative to.
228    relative_block_id: u32,
229
230    /// How units should be arranged (line, column, crescent, etc.).
231    entity_arrangement: EntityArrangement,
232
233    /// Spacing between units in this block.
234    inter_entity_spacing: f32,
235
236    /// Y-axis offset for crescent formations.
237    crescent_y_offset: f32,
238
239    /// X offset relative to the reference block.
240    position_x: f32,
241
242    /// Y offset relative to the reference block.
243    position_y: f32,
244
245    /// Minimum number of units required to use this block.
246    minimum_entity_threshold: i32,
247
248    /// Maximum number of units that can be placed in this block.
249    maximum_entity_threshold: i32,
250
251    /// Ordered list of preferred unit types for this block.
252    entity_preferences: Vec<EntityPreference>,
253}
254
255/// Defines a preference for a specific type of unit to occupy a formation block.
256///
257/// Multiple preferences can be defined in priority order, so the AI will try to place
258/// the highest priority matching units first.
259#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
260#[getset(get = "pub", get_mut = "pub", set = "pub")]
261pub struct EntityPreference {
262    /// Priority for this entity type (higher = more preferred).
263    priority: f32,
264
265    /// The type of unit entity (infantry, cavalry, artillery, etc.).
266    ///
267    /// Note: This is called EntityClass in Rome 2 and EntityDescription in Shogun 2,
268    /// but represents the same concept.
269    entity: Entity,
270
271    /// Weight class of the unit (light, medium, heavy, etc.). Introduced in Rome 2.
272    entity_weight: EntityWeight,
273
274    /// Unknown field, present in Troy and later. Vanilla files almost always use 6.
275    uk_1: u32,
276
277    /// Entity class string identifier, present in Troy and later.
278    entity_class: String,
279}
280
281/// A spanning block that encompasses multiple other blocks.
282///
283/// Used to group related blocks together for organization or special handling.
284#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
285#[getset(get = "pub", get_mut = "pub", set = "pub")]
286pub struct Spanning {
287    /// IDs of the blocks that this spanning block encompasses.
288    spanned_block_ids: Vec<u32>,
289}
290
291/// Binary layout of a Group Formations file, which depends on the game it belongs to.
292#[derive(Clone, Copy, Debug, PartialEq, Eq)]
293pub enum GroupFormationsFormat {
294
295    /// Shogun 2.
296    Shogun2,
297
298    /// Rome 2, Attila and Thrones of Britannia.
299    Rome2,
300
301    /// Troy and Pharaoh.
302    Troy,
303
304    /// Three Kingdoms and Warhammer 3.
305    Warhammer3,
306}
307
308/// AI purpose flags indicating when a formation should be used.
309///
310/// - V1: Shogun 2 flag layout (different bit assignments from Rome 2+).
311/// - V2: Rome 2 and later flag layout.
312#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
313pub enum AIPurpose {
314    V1(versions::v1::AIPurposeFlags),
315    V2(versions::v2::AIPurposeFlags),
316}
317
318/// How units should be arranged within a formation block (line, column, crescent, etc.).
319///
320/// Identical across all game versions.
321#[derive(Default, Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
322#[repr(u32)]
323pub enum EntityArrangement {
324    #[default] Line = 0,
325    Column = 1,
326    CrescentFront = 2,
327    CrescentBack = 3,
328}
329
330/// Unit category classifications (cavalry, infantry melee, infantry ranged, naval, etc.).
331///
332/// Identical across all game versions.
333#[derive(Default, Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
334#[repr(u32)]
335pub enum UnitCategory {
336    #[default] Cavalry = 0,
337    InfantryMelee = 13,
338    InfantryRanged = 14,
339    NavalHeavy = 15,
340    NavalMedium = 16,
341    NavalLight = 17,
342}
343
344/// Entity type classifications.
345///
346/// - V1: Shogun 2 entity types (65+ specific unit classes like CavalryHeavy, InfantryLine, etc.).
347/// - V2: Rome 2 and later entity types (18 abstract classes like InfMel, CavShk, etc.).
348#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
349pub enum Entity {
350    V1(versions::v1::EntityType),
351    V2(versions::v2::EntityType),
352}
353
354/// Entity weight classifications (light, medium, heavy, etc.).
355///
356/// Introduced in Rome 2. Identical across all post-Shogun 2 game versions.
357/// Shogun 2 does not use entity weights.
358#[derive(Default, Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
359#[repr(u32)]
360pub enum EntityWeight {
361    VeryLight = 0,
362    Light = 1,
363    Medium = 2,
364    Heavy = 3,
365    VeryHeavy = 4,
366    SuperHeavy = 5,
367    #[default] Any = 6,
368}
369
370//---------------------------------------------------------------------------//
371//                          Implementation of GroupFormations
372//---------------------------------------------------------------------------//
373
374impl GroupFormationsFormat {
375
376    /// Returns the format used by the provided game.
377    ///
378    /// # Arguments
379    ///
380    /// * `game_info` - The game the file belongs to.
381    ///
382    /// # Returns
383    ///
384    /// The format of the game's Group Formations file.
385    ///
386    /// # Errors
387    ///
388    /// Returns an error if the game doesn't support Group Formations files.
389    pub fn from_game(game_info: &GameInfo) -> Result<Self> {
390        match game_info.key() {
391            KEY_PHARAOH_DYNASTIES |
392            KEY_PHARAOH |
393            KEY_TROY => Ok(Self::Troy),
394            KEY_THREE_KINGDOMS |
395            KEY_WARHAMMER_3 => Ok(Self::Warhammer3),
396            KEY_THRONES_OF_BRITANNIA |
397            KEY_ATTILA |
398            KEY_ROME_2 => Ok(Self::Rome2),
399            KEY_SHOGUN_2_COMPLETE_EDITION |
400            KEY_SHOGUN_2 => Ok(Self::Shogun2),
401
402            // Warhammer, Warhammer 2, Napoleon and Empire formats are not yet researched.
403            _ => Err(RLibError::DecodingUnsupportedGameSelected(game_info.key().to_string())),
404        }
405    }
406
407    /// Returns if formations in this format have a list of supported factions.
408    pub fn has_ai_supported_factions(&self) -> bool {
409        matches!(self, Self::Shogun2 | Self::Rome2 | Self::Warhammer3)
410    }
411
412    /// Returns if formations in this format have a list of supported subcultures.
413    pub fn has_ai_supported_subcultures(&self) -> bool {
414        matches!(self, Self::Rome2 | Self::Troy | Self::Warhammer3)
415    }
416
417    /// Returns if formations in this format have the `uk_2` field.
418    pub fn has_formation_uk_2(&self) -> bool {
419        matches!(self, Self::Troy | Self::Warhammer3)
420    }
421
422    /// Returns if entity preferences in this format have an entity weight.
423    pub fn has_entity_weight(&self) -> bool {
424        matches!(self, Self::Rome2 | Self::Troy | Self::Warhammer3)
425    }
426
427    /// Returns if entity preferences in this format have the `uk_1` and `entity_class` fields.
428    pub fn has_entity_class(&self) -> bool {
429        matches!(self, Self::Troy | Self::Warhammer3)
430    }
431
432    /// Returns an empty set of AI purpose flags of the version used by this format.
433    pub fn default_ai_purpose(&self) -> AIPurpose {
434        match self {
435            Self::Shogun2 => AIPurpose::V1(versions::v1::AIPurposeFlags::empty()),
436            Self::Rome2 | Self::Troy | Self::Warhammer3 => AIPurpose::V2(versions::v2::AIPurposeFlags::empty()),
437        }
438    }
439
440    /// Returns all the entity types of the version used by this format.
441    pub fn entities(&self) -> Vec<Entity> {
442        match self {
443            Self::Shogun2 => versions::v1::EntityType::ALL.iter().map(|entity| Entity::V1(*entity)).collect(),
444            Self::Rome2 | Self::Troy | Self::Warhammer3 => versions::v2::EntityType::ALL.iter().map(|entity| Entity::V2(*entity)).collect(),
445        }
446    }
447
448    /// Returns the default entity type of the version used by this format.
449    pub fn default_entity(&self) -> Entity {
450        match self {
451            Self::Shogun2 => Entity::V1(versions::v1::EntityType::Any),
452            Self::Rome2 => Entity::V2(versions::v2::EntityType::InfMel),
453
454            // Vanilla files pair the "any" entity class with this value.
455            Self::Troy | Self::Warhammer3 => Entity::V2(versions::v2::EntityType::Invalid),
456        }
457    }
458}
459
460impl GroupFormation {
461
462    /// Creates an empty formation valid for the provided format.
463    ///
464    /// # Arguments
465    ///
466    /// * `name` - Name of the new formation.
467    /// * `format` - Format of the file the formation will belong to.
468    ///
469    /// # Returns
470    ///
471    /// A formation with no blocks and the AI purpose version expected by the format.
472    pub fn new(name: &str, format: GroupFormationsFormat) -> Self {
473        Self {
474            name: name.to_owned(),
475            ai_purpose: format.default_ai_purpose(),
476            ..Default::default()
477        }
478    }
479}
480
481impl EntityPreference {
482
483    /// Creates an entity preference valid for the provided format, with the defaults used by vanilla files.
484    ///
485    /// # Arguments
486    ///
487    /// * `format` - Format of the file the preference will belong to.
488    ///
489    /// # Returns
490    ///
491    /// An entity preference with the entity version expected by the format.
492    pub fn new(format: GroupFormationsFormat) -> Self {
493        Self {
494            priority: 1.0,
495            entity: format.default_entity(),
496            entity_weight: EntityWeight::Any,
497            uk_1: if format.has_entity_class() { 6 } else { 0 },
498            entity_class: if format.has_entity_class() { "any".to_owned() } else { String::new() },
499        }
500    }
501}
502
503impl AIPurpose {
504
505    /// Returns the name and bit of every flag of the same version as these flags.
506    pub fn flags(&self) -> Vec<(&'static str, u32)> {
507        match self {
508            Self::V1(_) => versions::v1::AIPurposeFlags::FLAGS.iter().map(|flag| (flag.name(), flag.value().bits())).collect(),
509            Self::V2(_) => versions::v2::AIPurposeFlags::FLAGS.iter().map(|flag| (flag.name(), flag.value().bits())).collect(),
510        }
511    }
512
513    /// Returns the raw bits of these flags.
514    pub fn bits(&self) -> u32 {
515        match self {
516            Self::V1(flags) => flags.bits(),
517            Self::V2(flags) => flags.bits(),
518        }
519    }
520
521    /// Returns flags of the same version as these flags, with the provided bits.
522    pub fn with_bits(&self, bits: u32) -> Self {
523        match self {
524            Self::V1(_) => Self::V1(versions::v1::AIPurposeFlags::from_bits_retain(bits)),
525            Self::V2(_) => Self::V2(versions::v2::AIPurposeFlags::from_bits_retain(bits)),
526        }
527    }
528
529    /// Returns the raw bits of these flags, if they're the V1 (Shogun 2) version.
530    pub(crate) fn v1_bits(&self) -> Result<u32> {
531        match self {
532            Self::V1(flags) => Ok(flags.bits()),
533            Self::V2(_) => Err(RLibError::EncodingGroupFormationsMismatchedVersion("AIPurpose".to_owned())),
534        }
535    }
536
537    /// Returns the raw bits of these flags, if they're the V2 (Rome 2 and later) version.
538    pub(crate) fn v2_bits(&self) -> Result<u32> {
539        match self {
540            Self::V2(flags) => Ok(flags.bits()),
541            Self::V1(_) => Err(RLibError::EncodingGroupFormationsMismatchedVersion("AIPurpose".to_owned())),
542        }
543    }
544}
545
546impl Entity {
547
548    /// Returns the raw value of this entity, if it's the V1 (Shogun 2) version.
549    pub(crate) fn v1_value(&self) -> Result<u32> {
550        match self {
551            Self::V1(entity) => Ok((*entity).into()),
552            Self::V2(_) => Err(RLibError::EncodingGroupFormationsMismatchedVersion("Entity".to_owned())),
553        }
554    }
555
556    /// Returns the raw value of this entity, if it's the V2 (Rome 2 and later) version.
557    pub(crate) fn v2_value(&self) -> Result<u32> {
558        match self {
559            Self::V2(entity) => Ok((*entity).into()),
560            Self::V1(_) => Err(RLibError::EncodingGroupFormationsMismatchedVersion("Entity".to_owned())),
561        }
562    }
563}
564
565impl EntityArrangement {
566
567    /// All the possible values, in their binary order.
568    pub const ALL: [Self; 4] = [Self::Line, Self::Column, Self::CrescentFront, Self::CrescentBack];
569}
570
571impl UnitCategory {
572
573    /// All the possible values, in their binary order.
574    pub const ALL: [Self; 6] = [Self::Cavalry, Self::InfantryMelee, Self::InfantryRanged, Self::NavalHeavy, Self::NavalMedium, Self::NavalLight];
575}
576
577impl EntityWeight {
578
579    /// All the possible values, in their binary order.
580    pub const ALL: [Self; 7] = [Self::VeryLight, Self::Light, Self::Medium, Self::Heavy, Self::VeryHeavy, Self::SuperHeavy, Self::Any];
581}
582
583impl Display for Entity {
584    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
585        match self {
586            Self::V1(entity) => entity.fmt(f),
587            Self::V2(entity) => entity.fmt(f),
588        }
589    }
590}
591
592impl Display for EntityArrangement {
593    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
594        match self {
595            Self::Line => write!(f, "Line"),
596            Self::Column => write!(f, "Column"),
597            Self::CrescentFront => write!(f, "Crescent Front"),
598            Self::CrescentBack => write!(f, "Crescent Back"),
599        }
600    }
601}
602
603impl Display for UnitCategory {
604    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
605        match self {
606            Self::Cavalry => write!(f, "Cavalry"),
607            Self::InfantryMelee => write!(f, "Melee Infantry"),
608            Self::InfantryRanged => write!(f, "Ranged Infantry"),
609            Self::NavalHeavy => write!(f, "Heavy Naval"),
610            Self::NavalMedium => write!(f, "Medium Naval"),
611            Self::NavalLight => write!(f, "Light Naval"),
612        }
613    }
614}
615
616impl Display for EntityWeight {
617    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
618        match self {
619            Self::VeryLight => write!(f, "Very Light"),
620            Self::Light => write!(f, "Light"),
621            Self::Medium => write!(f, "Medium"),
622            Self::Heavy => write!(f, "Heavy"),
623            Self::VeryHeavy => write!(f, "Very Heavy"),
624            Self::SuperHeavy => write!(f, "Super Heavy"),
625            Self::Any => write!(f, "Any"),
626        }
627    }
628}
629
630impl Block {
631
632    /// Returns the ids of the blocks this block needs to be positioned first.
633    ///
634    /// # Returns
635    ///
636    /// The parent block for relative containers, the spanned blocks for spans, and nothing for absolute containers.
637    pub fn dependencies(&self) -> &[u32] {
638        match self {
639            Self::ContainerAbsolute(_) => &[],
640            Self::ContainerRelative(container) => std::slice::from_ref(&container.relative_block_id),
641            Self::Spanning(span) => &span.spanned_block_ids,
642        }
643    }
644}
645
646impl Default for Block {
647    fn default() -> Self {
648        Self::ContainerAbsolute(ContainerAbsolute::default())
649    }
650}
651
652impl Default for AIPurpose {
653    fn default() -> Self {
654        Self::V1(versions::v1::AIPurposeFlags::default())
655    }
656}
657
658impl Default for Entity {
659    fn default() -> Self {
660        Self::V2(versions::v2::EntityType::default())
661    }
662}
663
664impl TryFrom<u32> for EntityArrangement {
665    type Error = RLibError;
666    fn try_from(value: u32) -> Result<Self> {
667        match value {
668            _ if value == Self::Line as u32 => Ok(Self::Line),
669            _ if value == Self::Column as u32 => Ok(Self::Column),
670            _ if value == Self::CrescentFront as u32 => Ok(Self::CrescentFront),
671            _ if value == Self::CrescentBack as u32 => Ok(Self::CrescentBack),
672            _ => Err(RLibError::DecodingGroupFormationsUnknownEnumValue("EntityArrangement".to_string(), value)),
673        }
674    }
675}
676
677impl From<EntityArrangement> for u32 {
678    fn from(value: EntityArrangement) -> u32 {
679        value as u32
680    }
681}
682
683impl TryFrom<u32> for UnitCategory {
684    type Error = RLibError;
685    fn try_from(value: u32) -> Result<Self> {
686        match value {
687            _ if value == Self::Cavalry as u32 => Ok(Self::Cavalry),
688            _ if value == Self::InfantryMelee as u32 => Ok(Self::InfantryMelee),
689            _ if value == Self::InfantryRanged as u32 => Ok(Self::InfantryRanged),
690            _ if value == Self::NavalHeavy as u32 => Ok(Self::NavalHeavy),
691            _ if value == Self::NavalMedium as u32 => Ok(Self::NavalMedium),
692            _ if value == Self::NavalLight as u32 => Ok(Self::NavalLight),
693            _ => Err(RLibError::DecodingGroupFormationsUnknownEnumValue("UnitCategory".to_string(), value)),
694        }
695    }
696}
697
698impl From<UnitCategory> for u32 {
699    fn from(value: UnitCategory) -> u32 {
700        value as u32
701    }
702}
703
704impl TryFrom<u32> for EntityWeight {
705    type Error = RLibError;
706    fn try_from(value: u32) -> Result<Self> {
707        match value {
708            _ if value == Self::VeryLight as u32 => Ok(Self::VeryLight),
709            _ if value == Self::Light as u32 => Ok(Self::Light),
710            _ if value == Self::Medium as u32 => Ok(Self::Medium),
711            _ if value == Self::Heavy as u32 => Ok(Self::Heavy),
712            _ if value == Self::VeryHeavy as u32 => Ok(Self::VeryHeavy),
713            _ if value == Self::SuperHeavy as u32 => Ok(Self::SuperHeavy),
714            _ if value == Self::Any as u32 => Ok(Self::Any),
715            _ => Err(RLibError::DecodingGroupFormationsUnknownEnumValue("EntityWeight".to_string(), value)),
716        }
717    }
718}
719
720impl From<EntityWeight> for u32 {
721    fn from(value: EntityWeight) -> u32 {
722        value as u32
723    }
724}
725
726impl Decodeable for GroupFormations {
727
728    fn decode<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
729        let extra_data = extra_data.as_ref().ok_or(RLibError::DecodingMissingExtraData)?;
730        let game_info = extra_data.game_info.ok_or_else(|| RLibError::DecodingMissingExtraDataField("game_info".to_owned()))?;
731
732        let mut decoded = Self::default();
733        let data_len = data.len()?;
734
735        match GroupFormationsFormat::from_game(game_info)? {
736            GroupFormationsFormat::Troy => decoded.decode_troy(data)?,
737            GroupFormationsFormat::Warhammer3 => decoded.decode_wh3(data)?,
738            GroupFormationsFormat::Rome2 => decoded.decode_rom_2(data)?,
739            GroupFormationsFormat::Shogun2 => decoded.decode_sho_2(data)?,
740        }
741
742        check_size_mismatch(data.stream_position()? as usize, data_len as usize)?;
743
744        Ok(decoded)
745    }
746}
747
748impl Encodeable for GroupFormations {
749
750    fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
751        let extra_data = extra_data.as_ref().ok_or(RLibError::EncodingMissingExtraData)?;
752        let game_info = extra_data.game_info.ok_or_else(|| RLibError::DecodingMissingExtraDataField("game_info".to_owned()))?;
753
754        match GroupFormationsFormat::from_game(game_info)? {
755            GroupFormationsFormat::Troy => self.encode_troy(buffer)?,
756            GroupFormationsFormat::Warhammer3 => self.encode_wh3(buffer)?,
757            GroupFormationsFormat::Rome2 => self.encode_rom_2(buffer)?,
758            GroupFormationsFormat::Shogun2 => self.encode_sho_2(buffer)?,
759        };
760
761        Ok(())
762    }
763}