use crate::data::ResourceLoader;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TtxStat {
Health,
TorpedoProtection,
Speed,
TurningRadius,
RudderTime,
ArmorMin,
ArmorMax,
BatteryCapacity,
BatteryRegeneration,
ArtilleryReloadTime,
ArtilleryRange,
ArtilleryDispersion,
ArtilleryAmmoSwitchTime,
GunCaliber,
GunNumBarrels,
GunNumGuns,
GunRotationSpeed,
GunRotationTime,
ShellDamage,
ShellCaliber,
ShellSpeed,
ShellPenetration,
ShellBurnChance,
ShellFloodChance,
ShellMaxAmmo,
ShellDisabledUnderwater,
SecondaryRange,
SecondaryReloadTime,
SecondaryDispersion,
SecondaryAmmoSwitchTime,
SecondaryGunCaliber,
SecondaryGunNumBarrels,
SecondaryGunNumGuns,
SecondaryGunRotationSpeed,
SecondaryGunRotationTime,
SecondaryShellDamage,
SecondaryShellCaliber,
SecondaryShellSpeed,
SecondaryShellPenetration,
SecondaryShellBurnChance,
SecondaryShellFloodChance,
SecondaryShellMaxAmmo,
TorpedoReloadTime,
LauncherRotationSpeed,
LauncherRotationTime,
LauncherNumBarrels,
TorpedoDamage,
TorpedoSpeed,
TorpedoRange,
TorpedoVisibility,
TorpedoDistanceOfMaxDamage,
TorpedoIsDamageIncreasing,
TorpedoDisabledUnderwater,
FireControlMaxDist,
SeaDetection,
SeaDetectionOnFire,
AirDetection,
AirDetectionOnFire,
DetectionInSmoke,
SecondaryRangeDetection,
PeriscopeDepthDetection,
}
impl TtxStat {
pub const ALL: &'static [TtxStat] = &[
TtxStat::Health,
TtxStat::TorpedoProtection,
TtxStat::Speed,
TtxStat::TurningRadius,
TtxStat::RudderTime,
TtxStat::ArmorMin,
TtxStat::ArmorMax,
TtxStat::BatteryCapacity,
TtxStat::BatteryRegeneration,
TtxStat::ArtilleryReloadTime,
TtxStat::ArtilleryRange,
TtxStat::ArtilleryDispersion,
TtxStat::ArtilleryAmmoSwitchTime,
TtxStat::GunCaliber,
TtxStat::GunNumBarrels,
TtxStat::GunNumGuns,
TtxStat::GunRotationSpeed,
TtxStat::GunRotationTime,
TtxStat::ShellDamage,
TtxStat::ShellCaliber,
TtxStat::ShellSpeed,
TtxStat::ShellPenetration,
TtxStat::ShellBurnChance,
TtxStat::ShellFloodChance,
TtxStat::ShellMaxAmmo,
TtxStat::ShellDisabledUnderwater,
TtxStat::SecondaryRange,
TtxStat::SecondaryReloadTime,
TtxStat::SecondaryDispersion,
TtxStat::SecondaryAmmoSwitchTime,
TtxStat::SecondaryGunCaliber,
TtxStat::SecondaryGunNumBarrels,
TtxStat::SecondaryGunNumGuns,
TtxStat::SecondaryGunRotationSpeed,
TtxStat::SecondaryGunRotationTime,
TtxStat::SecondaryShellDamage,
TtxStat::SecondaryShellCaliber,
TtxStat::SecondaryShellSpeed,
TtxStat::SecondaryShellPenetration,
TtxStat::SecondaryShellBurnChance,
TtxStat::SecondaryShellFloodChance,
TtxStat::SecondaryShellMaxAmmo,
TtxStat::TorpedoReloadTime,
TtxStat::LauncherRotationSpeed,
TtxStat::LauncherRotationTime,
TtxStat::LauncherNumBarrels,
TtxStat::TorpedoDamage,
TtxStat::TorpedoSpeed,
TtxStat::TorpedoRange,
TtxStat::TorpedoVisibility,
TtxStat::TorpedoDistanceOfMaxDamage,
TtxStat::TorpedoIsDamageIncreasing,
TtxStat::TorpedoDisabledUnderwater,
TtxStat::FireControlMaxDist,
TtxStat::SeaDetection,
TtxStat::SeaDetectionOnFire,
TtxStat::AirDetection,
TtxStat::AirDetectionOnFire,
TtxStat::DetectionInSmoke,
TtxStat::SecondaryRangeDetection,
TtxStat::PeriscopeDepthDetection,
];
pub fn field_key(self) -> &'static str {
match self {
TtxStat::Health => "durability.health",
TtxStat::TorpedoProtection => "durability.torpedo_protection",
TtxStat::Speed => "mobility.speed",
TtxStat::TurningRadius => "mobility.turning_radius",
TtxStat::RudderTime => "mobility.rudder_time",
TtxStat::ArmorMin => "armor.min",
TtxStat::ArmorMax => "armor.max",
TtxStat::BatteryCapacity => "battery.capacity",
TtxStat::BatteryRegeneration => "battery.regeneration",
TtxStat::ArtilleryReloadTime => "artillery.reload_time",
TtxStat::ArtilleryRange => "artillery.range",
TtxStat::ArtilleryDispersion => "artillery.dispersion",
TtxStat::ArtilleryAmmoSwitchTime => "artillery.ammo_switch_time",
TtxStat::GunCaliber => "artillery.gun.caliber",
TtxStat::GunNumBarrels => "artillery.gun.num_barrels",
TtxStat::GunNumGuns => "artillery.gun.num_guns",
TtxStat::GunRotationSpeed => "artillery.gun.rotation_speed",
TtxStat::GunRotationTime => "artillery.gun.rotation_time",
TtxStat::ShellDamage => "artillery.shells.damage",
TtxStat::ShellCaliber => "artillery.shells.caliber",
TtxStat::ShellSpeed => "artillery.shells.speed",
TtxStat::ShellPenetration => "artillery.shells.penetration",
TtxStat::ShellBurnChance => "artillery.shells.burn_chance",
TtxStat::ShellFloodChance => "artillery.shells.flood_chance",
TtxStat::ShellMaxAmmo => "artillery.shells.max_ammo",
TtxStat::ShellDisabledUnderwater => "artillery.shells.disabled_underwater",
TtxStat::SecondaryRange => "secondary.range",
TtxStat::SecondaryReloadTime => "secondary.reload_time",
TtxStat::SecondaryDispersion => "secondary.dispersion",
TtxStat::SecondaryAmmoSwitchTime => "secondary.ammo_switch_time",
TtxStat::SecondaryGunCaliber => "secondary.gun.caliber",
TtxStat::SecondaryGunNumBarrels => "secondary.gun.num_barrels",
TtxStat::SecondaryGunNumGuns => "secondary.gun.num_guns",
TtxStat::SecondaryGunRotationSpeed => "secondary.gun.rotation_speed",
TtxStat::SecondaryGunRotationTime => "secondary.gun.rotation_time",
TtxStat::SecondaryShellDamage => "secondary.shells.damage",
TtxStat::SecondaryShellCaliber => "secondary.shells.caliber",
TtxStat::SecondaryShellSpeed => "secondary.shells.speed",
TtxStat::SecondaryShellPenetration => "secondary.shells.penetration",
TtxStat::SecondaryShellBurnChance => "secondary.shells.burn_chance",
TtxStat::SecondaryShellFloodChance => "secondary.shells.flood_chance",
TtxStat::SecondaryShellMaxAmmo => "secondary.shells.max_ammo",
TtxStat::TorpedoReloadTime => "torpedoes.reload_time",
TtxStat::LauncherRotationSpeed => "torpedoes.launchers.rotation_speed",
TtxStat::LauncherRotationTime => "torpedoes.launchers.rotation_time",
TtxStat::LauncherNumBarrels => "torpedoes.launchers.num_barrels",
TtxStat::TorpedoDamage => "torpedoes.torpedoes.damage",
TtxStat::TorpedoSpeed => "torpedoes.torpedoes.speed",
TtxStat::TorpedoRange => "torpedoes.torpedoes.range",
TtxStat::TorpedoVisibility => "torpedoes.torpedoes.visibility",
TtxStat::TorpedoDistanceOfMaxDamage => "torpedoes.torpedoes.distance_of_max_damage",
TtxStat::TorpedoIsDamageIncreasing => "torpedoes.torpedoes.is_damage_increasing",
TtxStat::TorpedoDisabledUnderwater => "torpedoes.torpedoes.disabled_underwater",
TtxStat::FireControlMaxDist => "fire_control.max_dist",
TtxStat::SeaDetection => "visibility.sea_detection",
TtxStat::SeaDetectionOnFire => "visibility.sea_detection_on_fire",
TtxStat::AirDetection => "visibility.air_detection",
TtxStat::AirDetectionOnFire => "visibility.air_detection_on_fire",
TtxStat::DetectionInSmoke => "visibility.detection_in_smoke",
TtxStat::SecondaryRangeDetection => "visibility.secondary_range_detection",
TtxStat::PeriscopeDepthDetection => "visibility.periscope_depth_detection",
}
}
pub fn label_key(self) -> Option<&'static str> {
Some(match self {
TtxStat::Health => "IDS_SHIP_PARAM_HEALTH",
TtxStat::TorpedoProtection => "IDS_SHIP_PARAM_PTZDAMAGEPROB",
TtxStat::Speed => "IDS_SHIP_PARAM_MAXSPEED",
TtxStat::TurningRadius => "IDS_SHIP_PARAM_TURNINGRADIUS",
TtxStat::RudderTime => "IDS_SHIP_PARAM_RUDDER_TIME",
TtxStat::BatteryCapacity => "IDS_SHIP_PARAM_BATTERY_MAX_CAPACITY",
TtxStat::BatteryRegeneration => "IDS_SHIP_PARAM_BATTERY_REGEN_RATE",
TtxStat::ArtilleryReloadTime => "IDS_SHIP_PARAM_ARTILLERY_TIME_RELOAD",
TtxStat::ArtilleryRange => "IDS_SHIP_PARAM_ARTILLERY_MAX_DIST",
TtxStat::ArtilleryDispersion => "IDS_SHIP_PARAM_DISPERSION",
TtxStat::ArtilleryAmmoSwitchTime => "IDS_SHIP_PARAM_ARTILLERY_MIN_SWITCH_TIME",
TtxStat::GunCaliber => "IDS_SHIP_PARAM_ARTILLERY_CALIBER",
TtxStat::GunNumGuns => "IDS_SHIP_PARAM_ARTILLERY_GUNS_COUNT",
TtxStat::GunRotationSpeed => "IDS_SHIP_PARAM_ARTILLERY_ROTATION_SPEED",
TtxStat::GunRotationTime => "IDS_SHIP_PARAM_ROTATION_TIME",
TtxStat::ShellDamage => "IDS_SHIP_PARAM_ARTILLERY_MAX_DAMAGE",
TtxStat::ShellCaliber => "IDS_SHIP_PARAM_ARTILLERY_CALIBER",
TtxStat::ShellSpeed => "IDS_SHIP_PARAM_ARTILLERY_AMMO_SPEED",
TtxStat::ShellPenetration => "IDS_SHIP_PARAM_ARTILLERY_ALPHA_PIERCING",
TtxStat::ShellBurnChance => "IDS_SHIP_PARAM_ARTILLERY_BURN_PROB",
TtxStat::ShellFloodChance => "IDS_SHIP_PARAM_ARTILLERY_FLOOD_GENERATION",
TtxStat::ShellMaxAmmo => "IDS_SHIP_PARAM_ARTILLERY_MAX_AMMO_COUNT",
TtxStat::SecondaryRange => "IDS_SHIP_PARAM_ATBA_MAX_DIST",
TtxStat::SecondaryGunNumGuns => "IDS_SHIP_PARAM_ATBA_GUNS_COUNT",
TtxStat::TorpedoReloadTime => "IDS_SHIP_PARAM_TORPEDOES_TIME_RELOAD",
TtxStat::LauncherRotationSpeed => "IDS_SHIP_PARAM_TORPEDOES_ROTATION_SPEED",
TtxStat::LauncherRotationTime => "IDS_SHIP_PARAM_ROTATION_TIME",
TtxStat::LauncherNumBarrels => "IDS_SHIP_PARAM_TORPEDOES_GUNS_COUNT",
TtxStat::TorpedoDamage => "IDS_SHIP_PARAM_TORPEDO_DAMAGE",
TtxStat::TorpedoSpeed => "IDS_SHIP_PARAM_TORPEDO_SPEED",
TtxStat::TorpedoRange => "IDS_SHIP_PARAM_TORPEDO_MAX_DIST",
TtxStat::TorpedoVisibility => "IDS_SHIP_PARAM_TORPEDO_VISIBILITY_DIST",
TtxStat::TorpedoDistanceOfMaxDamage => "IDS_SHIP_PARAM_TORPEDO_DISTANCE_OF_MAX_DAMAGE",
TtxStat::TorpedoDisabledUnderwater => "IDS_SHIP_PARAM_TORPEDO_DISABLED_UNDERWATER",
TtxStat::FireControlMaxDist => "IDS_SHIP_PARAM_MAXIMUM_DISTANCE",
TtxStat::SeaDetection => "IDS_SHIP_PARAM_VISIBILITY_DIST_BY_SHIP",
TtxStat::SeaDetectionOnFire => "IDS_SHIP_PARAM_VISIBILITY_DIST_BY_FIRE",
TtxStat::AirDetection => "IDS_SHIP_PARAM_VISIBILITY_DIST_BY_PLANE",
TtxStat::SecondaryRangeDetection => "IDS_SHIP_PARAM_VISIBILITY_DIST_BY_ATBA",
TtxStat::PeriscopeDepthDetection => "IDS_SHIP_PARAM_VISIBILITY_DIST_BY_DEPTH_PERISCOPE",
TtxStat::ArmorMin | TtxStat::ArmorMax => return None,
TtxStat::GunNumBarrels => return None,
TtxStat::ShellDisabledUnderwater => return None,
TtxStat::TorpedoIsDamageIncreasing => return None,
TtxStat::AirDetectionOnFire => return None,
TtxStat::DetectionInSmoke => return None,
TtxStat::SecondaryReloadTime
| TtxStat::SecondaryDispersion
| TtxStat::SecondaryAmmoSwitchTime
| TtxStat::SecondaryGunCaliber
| TtxStat::SecondaryGunNumBarrels
| TtxStat::SecondaryGunRotationSpeed
| TtxStat::SecondaryGunRotationTime
| TtxStat::SecondaryShellDamage
| TtxStat::SecondaryShellCaliber
| TtxStat::SecondaryShellSpeed
| TtxStat::SecondaryShellPenetration
| TtxStat::SecondaryShellBurnChance
| TtxStat::SecondaryShellFloodChance
| TtxStat::SecondaryShellMaxAmmo => return None,
})
}
}
pub fn stat_label(stat: TtxStat, resource_loader: &dyn ResourceLoader) -> Option<String> {
let key = stat.label_key()?;
resource_loader.localized_name_from_id(key)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::game_params::types::Param;
use crate::game_types::GameParamId;
use crate::rpc::entitydefs::EntitySpec;
struct EchoLoader;
impl ResourceLoader for EchoLoader {
fn localized_name_from_param(&self, _p: &Param) -> Option<String> {
None
}
fn localized_name_from_id(&self, id: &str) -> Option<String> {
Some(id.to_string())
}
fn game_param_by_id(&self, _id: GameParamId) -> Option<crate::Rc<Param>> {
None
}
fn entity_specs(&self) -> &[EntitySpec] {
&[]
}
}
#[test]
fn known_key_resolves_through_loader() {
let label = stat_label(TtxStat::Health, &EchoLoader);
assert_eq!(label.as_deref(), Some("IDS_SHIP_PARAM_HEALTH"));
}
#[test]
fn secondary_range_resolves_to_atba_key() {
let label = stat_label(TtxStat::SecondaryRange, &EchoLoader);
assert_eq!(label.as_deref(), Some("IDS_SHIP_PARAM_ATBA_MAX_DIST"));
}
#[test]
fn secondary_gun_count_resolves_to_atba_key() {
let label = stat_label(TtxStat::SecondaryGunNumGuns, &EchoLoader);
assert_eq!(label.as_deref(), Some("IDS_SHIP_PARAM_ATBA_GUNS_COUNT"));
}
#[test]
fn secondary_without_atba_key_returns_none() {
assert!(TtxStat::SecondaryReloadTime.label_key().is_none());
assert!(TtxStat::SecondaryShellDamage.label_key().is_none());
assert!(stat_label(TtxStat::SecondaryReloadTime, &EchoLoader).is_none());
}
#[test]
fn stat_without_key_returns_none() {
assert!(TtxStat::ArmorMin.label_key().is_none());
assert!(stat_label(TtxStat::ArmorMin, &EchoLoader).is_none());
}
#[test]
fn all_keys_use_ship_param_namespace() {
for &stat in TtxStat::ALL {
if let Some(key) = stat.label_key() {
assert!(key.starts_with("IDS_SHIP_PARAM_"), "{stat:?} -> {key}");
}
}
}
#[test]
fn field_keys_are_unique() {
let mut keys: Vec<&str> = TtxStat::ALL.iter().map(|s| s.field_key()).collect();
keys.sort_unstable();
let before = keys.len();
keys.dedup();
assert_eq!(before, keys.len(), "duplicate field_key");
}
#[test]
fn unmatched_stats_are_exactly_the_known_gap() {
let mut unmatched: Vec<TtxStat> = TtxStat::ALL.iter().copied().filter(|s| s.label_key().is_none()).collect();
unmatched.sort_by_key(|s| s.field_key());
let mut expected = vec![
TtxStat::ArmorMin,
TtxStat::ArmorMax,
TtxStat::GunNumBarrels,
TtxStat::ShellDisabledUnderwater,
TtxStat::TorpedoIsDamageIncreasing,
TtxStat::AirDetectionOnFire,
TtxStat::DetectionInSmoke,
TtxStat::SecondaryReloadTime,
TtxStat::SecondaryDispersion,
TtxStat::SecondaryAmmoSwitchTime,
TtxStat::SecondaryGunCaliber,
TtxStat::SecondaryGunNumBarrels,
TtxStat::SecondaryGunRotationSpeed,
TtxStat::SecondaryGunRotationTime,
TtxStat::SecondaryShellDamage,
TtxStat::SecondaryShellCaliber,
TtxStat::SecondaryShellSpeed,
TtxStat::SecondaryShellPenetration,
TtxStat::SecondaryShellBurnChance,
TtxStat::SecondaryShellFloodChance,
TtxStat::SecondaryShellMaxAmmo,
];
expected.sort_by_key(|s| s.field_key());
assert_eq!(unmatched, expected);
}
}