Skip to content

Commit e0895dd

Browse files
committed
Ensured StratCon Campaigns Always Generate At Least One Track
- Refactored logic to extract reusable helper methods `getScenarioOdds` and `getDeploymentTime`, improving code clarity and maintainability. - Introduced fallback mechanism to guarantee a minimum of one track in StratCon Areas of Operation, preventing cases where zero tracks are generated. - Added `getTrackCount` method in `StratconCampaignState` to replace direct use of `getTracks().size()`. - Corrected potential division by zero errors when distributing track objects by ensuring a valid track count.
1 parent 7193b9a commit e0895dd

File tree

2 files changed

+103
-45
lines changed

2 files changed

+103
-45
lines changed

MekHQ/src/mekhq/campaign/stratcon/StratconCampaignState.java

+32-28
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
*/
2828
package mekhq.campaign.stratcon;
2929

30+
import java.io.PrintWriter;
31+
import java.time.LocalDate;
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
import javax.xml.namespace.QName;
35+
3036
import jakarta.xml.bind.JAXBContext;
3137
import jakarta.xml.bind.JAXBElement;
3238
import jakarta.xml.bind.Marshaller;
@@ -44,12 +50,6 @@
4450
import mekhq.campaign.mission.AtBScenario;
4551
import org.w3c.dom.Node;
4652

