use crate::data::ResourceLoader;
use crate::data::TranslationKey;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TtxStat {
Health,
TorpedoProtection,
Speed,
TurningRadius,
RudderTime,
ArmorMin,
ArmorMax,
BatteryCapacity,
BatteryRegeneration,
ArtilleryReloadTime,
ArtilleryRange,
ArtilleryDispersion,
ArtilleryDispersionVertical,
ArtilleryAmmoSwitchTime,
GunCaliber,
GunNumBarrels,
GunNumGuns,
GunRotationSpeed,
GunRotationTime,
ShellDamage,
ShellCaliber,
ShellSpeed,
ShellPenetration,
ShellBurnChance,
ShellFloodChance,
ShellMaxAmmo,
ShellDisabledUnderwater,
SecondaryRange,
SecondaryReloadTime,
SecondaryDispersion,
SecondaryDispersionVertical,
SecondaryGunCaliber,
SecondaryGunNumBarrels,
SecondaryGunNumGuns,
SecondaryGunRotationSpeed,
SecondaryGunRotationTime,
SecondaryShellDamage,
SecondaryShellCaliber,
SecondaryShellSpeed,
SecondaryShellPenetration,
SecondaryShellBurnChance,
SecondaryShellFloodChance,
SecondaryShellMaxAmmo,
TorpedoReloadTime,
LauncherRotationSpeed,
LauncherRotationTime,
LauncherNumBarrels,
TorpedoDamage,
TorpedoSpeed,
TorpedoRange,
TorpedoVisibility,
TorpedoDistanceOfMaxDamage,
TorpedoIsDamageIncreasing,
TorpedoDisabledUnderwater,
FireControlMaxDist,
ConsumableReloadTime,
ConsumableWorkTime,
ConsumableCharges,
ConsumablePreparationTime,
ConsumableMaxCapacity,
ConsumableDetectionRadius,
ConsumableRegenerationHpSpeed,
ConsumableSmokeRadius,
ConsumableSmokeLifetime,
ConsumableFightersCount,
ConsumableCallFightersRadius,
ConsumableCallFightersTimeDelay,
ConsumableCallFightersTimeFromHeaven,
ConsumablePlaneRegenerationRate,
SeaDetection,
SeaDetectionOnFire,
AirDetection,
AirDetectionOnFire,
DetectionInSmoke,
MainGunRangeDetection,
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::ArtilleryDispersionVertical,
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::SecondaryDispersionVertical,
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::ConsumableReloadTime,
TtxStat::ConsumableWorkTime,
TtxStat::ConsumableCharges,
TtxStat::ConsumablePreparationTime,
TtxStat::ConsumableMaxCapacity,
TtxStat::ConsumableDetectionRadius,
TtxStat::ConsumableRegenerationHpSpeed,
TtxStat::ConsumableSmokeRadius,
TtxStat::ConsumableSmokeLifetime,
TtxStat::ConsumableFightersCount,
TtxStat::ConsumableCallFightersRadius,
TtxStat::ConsumableCallFightersTimeDelay,
TtxStat::ConsumableCallFightersTimeFromHeaven,
TtxStat::ConsumablePlaneRegenerationRate,
TtxStat::SeaDetection,
TtxStat::SeaDetectionOnFire,
TtxStat::AirDetection,
TtxStat::AirDetectionOnFire,
TtxStat::DetectionInSmoke,
TtxStat::MainGunRangeDetection,
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::ArtilleryDispersionVertical => "artillery.dispersion_vertical",
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::SecondaryDispersionVertical => "secondary.dispersion_vertical",
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::ConsumableReloadTime => "consumable.reload_time",
TtxStat::ConsumableWorkTime => "consumable.work_time",
TtxStat::ConsumableCharges => "consumable.charges",
TtxStat::ConsumablePreparationTime => "consumable.preparation_time",
TtxStat::ConsumableMaxCapacity => "consumable.max_capacity",
TtxStat::ConsumableDetectionRadius => "consumable.detection_radius",
TtxStat::ConsumableRegenerationHpSpeed => "consumable.regeneration_hp_speed",
TtxStat::ConsumableSmokeRadius => "consumable.smoke_radius",
TtxStat::ConsumableSmokeLifetime => "consumable.smoke_lifetime",
TtxStat::ConsumableFightersCount => "consumable.fighters_count",
TtxStat::ConsumableCallFightersRadius => "consumable.call_fighters_radius",
TtxStat::ConsumableCallFightersTimeDelay => "consumable.call_fighters_time_delay",
TtxStat::ConsumableCallFightersTimeFromHeaven => "consumable.call_fighters_time_from_heaven",
TtxStat::ConsumablePlaneRegenerationRate => "consumable.plane_regeneration_rate",
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::MainGunRangeDetection => "visibility.main_gun_range_detection",
TtxStat::SecondaryRangeDetection => "visibility.secondary_range_detection",
TtxStat::PeriscopeDepthDetection => "visibility.periscope_depth_detection",
}
}
pub fn fallback_label(self) -> String {
self.field_key()
.split(['.', '_'])
.map(|word| {
let mut chars = word.chars();
match chars.next() {
Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
None => String::new(),
}
})
.collect::<Vec<_>>()
.join(" ")
}
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::MainGunRangeDetection => "IDS_SHIP_PARAM_VISIBILITY_DIST_BY_GK",
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::ArtilleryDispersionVertical => 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::SecondaryDispersionVertical
| TtxStat::SecondaryGunCaliber
| TtxStat::SecondaryGunNumBarrels
| TtxStat::SecondaryGunRotationSpeed
| TtxStat::SecondaryGunRotationTime
| TtxStat::SecondaryShellDamage
| TtxStat::SecondaryShellCaliber
| TtxStat::SecondaryShellSpeed
| TtxStat::SecondaryShellPenetration
| TtxStat::SecondaryShellBurnChance
| TtxStat::SecondaryShellFloodChance
| TtxStat::SecondaryShellMaxAmmo => return None,
TtxStat::ConsumableReloadTime
| TtxStat::ConsumableWorkTime
| TtxStat::ConsumableCharges
| TtxStat::ConsumablePreparationTime
| TtxStat::ConsumableMaxCapacity
| TtxStat::ConsumableDetectionRadius
| TtxStat::ConsumableRegenerationHpSpeed
| TtxStat::ConsumableSmokeRadius
| TtxStat::ConsumableSmokeLifetime
| TtxStat::ConsumableFightersCount
| TtxStat::ConsumableCallFightersRadius
| TtxStat::ConsumableCallFightersTimeDelay
| TtxStat::ConsumableCallFightersTimeFromHeaven
| TtxStat::ConsumablePlaneRegenerationRate => 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(&TranslationKey::new(key))
}
pub fn stat_display_label(stat: TtxStat, loader: &dyn ResourceLoader) -> String {
stat_label(stat, loader).unwrap_or_else(|| stat.fallback_label())
}
#[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: &crate::data::TranslationKey) -> Option<String> {
Some(id.as_str().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 display_label_falls_back_to_humanized_field_key() {
assert_eq!(TtxStat::ArtilleryDispersionVertical.fallback_label(), "Artillery Dispersion Vertical");
assert_eq!(TtxStat::SecondaryDispersionVertical.fallback_label(), "Secondary Dispersion Vertical");
}
#[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::ArtilleryDispersionVertical,
TtxStat::ShellDisabledUnderwater,
TtxStat::TorpedoIsDamageIncreasing,
TtxStat::AirDetectionOnFire,
TtxStat::DetectionInSmoke,
TtxStat::SecondaryReloadTime,
TtxStat::SecondaryDispersion,
TtxStat::SecondaryDispersionVertical,
TtxStat::SecondaryGunCaliber,
TtxStat::SecondaryGunNumBarrels,
TtxStat::SecondaryGunRotationSpeed,
TtxStat::SecondaryGunRotationTime,
TtxStat::SecondaryShellDamage,
TtxStat::SecondaryShellCaliber,
TtxStat::SecondaryShellSpeed,
TtxStat::SecondaryShellPenetration,
TtxStat::SecondaryShellBurnChance,
TtxStat::SecondaryShellFloodChance,
TtxStat::SecondaryShellMaxAmmo,
TtxStat::ConsumableReloadTime,
TtxStat::ConsumableWorkTime,
TtxStat::ConsumableCharges,
TtxStat::ConsumablePreparationTime,
TtxStat::ConsumableMaxCapacity,
TtxStat::ConsumableDetectionRadius,
TtxStat::ConsumableRegenerationHpSpeed,
TtxStat::ConsumableSmokeRadius,
TtxStat::ConsumableSmokeLifetime,
TtxStat::ConsumableFightersCount,
TtxStat::ConsumableCallFightersRadius,
TtxStat::ConsumableCallFightersTimeDelay,
TtxStat::ConsumableCallFightersTimeFromHeaven,
TtxStat::ConsumablePlaneRegenerationRate,
];
expected.sort_by_key(|s| s.field_key());
assert_eq!(unmatched, expected);
}
}