use super::ini::{ini_enum, ini_section, required, sections};
use crate::{Result, error::Error};
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub struct CombatIni {
pub misc: CombatSettings,
pub attacks: BTreeMap<u32, Attack>,
pub defenses: BTreeMap<u32, Defense>,
pub taunts: BTreeMap<u32, Taunt>,
}
impl CombatIni {
pub fn parse(text: &str) -> Result<Self> {
let sections = sections(text, "COMBAT.INI")?;
let mut result = Self {
misc: CombatSettings::parse(Some(required(§ions, "COMBAT.INI", "misc")?))?,
attacks: BTreeMap::new(),
defenses: BTreeMap::new(),
taunts: BTreeMap::new(),
};
for (name, section) in §ions {
for prefix in ["attack", "defend", "taunt"] {
let Some(suffix) = name.strip_prefix(prefix) else {
continue;
};
let id = suffix.parse::<u32>().map_err(|_| {
Error::Parse(format!("COMBAT.INI [{name}]: invalid section index"))
})?;
let duplicate = match prefix {
"attack" => result
.attacks
.insert(id, Attack::parse(Some(section))?)
.is_some(),
"defend" => result
.defenses
.insert(id, Defense::parse(Some(section))?)
.is_some(),
_ => result
.taunts
.insert(id, Taunt::parse(Some(section))?)
.is_some(),
};
if duplicate {
return Err(Error::Parse(format!(
"COMBAT.INI [{name}]: duplicate numeric section index {id}"
)));
}
}
}
Ok(result)
}
}
ini_section! {
CombatSettings {
sword_clank_1: i32 => "sword_clank_1",
sword_clank_2: i32 => "sword_clank_2",
non_engaged_dist: i32 => "non_engaged_dist",
in_face_dist: i32 => "in_face_dist",
non_engaged_spacing: i32 => "non_engaged_spacing",
in_combat_threshold: i32 => "in_combat_threshold",
player_can_die: bool => "player_can_die",
cyrus_defend_interval: i32 => "cyrus_defend_interval",
node_timer: i32 => "node_timer",
max_taunts: u32 => "max_taunts",
}
}
ini_enum! {
AttackType { Melee = 0, Missile = 1, Combo = 2, Stab = 3, Finishing = 4 }
}
ini_enum! {
Elevation { Regular = 0, Above = 1, Below = 2 }
}
ini_section! {
Attack {
attack_type: AttackType => "type",
arc: i32 => "arc",
arc_center: i32 => "arc_center",
elevation: Elevation => "elevation",
min_range: i32 => "min_range",
max_range: i32 => "max_range",
first_collide_frame: i32 => "first_collide_frame",
collide_duration: i32 => "collide_duration",
damage: i32 => "damage",
force: i32 => "force",
defense: i32 => "defense",
first_defend_frame: i32 => "first_defend_frame",
defend_duration: i32 => "defend_duration",
animation: i32 => "animation",
col_vertex: i32 => "col_vertex",
col_sphere_size: i32 => "col_sphere_size",
num_attacks: u32 => "num_attacks",
attack00: u32 => "attack00",
attack01: u32 => "attack01",
attack02: u32 => "attack02",
}
}
ini_section! {
Defense {
arc: i32 => "arc",
min_range: i32 => "min_range",
max_range: i32 => "max_range",
first_collide_frame: i32 => "first_collide_frame",
collide_duration: i32 => "collide_duration",
animation: i32 => "animation",
}
}
ini_enum! {
TauntTrigger {
BeginCombat = 0,
Attack = 1,
HurtByOpponent = 2,
HitOpponent = 3,
Defend = 4,
Move = 5,
Death = 6,
KilledOpponent = 7,
WantSwitchOut = 8,
WantSwitchIn = 9,
UnarmedMaleOpponent = 10,
UnarmedFemaleOpponent = 11,
}
}
ini_section! {
Taunt {
trigger: TauntTrigger => "type",
rtx_label: String => "rtx_label",
animation: i32 => "animation",
}
}