1use super::ini::{ini_enum, ini_section, required, sections};
3use crate::{Result, error::Error};
4use std::collections::BTreeMap;
5
6#[derive(Debug, Clone, PartialEq, serde::Serialize)]
8pub struct CombatIni {
9 pub misc: CombatSettings,
10 pub attacks: BTreeMap<u32, Attack>,
11 pub defenses: BTreeMap<u32, Defense>,
12 pub taunts: BTreeMap<u32, Taunt>,
13}
14
15impl CombatIni {
16 pub fn parse(text: &str) -> Result<Self> {
19 let sections = sections(text, "COMBAT.INI")?;
20 let mut result = Self {
21 misc: CombatSettings::parse(Some(required(§ions, "COMBAT.INI", "misc")?))?,
22 attacks: BTreeMap::new(),
23 defenses: BTreeMap::new(),
24 taunts: BTreeMap::new(),
25 };
26 for (name, section) in §ions {
27 for prefix in ["attack", "defend", "taunt"] {
28 let Some(suffix) = name.strip_prefix(prefix) else {
29 continue;
30 };
31 let id = suffix.parse::<u32>().map_err(|_| {
32 Error::Parse(format!("COMBAT.INI [{name}]: invalid section index"))
33 })?;
34 let duplicate = match prefix {
35 "attack" => result
36 .attacks
37 .insert(id, Attack::parse(Some(section))?)
38 .is_some(),
39 "defend" => result
40 .defenses
41 .insert(id, Defense::parse(Some(section))?)
42 .is_some(),
43 _ => result
44 .taunts
45 .insert(id, Taunt::parse(Some(section))?)
46 .is_some(),
47 };
48 if duplicate {
49 return Err(Error::Parse(format!(
50 "COMBAT.INI [{name}]: duplicate numeric section index {id}"
51 )));
52 }
53 }
54 }
55 Ok(result)
56 }
57}
58
59ini_section! {
60 CombatSettings {
62 sword_clank_1: i32 => "sword_clank_1",
63 sword_clank_2: i32 => "sword_clank_2",
64 non_engaged_dist: i32 => "non_engaged_dist",
65 in_face_dist: i32 => "in_face_dist",
66 non_engaged_spacing: i32 => "non_engaged_spacing",
67 in_combat_threshold: i32 => "in_combat_threshold",
68 player_can_die: bool => "player_can_die",
69 cyrus_defend_interval: i32 => "cyrus_defend_interval",
70 node_timer: i32 => "node_timer",
71 max_taunts: u32 => "max_taunts",
72 }
73}
74
75ini_enum! {
76 AttackType { Melee = 0, Missile = 1, Combo = 2, Stab = 3, Finishing = 4 }
78}
79
80ini_enum! {
81 Elevation { Regular = 0, Above = 1, Below = 2 }
83}
84
85ini_section! {
86 Attack {
88 attack_type: AttackType => "type",
89 arc: i32 => "arc",
90 arc_center: i32 => "arc_center",
91 elevation: Elevation => "elevation",
92 min_range: i32 => "min_range",
93 max_range: i32 => "max_range",
94 first_collide_frame: i32 => "first_collide_frame",
95 collide_duration: i32 => "collide_duration",
96 damage: i32 => "damage",
97 force: i32 => "force",
98 defense: i32 => "defense",
99 first_defend_frame: i32 => "first_defend_frame",
100 defend_duration: i32 => "defend_duration",
101 animation: i32 => "animation",
102 col_vertex: i32 => "col_vertex",
103 col_sphere_size: i32 => "col_sphere_size",
104 num_attacks: u32 => "num_attacks",
105 attack00: u32 => "attack00",
106 attack01: u32 => "attack01",
107 attack02: u32 => "attack02",
108 }
109}
110
111ini_section! {
112 Defense {
114 arc: i32 => "arc",
115 min_range: i32 => "min_range",
116 max_range: i32 => "max_range",
117 first_collide_frame: i32 => "first_collide_frame",
118 collide_duration: i32 => "collide_duration",
119 animation: i32 => "animation",
120 }
121}
122
123ini_enum! {
124 TauntTrigger {
126 BeginCombat = 0,
127 Attack = 1,
128 HurtByOpponent = 2,
129 HitOpponent = 3,
130 Defend = 4,
131 Move = 5,
132 Death = 6,
133 KilledOpponent = 7,
134 WantSwitchOut = 8,
135 WantSwitchIn = 9,
136 UnarmedMaleOpponent = 10,
137 UnarmedFemaleOpponent = 11,
138 }
139}
140
141ini_section! {
142 Taunt {
144 trigger: TauntTrigger => "type",
145 rtx_label: String => "rtx_label",
146 animation: i32 => "animation",
147 }
148}