1use 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
77pub 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#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
96#[getset(get = "pub", get_mut = "pub", set = "pub")]
97pub struct GroupFormations {
98 formations: Vec<GroupFormation>,
100}
101
102#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
107#[getset(get = "pub", get_mut = "pub", set = "pub")]
108pub struct GroupFormation {
109 name: String,
111
112 ai_priority: f32,
114
115 ai_purpose: AIPurpose,
117
118 uk_2: u32,
120
121 min_unit_category_percentage: Vec<MinUnitCategoryPercentage>,
123
124 ai_supported_subcultures: Vec<String>,
128
129 ai_supported_factions: Vec<String>,
133
134 group_formation_blocks: Vec<GroupFormationBlock>,
136}
137
138#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
142#[getset(get = "pub", get_mut = "pub", set = "pub")]
143pub struct MinUnitCategoryPercentage {
144 category: UnitCategory,
146
147 percentage: u32,
149}
150
151#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
156#[getset(get = "pub", get_mut = "pub", set = "pub")]
157pub struct GroupFormationBlock {
158 block_id: u32,
160
161 block: Block,
163}
164
165#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
171pub enum Block {
172 ContainerAbsolute(ContainerAbsolute),
174
175 ContainerRelative(ContainerRelative),
177
178 Spanning(Spanning)
180}
181
182#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
187#[getset(get = "pub", get_mut = "pub", set = "pub")]
188pub struct ContainerAbsolute {
189 block_priority: f32,
191
192 entity_arrangement: EntityArrangement,
194
195 inter_entity_spacing: f32,
197
198 crescent_y_offset: f32,
200
201 position_x: f32,
203
204 position_y: f32,
206
207 minimum_entity_threshold: i32,
209
210 maximum_entity_threshold: i32,
212
213 entity_preferences: Vec<EntityPreference>,
215}
216
217#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
222#[getset(get = "pub", get_mut = "pub", set = "pub")]
223pub struct ContainerRelative {
224 block_priority: f32,
226
227 relative_block_id: u32,
229
230 entity_arrangement: EntityArrangement,
232
233 inter_entity_spacing: f32,
235
236 crescent_y_offset: f32,
238
239 position_x: f32,
241
242 position_y: f32,
244
245 minimum_entity_threshold: i32,
247
248 maximum_entity_threshold: i32,
250
251 entity_preferences: Vec<EntityPreference>,
253}
254
255#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
260#[getset(get = "pub", get_mut = "pub", set = "pub")]
261pub struct EntityPreference {
262 priority: f32,
264
265 entity: Entity,
270
271 entity_weight: EntityWeight,
273
274 uk_1: u32,
276
277 entity_class: String,
279}
280
281#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
285#[getset(get = "pub", get_mut = "pub", set = "pub")]
286pub struct Spanning {
287 spanned_block_ids: Vec<u32>,
289}
290
291#[derive(Clone, Copy, Debug, PartialEq, Eq)]
293pub enum GroupFormationsFormat {
294
295 Shogun2,
297
298 Rome2,
300
301 Troy,
303
304 Warhammer3,
306}
307
308#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
313pub enum AIPurpose {
314 V1(versions::v1::AIPurposeFlags),
315 V2(versions::v2::AIPurposeFlags),
316}
317
318#[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#[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#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
349pub enum Entity {
350 V1(versions::v1::EntityType),
351 V2(versions::v2::EntityType),
352}
353
354#[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
370impl GroupFormationsFormat {
375
376 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 _ => Err(RLibError::DecodingUnsupportedGameSelected(game_info.key().to_string())),
404 }
405 }
406
407 pub fn has_ai_supported_factions(&self) -> bool {
409 matches!(self, Self::Shogun2 | Self::Rome2 | Self::Warhammer3)
410 }
411
412 pub fn has_ai_supported_subcultures(&self) -> bool {
414 matches!(self, Self::Rome2 | Self::Troy | Self::Warhammer3)
415 }
416
417 pub fn has_formation_uk_2(&self) -> bool {
419 matches!(self, Self::Troy | Self::Warhammer3)
420 }
421
422 pub fn has_entity_weight(&self) -> bool {
424 matches!(self, Self::Rome2 | Self::Troy | Self::Warhammer3)
425 }
426
427 pub fn has_entity_class(&self) -> bool {
429 matches!(self, Self::Troy | Self::Warhammer3)
430 }
431
432 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 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 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 Self::Troy | Self::Warhammer3 => Entity::V2(versions::v2::EntityType::Invalid),
456 }
457 }
458}
459
460impl GroupFormation {
461
462 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 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 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 pub fn bits(&self) -> u32 {
515 match self {
516 Self::V1(flags) => flags.bits(),
517 Self::V2(flags) => flags.bits(),
518 }
519 }
520
521 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 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 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 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 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 pub const ALL: [Self; 4] = [Self::Line, Self::Column, Self::CrescentFront, Self::CrescentBack];
569}
570
571impl UnitCategory {
572
573 pub const ALL: [Self; 6] = [Self::Cavalry, Self::InfantryMelee, Self::InfantryRanged, Self::NavalHeavy, Self::NavalMedium, Self::NavalLight];
575}
576
577impl EntityWeight {
578
579 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 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}