diff --git a/crates/helm-bridge/src/lib.rs b/crates/helm-bridge/src/lib.rs index 28452ac..6225def 100644 --- a/crates/helm-bridge/src/lib.rs +++ b/crates/helm-bridge/src/lib.rs @@ -189,6 +189,9 @@ pub const CATALOGUE_FIELDS: &[&str] = &[ "kgPerShot", "shots", "damageShort", + // An infantry armour kit's, which is what a platoon has instead of + // armour points. + "damageDivisor", "explosive", "heat", "longRange", @@ -393,6 +396,9 @@ fn equipment_record(v: &Value) -> Option { variable_battle_value: f(v, "bv").is_some_and(|b| b <= i32::MIN as f64), cost: f(v, "cost"), damage_capacity: i(v, "baseDamageCapacity").filter(|c| *c > 0), + // Only an infantry armour kit has one, and MegaMek writes 1.0 on the + // rest of MiscType rather than leaving it out. + damage_divisor: f(v, "damageDivisor"), flags: v .get("_flags") .and_then(Value::as_array) diff --git a/crates/helm-core/src/bv.rs b/crates/helm-core/src/bv.rs index d8725d1..9598b91 100644 --- a/crates/helm-core/src/bv.rs +++ b/crates/helm-core/src/bv.rs @@ -57,6 +57,38 @@ impl BvBreakdown { } } +/// MegaMek's `bvMultipliers`, indexed `[gunnery][piloting]` and clamped to +/// 0..=8 at both ends the way `BVCalculator.bvSkillMultiplier` clamps. +/// +/// Transcribed rather than derived: there is no formula behind it, it is a +/// published table, and a plausible-looking curve fitted to it would be wrong +/// in the corners. +/// +/// It lives here rather than beside the crew that usually reads it because a +/// conventional infantry platoon needs it too: a platoon that can climb a Mek +/// without the gear for it is scored as untrained at it, and that is a +/// property of the design rather than of anybody flying it. +#[rustfmt::skip] +const BV_MULTIPLIERS: [[f64; 9]; 9] = [ + [2.42, 2.31, 2.21, 2.10, 1.93, 1.75, 1.68, 1.59, 1.50], + [2.21, 2.11, 2.02, 1.92, 1.76, 1.60, 1.54, 1.46, 1.38], + [1.93, 1.85, 1.76, 1.68, 1.54, 1.40, 1.35, 1.28, 1.21], + [1.66, 1.58, 1.51, 1.44, 1.32, 1.20, 1.16, 1.10, 1.04], + [1.38, 1.32, 1.26, 1.20, 1.10, 1.00, 0.95, 0.90, 0.85], + [1.31, 1.19, 1.13, 1.08, 0.99, 0.90, 0.86, 0.81, 0.77], + [1.24, 1.12, 1.07, 1.02, 0.94, 0.85, 0.81, 0.77, 0.72], + [1.17, 1.06, 1.01, 0.96, 0.88, 0.80, 0.76, 0.72, 0.68], + [1.10, 0.99, 0.95, 0.90, 0.83, 0.75, 0.71, 0.68, 0.64], +]; + +/// The battle value multiplier for a gunnery and piloting pair, clamped like +/// MegaMek's. +pub fn bv_skill_multiplier(gunnery: u8, piloting: u8) -> f64 { + let g = (gunnery as usize).min(8); + let p = (piloting as usize).min(8); + BV_MULTIPLIERS[g][p] +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/helm-core/src/catalogue.rs b/crates/helm-core/src/catalogue.rs index d790270..445c4cf 100644 --- a/crates/helm-core/src/catalogue.rs +++ b/crates/helm-core/src/catalogue.rs @@ -97,6 +97,12 @@ pub struct EquipmentEntry { /// Armour points this item carries in its own right. Modular armour is /// the case that matters: it is armour bolted on, and it counts as armour. pub damage_capacity: Option, + /// What an infantry armour kit divides incoming damage by. + /// + /// A platoon's protection is not armour points but this: a kit rated 2 + /// halves what reaches the troopers, and battle value charges for it + /// directly. Only armour kits carry one; everything else is `None`. + pub damage_divisor: Option, /// Whether this item's battle value depends on the design carrying it. /// /// A hatchet is worth a fifth of the Mek swinging it, so the catalogue diff --git a/crates/helm-core/src/lib.rs b/crates/helm-core/src/lib.rs index 3093338..1c54cd3 100644 --- a/crates/helm-core/src/lib.rs +++ b/crates/helm-core/src/lib.rs @@ -32,7 +32,7 @@ mod rng; mod structure; mod unit; -pub use bv::{BvBreakdown, RATING_TOLERANCE}; +pub use bv::{BvBreakdown, RATING_TOLERANCE, bv_skill_multiplier}; pub use catalogue::{Catalogue, EquipmentEntry}; pub use computed::{AlphaStrike, ComputedStats}; pub use design::{ diff --git a/crates/helm-force/src/pilot.rs b/crates/helm-force/src/pilot.rs index cf3d4dd..1cab03e 100644 --- a/crates/helm-force/src/pilot.rs +++ b/crates/helm-force/src/pilot.rs @@ -47,30 +47,12 @@ impl Pilot { } } -/// MegaMek's `bvMultipliers`, indexed `[gunnery][piloting]` and clamped to -/// 0..=8 at both ends the way `BVCalculator.bvSkillMultiplier` clamps. -/// -/// Transcribed rather than derived: there is no formula behind it, it is a -/// published table, and a plausible-looking curve fitted to it would be wrong -/// in the corners. -#[rustfmt::skip] -const BV_MULTIPLIERS: [[f64; 9]; 9] = [ - [2.42, 2.31, 2.21, 2.10, 1.93, 1.75, 1.68, 1.59, 1.50], - [2.21, 2.11, 2.02, 1.92, 1.76, 1.60, 1.54, 1.46, 1.38], - [1.93, 1.85, 1.76, 1.68, 1.54, 1.40, 1.35, 1.28, 1.21], - [1.66, 1.58, 1.51, 1.44, 1.32, 1.20, 1.16, 1.10, 1.04], - [1.38, 1.32, 1.26, 1.20, 1.10, 1.00, 0.95, 0.90, 0.85], - [1.31, 1.19, 1.13, 1.08, 0.99, 0.90, 0.86, 0.81, 0.77], - [1.24, 1.12, 1.07, 1.02, 0.94, 0.85, 0.81, 0.77, 0.72], - [1.17, 1.06, 1.01, 0.96, 0.88, 0.80, 0.76, 0.72, 0.68], - [1.10, 0.99, 0.95, 0.90, 0.83, 0.75, 0.71, 0.68, 0.64], -]; - /// The battle value multiplier for a crew, clamped like MegaMek's. +/// +/// The table itself is `helm_core`'s, because battle value needs it too: an +/// infantry platoon's own figure depends on a skill the design fixes. pub fn bv_skill_multiplier(gunnery: u8, piloting: u8) -> f64 { - let g = (gunnery as usize).min(8); - let p = (piloting as usize).min(8); - BV_MULTIPLIERS[g][p] + helm_core::bv_skill_multiplier(gunnery, piloting) } /// A design's battle value as flown by this crew.