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,