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

feat: uses ACAR to resolve refused scenarios. #5792

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 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
14 changes: 10 additions & 4 deletions MekHQ/src/mekhq/MHQOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
import mekhq.gui.enums.PersonnelFilterStyle;

public final class MHQOptions extends SuiteOptions {

// region Better Colors
private static final Color CRIMSON_RED = new Color(0xB22222);

// endregion Better Colors

// region Display Tab
public String getDisplayDateFormat() {
return userPreferences.node(MHQConstants.DISPLAY_NODE).get(MHQConstants.DISPLAY_DATE_FORMAT, "yyyy-MM-dd");
Expand Down Expand Up @@ -242,7 +248,7 @@ public void setDeployedBackground(Color value) {

public Color getBelowContractMinimumForeground() {
return new Color(userPreferences.node(MHQConstants.DISPLAY_NODE)
.getInt(MHQConstants.BELOW_CONTRACT_MINIMUM_FOREGROUND, Color.RED.getRGB()));
.getInt(MHQConstants.BELOW_CONTRACT_MINIMUM_FOREGROUND, CRIMSON_RED.getRGB()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're updating this, it would be better to use the negative or warning event color, so users can configure it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe, but I dont know what you are referencing here

}

public void setBelowContractMinimumForeground(Color value) {
Expand Down Expand Up @@ -435,7 +441,7 @@ public void setLoanOverdueForeground(Color value) {

public Color getLoanOverdueBackground() {
return new Color(userPreferences.node(MHQConstants.DISPLAY_NODE).getInt(MHQConstants.LOAN_OVERDUE_BACKGROUND,
Color.RED.getRGB()));
CRIMSON_RED.getRGB()));
}

public void setLoanOverdueBackground(Color value) {
Expand All @@ -453,7 +459,7 @@ public void setInjuredForeground(Color value) {

public Color getInjuredBackground() {
return new Color(userPreferences.node(MHQConstants.DISPLAY_NODE).getInt(MHQConstants.INJURED_BACKGROUND,
Color.RED.getRGB()));
CRIMSON_RED.getRGB()));
}

public void setInjuredBackground(Color value) {
Expand Down Expand Up @@ -562,7 +568,7 @@ public void setStratConHexCoordForeground(Color value) {

public Color getFontColorNegative() {
return new Color(userPreferences.node(MHQConstants.DISPLAY_NODE).getInt(MHQConstants.FONT_COLOR_NEGATIVE,
Color.RED.getRGB()));
CRIMSON_RED.getRGB()));
}

/**
Expand Down
25 changes: 14 additions & 11 deletions MekHQ/src/mekhq/campaign/Campaign.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
import mekhq.campaign.rating.IUnitRating;
import mekhq.campaign.rating.UnitRatingMethod;
import mekhq.campaign.storyarc.StoryArc;
import mekhq.campaign.stratcon.StratconCampaignState;
import mekhq.campaign.stratcon.StratconContractInitializer;
import mekhq.campaign.stratcon.StratconRulesManager;
import mekhq.campaign.stratcon.StratconTrackState;
Expand Down Expand Up @@ -162,6 +163,8 @@
import static mekhq.campaign.personnel.education.EducationController.getAcademy;
import static mekhq.campaign.personnel.education.TrainingCombatTeams.processTrainingCombatTeams;
import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.Payout.isBreakingContract;
import static mekhq.campaign.stratcon.StratconRulesManager.processIgnoredScenario;
import static mekhq.campaign.stratcon.StratconRulesManager.simulateTheResultsOfIgnoredScenario;
import static mekhq.campaign.stratcon.SupportPointNegotiation.negotiateAdditionalSupportPoints;
import static mekhq.campaign.unit.Unit.SITE_FACILITY_BASIC;
import static mekhq.campaign.universe.Factions.getFactionLogo;
Expand Down Expand Up @@ -3966,23 +3969,23 @@ && getLocation().getJumpPath().getLastSystem().getId().equals(contract.getSystem

for (final Scenario scenario : contract.getCurrentAtBScenarios()) {
if ((scenario.getDate() != null) && scenario.getDate().isBefore(getLocalDate())) {
if (getCampaignOptions().isUseStratCon() && (scenario instanceof AtBDynamicScenario)) {
final boolean stub = StratconRulesManager.processIgnoredScenario(
(AtBDynamicScenario) scenario, contract.getStratconCampaignState());
var finalState = ScenarioStatus.REFUSED_ENGAGEMENT;

if (stub) {
if (getCampaignOptions().isUseStratCon() && (scenario instanceof AtBDynamicScenario atBDynamicScenario)) {

// if (processIgnoredScenario(atBDynamicScenario, contract.getStratconCampaignState())) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're confident we're making this move, we probably want to remove the code rather than commenting it out.

if (scenario.getStratConScenarioType().isResupply()) {
processAbandonedConvoy(this, contract, (AtBDynamicScenario) scenario);
scenario.convertToStub(this, finalState);
} else {
simulateTheResultsOfIgnoredScenario(this, atBDynamicScenario, contract.getStratconCampaignState());
}

scenario.convertToStub(this, ScenarioStatus.REFUSED_ENGAGEMENT);
} else {
scenario.clearAllForcesAndPersonnel(this);
}
// } else {
// scenario.clearAllForcesAndPersonnel(this);
// }
} else {
scenario.convertToStub(this, ScenarioStatus.REFUSED_ENGAGEMENT);
scenario.convertToStub(this, finalState);
contract.addPlayerMinorBreach();

addReport("Failure to deploy for " + scenario.getName()
+ " resulted in a minor contract breach.");
}
Expand Down
22 changes: 21 additions & 1 deletion MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,13 @@ public class AtBSetupForces extends SetupForces {
private final List<Unit> units;
private final AtBScenario scenario;
private final ForceConsolidation forceConsolidationMethod;

private final Set<Integer> teamIds = new HashSet<>();
private final OrderFactory orderFactory;

public AtBSetupForces(Campaign campaign, List<Unit> units, AtBScenario scenario) {
this(campaign, units, scenario, new SingletonForces(), new OrderFactory(campaign, scenario));
}

public AtBSetupForces(Campaign campaign, List<Unit> units, AtBScenario scenario, ForceConsolidation forceConsolidationMethod) {
this(campaign, units, scenario, forceConsolidationMethod, new OrderFactory(campaign, scenario));
}
Expand All @@ -63,6 +67,17 @@ public AtBSetupForces(Campaign campaign, List<Unit> units, AtBScenario scenario,
this.scenario = scenario;
this.forceConsolidationMethod = forceConsolidationMethod;
this.orderFactory = orderFactory;
setupTeamIds();
}

private void setupTeamIds() {
if (!units.isEmpty()) {
teamIds.add(1);
}
for (int i = 0; i < scenario.getNumBots(); i++) {
BotForce bf = scenario.getBotForce(i);
teamIds.add(bf.getTeam());
}
}

/**
Expand All @@ -84,6 +99,11 @@ public void addOrdersToForces(SimulationContext context) {
context.getOrders().resetOrders();
}

@Override
public boolean isTeamPresent(int teamId) {
return teamIds.contains(teamId);
}

private static class FailedToConvertForceToFormationException extends RuntimeException {
public FailedToConvertForceToFormationException(Throwable cause) {
super(cause);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,9 @@ public boolean objectiveMet(ScenarioObjective objective, int qualifyingUnitCount
}

double potentialObjectiveUnitCount = getPotentialObjectiveUnits().get(objective).size();

if (potentialObjectiveUnitCount == 0.0) {
return false;
}
return qualifyingUnitCount / potentialObjectiveUnitCount >= (double) objective.getPercentage() / 100;
}

Expand Down
81 changes: 64 additions & 17 deletions MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@
*/
package mekhq.campaign.stratcon;

import megamek.common.Board;
import megamek.common.Minefield;
import megamek.common.TargetRoll;
import megamek.common.annotations.Nullable;
import megamek.common.autoresolve.Resolver;
import megamek.common.autoresolve.acar.SimulatedClient;
import megamek.common.autoresolve.acar.SimulationOptions;
import megamek.common.autoresolve.event.AutoResolveConcludedEvent;
import megamek.common.event.Subscribe;
import megamek.logging.MMLogger;
import mekhq.MHQConstants;
import mekhq.MekHQ;
import mekhq.campaign.Campaign;
import mekhq.campaign.ResolveScenarioTracker;
import mekhq.campaign.autoresolve.AtBSetupForces;
import mekhq.campaign.event.NewDayEvent;
import mekhq.campaign.event.ScenarioChangedEvent;
import mekhq.campaign.event.StratconDeploymentEvent;
Expand All @@ -40,6 +46,7 @@
import mekhq.campaign.mission.enums.AtBMoraleLevel;
import mekhq.campaign.mission.enums.CombatRole;
import mekhq.campaign.mission.enums.ContractCommandRights;
import mekhq.campaign.mission.enums.ScenarioStatus;
import mekhq.campaign.mission.resupplyAndCaches.StarLeagueCache;
import mekhq.campaign.mission.resupplyAndCaches.StarLeagueCache.CacheType;
import mekhq.campaign.personnel.Person;
Expand All @@ -51,14 +58,15 @@
import mekhq.campaign.unit.Unit;
import org.apache.commons.math3.util.Pair;

import javax.swing.*;
import java.awt.*;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;

import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.round;
import static java.lang.Math.*;
import static megamek.codeUtilities.ObjectUtility.getRandomItem;
import static megamek.common.Compute.d6;
import static megamek.common.Compute.randomInt;
Expand All @@ -69,19 +77,11 @@
import static mekhq.campaign.mission.AtBDynamicScenarioFactory.finalizeScenario;
import static mekhq.campaign.mission.ScenarioForceTemplate.ForceAlignment.Allied;
import static mekhq.campaign.mission.ScenarioForceTemplate.ForceAlignment.Opposing;
import static mekhq.campaign.mission.ScenarioMapParameters.MapLocation.AllGroundTerrain;
import static mekhq.campaign.mission.ScenarioMapParameters.MapLocation.LowAtmosphere;
import static mekhq.campaign.mission.ScenarioMapParameters.MapLocation.Space;
import static mekhq.campaign.mission.ScenarioMapParameters.MapLocation.SpecificGroundTerrain;
import static mekhq.campaign.personnel.SkillType.S_ADMIN;
import static mekhq.campaign.personnel.SkillType.S_TACTICS;
import static mekhq.campaign.personnel.SkillType.getSkillHash;
import static mekhq.campaign.mission.ScenarioMapParameters.MapLocation.*;
import static mekhq.campaign.personnel.SkillType.*;
import static mekhq.campaign.stratcon.StratconContractInitializer.getUnoccupiedCoords;
import static mekhq.campaign.stratcon.StratconRulesManager.ReinforcementEligibilityType.AUXILIARY;
import static mekhq.campaign.stratcon.StratconRulesManager.ReinforcementResultsType.DELAYED;
import static mekhq.campaign.stratcon.StratconRulesManager.ReinforcementResultsType.FAILED;
import static mekhq.campaign.stratcon.StratconRulesManager.ReinforcementResultsType.INTERCEPTED;
import static mekhq.campaign.stratcon.StratconRulesManager.ReinforcementResultsType.SUCCESS;
import static mekhq.campaign.stratcon.StratconRulesManager.ReinforcementResultsType.*;
import static mekhq.campaign.stratcon.StratconScenarioFactory.convertSpecificUnitTypeToGeneral;
import static mekhq.utilities.ReportingUtilities.CLOSING_SPAN_TAG;
import static mekhq.utilities.ReportingUtilities.spanOpeningWithCustomColor;
Expand Down Expand Up @@ -2749,6 +2749,52 @@ public static boolean processIgnoredScenario(AtBDynamicScenario scenario, Stratc

}

/**
* Processes an ignored dynamic scenario - locates it on one of the tracks and
* checks if it should or should not run ACAR to resolve the scenario automatically.
* It will run the scenario if the team 1 is currently present in the scenario, as in
* attached allied forces.
*/
public static void simulateTheResultsOfIgnoredScenario(Campaign campaign, AtBDynamicScenario atbScenario, StratconCampaignState campaignState) {
var setupForces = new AtBSetupForces(campaign, new ArrayList<>(), atbScenario);
if (setupForces.isTeamPresent(1)) {
var resolver = Resolver.simulationRun(new AtBSetupForces(campaign, new ArrayList<>(), atbScenario),
SimulationOptions.empty(),
new Board(atbScenario.getMapX(), atbScenario.getMapY()));
var result = resolver.resolveSimulation();

var tracker = new ResolveScenarioTracker(atbScenario, campaign, false);
tracker.setClient(new SimulatedClient(result.getGame()));
tracker.setEvent(result);
tracker.processGame();

var objectiveProcessor = new ScenarioObjectiveProcessor();
var objectives = atbScenario.getScenarioObjectives();
objectiveProcessor.evaluateScenarioObjectives(tracker);

var objectiveUnits = objectiveProcessor.getQualifyingObjectiveUnits();
Map<ScenarioObjective, Integer> objectiveUnitCounts = new HashMap<>();
for (var entry : objectiveUnits.entrySet()) {
objectiveUnitCounts.put(entry.getKey(), entry.getValue().size());
}

ScenarioStatus scenarioStatus = objectiveProcessor.determineScenarioStatus(tracker.getScenario(), new HashMap<>(), objectiveUnitCounts);
StringBuilder report = new StringBuilder();
for (var objective : objectives) {
report.append(objectiveProcessor.processObjective(campaign, objective, objectiveProcessor.getPotentialObjectiveUnits().get(objective).size(), null, tracker, false));
}
tracker.resolveScenario(scenarioStatus, report.toString());
processScenarioCompletion(tracker);
} else {
if (processIgnoredScenario(atbScenario, campaignState)) {
atbScenario.convertToStub(campaign, ScenarioStatus.REFUSED_ENGAGEMENT);
} else {
atbScenario.clearAllForcesAndPersonnel(campaign);
}
}

}

/**
* Processes an ignored Stratcon scenario
*
Expand All @@ -2759,9 +2805,10 @@ public static boolean processIgnoredScenario(StratconScenario scenario, Stratcon
for (StratconTrackState track : campaignState.getTracks()) {
if (track.getScenarios().containsKey(scenario.getCoords())) {
// subtract VP if scenario is 'required'
if (scenario.isTurningPoint()) {
campaignState.updateVictoryPoints(-1);
}
// this is already processed in the simulateTheResultsOfIgnoredScenario
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, too. If we're removing this functionality let's remove the dead code instead of just commenting it out.

// if (scenario.isTurningPoint()) {
// campaignState.updateVictoryPoints(-1);
// }

track.removeScenario(scenario);

Expand Down
Loading