From 352fccc7f6e9f85b22632172fc5036a16409b0fe Mon Sep 17 00:00:00 2001 From: "@permadeath.com" Date: Mon, 17 Aug 2026 15:08:55 -0400 Subject: [PATCH] feat(lobby): who is in the machine, under the machine The force card shows the crew on its own line under each unit: the skills whenever the scenario rates them, and the pilot's name when the file gives one. Skills are what BV 2.0 multiplies by, so the two numbers are what explains the battle value on the line below them. Both halves needed carrying. V2 parsed `crew: { gunnery, piloting }` and threw the name away, so it now keeps it in both the flow and block forms - a unit names itself with `fullname:`/`chassis:`, so a bare `name:` reaching the unit handler is a crew key. `mms index` then writes pilot and skills per unit into daily.json beside the battle value it already wrote. No "Unnamed" placeholder where a file rates a crew without naming it: that is a normal thing for a scenario to write, and a made-up word says less than the skills on their own. --- crates/mms/src/bin/mms.rs | 25 ++++++- crates/mms/src/v2.rs | 26 +++++++ web/src/content/daily.json | 150 ++++++++++++++++++------------------- web/src/screens/daily.ts | 17 ++++- web/src/screens/lobby.ts | 20 +++-- 5 files changed, 154 insertions(+), 84 deletions(-) diff --git a/crates/mms/src/bin/mms.rs b/crates/mms/src/bin/mms.rs index 4af02fb..71ec6f9 100644 --- a/crates/mms/src/bin/mms.rs +++ b/crates/mms/src/bin/mms.rs @@ -148,6 +148,12 @@ struct Force { struct UnitRow { chassis: String, + /// The crew, when the file names or rates one. Skills are what BV 2.0 + /// multiplies by, so a card that shows a battle value should be able to + /// show what produced it; the name is shown when there is one. + pilot: Option, + gunnery: Option, + piloting: Option, /// Where this unit sits in its faction's list, as the file wrote it. /// /// The card orders a force left to right along the board, but the report @@ -177,6 +183,9 @@ fn forces_from_v1(scenario: &ScenarioV1) -> Vec { .enumerate() .map(|(index, unit)| UnitRow { chassis: unit.chassis.clone(), + pilot: Some(unit.pilot.clone()).filter(|p| !p.is_empty()), + gunnery: Some(unit.gunnery), + piloting: Some(unit.piloting), index, position: unit.position, facing: unit.facing_applied(), @@ -200,6 +209,9 @@ fn forces_from_v2(scenario: &headquarters_mms::v2::ScenarioV2) -> Vec { .enumerate() .map(|(index, unit)| UnitRow { chassis: unit.name.clone(), + pilot: unit.pilot.clone(), + gunnery: unit.gunnery, + piloting: unit.piloting, index, position: unit.at, // The number the file writes, back as the letter the card @@ -353,8 +365,19 @@ fn entry( Some(bv) => format!(", \"bv\": {bv}"), None => String::new(), }; + // The crew, so a force card can say who is in the machine and at + // what skill. Skills are what BV 2.0 multiplies by, so the figure + // beside them is explained by them. + let crew = match (unit.gunnery, unit.piloting) { + (Some(g), Some(p)) => format!(", \"gunnery\": {g}, \"piloting\": {p}"), + _ => String::new(), + }; + let pilot = match unit.pilot.as_deref() { + Some(name) => format!(", \"pilot\": {}", json_string(name)), + None => String::new(), + }; units.push(format!( - " {{ \"name\": {}, \"row\": {row}, \"facing\": {}{bv} }}", + " {{ \"name\": {}, \"row\": {row}, \"facing\": {}{bv}{pilot}{crew} }}", json_string(&unit.chassis), json_string(&facing) )); diff --git a/crates/mms/src/v2.rs b/crates/mms/src/v2.rs index 3cfd5c6..08e8ae1 100644 --- a/crates/mms/src/v2.rs +++ b/crates/mms/src/v2.rs @@ -357,6 +357,9 @@ pub struct UnitV2 { pub facing: Option, pub gunnery: Option, pub piloting: Option, + /// `crew: { name: … }`. Scenario files name their crews and the card shows + /// the name when there is one; a unit with only skills has none. + pub pilot: Option, } /// V2's six facings, in MegaMek's own order — `Facing.java`, and the @@ -527,6 +530,10 @@ fn read_unit_field(unit: &mut UnitV2, field: &str) { } "gunnery" => unit.gunnery = value.parse().ok(), "piloting" => unit.piloting = value.parse().ok(), + // A unit names itself with `fullname:`/`chassis:`, so a bare `name:` + // reaching here is the block form of `crew:` - its child keys arrive + // through this same handler. + "name" => unit.pilot = Some(unquote(value).to_string()), "crew" => { // `crew: { name: X, gunnery: 4, piloting: 5 }` for part in value @@ -538,6 +545,7 @@ fn read_unit_field(unit: &mut UnitV2, field: &str) { match k.trim() { "gunnery" => unit.gunnery = v.trim().parse().ok(), "piloting" => unit.piloting = v.trim().parse().ok(), + "name" => unit.pilot = Some(v.trim().to_string()), _ => {} } } @@ -621,6 +629,24 @@ factions: /// pilot silently stays 4/5 and the force's battle value is wrong. A file /// of ours that used it would be quietly unbalanced, so it must not parse /// into a skill here either - the absence is the signal. + #[test] + fn a_crew_name_is_read_in_both_forms() { + let flow = ScenarioV2::parse( + "MMSVersion: 2\nfactions:\n - name: A\n units:\n - fullname: Locust LCT-1V\n crew: { name: Talon 1, gunnery: 5, piloting: 4 }\n", + ); + let unit = &flow.factions[0].units[0]; + assert_eq!(unit.pilot.as_deref(), Some("Talon 1")); + assert_eq!((unit.gunnery, unit.piloting), (Some(5), Some(4))); + assert_eq!(unit.name, "Locust LCT-1V", "the unit still names itself"); + + let block = ScenarioV2::parse( + "MMSVersion: 2\nfactions:\n - name: A\n units:\n - fullname: Wasp WSP-1A\n crew:\n name: Hammer 1\n gunnery: 3\n piloting: 3\n", + ); + let unit = &block.factions[0].units[0]; + assert_eq!(unit.pilot.as_deref(), Some("Hammer 1")); + assert_eq!(unit.name, "Wasp WSP-1A", "the unit still names itself"); + } + #[test] fn a_skill_shorthand_is_not_mistaken_for_a_crew() { let s = ScenarioV2::parse( diff --git a/web/src/content/daily.json b/web/src/content/daily.json index c9705bf..b813000 100644 --- a/web/src/content/daily.json +++ b/web/src/content/daily.json @@ -17,10 +17,10 @@ "tons": 250, "cost": 31036146, "units": [ - { "name": "Archer ARC-2R", "row": 2, "facing": "N", "bv": 1477 }, - { "name": "Archer ARC-4M", "row": 2, "facing": "N", "bv": 1705 }, - { "name": "Argus AGS-4D", "row": 2, "facing": "N", "bv": 1638 }, - { "name": "Hellhound (Conjurer) (Standard)", "row": 2, "facing": "N", "bv": 1813 } + { "name": "Archer ARC-2R", "row": 2, "facing": "N", "bv": 1477, "pilot": "Challenger 1", "gunnery": 4, "piloting": 5 }, + { "name": "Archer ARC-4M", "row": 2, "facing": "N", "bv": 1705, "pilot": "Challenger 2", "gunnery": 4, "piloting": 5 }, + { "name": "Argus AGS-4D", "row": 2, "facing": "N", "bv": 1638, "pilot": "Challenger 3", "gunnery": 4, "piloting": 5 }, + { "name": "Hellhound (Conjurer) (Standard)", "row": 2, "facing": "N", "bv": 1813, "pilot": "Challenger 4", "gunnery": 4, "piloting": 5 } ] }, { @@ -31,10 +31,10 @@ "tons": 290, "cost": 52712660, "units": [ - { "name": "Atlas AS7-D", "row": 0, "facing": "S", "bv": 1897 }, - { "name": "Bushwacker BSW-L1", "row": 0, "facing": "S", "bv": 1513 }, - { "name": "Enfield END-6J", "row": 0, "facing": "S", "bv": 1417 }, - { "name": "Templar TLR1-OB", "row": 0, "facing": "S", "bv": 1806 } + { "name": "Atlas AS7-D", "row": 0, "facing": "S", "bv": 1897, "pilot": "OPFOR 1", "gunnery": 4, "piloting": 5 }, + { "name": "Bushwacker BSW-L1", "row": 0, "facing": "S", "bv": 1513, "pilot": "OPFOR 2", "gunnery": 4, "piloting": 5 }, + { "name": "Enfield END-6J", "row": 0, "facing": "S", "bv": 1417, "pilot": "OPFOR 3", "gunnery": 4, "piloting": 5 }, + { "name": "Templar TLR1-OB", "row": 0, "facing": "S", "bv": 1806, "pilot": "OPFOR 4", "gunnery": 4, "piloting": 5 } ] } ] @@ -57,7 +57,7 @@ "tons": 90, "cost": 40261000, "units": [ - { "name": "Alpha Wolf A", "row": 1, "facing": "N", "bv": 5771 } + { "name": "Alpha Wolf A", "row": 1, "facing": "N", "bv": 5771, "pilot": "Challenger 1", "gunnery": 2, "piloting": 3 } ] }, { @@ -68,10 +68,10 @@ "tons": 80, "cost": 6333680, "units": [ - { "name": "Locust LCT-1S", "row": 0, "facing": "S", "bv": 396 }, - { "name": "Locust LCT-1V", "row": 0, "facing": "S", "bv": 389 }, - { "name": "Stinger STG-3G", "row": 0, "facing": "S", "bv": 447 }, - { "name": "Stinger STG-3R", "row": 0, "facing": "S", "bv": 323 } + { "name": "Locust LCT-1S", "row": 0, "facing": "S", "bv": 396, "pilot": "Talon 1", "gunnery": 5, "piloting": 5 }, + { "name": "Locust LCT-1V", "row": 0, "facing": "S", "bv": 389, "pilot": "Talon 2", "gunnery": 5, "piloting": 5 }, + { "name": "Stinger STG-3G", "row": 0, "facing": "S", "bv": 447, "pilot": "Talon 3", "gunnery": 5, "piloting": 5 }, + { "name": "Stinger STG-3R", "row": 0, "facing": "S", "bv": 323, "pilot": "Talon 4", "gunnery": 5, "piloting": 5 } ] }, { @@ -82,10 +82,10 @@ "tons": 100, "cost": 10657460, "units": [ - { "name": "Wasp WSP-1W", "row": 2, "facing": "NE", "bv": 336 }, - { "name": "Wasp WSP-1A", "row": 2, "facing": "NE", "bv": 346 }, - { "name": "Firefly FFL-3PP", "row": 2, "facing": "NE", "bv": 799 }, - { "name": "Firefly FFL-3PP3", "row": 2, "facing": "NE", "bv": 727 } + { "name": "Wasp WSP-1W", "row": 2, "facing": "NE", "bv": 336, "pilot": "Hammer 1", "gunnery": 5, "piloting": 5 }, + { "name": "Wasp WSP-1A", "row": 2, "facing": "NE", "bv": 346, "pilot": "Hammer 2", "gunnery": 5, "piloting": 5 }, + { "name": "Firefly FFL-3PP", "row": 2, "facing": "NE", "bv": 799, "pilot": "Hammer 3", "gunnery": 5, "piloting": 5 }, + { "name": "Firefly FFL-3PP3", "row": 2, "facing": "NE", "bv": 727, "pilot": "Hammer 4", "gunnery": 5, "piloting": 5 } ] }, { @@ -96,10 +96,10 @@ "tons": 120, "cost": 10496263, "units": [ - { "name": "Spider SDR-5D", "row": 2, "facing": "NW", "bv": 472 }, - { "name": "Spider SDR-5K", "row": 2, "facing": "NW", "bv": 453 }, - { "name": "Flea FLE-4", "row": 2, "facing": "NW", "bv": 389 }, - { "name": "Cicada CDA-3C", "row": 2, "facing": "NW", "bv": 694 } + { "name": "Spider SDR-5D", "row": 2, "facing": "NW", "bv": 472, "pilot": "Anvil 1", "gunnery": 5, "piloting": 5 }, + { "name": "Spider SDR-5K", "row": 2, "facing": "NW", "bv": 453, "pilot": "Anvil 2", "gunnery": 5, "piloting": 5 }, + { "name": "Flea FLE-4", "row": 2, "facing": "NW", "bv": 389, "pilot": "Anvil 3", "gunnery": 5, "piloting": 5 }, + { "name": "Cicada CDA-3C", "row": 2, "facing": "NW", "bv": 694, "pilot": "Anvil 4", "gunnery": 5, "piloting": 5 } ] } ] @@ -121,10 +121,10 @@ "tons": 200, "cost": 22654400, "units": [ - { "name": "Clint CLNT-5U", "row": 2, "facing": "N", "bv": 1275 }, - { "name": "Crusader CRD-3R", "row": 2, "facing": "N", "bv": 1317 }, - { "name": "Osiris OSR-3D", "row": 2, "facing": "N", "bv": 1138 }, - { "name": "Thunderbolt TDR-5S", "row": 2, "facing": "N", "bv": 1335 } + { "name": "Clint CLNT-5U", "row": 2, "facing": "N", "bv": 1275, "pilot": "Challenger 1", "gunnery": 4, "piloting": 5 }, + { "name": "Crusader CRD-3R", "row": 2, "facing": "N", "bv": 1317, "pilot": "Challenger 2", "gunnery": 4, "piloting": 5 }, + { "name": "Osiris OSR-3D", "row": 2, "facing": "N", "bv": 1138, "pilot": "Challenger 3", "gunnery": 4, "piloting": 5 }, + { "name": "Thunderbolt TDR-5S", "row": 2, "facing": "N", "bv": 1335, "pilot": "Challenger 4", "gunnery": 4, "piloting": 5 } ] }, { @@ -135,8 +135,8 @@ "tons": 120, "cost": 32392833, "units": [ - { "name": "Black Hawk (Nova) A", "row": 0, "facing": "S", "bv": 2422 }, - { "name": "Thor (Summoner) D", "row": 0, "facing": "S", "bv": 2643 } + { "name": "Black Hawk (Nova) A", "row": 0, "facing": "S", "bv": 2422, "pilot": "OPFOR 1", "gunnery": 4, "piloting": 5 }, + { "name": "Thor (Summoner) D", "row": 0, "facing": "S", "bv": 2643, "pilot": "OPFOR 2", "gunnery": 4, "piloting": 5 } ] } ] @@ -158,10 +158,10 @@ "tons": 190, "cost": 21471437, "units": [ - { "name": "Clint CLNT-5U", "row": 2, "facing": "N", "bv": 1275 }, - { "name": "Crusader CRD-3R", "row": 2, "facing": "N", "bv": 1317 }, - { "name": "Griffin GRF-1N", "row": 2, "facing": "N", "bv": 1272 }, - { "name": "Javelin JVN-11D", "row": 2, "facing": "N", "bv": 1104 } + { "name": "Clint CLNT-5U", "row": 2, "facing": "N", "bv": 1275, "pilot": "Challenger 1", "gunnery": 4, "piloting": 5 }, + { "name": "Crusader CRD-3R", "row": 2, "facing": "N", "bv": 1317, "pilot": "Challenger 2", "gunnery": 4, "piloting": 5 }, + { "name": "Griffin GRF-1N", "row": 2, "facing": "N", "bv": 1272, "pilot": "Challenger 3", "gunnery": 4, "piloting": 5 }, + { "name": "Javelin JVN-11D", "row": 2, "facing": "N", "bv": 1104, "pilot": "Challenger 4", "gunnery": 4, "piloting": 5 } ] }, { @@ -172,10 +172,10 @@ "tons": 220, "cost": 38495506, "units": [ - { "name": "Hellspawn HSN-7D", "row": 0, "facing": "S", "bv": 1220 }, - { "name": "Tessen TSN-1C", "row": 0, "facing": "S", "bv": 1162 }, - { "name": "Warhammer WHM-6R", "row": 0, "facing": "S", "bv": 1299 }, - { "name": "Wraith TR1", "row": 0, "facing": "S", "bv": 1287 } + { "name": "Hellspawn HSN-7D", "row": 0, "facing": "S", "bv": 1220, "pilot": "OPFOR 1", "gunnery": 4, "piloting": 5 }, + { "name": "Tessen TSN-1C", "row": 0, "facing": "S", "bv": 1162, "pilot": "OPFOR 2", "gunnery": 4, "piloting": 5 }, + { "name": "Warhammer WHM-6R", "row": 0, "facing": "S", "bv": 1299, "pilot": "OPFOR 3", "gunnery": 4, "piloting": 5 }, + { "name": "Wraith TR1", "row": 0, "facing": "S", "bv": 1287, "pilot": "OPFOR 4", "gunnery": 4, "piloting": 5 } ] } ] @@ -197,9 +197,9 @@ "tons": 240, "cost": 23363246, "units": [ - { "name": "Archer ARC-2R", "row": 2, "facing": "N", "bv": 1477 }, - { "name": "Archer ARC-4M", "row": 2, "facing": "N", "bv": 1705 }, - { "name": "Atlas AS7-D", "row": 2, "facing": "N", "bv": 1897 } + { "name": "Archer ARC-2R", "row": 2, "facing": "N", "bv": 1477, "pilot": "Challenger 1", "gunnery": 4, "piloting": 5 }, + { "name": "Archer ARC-4M", "row": 2, "facing": "N", "bv": 1705, "pilot": "Challenger 2", "gunnery": 4, "piloting": 5 }, + { "name": "Atlas AS7-D", "row": 2, "facing": "N", "bv": 1897, "pilot": "Challenger 3", "gunnery": 4, "piloting": 5 } ] }, { @@ -210,9 +210,9 @@ "tons": 230, "cost": 42589250, "units": [ - { "name": "Marauder MAD-5S", "row": 0, "facing": "S", "bv": 1799 }, - { "name": "Thanatos TNS-4S", "row": 0, "facing": "S", "bv": 1844 }, - { "name": "Zeus ZEU-6T", "row": 0, "facing": "S", "bv": 1436 } + { "name": "Marauder MAD-5S", "row": 0, "facing": "S", "bv": 1799, "pilot": "OPFOR 1", "gunnery": 4, "piloting": 5 }, + { "name": "Thanatos TNS-4S", "row": 0, "facing": "S", "bv": 1844, "pilot": "OPFOR 2", "gunnery": 4, "piloting": 5 }, + { "name": "Zeus ZEU-6T", "row": 0, "facing": "S", "bv": 1436, "pilot": "OPFOR 3", "gunnery": 4, "piloting": 5 } ] } ] @@ -234,10 +234,10 @@ "tons": 140, "cost": 5621667, "units": [ - { "name": "Drillson Heavy Hover Tank (Standard)", "row": 2, "facing": "N", "bv": 949 }, - { "name": "Heavy LRM Carrier (Standard)", "row": 2, "facing": "N", "bv": 948 }, - { "name": "Savannah Master Hovercraft (Standard)", "row": 2, "facing": "N", "bv": 215 }, - { "name": "Savannah Master Hovercraft (Standard)", "row": 2, "facing": "N", "bv": 215 } + { "name": "Drillson Heavy Hover Tank (Standard)", "row": 2, "facing": "N", "bv": 949, "pilot": "Challenger 1", "gunnery": 4, "piloting": 5 }, + { "name": "Heavy LRM Carrier (Standard)", "row": 2, "facing": "N", "bv": 948, "pilot": "Challenger 2", "gunnery": 4, "piloting": 5 }, + { "name": "Savannah Master Hovercraft (Standard)", "row": 2, "facing": "N", "bv": 215, "pilot": "Challenger 3", "gunnery": 4, "piloting": 5 }, + { "name": "Savannah Master Hovercraft (Standard)", "row": 2, "facing": "N", "bv": 215, "pilot": "Challenger 4", "gunnery": 4, "piloting": 5 } ] }, { @@ -248,8 +248,8 @@ "tons": 80, "cost": 15014300, "units": [ - { "name": "Osiris OSR-3D", "row": 0, "facing": "S", "bv": 1138 }, - { "name": "Uziel UZL-3S", "row": 0, "facing": "S", "bv": 1189 } + { "name": "Osiris OSR-3D", "row": 0, "facing": "S", "bv": 1138, "pilot": "OPFOR 1", "gunnery": 4, "piloting": 5 }, + { "name": "Uziel UZL-3S", "row": 0, "facing": "S", "bv": 1189, "pilot": "OPFOR 2", "gunnery": 4, "piloting": 5 } ] } ] @@ -271,7 +271,7 @@ "tons": 95, "cost": 28607719, "units": [ - { "name": "Turkina D", "row": 2, "facing": "N", "bv": 3132 } + { "name": "Turkina D", "row": 2, "facing": "N", "bv": 3132, "pilot": "Challenger 1", "gunnery": 4, "piloting": 5 } ] }, { @@ -282,9 +282,9 @@ "tons": 140, "cost": 15519704, "units": [ - { "name": "Hussar HSR-500-D", "row": 0, "facing": "S", "bv": 977 }, - { "name": "Shadow Hawk SHD-2H", "row": 0, "facing": "S", "bv": 1064 }, - { "name": "Wolverine WVR-6R", "row": 0, "facing": "S", "bv": 1101 } + { "name": "Hussar HSR-500-D", "row": 0, "facing": "S", "bv": 977, "pilot": "OPFOR 1", "gunnery": 4, "piloting": 5 }, + { "name": "Shadow Hawk SHD-2H", "row": 0, "facing": "S", "bv": 1064, "pilot": "OPFOR 2", "gunnery": 4, "piloting": 5 }, + { "name": "Wolverine WVR-6R", "row": 0, "facing": "S", "bv": 1101, "pilot": "OPFOR 3", "gunnery": 4, "piloting": 5 } ] } ] @@ -306,9 +306,9 @@ "tons": 215, "cost": 15010983, "units": [ - { "name": "Archer ARC-2R", "row": 2, "facing": "N", "bv": 1477 }, - { "name": "Crusader CRD-3R", "row": 2, "facing": "N", "bv": 1317 }, - { "name": "Heavy LRM Carrier (Standard)", "row": 2, "facing": "N", "bv": 948 } + { "name": "Archer ARC-2R", "row": 2, "facing": "N", "bv": 1477, "pilot": "Challenger 1", "gunnery": 4, "piloting": 5 }, + { "name": "Crusader CRD-3R", "row": 2, "facing": "N", "bv": 1317, "pilot": "Challenger 2", "gunnery": 4, "piloting": 5 }, + { "name": "Heavy LRM Carrier (Standard)", "row": 2, "facing": "N", "bv": 948, "pilot": "Challenger 3", "gunnery": 4, "piloting": 5 } ] }, { @@ -319,8 +319,8 @@ "tons": 175, "cost": 28965250, "units": [ - { "name": "Atlas AS7-D", "row": 0, "facing": "S", "bv": 1897 }, - { "name": "Thanatos TNS-4S", "row": 0, "facing": "S", "bv": 1844 } + { "name": "Atlas AS7-D", "row": 0, "facing": "S", "bv": 1897, "pilot": "OPFOR 1", "gunnery": 4, "piloting": 5 }, + { "name": "Thanatos TNS-4S", "row": 0, "facing": "S", "bv": 1844, "pilot": "OPFOR 2", "gunnery": 4, "piloting": 5 } ] } ] @@ -342,9 +342,9 @@ "tons": 125, "cost": 15225435, "units": [ - { "name": "Clint CLNT-5U", "row": 2, "facing": "N", "bv": 1275 }, - { "name": "Cougar H", "row": 2, "facing": "N", "bv": 1323 }, - { "name": "Drillson Heavy Hover Tank (Standard)", "row": 2, "facing": "N", "bv": 949 } + { "name": "Clint CLNT-5U", "row": 2, "facing": "N", "bv": 1275, "pilot": "Challenger 1", "gunnery": 4, "piloting": 5 }, + { "name": "Cougar H", "row": 2, "facing": "N", "bv": 1323, "pilot": "Challenger 2", "gunnery": 4, "piloting": 5 }, + { "name": "Drillson Heavy Hover Tank (Standard)", "row": 2, "facing": "N", "bv": 949, "pilot": "Challenger 3", "gunnery": 4, "piloting": 5 } ] }, { @@ -355,9 +355,9 @@ "tons": 125, "cost": 23389500, "units": [ - { "name": "Hellspawn HSN-7D", "row": 0, "facing": "S", "bv": 1220 }, - { "name": "Osiris OSR-3D", "row": 0, "facing": "S", "bv": 1138 }, - { "name": "Uziel UZL-3S", "row": 0, "facing": "S", "bv": 1189 } + { "name": "Hellspawn HSN-7D", "row": 0, "facing": "S", "bv": 1220, "pilot": "OPFOR 1", "gunnery": 4, "piloting": 5 }, + { "name": "Osiris OSR-3D", "row": 0, "facing": "S", "bv": 1138, "pilot": "OPFOR 2", "gunnery": 4, "piloting": 5 }, + { "name": "Uziel UZL-3S", "row": 0, "facing": "S", "bv": 1189, "pilot": "OPFOR 3", "gunnery": 4, "piloting": 5 } ] } ] @@ -379,10 +379,10 @@ "tons": 80, "cost": 8207457, "units": [ - { "name": "Commando COM-7S", "row": 2, "facing": "N", "bv": 658 }, - { "name": "Razorback RZK-9S", "row": 2, "facing": "N", "bv": 855 }, - { "name": "Savannah Master Hovercraft (Standard)", "row": 2, "facing": "N", "bv": 215 }, - { "name": "Stinger STG-3R", "row": 2, "facing": "N", "bv": 359 } + { "name": "Commando COM-7S", "row": 2, "facing": "N", "bv": 658, "pilot": "Challenger 1", "gunnery": 4, "piloting": 5 }, + { "name": "Razorback RZK-9S", "row": 2, "facing": "N", "bv": 855, "pilot": "Challenger 2", "gunnery": 4, "piloting": 5 }, + { "name": "Savannah Master Hovercraft (Standard)", "row": 2, "facing": "N", "bv": 215, "pilot": "Challenger 3", "gunnery": 4, "piloting": 5 }, + { "name": "Stinger STG-3R", "row": 2, "facing": "N", "bv": 359, "pilot": "Challenger 4", "gunnery": 4, "piloting": 5 } ] }, { @@ -393,8 +393,8 @@ "tons": 80, "cost": 7728883, "units": [ - { "name": "Drillson Heavy Hover Tank (Standard)", "row": 0, "facing": "S", "bv": 949 }, - { "name": "Osiris OSR-3D", "row": 0, "facing": "S", "bv": 1138 } + { "name": "Drillson Heavy Hover Tank (Standard)", "row": 0, "facing": "S", "bv": 949, "pilot": "OPFOR 1", "gunnery": 4, "piloting": 5 }, + { "name": "Osiris OSR-3D", "row": 0, "facing": "S", "bv": 1138, "pilot": "OPFOR 2", "gunnery": 4, "piloting": 5 } ] } ] @@ -416,11 +416,11 @@ "tons": 155, "cost": 20596750, "units": [ - { "name": "Commando COM-7S", "row": 2, "facing": "N", "bv": 658 }, - { "name": "Razorback RZK-9S", "row": 2, "facing": "N", "bv": 855 }, - { "name": "Razorback RZK-9S", "row": 2, "facing": "N", "bv": 855 }, - { "name": "Stiletto STO-4A", "row": 2, "facing": "N", "bv": 859 }, - { "name": "Stiletto STO-4A", "row": 2, "facing": "N", "bv": 859 } + { "name": "Commando COM-7S", "row": 2, "facing": "N", "bv": 658, "pilot": "Challenger 1", "gunnery": 4, "piloting": 5 }, + { "name": "Razorback RZK-9S", "row": 2, "facing": "N", "bv": 855, "pilot": "Challenger 2", "gunnery": 4, "piloting": 5 }, + { "name": "Razorback RZK-9S", "row": 2, "facing": "N", "bv": 855, "pilot": "Challenger 3", "gunnery": 4, "piloting": 5 }, + { "name": "Stiletto STO-4A", "row": 2, "facing": "N", "bv": 859, "pilot": "Challenger 4", "gunnery": 4, "piloting": 5 }, + { "name": "Stiletto STO-4A", "row": 2, "facing": "N", "bv": 859, "pilot": "Challenger 5", "gunnery": 4, "piloting": 5 } ] }, { @@ -431,8 +431,8 @@ "tons": 165, "cost": 40861896, "units": [ - { "name": "Sagittaire SGT-8R", "row": 0, "facing": "S", "bv": 2088 }, - { "name": "Thor (Summoner) A", "row": 0, "facing": "S", "bv": 2145 } + { "name": "Sagittaire SGT-8R", "row": 0, "facing": "S", "bv": 2088, "pilot": "OPFOR 1", "gunnery": 4, "piloting": 5 }, + { "name": "Thor (Summoner) A", "row": 0, "facing": "S", "bv": 2145, "pilot": "OPFOR 2", "gunnery": 4, "piloting": 5 } ] } ] diff --git a/web/src/screens/daily.ts b/web/src/screens/daily.ts index b1d5c2c..6fdb174 100644 --- a/web/src/screens/daily.ts +++ b/web/src/screens/daily.ts @@ -71,6 +71,16 @@ export type Placement = { * reader has to cope with not knowing. */ bv?: number; + /** + * The crew, when the scenario names or rates one. + * + * The skills are what BV 2.0 multiplies by, so they are what explains the + * figure beside them. The name is the file's own; a V2 crew that gives only + * skills has none. + */ + pilot?: string; + gunnery?: number; + piloting?: number; }; /** One force: what it fields and what it is worth. */ @@ -199,7 +209,12 @@ export function dailyScenario(challenge: Challenge): Scenario { name: side.name, team: side.team ?? null, start: side.start, - units: side.units.map((unit) => ({ name: unit.name })), + units: side.units.map((unit) => ({ + name: unit.name, + pilot: unit.pilot, + gunnery: unit.gunnery, + piloting: unit.piloting, + })), })), }; } diff --git a/web/src/screens/lobby.ts b/web/src/screens/lobby.ts index d3f15fe..03cecab 100644 --- a/web/src/screens/lobby.ts +++ b/web/src/screens/lobby.ts @@ -658,14 +658,20 @@ export function lobbyScreen(session: Session, entry: LobbyEntry): Node[] { const figures: string[] = []; if (bv !== null) figures.push(`${bv.toLocaleString()} BV`); if (stats) figures.push(`${tonsText(stats.tons)} t`); - // The crew, when the scenario names one. Every screen used to say - // "Pilot: n/a" about fights whose own files name the pilot and give - // their gunnery and piloting - that was the transcription's gap, not - // the game's. - const crew = + // The crew, on its own line under the machine. The skills always show + // when the scenario rates them - they are what BV 2.0 multiplies by, so + // they are what explains the figure below - and the name joins them only + // when the file gives one. No "Unnamed" placeholder: a crew with skills + // and no name is a normal thing for a scenario to write, and inventing a + // word for it says less than the skills alone. + const crew = [ + pilot, gunnery !== undefined && piloting !== undefined - ? `${pilot ?? "Unnamed"} · ${gunnery}/${piloting}` - : (pilot ?? ""); + ? `${gunnery}/${piloting}` + : null, + ] + .filter(Boolean) + .join(" · "); list.append( el("li", { className: "force-unit-row" }, [ canvas, -- 2.51.2