const ARMOR_TYPE_PREFIXES: &[&[&str]] = &[
&["AuTurret", "Turret", "Tur", "SGBarbetteSS", "SGDownSS", "SS_SGBarbette", "SS_SGDown"],
&["Cit", "SideCit", "DeckCit", "TransCit", "InclinCit"],
&[],
&[],
&["Cas", "SideCas", "DeckCas", "TransCas", "InclinCas"],
&["SSC", "SideSSC", "DeckSSC", "TransSSC", "InclinSSC"],
&["SS", "SideSS", "DeckSS", "TransSS", "InclinSS"],
&["Bulge", "Belt", "Bottom", "OCit"],
&[],
&[
"Bow",
"SideBow",
"DeckBow",
"TransBow",
"InclinBow",
"St",
"SideStern",
"DeckStern",
"TransStern",
"InclinStern",
],
&[],
];
pub fn armor_type_classifies(name: &str) -> bool {
let is_dual = name.starts_with("Dual");
let dual_tokens: Vec<&str> = if is_dual { name.split('_').skip(1).take(2).collect() } else { Vec::new() };
for prefixes in ARMOR_TYPE_PREFIXES {
for prefix in *prefixes {
if name.starts_with(prefix) {
return true;
}
if is_dual && dual_tokens.iter().any(|t| t == prefix) {
return true;
}
}
}
false
}
pub const COLLISION_MATERIAL_NAMES: &[&str] = &[
"common", "zero", "Dual_SSC_Bow_Side", "Dual_SSC_St_Side", "Dual_Cas_OCit_Belt", "Dual_OCit_St_Trans", "Dual_OCit_Bow_Trans", "Dual_Cit_Bow_Side", "Dual_Cit_Bow_Belt", "Dual_Cit_Bow_ArtSide", "Dual_Cit_St_Side", "Dual_Cit_St_Belt", "Bottom", "Dual_Cit_St_ArtSide", "Dual_Cas_Bow_Belt", "Dual_Cas_St_Belt", "Dual_Cas_SSC_Belt", "Dual_SSC_Bow_ConstrSide", "Dual_SSC_St_ConstrSide", "Cas_Inclin", "SSC_Inclin", "Dual_Cas_SSC_Inclin", "Dual_Cas_Bow_Inclin", "Dual_Cas_St_Inclin", "Dual_SSC_Bow_Inclin", "Dual_SSC_St_Inclin", "Dual_Cit_Bow_Bulge", "Dual_Cit_St_Bulge", "Dual_Cas_SS_Belt", "Dual_Cit_Cas_ArtDeck", "Dual_Cit_Cas_ArtSide", "Dual_OCit_OCit_Side", "TurretSide", "TurretTop", "TurretFront", "TurretAft", "FunnelSide", "ArtBottom", "ArtSide", "ArtTop", "AuTurretAft", "AuTurretBarbette", "AuTurretDown", "AuTurretFwd", "AuTurretSide", "AuTurretTop", "Bow_Belt", "Bow_Bottom", "Bow_ConstrSide", "Bow_Deck", "Bow_Inclin", "Bow_Trans", "BridgeBottom", "BridgeSide", "BridgeTop", "Cas_AftTrans", "Cas_Belt", "Cas_Deck", "Cas_FwdTrans", "Cit_AftTrans", "Cit_Barbette", "Cit_Belt", "Cit_Bottom", "Cit_Bulge", "Cit_Deck", "Cit_FwdTrans", "Cit_Inclin", "Cit_Side", "Dual_Cit_Cas_Bulge", "ConstrSide", "Dual_Cit_Cas_Belt", "Bow_Fdck", "St_Fdck", "KdpBottom", "KdpSide", "KdpTop", "OCit_AftTrans", "OCit_Belt", "OCit_Deck", "OCit_FwdTrans", "RudderAft", "RudderFwd", "RudderSide", "RudderTop", "SSC_AftTrans", "SSCasemate", "SSC_ConstrSide", "SSC_Deck", "SSC_FwdTrans", "SS_Side", "SS_Top", "St_Belt", "St_Bottom", "St_ConstrSide", "St_Deck", "St_Inclin", "St_Trans", "TurretBarbette", "TurretBarbette2", "TurretDown", "TurretFwd", "Bulge", "Trans", "Deck", "Belt", "Dual_Cit_SSC_Bulge", "Inclin", "SS_BridgeTop", "SS_BridgeSide", "SS_BridgeBottom", "Cas_Bottom", "SideCit", "DeckCit", "TransCit", "InclinCit", "SideCas", "DeckCas", "TransCas", "InclinCas", "SideSSC", "DeckSSC", "TransSSC", "InclinSSC", "SideBow", "DeckBow", "TransBow", "InclinBow", "SideStern", "DeckStern", "TransStern", "InclinStern", "SideSS", "DeckSS", "TransSS", "Tur1GkBar", "Tur2GkBar", "Tur3GkBar", "Tur4GkBar", "Tur5GkBar", "Tur6GkBar", "Tur7GkBar", "Tur8GkBar", "Tur9GkBar", "Tur10GkBar", "Tur11GkBar", "Tur12GkBar", "Tur13GkBar", "Tur14GkBar", "Tur15GkBar", "Tur16GkBar", "Tur17GkBar", "Tur18GkBar", "Tur19GkBar", "Tur20GkBar", "Dual_Cas_Bow_Trans", "Dual_Cas_Bow_Deck", "Dual_Cas_St_Trans", "Dual_Cas_St_Deck", "Dual_Cas_SSC_Deck", "Dual_Cas_SSC_Trans", "Dual_Cas_SS_Deck", "Dual_Cas_SS_Trans", "Dual_SSC_Bow_Trans", "Dual_SSC_Bow_Deck", "Dual_SSC_St_Trans", "Dual_SSC_St_Deck", "Dual_SSC_SS_Deck", "Dual_SSC_SS_Trans", "Dual_Bow_SS_Deck", "Dual_Bow_SS_Trans", "Dual_St_SS_Deck", "Dual_St_SS_Trans", "Dual_Cit_Bow_Bottom", "Dual_Cit_St_Bottom", "Tur1GkDown", "Tur2GkDown", "Tur3GkDown", "Tur4GkDown", "Tur5GkDown", "Tur6GkDown", "Tur7GkDown", "Tur8GkDown", "Tur9GkDown", "Tur10GkDown", "Tur11GkDown", "Tur12GkDown", "Tur13GkDown", "Tur14GkDown", "Tur15GkDown", "Tur16GkDown", "Tur17GkDown", "Tur18GkDown", "Tur19GkDown", "Tur20GkDown", "Dual_Cit_Cit_Deck", "Dual_Cit_Cit_Inclin", "Dual_Cit_Cit_Trans", "Dual_Cit_Cit_Side", "Dual_Cas_Cas_Belt", "Dual_Cas_Cas_Deck", "Dual_SSC_SSC_ConstrSide", "Dual_SSC_SSC_Deck", "Dual_Bow_Bow_Deck", "Dual_Bow_Bow_ConstrSide", "Dual_St_St_Deck", "Dual_St_St_ConstrSide", "Dual_SS_SS_Top", "Dual_SS_SS_Side", "Dual_Cit_Bow_ArtDeck", "Dual_Cit_St_ArtDeck", "Dual_Cas_Bow_Side", "Dual_Cas_St_Side", "Dual_Cit_Cas_Side", "Dual_Cit_SSC_Side", "Tur1GkTop", "Tur2GkTop", "Tur3GkTop", "Tur4GkTop", "Tur5GkTop", "Tur6GkTop", "Tur7GkTop", "Tur8GkTop", "Tur9GkTop", "Tur10GkTop", "Tur11GkTop", "Tur12GkTop", "Tur13GkTop", "Tur14GkTop", "Tur15GkTop", "Tur16GkTop", "Tur17GkTop", "Tur18GkTop", "Tur19GkTop", "Tur20GkTop", "Cas_Hang", "Cas_Fdck", "SSC_Fdck", "SSC_Hang", "SS_SGBarbette", "SS_SGDown", "SGBarbetteSS", "SGDownSS", "Dual_Cit_Cas_Deck", "Dual_Cit_Cas_Inclin", "Dual_Cit_Cas_Trans", "Dual_Cit_SSC_Deck", "Dual_Cit_SSC_Inclin", "Dual_Cit_SSC_Trans", "Dual_Cit_Bow_Trans", "Dual_Cit_Bow_Inclin", "Dual_Cit_Bow_Deck", "Dual_Cit_St_Trans", "Dual_Cit_St_Inclin", "Dual_Cit_St_Deck", "Dual_Cit_SS_Deck", ];
pub fn collision_material_name(id: u8) -> &'static str {
use std::sync::Mutex;
static WARNED: Mutex<[bool; 256]> = Mutex::new([false; 256]);
let idx = id as usize;
if idx < COLLISION_MATERIAL_NAMES.len() {
COLLISION_MATERIAL_NAMES[idx]
} else {
let mut warned = WARNED.lock().unwrap();
if !warned[idx] {
warned[idx] = true;
eprintln!(
"BUG: collision material ID {id} is beyond the known table (max {}). \
The game's material table has likely been updated.",
COLLISION_MATERIAL_NAMES.len() - 1
);
}
"unknown"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classifies_known_armor_plates() {
assert!(armor_type_classifies("Cit_Belt"));
assert!(armor_type_classifies("Tur1GkBar"));
assert!(armor_type_classifies("TurretFwd"));
assert!(armor_type_classifies("Belt"));
assert!(armor_type_classifies("SS_Side"));
}
#[test]
fn excludes_unclassified_plates() {
assert!(!armor_type_classifies("RudderSide"));
assert!(!armor_type_classifies("BridgeSide"));
assert!(!armor_type_classifies("KdpSide"));
assert!(!armor_type_classifies("FunnelSide"));
assert!(!armor_type_classifies("common"));
assert!(!armor_type_classifies("zero"));
}
#[test]
fn classifies_dual_via_split_tokens() {
assert!(armor_type_classifies("Dual_Cit_Cas_Belt"));
assert!(armor_type_classifies("Dual_OCit_OCit_Side"));
}
#[test]
fn table_covers_full_id_range() {
assert_eq!(COLLISION_MATERIAL_NAMES.len(), 255);
assert_eq!(collision_material_name(61), "Cit_Belt");
assert_eq!(collision_material_name(134), "Tur1GkBar");
assert_eq!(collision_material_name(100), "TurretFwd");
}
}