From b5d37721c4bddc82129e411d67503938433b4acf Mon Sep 17 00:00:00 2001 From: "@permadeath.com" Date: Wed, 19 Aug 2026 19:43:53 -0400 Subject: [PATCH] feat(unit-rules): helm wear Prints what was taken off and what the machine came to, as a repair list read the other way round. --- README.md | 5 +++ crates/helm-cli/src/main.rs | 75 +++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/README.md b/README.md index 9b14152..001e016 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,11 @@ equipment shot out and the magazines to fill, location by location: helm repairs --megamek --bridge-dir --why +The other direction, for fitting a machine that has already been in a fight +into a scenario's budget - damage a design until it is worth a given figure: + + helm wear "Atlas AS7-D" --to 1500 --megamek --bridge-dir + `--clusters` is the one that matters. A ranked list of the worst disagreements shows whichever design is strangest; grouping them by what they have in common shows which *rule* is missing, and its second section ranks the failures by how diff --git a/crates/helm-cli/src/main.rs b/crates/helm-cli/src/main.rs index ae63a2f..edabf4f 100644 --- a/crates/helm-cli/src/main.rs +++ b/crates/helm-cli/src/main.rs @@ -25,6 +25,7 @@ fn main() -> ExitCode { Some("force") => force(&args[1..]), Some("check") => check(&args[1..]), Some("repairs") => repairs(&args[1..]), + Some("wear") => wear(&args[1..]), Some("-h") | Some("--help") | Some("help") | None => { usage(); return ExitCode::SUCCESS; @@ -52,6 +53,7 @@ USAGE helm force --megamek --bridge-dir helm check --megamek --bridge-dir helm repairs --megamek --bridge-dir + helm wear --to --megamek --bridge-dir BUILD --megamek A MegaMek install. Reads data/mekfiles/unit_files.zip, @@ -88,6 +90,12 @@ CHECK more plate or frame than a design was built with. Exits non-zero when a file has something wrong with it. +WEAR + Damage a design until it is worth a given battle value, for fitting a + machine that has already been in a fight into a scenario's budget. Plate + first, then the frame under it, spread evenly across the locations. + --to The figure to aim for. Required. + REPAIRS What a force needs putting right, location by location: points of plate, points of frame, the equipment shot out and the magazines to fill. @@ -114,6 +122,7 @@ struct Opts { rules: bool, bench: bool, why: bool, + to: Option, } fn parse_opts(args: &[String]) -> Result { @@ -128,6 +137,7 @@ fn parse_opts(args: &[String]) -> Result { rules: false, bench: false, why: false, + to: None, }; let mut i = 0; while i < args.len() { @@ -170,6 +180,14 @@ fn parse_opts(args: &[String]) -> Result { o.bench = true; i += 1; } + "--to" => { + o.to = Some( + need(i)? + .parse() + .map_err(|_| "--to takes a battle value".to_string())?, + ); + i += 2; + } "--why" => { o.why = true; i += 1; @@ -597,6 +615,63 @@ fn check(args: &[String]) -> Result<(), String> { )) } +/// Wear a design down to a battle value. +fn wear(args: &[String]) -> Result<(), String> { + let name = args.first().filter(|a| !a.starts_with("--")).ok_or( + "name a design: helm wear \"Atlas AS7-D\" --to 1500 --megamek --bridge-dir ", + )?; + let opts = parse_opts(&args[1..])?; + let target = opts + .to + .ok_or("--to is required: there is nothing to aim at without it")?; + let megamek = opts.megamek.ok_or("--megamek is required")?; + let bridge = opts + .bridge_dir + .ok_or("--bridge-dir is required: the catalogue lives there")?; + + let library = read_library(&megamek)?; + let catalogue = + helm_bridge::read_catalogue(&bridge.join("equipment.jsonl")).map_err(|e| e.to_string())?; + let unit = library + .units + .iter() + .find(|u| u.display_name().eq_ignore_ascii_case(name)) + .ok_or_else(|| format!("no design called {name}"))?; + + let worn = helm_bv::wear_to(unit, &catalogue, target).map_err(|e| e.to_string())?; + println!( + "{} is {} whole, {} worn{}", + unit.display_name(), + worn.whole, + worn.value, + if worn.is_exact() { + String::new() + } else { + format!(" - {} off {target}", worn.missed_by) + } + ); + let repairs = helm_bv::repairs(unit, &worn.condition); + for repair in &repairs { + let mut parts = Vec::new(); + if repair.armor > 0 { + parts.push(format!("{} armour", repair.armor)); + } + if repair.rear_armor > 0 { + parts.push(format!("{} rear armour", repair.rear_armor)); + } + if repair.structure > 0 { + parts.push(format!("{} structure", repair.structure)); + } + println!(" {:<16} {}", repair.location, parts.join(", ")); + } + let total = helm_bv::Total::of(&repairs); + println!( + " {:<16} {} armour and {} structure gone", + "", total.armor, total.structure + ); + Ok(()) +} + /// What a post-match `.mul` would take to put right. /// /// The other half of reading a file that came back from a battle: `--why` says -- 2.51.2