47-
import javax.xml.namespace.QName;
48-
import java.io.PrintWriter;
49-
import java.time.LocalDate;
50-
import java.util.ArrayList;
51-
import java.util.List;
52-
5353
/**
5454
* Contract-level state object for a StratCon campaign.
5555
*
@@ -117,6 +117,10 @@ public List<StratconTrackState> getTracks() {
117117
return tracks;
118118
}
119119

120+
public int getTrackCount() {
121+
return tracks.size();
122+
}
123+
120124
public void addTrack(StratconTrackState track) {
121125
tracks.add(track);
122126
}
@@ -144,13 +148,13 @@ public int getSupportPoints() {
144148
* Modifies the current support points by the specified amount.
145149
*
146150
* <p>
147-
* This method increases or decreases the support points by the given number.
148-
* It adds the value of {@code change} to the existing support points total.
149-
* This can be used to reflect changes due to various gameplay events or actions.
151+
* This method increases or decreases the support points by the given number. It adds the value of {@code change} to
152+
* the existing support points total. This can be used to reflect changes due to various gameplay events or
153+
* actions.
150154
* </p>
151155
*
152-
* @param change The amount to adjust the support points by. Positive values will
153-
* increase the support points, while negative values will decrease them.
156+
* @param change The amount to adjust the support points by. Positive values will increase the support points, while
157+
* negative values will decrease them.
154158
*/
155159
public void changeSupportPoints(int change) {
156160
supportPoints += change;
@@ -210,10 +214,11 @@ public void useSupportPoints(int decrement) {
210214
}
211215

212216
/**
213-
* Convenience/speed method of determining whether or not a force with the given
214-
* ID has been deployed to a track in this campaign.
217+
* Convenience/speed method of determining whether or not a force with the given ID has been deployed to a track in
218+
* this campaign.
215219
*
216220
* @param forceID the force ID to check
221+
*
217222
* @return Deployed or not.
218223
*/
219224
public boolean isForceDeployedHere(int forceID) {
@@ -227,8 +232,7 @@ public boolean isForceDeployedHere(int forceID) {
227232
}
228233

229234
/**
230-
* Removes the scenario with the given campaign scenario ID from any tracks
231-
* where it's present
235+
* Removes the scenario with the given campaign scenario ID from any tracks where it's present
232236
*/
233237
public void removeStratconScenario(int scenarioID) {
234238
for (StratconTrackState trackState : tracks) {
@@ -240,23 +244,23 @@ public void removeStratconScenario(int scenarioID) {
240244
* Retrieves the {@link StratconScenario} associated with a given {@link AtBScenario}.
241245
*
242246
* <p>
243-
* This method searches through all {@link StratconTrackState} objects in the {@link StratconCampaignState}
244-
* to find the first {@link StratconScenario} whose backing scenario matches the specified {@link AtBScenario}.
245-
* If no such scenario is found, it returns {@code null}.
247+
* This method searches through all {@link StratconTrackState} objects in the {@link StratconCampaignState} to find
248+
* the first {@link StratconScenario} whose backing scenario matches the specified {@link AtBScenario}. If no such
249+
* scenario is found, it returns {@code null}.
246250
* </p>
247251
*
248252
* <strong>Usage:</strong>
249253
* <p>
250-
* Use this method to easily fetch the {@link StratconScenario} associated with the provided
251-
* {@link AtBScenario}.
254+
* Use this method to easily fetch the {@link StratconScenario} associated with the provided {@link AtBScenario}.
252255
* </p>
253256
*
254257
* @param campaign The {@link Campaign} containing the data to search through.
255258
* @param scenario The {@link AtBScenario} to find the corresponding {@link StratconScenario} for.
259+
*
256260
* @return The matching {@link StratconScenario}, or {@code null} if no corresponding scenario is found.
257261
*/
258262
public static @Nullable StratconScenario getStratconScenarioFromAtBScenario(Campaign campaign,
259-
AtBScenario scenario) {
263+
AtBScenario scenario) {
260264
AtBContract contract = scenario.getContract(campaign);
261265
if (contract == null) {
262266
return null;
@@ -279,16 +283,16 @@ public void removeStratconScenario(int scenarioID) {
279283
}
280284

281285
/**
282-
* Serialize this instance of a campaign state to a PrintWriter
283-
* Omits initial xml declaration
286+
* Serialize this instance of a campaign state to a PrintWriter Omits initial xml declaration
284287
*
285288
* @param pw The destination print writer
286289
*/
287290
public void Serialize(PrintWriter pw) {
288291
try {
289292
JAXBContext context = JAXBContext.newInstance(StratconCampaignState.class);
290293
JAXBElement<StratconCampaignState> stateElement = new JAXBElement<>(new QName(ROOT_XML_ELEMENT_NAME),
291-
StratconCampaignState.class, this);
294+
StratconCampaignState.class,
295+
this);
292296
Marshaller m = context.createMarshaller();
293297
m.setProperty(Marshaller.JAXB_FRAGMENT, true);
294298
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
@@ -299,10 +303,10 @@ public void Serialize(PrintWriter pw) {
299303
}
300304

301305
/**
302-
* Attempt to deserialize an instance of a Campaign State from the passed-in XML
303-
* Node
306+
* Attempt to deserialize an instance of a Campaign State from the passed-in XML Node
304307
*
305308
* @param xmlNode The node with the campaign state
309+
*
306310
* @return Possibly an instance of a StratconCampaignState
307311
*/
308312
public static StratconCampaignState Deserialize(Node xmlNode) {
@@ -331,8 +335,8 @@ public static StratconCampaignState Deserialize(Node xmlNode) {
331335
}
332336

333337
/**
334-
* This adapter provides a way to convert between a LocalDate and the ISO-8601 string
335-
* representation of the date that is used for XML marshaling and unmarshalling in JAXB.
338+
* This adapter provides a way to convert between a LocalDate and the ISO-8601 string representation of the date
339+
* that is used for XML marshaling and unmarshalling in JAXB.
336340
*/
337341
public static class LocalDateAdapter extends XmlAdapter<String, LocalDate> {
338342
@Override

MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java

+71-17
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ public class StratconContractInitializer {
6565
/**
6666
* Initializes the campaign state given a contract, campaign and contract definition
6767
*/
68-
public static void initializeCampaignState(AtBContract contract, Campaign campaign, StratconContractDefinition contractDefinition) {
68+
public static void initializeCampaignState(AtBContract contract, Campaign campaign,
69+
StratconContractDefinition contractDefinition) {
6970
StratconCampaignState campaignState = new StratconCampaignState(contract);
7071
campaignState.setBriefingText(contractDefinition.getBriefing() +
7172
"<br/>" +
@@ -91,10 +92,8 @@ public static void initializeCampaignState(AtBContract contract, Campaign campai
9192
int planetaryTemperature = campaign.getLocation().getPlanet().getTemperature(campaign.getLocalDate());
9293

9394
for (int x = 0; x < maximumTrackIndex; x++) {
94-
int scenarioOdds = contractDefinition.getScenarioOdds()
95-
.get(Compute.randomInt(contractDefinition.getScenarioOdds().size()));
96-
int deploymentTime = contractDefinition.getDeploymentTimes()
97-
.get(Compute.randomInt(contractDefinition.getDeploymentTimes().size()));
95+
int scenarioOdds = getScenarioOdds(contractDefinition);
96+
int deploymentTime = getDeploymentTime(contractDefinition);
9897

9998
StratconTrackState track = initializeTrackState(NUM_LANCES_PER_TRACK,
10099
scenarioOdds,
@@ -109,16 +108,24 @@ public static void initializeCampaignState(AtBContract contract, Campaign campai
109108
// required lances.
110109
int oddLanceCount = contract.getRequiredCombatTeams() % NUM_LANCES_PER_TRACK;
111110
if (oddLanceCount > 0) {
112-
int scenarioOdds = contractDefinition.getScenarioOdds()
113-
.get(Compute.randomInt(contractDefinition.getScenarioOdds().size()));
114-
int deploymentTime = contractDefinition.getDeploymentTimes()
115-
.get(Compute.randomInt(contractDefinition.getDeploymentTimes().size()));
111+
int scenarioOdds = getScenarioOdds(contractDefinition);
112+
int deploymentTime = getDeploymentTime(contractDefinition);
116113

117114
StratconTrackState track = initializeTrackState(oddLanceCount,
118115
scenarioOdds,
119116
deploymentTime,
120117
planetaryTemperature);
121-
track.setDisplayableName(String.format("Sector %d", campaignState.getTracks().size()));
118+
track.setDisplayableName(String.format("Sector %d", campaignState.getTrackCount()));
119+
campaignState.addTrack(track);
120+
}
121+
122+
// Last chance generation, to ensure we never generate a StratCon map with 0 tracks
123+
if (campaignState.getTrackCount() == 0) {
124+
int scenarioOdds = getScenarioOdds(contractDefinition);
125+
int deploymentTime = getDeploymentTime(contractDefinition);
126+
127+
StratconTrackState track = initializeTrackState(1, scenarioOdds, deploymentTime, planetaryTemperature);
128+
track.setDisplayableName(String.format("Sector %d", campaignState.getTrackCount()));
122129
campaignState.addTrack(track);
123130
}
124131

@@ -129,7 +136,7 @@ public static void initializeCampaignState(AtBContract contract, Campaign campai
129136
(int) Math.max(1,
130137
-objectiveParams.objectiveCount * contract.getRequiredCombatTeams());
131138

132-
List<Integer> trackObjects = trackObjectDistribution(objectiveCount, campaignState.getTracks().size());
139+
List<Integer> trackObjects = trackObjectDistribution(objectiveCount, campaignState.getTrackCount());
133140

134141
for (int x = 0; x < trackObjects.size(); x++) {
135142
int numObjects = trackObjects.get(x);
@@ -197,7 +204,7 @@ public static void initializeCampaignState(AtBContract contract, Campaign campai
197204
(int) (-contractDefinition.getAlliedFacilityCount() *
198205
contract.getRequiredCombatTeams());
199206

200-
List<Integer> trackObjects = trackObjectDistribution(facilityCount, campaignState.getTracks().size());
207+
List<Integer> trackObjects = trackObjectDistribution(facilityCount, campaignState.getTrackCount());
201208

202209
for (int x = 0; x < trackObjects.size(); x++) {
203210
int numObjects = trackObjects.get(x);
@@ -214,7 +221,7 @@ public static void initializeCampaignState(AtBContract contract, Campaign campai
214221
(int) contractDefinition.getHostileFacilityCount() :
215222
(int) (-contractDefinition.getHostileFacilityCount() * contract.getRequiredCombatTeams());
216223

217-
trackObjects = trackObjectDistribution(facilityCount, campaignState.getTracks().size());
224+
trackObjects = trackObjectDistribution(facilityCount, campaignState.getTrackCount());
218225

219226
for (int x = 0; x < trackObjects.size(); x++) {
220227
int numObjects = trackObjects.get(x);
@@ -283,10 +290,50 @@ public static void initializeCampaignState(AtBContract contract, Campaign campai
283290
// now we're done
284291
}
285292

293+
/**
294+
* Retrieves a random deployment time from the provided {@link StratconContractDefinition}.
295+
*
296+
* <p>The deployment time is selected randomly from the list of deployment times in the
297+
* given {@code StratconContractDefinition}.</p>
298+
*
299+
* @param contractDefinition the contract definition containing deployment time options
300+
*
301+
* @return a randomly selected deployment time
302+
*
303+
* @throws IllegalArgumentException if the list of deployment times is empty
304+
* @throws NullPointerException if {@code contractDefinition} or its deployment times list is null
305+
* @author Illiani
306+
* @since 0.50.05
307+
*/
308+
private static int getDeploymentTime(StratconContractDefinition contractDefinition) {
309+
return contractDefinition.getDeploymentTimes()
310+
.get(Compute.randomInt(contractDefinition.getDeploymentTimes().size()));
311+
}
312+
313+
/**
314+
* Retrieves a random scenario odds value from the provided {@link StratconContractDefinition}.
315+
*
316+
* <p>The scenario odds are selected randomly from the list of scenario odds in the
317+
* given {@code StratconContractDefinition}.</p>
318+
*
319+
* @param contractDefinition the contract definition containing scenario odds options
320+
*
321+
* @return a randomly selected scenario odds value
322+
*
323+
* @throws IllegalArgumentException if the list of scenario odds is empty
324+
* @throws NullPointerException if {@code contractDefinition} or its scenario odds list is null
325+
* @author Illiani
326+
* @since 0.50.05
327+
*/
328+
private static int getScenarioOdds(StratconContractDefinition contractDefinition) {
329+
return contractDefinition.getScenarioOdds().get(Compute.randomInt(contractDefinition.getScenarioOdds().size()));
330+
}
331+
286332
/**
287333
* Set up initial state of a track, dimensions are based on number of assigned lances.
288334
*/
289-
public static StratconTrackState initializeTrackState(int numLances, int scenarioOdds, int deploymentTime, int planetaryTemp) {
335+
public static StratconTrackState initializeTrackState(int numLances, int scenarioOdds, int deploymentTime,
336+
int planetaryTemp) {
290337
// to initialize a track,
291338
// 1. we set the # of required lances
292339
// 2. set the track size to a total of numlances * 28 hexes, a rectangle that is
@@ -325,6 +372,9 @@ public static StratconTrackState initializeTrackState(int numLances, int scenari
325372
* Generates an array list representing the number of objects to place in a given number of tracks.
326373
*/
327374
private static List<Integer> trackObjectDistribution(int numObjects, int numTracks) {
375+
// This ensures we're not at risk of dividing by 0
376+
numTracks = Math.max(1, numTracks);
377+
328378
List<Integer> retVal = new ArrayList<>();
329379
int leftOver = numObjects % numTracks;
330380

@@ -350,7 +400,8 @@ private static List<Integer> trackObjectDistribution(int numObjects, int numTrac
350400
* Avoids places with existing facilities and scenarios, capable of taking facility sub set and setting strategic
351401
* objective flag.
352402
*/
353-
private static void initializeTrackFacilities(StratconTrackState trackState, int numFacilities, ForceAlignment owner, boolean strategicObjective, List<String> modifiers) {
403+
private static void initializeTrackFacilities(StratconTrackState trackState, int numFacilities,
404+
ForceAlignment owner, boolean strategicObjective, List<String> modifiers) {
354405

355406
int trackSize = trackState.getWidth() * trackState.getHeight();
356407

@@ -424,7 +475,9 @@ private static void initializeTrackFacilities(StratconTrackState trackState, int
424475
* @param objectiveModifiers a list of optional {@link String} modifiers to apply to the generated scenarios; can be
425476
* {@code null} if no modifiers are required
426477
*/
427-
private static void initializeObjectiveScenarios(Campaign campaign, AtBContract contract, StratconTrackState trackState, int numScenarios, List<String> objectiveScenarios, List<String> objectiveModifiers) {
478+
private static void initializeObjectiveScenarios(Campaign campaign, AtBContract contract,
479+
StratconTrackState trackState, int numScenarios, List<String> objectiveScenarios,
480+
List<String> objectiveModifiers) {
428481
// pick scenario from subset
429482
// place it on the map somewhere nothing else has been placed yet
430483
// if it's a facility scenario, place the facility
@@ -551,7 +604,8 @@ private static void initializeObjectiveScenarios(Campaign campaign, AtBContract
551604
* @return a {@link StratconCoords} object representing the location of a suitable, unoccupied coordinate, or
552605
* {@code null} if no valid coordinates are available
553606
*/
554-
public static @Nullable StratconCoords getUnoccupiedCoords(StratconTrackState trackState, boolean allowPlayerFacilities, boolean allowPlayerForces, boolean emphasizeStrategicTargets) {
607+
public static @Nullable StratconCoords getUnoccupiedCoords(StratconTrackState trackState,
608+
boolean allowPlayerFacilities, boolean allowPlayerForces, boolean emphasizeStrategicTargets) {
555609
final int trackHeight = trackState.getHeight();
556610
final int trackWidth = trackState.getWidth();
557611

0 commit comments

Comments
 (0)