Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensured StratCon Campaigns Always Generate At Least One Track (Sentry) #6581

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 32 additions & 28 deletions MekHQ/src/mekhq/campaign/stratcon/StratconCampaignState.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
*/
package mekhq.campaign.stratcon;

import java.io.PrintWriter;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.Marshaller;
Expand All @@ -44,12 +50,6 @@
import mekhq.campaign.mission.AtBScenario;
import org.w3c.dom.Node;

import javax.xml.namespace.QName;
import java.io.PrintWriter;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

/**
* Contract-level state object for a StratCon campaign.
*
Expand Down Expand Up @@ -117,6 +117,10 @@ public List<StratconTrackState> getTracks() {
return tracks;
}

public int getTrackCount() {
return tracks.size();
}

public void addTrack(StratconTrackState track) {
tracks.add(track);
}
Expand Down Expand Up @@ -144,13 +148,13 @@ public int getSupportPoints() {
* Modifies the current support points by the specified amount.
*
* <p>
* This method increases or decreases the support points by the given number.
* It adds the value of {@code change} to the existing support points total.
* This can be used to reflect changes due to various gameplay events or actions.
* This method increases or decreases the support points by the given number. It adds the value of {@code change} to
* the existing support points total. This can be used to reflect changes due to various gameplay events or
* actions.
* </p>
*
* @param change The amount to adjust the support points by. Positive values will
* increase the support points, while negative values will decrease them.
* @param change The amount to adjust the support points by. Positive values will increase the support points, while
* negative values will decrease them.
*/
public void changeSupportPoints(int change) {
supportPoints += change;
Expand Down Expand Up @@ -210,10 +214,11 @@ public void useSupportPoints(int decrement) {
}

/**
* Convenience/speed method of determining whether or not a force with the given
* ID has been deployed to a track in this campaign.
* Convenience/speed method of determining whether or not a force with the given ID has been deployed to a track in
* this campaign.
*
* @param forceID the force ID to check
*
* @return Deployed or not.
*/
public boolean isForceDeployedHere(int forceID) {
Expand All @@ -227,8 +232,7 @@ public boolean isForceDeployedHere(int forceID) {
}

/**
* Removes the scenario with the given campaign scenario ID from any tracks
* where it's present
* Removes the scenario with the given campaign scenario ID from any tracks where it's present
*/
public void removeStratconScenario(int scenarioID) {
for (StratconTrackState trackState : tracks) {
Expand All @@ -240,23 +244,23 @@ public void removeStratconScenario(int scenarioID) {
* Retrieves the {@link StratconScenario} associated with a given {@link AtBScenario}.
*
* <p>
* This method searches through all {@link StratconTrackState} objects in the {@link StratconCampaignState}
* to find the first {@link StratconScenario} whose backing scenario matches the specified {@link AtBScenario}.
* If no such scenario is found, it returns {@code null}.
* This method searches through all {@link StratconTrackState} objects in the {@link StratconCampaignState} to find
* the first {@link StratconScenario} whose backing scenario matches the specified {@link AtBScenario}. If no such
* scenario is found, it returns {@code null}.
* </p>
*
* <strong>Usage:</strong>
* <p>
* Use this method to easily fetch the {@link StratconScenario} associated with the provided
* {@link AtBScenario}.
* Use this method to easily fetch the {@link StratconScenario} associated with the provided {@link AtBScenario}.
* </p>
*
* @param campaign The {@link Campaign} containing the data to search through.
* @param scenario The {@link AtBScenario} to find the corresponding {@link StratconScenario} for.
*
* @return The matching {@link StratconScenario}, or {@code null} if no corresponding scenario is found.
*/
public static @Nullable StratconScenario getStratconScenarioFromAtBScenario(Campaign campaign,
AtBScenario scenario) {
AtBScenario scenario) {
AtBContract contract = scenario.getContract(campaign);
if (contract == null) {
return null;
Expand All @@ -279,16 +283,16 @@ public void removeStratconScenario(int scenarioID) {
}

/**
* Serialize this instance of a campaign state to a PrintWriter
* Omits initial xml declaration
* Serialize this instance of a campaign state to a PrintWriter Omits initial xml declaration
*
* @param pw The destination print writer
*/
public void Serialize(PrintWriter pw) {
try {
JAXBContext context = JAXBContext.newInstance(StratconCampaignState.class);
JAXBElement<StratconCampaignState> stateElement = new JAXBElement<>(new QName(ROOT_XML_ELEMENT_NAME),
StratconCampaignState.class, this);
StratconCampaignState.class,
this);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FRAGMENT, true);
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Expand All @@ -299,10 +303,10 @@ public void Serialize(PrintWriter pw) {
}

/**
* Attempt to deserialize an instance of a Campaign State from the passed-in XML
* Node
* Attempt to deserialize an instance of a Campaign State from the passed-in XML Node
*
* @param xmlNode The node with the campaign state
*
* @return Possibly an instance of a StratconCampaignState
*/
public static StratconCampaignState Deserialize(Node xmlNode) {
Expand Down Expand Up @@ -331,8 +335,8 @@ public static StratconCampaignState Deserialize(Node xmlNode) {
}

/**
* This adapter provides a way to convert between a LocalDate and the ISO-8601 string
* representation of the date that is used for XML marshaling and unmarshalling in JAXB.
* This adapter provides a way to convert between a LocalDate and the ISO-8601 string representation of the date
* that is used for XML marshaling and unmarshalling in JAXB.
*/
public static class LocalDateAdapter extends XmlAdapter<String, LocalDate> {
@Override
Expand Down
88 changes: 71 additions & 17 deletions MekHQ/src/mekhq/campaign/stratcon/StratconContractInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ public class StratconContractInitializer {
/**
* Initializes the campaign state given a contract, campaign and contract definition
*/
public static void initializeCampaignState(AtBContract contract, Campaign campaign, StratconContractDefinition contractDefinition) {
public static void initializeCampaignState(AtBContract contract, Campaign campaign,
StratconContractDefinition contractDefinition) {
StratconCampaignState campaignState = new StratconCampaignState(contract);
campaignState.setBriefingText(contractDefinition.getBriefing() +
"<br/>" +
Expand All @@ -91,10 +92,8 @@ public static void initializeCampaignState(AtBContract contract, Campaign campai
int planetaryTemperature = campaign.getLocation().getPlanet().getTemperature(campaign.getLocalDate());

for (int x = 0; x < maximumTrackIndex; x++) {
int scenarioOdds = contractDefinition.getScenarioOdds()
.get(Compute.randomInt(contractDefinition.getScenarioOdds().size()));
int deploymentTime = contractDefinition.getDeploymentTimes()
.get(Compute.randomInt(contractDefinition.getDeploymentTimes().size()));
int scenarioOdds = getScenarioOdds(contractDefinition);
int deploymentTime = getDeploymentTime(contractDefinition);

StratconTrackState track = initializeTrackState(NUM_LANCES_PER_TRACK,
scenarioOdds,
Expand All @@ -109,16 +108,24 @@ public static void initializeCampaignState(AtBContract contract, Campaign campai
// required lances.
int oddLanceCount = contract.getRequiredCombatTeams() % NUM_LANCES_PER_TRACK;
if (oddLanceCount > 0) {
int scenarioOdds = contractDefinition.getScenarioOdds()
.get(Compute.randomInt(contractDefinition.getScenarioOdds().size()));
int deploymentTime = contractDefinition.getDeploymentTimes()
.get(Compute.randomInt(contractDefinition.getDeploymentTimes().size()));
int scenarioOdds = getScenarioOdds(contractDefinition);
int deploymentTime = getDeploymentTime(contractDefinition);

StratconTrackState track = initializeTrackState(oddLanceCount,
scenarioOdds,
deploymentTime,
planetaryTemperature);
track.setDisplayableName(String.format("Sector %d", campaignState.getTracks().size()));
track.setDisplayableName(String.format("Sector %d", campaignState.getTrackCount()));
campaignState.addTrack(track);
}

// Last chance generation, to ensure we never generate a StratCon map with 0 tracks
if (campaignState.getTrackCount() == 0) {
int scenarioOdds = getScenarioOdds(contractDefinition);
int deploymentTime = getDeploymentTime(contractDefinition);

StratconTrackState track = initializeTrackState(1, scenarioOdds, deploymentTime, planetaryTemperature);
track.setDisplayableName(String.format("Sector %d", campaignState.getTrackCount()));
campaignState.addTrack(track);
}

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

List<Integer> trackObjects = trackObjectDistribution(objectiveCount, campaignState.getTracks().size());
List<Integer> trackObjects = trackObjectDistribution(objectiveCount, campaignState.getTrackCount());

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

List<Integer> trackObjects = trackObjectDistribution(facilityCount, campaignState.getTracks().size());
List<Integer> trackObjects = trackObjectDistribution(facilityCount, campaignState.getTrackCount());

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

trackObjects = trackObjectDistribution(facilityCount, campaignState.getTracks().size());
trackObjects = trackObjectDistribution(facilityCount, campaignState.getTrackCount());

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

/**
* Retrieves a random deployment time from the provided {@link StratconContractDefinition}.
*
* <p>The deployment time is selected randomly from the list of deployment times in the
* given {@code StratconContractDefinition}.</p>
*
* @param contractDefinition the contract definition containing deployment time options
*
* @return a randomly selected deployment time
*
* @throws IllegalArgumentException if the list of deployment times is empty
* @throws NullPointerException if {@code contractDefinition} or its deployment times list is null
* @author Illiani
* @since 0.50.05
*/
private static int getDeploymentTime(StratconContractDefinition contractDefinition) {
return contractDefinition.getDeploymentTimes()
.get(Compute.randomInt(contractDefinition.getDeploymentTimes().size()));
}

/**
* Retrieves a random scenario odds value from the provided {@link StratconContractDefinition}.
*
* <p>The scenario odds are selected randomly from the list of scenario odds in the
* given {@code StratconContractDefinition}.</p>
*
* @param contractDefinition the contract definition containing scenario odds options
*
* @return a randomly selected scenario odds value
*
* @throws IllegalArgumentException if the list of scenario odds is empty
* @throws NullPointerException if {@code contractDefinition} or its scenario odds list is null
* @author Illiani
* @since 0.50.05
*/
private static int getScenarioOdds(StratconContractDefinition contractDefinition) {
return contractDefinition.getScenarioOdds().get(Compute.randomInt(contractDefinition.getScenarioOdds().size()));
}

/**
* Set up initial state of a track, dimensions are based on number of assigned lances.
*/
public static StratconTrackState initializeTrackState(int numLances, int scenarioOdds, int deploymentTime, int planetaryTemp) {
public static StratconTrackState initializeTrackState(int numLances, int scenarioOdds, int deploymentTime,
int planetaryTemp) {
// to initialize a track,
// 1. we set the # of required lances
// 2. set the track size to a total of numlances * 28 hexes, a rectangle that is
Expand Down Expand Up @@ -325,6 +372,9 @@ public static StratconTrackState initializeTrackState(int numLances, int scenari
* Generates an array list representing the number of objects to place in a given number of tracks.
*/
private static List<Integer> trackObjectDistribution(int numObjects, int numTracks) {
// This ensures we're not at risk of dividing by 0
numTracks = Math.max(1, numTracks);

List<Integer> retVal = new ArrayList<>();
int leftOver = numObjects % numTracks;

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

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

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

Expand Down
Loading