diff --git a/bridge/sds/Observation.java b/bridge/sds/Observation.java index cb45bb6..49bbe06 100644 --- a/bridge/sds/Observation.java +++ b/bridge/sds/Observation.java @@ -419,6 +419,18 @@ public final class Observation { ObjectNode wn = weapons.addObject(); wn.put("id", e.getEquipmentNum(w)); wn.put("name", wt.getName()); + // Where it is bolted on. An arm gun, a torso gun and a leg gun do + // not bear on the same hexes: Mek.getWeaponArc reads the mount, and + // Mek.isSecondaryArcWeapon is false for the legs, so a leg weapon + // fires forward and stays put when the torso twists. Sent as + // MegaMek's own abbreviation, the same key "locations" uses. + wn.put("location", e.getLocationAbbr(w.getLocation())); + // Rear-mounted beats the location outright - the weapon fires into + // the rear arc wherever it sits. Sent only when true, like + // "destroyed" on a location. + if (w.isRearMounted()) { + wn.put("rearMounted", true); + } wn.put("heat", wt.getHeat()); wn.put("short", wt.getShortRange()); wn.put("medium", wt.getMediumRange()); diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 178eba3..dca0426 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -77,7 +77,8 @@ the board is a property of the *unit*, which is why `movementMode` and "locations": [{"name": "CT", "armor": 20, "armorMax": 22, "rearArmor": 6, "rearArmorMax": 8, "internal": 18, "internalMax": 18}], - "weapons": [{"id": 3, "name": "LRM 10", "heat": 4, + "weapons": [{"id": 3, "name": "LRM 10", "location": "RT", + "heat": 4, "short": 7, "medium": 14, "long": 21, "rackSize": 10, "damagePerPacket": 1.0, "usable": true, @@ -95,6 +96,16 @@ and side torsos. Every number is a count; MegaMek's negative armour states converted to zero, and a location that is gone carries `"destroyed": true` instead. A location the unit does not have is left out. +A weapon's `location` is where it is bolted on, by MegaMek's own abbreviation — +the same names `locations` uses. The mount is what decides which hexes a weapon +bears on: `Mek.getWeaponArc` reads it, and `Mek.isSecondaryArcWeapon` is false +for the legs, so a leg weapon fires along the unit's facing and a torso twist +does not move it while an arm or torso weapon follows the twist. `rearMounted` +is sent only when true and beats the location outright: the weapon fires into +the rear arc wherever it sits. A weapon with no `location` — an observation +recorded before the field existed — is read as a centre-torso weapon, which +fires forward and follows the twist like every mount but the arms and the legs. + `ammo` is what is in the tube, and is absent for an energy weapon and for an empty rack. A launcher's damage is not a property of the launcher: an SRM-6 throws two points a missile with standard ammo and none with Inferno, which diff --git a/plan/protocol.md b/plan/protocol.md index 879c2b2..d4db8d6 100644 --- a/plan/protocol.md +++ b/plan/protocol.md @@ -27,6 +27,13 @@ Adding a field is deliberate, with a reason in the commit message. cannot be right without it. On the wire as a weapon's `ammo`. `damagePerPacket` is still computed from the launcher alone; making it read the munition belongs to [ev](ev.md) +- [x] **Mount location per weapon** - which weapons bear from a candidate + position depends on where they are bolted on, and the wire carried ranges + and damage and nothing else. On the wire as a weapon's `location`, in + MegaMek's own abbreviations, with `rearMounted` beside it. The case that + makes it load-bearing is the legs: `Mek.isSecondaryArcWeapon` is false for + them, so a leg weapon does not follow the torso twist, and a candidate + generator that assumed a torso mount would overvalue twisting - [ ] **Per-unit levels in the round tick** - on the match result as `entities`, `{seat: {entity id: {bv, armor, internal, potential, force}}}`, beside the side totals it is the per-unit form of. A destroyed unit drops