From 22da517753368b8d957a8141031626fab5487641 Mon Sep 17 00:00:00 2001 From: Scoppio Date: Wed, 15 Jan 2025 19:15:54 -0300 Subject: [PATCH] feat: uses ACAR to resolve refused scenarios. --- MekHQ/src/mekhq/MHQOptions.java | 14 +++- MekHQ/src/mekhq/campaign/Campaign.java | 25 +++--- .../campaign/autoresolve/AtBSetupForces.java | 22 ++++- .../mission/ScenarioObjectiveProcessor.java | 4 +- .../stratcon/StratconRulesManager.java | 81 +++++++++++++++---- 5 files changed, 112 insertions(+), 34 deletions(-) diff --git a/MekHQ/src/mekhq/MHQOptions.java b/MekHQ/src/mekhq/MHQOptions.java index 186f546d42d..e6b60468538 100644 --- a/MekHQ/src/mekhq/MHQOptions.java +++ b/MekHQ/src/mekhq/MHQOptions.java @@ -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"); @@ -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())); } public void setBelowContractMinimumForeground(Color value) { @@ -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) { @@ -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) { @@ -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())); } /** diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 560a80fafa6..234d0cc7f41 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -104,6 +104,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; @@ -158,6 +159,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; @@ -3939,23 +3942,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())) { 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."); } diff --git a/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java b/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java index aee7980a774..5590412d321 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java @@ -50,9 +50,13 @@ public class AtBSetupForces extends SetupForces { private final List units; private final AtBScenario scenario; private final ForceConsolidation forceConsolidationMethod; - + private final Set teamIds = new HashSet<>(); private final OrderFactory orderFactory; + public AtBSetupForces(Campaign campaign, List units, AtBScenario scenario) { + this(campaign, units, scenario, new SingletonForces(), new OrderFactory(campaign, scenario)); + } + public AtBSetupForces(Campaign campaign, List units, AtBScenario scenario, ForceConsolidation forceConsolidationMethod) { this(campaign, units, scenario, forceConsolidationMethod, new OrderFactory(campaign, scenario)); } @@ -63,6 +67,17 @@ public AtBSetupForces(Campaign campaign, List 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()); + } } /** @@ -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); diff --git a/MekHQ/src/mekhq/campaign/mission/ScenarioObjectiveProcessor.java b/MekHQ/src/mekhq/campaign/mission/ScenarioObjectiveProcessor.java index cfad77a3050..7845d015862 100644 --- a/MekHQ/src/mekhq/campaign/mission/ScenarioObjectiveProcessor.java +++ b/MekHQ/src/mekhq/campaign/mission/ScenarioObjectiveProcessor.java @@ -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; } diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index 64bacbf9373..da0389c20f9 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -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; @@ -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; @@ -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; @@ -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; @@ -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 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 * @@ -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 +// if (scenario.isTurningPoint()) { +// campaignState.updateVictoryPoints(-1); +// } track.removeScenario(scenario);