use serde::{Deserialize, Serialize};
use tracing::warn;
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, specta::Type)]
#[serde(rename_all = "camelCase")]
pub struct Gait {
gait_type: GaitType,
name: String,
max_speed: u32,
energy_use: u32,
modifiers: Vec<Modifier>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, specta::Type)]
#[serde(rename_all = "camelCase")]
#[allow(clippy::module_name_repetitions)]
pub enum GaitType {
Walk,
Crawl,
Climb,
Swim,
Fly,
Other(String),
#[default]
Unknown,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, specta::Type)]
#[serde(rename_all = "camelCase")]
pub enum Modifier {
LayersSlow,
Strength,
Agility,
StealthSlows {
percentage: u32,
},
NoBuildUp,
BuildUp {
time: u32,
turning_max: u32,
start_speed: u32,
},
}
impl Gait {
pub fn from_value(value: &str) -> Self {
let mut gait = Self::default();
let mut parts = value.split(':');
let mut has_build_up = false;
gait.gait_type = match parts.next() {
Some("WALK") => GaitType::Walk,
Some("CLIMB") => GaitType::Climb,
Some("SWIM") => GaitType::Swim,
Some("CRAWL") => GaitType::Crawl,
Some("FLY") => GaitType::Fly,
Some(other) => GaitType::Other(other.to_string()),
None => GaitType::Unknown,
};
gait.name = parts.next().unwrap_or("").to_string();
gait.max_speed = parts.next().unwrap_or("0").parse().unwrap_or(0);
if let Some(raw_value) = parts.next() {
if raw_value == "NO_BUILD_UP" {
gait.modifiers.push(Modifier::NoBuildUp);
} else if let Ok(value) = raw_value.parse() {
gait.modifiers.push(Modifier::BuildUp {
time: value,
turning_max: 0,
start_speed: 0,
});
has_build_up = true;
}
}
if has_build_up {
if let Some(raw_value) = parts.next() {
if let Ok(value) = raw_value.parse::<u32>() {
if let Some(Modifier::BuildUp {
time,
turning_max: _,
start_speed,
}) = gait.modifiers.pop()
{
gait.modifiers.push(Modifier::BuildUp {
time,
turning_max: value,
start_speed,
});
}
}
}
if let Some(raw_value) = parts.next() {
if let Ok(value) = raw_value.parse::<u32>() {
if let Some(Modifier::BuildUp {
time,
turning_max,
start_speed: _,
}) = gait.modifiers.pop()
{
gait.modifiers.push(Modifier::BuildUp {
time,
turning_max,
start_speed: value,
});
}
}
}
}
gait.energy_use = parts.next().unwrap_or("0").parse().unwrap_or(0);
parts.clone().enumerate().for_each(|(idx, s)| match s {
"LAYERS_SLOW" => gait.modifiers.push(Modifier::LayersSlow),
"STRENGTH" => gait.modifiers.push(Modifier::Strength),
"AGILITY" => gait.modifiers.push(Modifier::Agility),
"STEALTH_SLOWS" => {
if let Some(raw_value) = parts.nth(idx + 1) {
if let Ok(value) = raw_value.parse() {
gait.modifiers
.push(Modifier::StealthSlows { percentage: value });
}
} else {
warn!("STEALTH_SLOWS modifier is missing a value in {value}");
}
}
_ => {}
});
gait
}
pub fn is_empty(&self) -> bool {
self.gait_type == GaitType::Unknown
}
}