diff --git a/crates/helm-bv/src/wear.rs b/crates/helm-bv/src/wear.rs index bbffa2e..ace46ff 100644 --- a/crates/helm-bv/src/wear.rs +++ b/crates/helm-bv/src/wear.rs @@ -198,11 +198,55 @@ pub(crate) fn polish( patch(unit, from, target, best, &mut consider) } +/// Which locations have been shot through, by the code the armour is keyed by. +/// +/// Damage only reaches the frame through plate that is gone, so a location +/// with a bent frame or a wrecked component is one whose armour was stripped +/// to nothing on the side the fire came from. Putting plate back on it would +/// describe a machine damaged through armour that was never breached - and +/// that is a file MegaMek will read back as exactly that. +fn breached(unit: &Unit, condition: &Condition) -> std::collections::BTreeSet { + let shape = helm_unitfile::locations_for_config(unit.config.as_deref()); + let built = unit.mass.and_then(|tons| { + helm_core::structure_per_location( + tons, + helm_core::Shape::from_config(unit.config.as_deref()), + ) + }); + let mut out = std::collections::BTreeSet::new(); + for (code, name) in shape { + let bent = built + .as_ref() + .and_then(|frame| frame.iter().find(|(l, _)| l == code)) + .zip(condition.structure.get(*code)) + .is_some_and(|((_, whole), left)| left < whole); + let wrecked = condition + .destroyed + .get(*name) + .is_some_and(|s| !s.is_empty()); + if bent || wrecked { + out.insert((*code).to_string()); + } + } + out +} + +/// The location an armour key belongs to. Rear plate is part of its torso. +fn location_of(key: &str) -> &str { + match key { + "RTC" => "CT", + "RTL" => "LT", + "RTR" => "RT", + other => other, + } +} + /// Put armour back a point at a time, until the figure comes up to the target. /// /// Onto the least damaged location first, which buffs out the scratches and /// leaves the holes: a machine that took a leg full of autocannon should still -/// look like it afterwards. +/// look like it afterwards. Never onto a location that was shot through - see +/// [`breached`]. fn patch( unit: &Unit, from: Condition, @@ -210,6 +254,7 @@ fn patch( best: &mut Worn, mut consider: impl FnMut(Condition, &mut Worn) -> Result, ) -> Result<(), Unsupported> { + let shot_through = breached(unit, &from); let mut condition = from; for _ in 0..REFINEMENTS { if best.is_exact() { @@ -219,9 +264,11 @@ fn patch( .armor .iter() .filter(|(location, points)| { - unit.armor_locations - .get(*location) - .is_some_and(|built| points < &built) + !shot_through.contains(location_of(location)) + && unit + .armor_locations + .get(*location) + .is_some_and(|built| points < &built) }) .min_by(|a, b| { let missing = |(location, points): (&String, &i64)| {