Skip to content

Commit 151ca64

Browse files
authored
MM Scenario language improvements (MegaMek#7486)
A byproduct of working on the stranded turns problem solved by MegaMek#7485. These changes allow setting up transported units in MM scenarios and give a tank an engine crit, making it immobile. Also a null guard in Game that is not necessary but won't hurt.
2 parents 1defbc5 + fa8b6a9 commit 151ca64

File tree

6 files changed

+193
-18
lines changed

6 files changed

+193
-18
lines changed

megamek/docs/Scenarios/ScenarioV2 HowTo.mms

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,9 @@ factions:
344344
RT: [ 1, 3 ]
345345
CT: 1
346346
# non-location crits (TODO)
347+
# Tanks
348+
# Engine hits (TW p.194); the number is ignored (more than one hit has no further effect) and can be omitted
349+
engine: 1
347350
# motive: 1
348351
# firecontrol: 1
349352

@@ -498,6 +501,13 @@ c3:
498501
connected: [ 302, 208 ]
499502
# atm, double master units are not supported
500503

504+
# ###############################################
505+
# Transporting units:
506+
transports:
507+
# the carrier and the carried unit ids
508+
102: [ 104, 105 ]
509+
202: [ 205 ]
510+
501511

502512
# ###############################################
503513
# Game End, Victory and Victory/Defeat/Other Messages

megamek/src/megamek/common/game/Game.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,8 @@ public void setBoardDirect(final Board board) {
228228
setBoard(0, board);
229229
}
230230

231-
public boolean containsMinefield(Coords coords) {
232-
return minefields.containsKey(coords);
231+
public boolean containsMinefield(@Nullable Coords coords) {
232+
return (coords != null) && minefields.containsKey(coords);
233233
}
234234

235235
public Vector<Minefield> getMinefields(Coords coords) {

megamek/src/megamek/common/jacksonAdapters/EntityDeserializer.java

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import megamek.common.units.IAero;
6161
import megamek.common.units.IBomber;
6262
import megamek.common.units.Mek;
63+
import megamek.common.units.Tank;
6364

6465
public class EntityDeserializer extends StdDeserializer<Entity> {
6566

@@ -92,6 +93,7 @@ public class EntityDeserializer extends StdDeserializer<Entity> {
9293
public static final String FLEE_AREA = "fleefrom";
9394
private static final String AREA = "area";
9495
private static final String BOMBS = "bombs";
96+
private static final String ENGINE = "engine";
9597

9698
public EntityDeserializer() {
9799
this(null);
@@ -303,26 +305,33 @@ private void assignVelocity(Entity entity, JsonNode node) {
303305
}
304306

305307
private void assignCrits(Entity entity, JsonNode node) {
306-
if (!(entity instanceof Mek) || !node.has(CRITS)) {
307-
// Implementation very different for different entities; for now: Meks
308+
if (!node.has(CRITS)) {
308309
return;
309310
}
310311
JsonNode critsNode = node.get(CRITS);
311-
for (int location = 0; location < entity.locations(); location++) {
312-
String locationAbbr = entity.getLocationAbbr(location);
313-
if (critsNode.has(locationAbbr)) {
314-
for (int slot : parseArrayOrSingleNode(critsNode.get(locationAbbr))) {
315-
int zeroBasedSlot = slot - 1;
316-
CriticalSlot cs = entity.getCritical(location, zeroBasedSlot);
317-
if ((cs == null) || !cs.isHittable()) {
318-
throw new IllegalArgumentException("Invalid slot " + location + ":" + slot + " on " + entity);
319-
} else {
320-
cs.setHit(true);
321-
if ((cs.getType() == CriticalSlot.TYPE_SYSTEM) && (cs.getIndex() == Mek.SYSTEM_ENGINE)) {
322-
entity.engineHitsThisPhase++;
312+
if (entity instanceof Tank tank) {
313+
if (critsNode.has(ENGINE)) {
314+
tank.engineHit();
315+
}
316+
317+
} else if (entity instanceof Mek) {
318+
319+
for (int location = 0; location < entity.locations(); location++) {
320+
String locationAbbr = entity.getLocationAbbr(location);
321+
if (critsNode.has(locationAbbr)) {
322+
for (int slot : parseArrayOrSingleNode(critsNode.get(locationAbbr))) {
323+
int zeroBasedSlot = slot - 1;
324+
CriticalSlot cs = entity.getCritical(location, zeroBasedSlot);
325+
if ((cs == null) || !cs.isHittable()) {
326+
throw new IllegalArgumentException("Invalid slot " + location + ":" + slot + " on " + entity);
323327
} else {
324-
Mounted<?> mounted = cs.getMount();
325-
mounted.setDestroyed(true);
328+
cs.setHit(true);
329+
if ((cs.getType() == CriticalSlot.TYPE_SYSTEM) && (cs.getIndex() == Mek.SYSTEM_ENGINE)) {
330+
entity.engineHitsThisPhase++;
331+
} else {
332+
Mounted<?> mounted = cs.getMount();
333+
mounted.setDestroyed(true);
334+
}
326335
}
327336
}
328337
}

megamek/src/megamek/common/scenario/ScenarioV2.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,20 @@ public IGame createGame() throws IOException, ScenarioLoaderException {
233233
}
234234
}
235235

236+
List<TransportsScenarioParser.ParsedTransportsInfo> transportsInfos = TransportsScenarioParser.parse(node);
237+
for (TransportsScenarioParser.ParsedTransportsInfo transport : transportsInfos) {
238+
try {
239+
Entity carrier = twGame.getEntity(transport.carrierId);
240+
List<Entity> units = transport.carriedUnits.stream().map(twGame::getEntity).toList();
241+
for (Entity unit : units) {
242+
carrier.load(unit);
243+
unit.setTransportId(transport.carrierId);
244+
}
245+
} catch (Exception e) {
246+
throw new ScenarioLoaderException("Faulty transports definition: " + transport);
247+
}
248+
}
249+
236250
twGame.setupDeployment();
237251
if (node.has(PARAM_GAME_EXTERNAL_ID)) {
238252
twGame.setExternalGameId(node.get(PARAM_GAME_EXTERNAL_ID).intValue());
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright (C) 2025 The MegaMek Team. All Rights Reserved.
3+
*
4+
* This file is part of MegaMek.
5+
*
6+
* MegaMek is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License (GPL),
8+
* version 3 or (at your option) any later version,
9+
* as published by the Free Software Foundation.
10+
*
11+
* MegaMek is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty
13+
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14+
* See the GNU General Public License for more details.
15+
*
16+
* A copy of the GPL should have been included with this project;
17+
* if not, see <https://www.gnu.org/licenses/>.
18+
*
19+
* NOTICE: The MegaMek organization is a non-profit group of volunteers
20+
* creating free software for the BattleTech community.
21+
*
22+
* MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks
23+
* of The Topps Company, Inc. All Rights Reserved.
24+
*
25+
* Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of
26+
* InMediaRes Productions, LLC.
27+
*
28+
* MechWarrior Copyright Microsoft Corporation. MegaMek was created under
29+
* Microsoft's "Game Content Usage Rules"
30+
* <https://www.xbox.com/en-US/developers/rules> and it is not endorsed by or
31+
* affiliated with Microsoft.
32+
*/
33+
package megamek.common.scenario;
34+
35+
import java.util.ArrayList;
36+
import java.util.HashSet;
37+
import java.util.Iterator;
38+
import java.util.List;
39+
import java.util.Set;
40+
41+
import com.fasterxml.jackson.core.JsonProcessingException;
42+
import com.fasterxml.jackson.databind.JsonNode;
43+
import megamek.common.units.Entity;
44+
45+
/**
46+
* This class is used for parsing transported units information from MM V2 scenarios (.mms) into an intermediate
47+
* ParsedTransportsInfo object.
48+
*
49+
* @see ScenarioV2
50+
*/
51+
class TransportsScenarioParser {
52+
53+
private static final String TRANSPORTS = "transports";
54+
55+
static class ParsedTransportsInfo {
56+
57+
/** The carrying unit */
58+
int carrierId = Entity.NONE;
59+
60+
/** The carried unit(s) */
61+
Set<Integer> carriedUnits = new HashSet<>();
62+
63+
@Override
64+
public String toString() {
65+
return "TransportsInfo: C = " + carrierId + ", P = " + carriedUnits;
66+
}
67+
}
68+
69+
/**
70+
* Parses transported units info from an MM V2 scenarios (.mms) file. The info is only read, but not checked for
71+
* any inconsistency.
72+
*
73+
* @param node The scenario node; transports info is at top level (not under any other node)
74+
*
75+
* @return all parsed transports of all players
76+
*
77+
* @throws JsonProcessingException When malformed info is present (no game rule checks are made)
78+
*/
79+
public static List<ParsedTransportsInfo> parse(JsonNode node) throws JsonProcessingException {
80+
List<ParsedTransportsInfo> result = new ArrayList<>();
81+
if (node.has(TRANSPORTS)) {
82+
for (Iterator<String> it = node.get(TRANSPORTS).fieldNames(); it.hasNext(); ) {
83+
String carrierId = it.next();
84+
result.add(readTransport(node.get(TRANSPORTS), carrierId));
85+
}
86+
}
87+
return result;
88+
}
89+
90+
private static ParsedTransportsInfo readTransport(JsonNode transportsNode, String carrierId) {
91+
var transport = new ParsedTransportsInfo();
92+
transport.carrierId = Integer.parseInt(carrierId);
93+
if (transportsNode.isArray()) {
94+
// multiple carried units (or single as array)
95+
transportsNode.get(carrierId).forEach(element -> transport.carriedUnits.add(element.asInt()));
96+
} else {
97+
// single carried unit
98+
transport.carriedUnits.add(transportsNode.get(carrierId).asInt());
99+
}
100+
return transport;
101+
}
102+
103+
private TransportsScenarioParser() {}
104+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0.
2+
# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/
3+
#
4+
# NOTICE: The MegaMek organization is a non-profit group of volunteers
5+
# creating free software for the BattleTech community.
6+
#
7+
# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks
8+
# of The Topps Company, Inc. All Rights Reserved.
9+
#
10+
# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of
11+
# InMediaRes Productions, LLC.
12+
#
13+
# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under
14+
# Microsoft's "Game Content Usage Rules"
15+
# <https://www.xbox.com/en-US/developers/rules> and it is not endorsed by or
16+
# affiliated with Microsoft.
17+
MMSVersion: 2
18+
name: Infantry Carrier
19+
planet: None
20+
description: A scenario with infantry carried in an immobile unit (will trigger unloading a stranded unit)
21+
map: testiceonwater.board
22+
options:
23+
off: check_victory
24+
factions:
25+
- name: Test Player
26+
27+
units:
28+
- fullname: Foot Platoon (MG)
29+
id: 105
30+
31+
- fullname: Diner HQ DHQ-2
32+
id: 100
33+
crits:
34+
engine:
35+
36+
transports:
37+
100: 105
38+

0 commit comments

Comments
 (0)