From 95e133d4de3d3e90c98f36913cab62326311d4f9 Mon Sep 17 00:00:00 2001 From: Scoppio Date: Thu, 2 Jan 2025 20:07:41 -0300 Subject: [PATCH 1/6] feat: utf-8 for i18n properties, new report for start of scenario on acar, fix for formation values and player name on acar --- .../resources/acs-report-messages.properties | 6 ++ .../acar/phase/StartingScenarioPhase.java | 5 ++ .../acar/report/HtmlGameLogger.java | 8 +- .../report/StartingScenarioPhaseReporter.java | 90 +++++++++++++++++++ .../autoresolve/component/Formation.java | 9 +- .../autoresolve/converter/SetupForces.java | 2 +- .../mekhq/utilities/Internationalization.java | 21 ++++- 7 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/StartingScenarioPhaseReporter.java diff --git a/MekHQ/resources/mekhq/resources/acs-report-messages.properties b/MekHQ/resources/mekhq/resources/acs-report-messages.properties index 2a089aeee49..7dcdd4822b5 100644 --- a/MekHQ/resources/mekhq/resources/acs-report-messages.properties +++ b/MekHQ/resources/mekhq/resources/acs-report-messages.properties @@ -34,6 +34,12 @@ # You should have received a copy of the GNU General Public License # along with MekHQ. If not, see . # +100=

Abstract Combat Auto Resolution

+ +101=

● Starting Scenario Phase

+102=Team {0} Formations: +103={0} has {1} units. +104={0}, Armor: {1}%, Structure: {2}%, Crew: {3}, Hits: {4} 999= 1000=

○ Initiative Phase for Round {0}

diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/StartingScenarioPhase.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/StartingScenarioPhase.java index 20afcbaa66e..bb5805b82d4 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/StartingScenarioPhase.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/StartingScenarioPhase.java @@ -21,6 +21,7 @@ import megamek.common.enums.GamePhase; import mekhq.campaign.autoresolve.acar.SimulationManager; +import mekhq.campaign.autoresolve.acar.report.StartingScenarioPhaseReporter; public class StartingScenarioPhase extends PhaseHandler { @@ -35,5 +36,9 @@ protected void executePhase() { getSimulationManager().calculatePlayerInitialCounts(); getSimulationManager().getGame().setupTeams(); getSimulationManager().getGame().setupDeployment(); + + var reporter = new StartingScenarioPhaseReporter(getSimulationManager().getGame(), getSimulationManager()::addReport); + reporter.startingScenarioHeader(); + reporter.formationsSetup(getSimulationManager()); } } diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/HtmlGameLogger.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/HtmlGameLogger.java index 15276db4973..568d84d1448 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/HtmlGameLogger.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/HtmlGameLogger.java @@ -52,16 +52,18 @@ protected void initialize() { Simulation Game Log - """); - appendRaw(""" """ ); - appendRaw("

Log file opened " + LocalDateTime.now() + "

"); + appendRaw("

Log file opened " + LocalDateTime.now() + "

"); } } diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/StartingScenarioPhaseReporter.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/StartingScenarioPhaseReporter.java new file mode 100644 index 00000000000..bb828e215d8 --- /dev/null +++ b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/StartingScenarioPhaseReporter.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MekHQ. + * + * MekHQ is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MekHQ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MekHQ. If not, see . + */ + +package mekhq.campaign.autoresolve.acar.report; + +import megamek.common.Entity; +import megamek.common.IGame; +import megamek.common.Player; +import mekhq.campaign.autoresolve.acar.SimulationContext; +import mekhq.campaign.autoresolve.acar.SimulationManager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.function.Consumer; + +public class StartingScenarioPhaseReporter { + + private final IGame game; + private final Consumer reportConsumer; + + public StartingScenarioPhaseReporter(IGame game, Consumer reportConsumer) { + this.reportConsumer = reportConsumer; + this.game = game; + } + + public void startingScenarioHeader() { + reportConsumer.accept(new PublicReportEntry(100)); + reportConsumer.accept(new PublicReportEntry(101)); + } + + public void formationsSetup(SimulationManager gameManager) { + var players = gameManager.getGame().getPlayersList(); + var teamMap = new HashMap>(); + + for (var player : players) { + var team = player.getTeam(); + if (!teamMap.containsKey(team)) { + teamMap.put(team, new ArrayList<>()); + } + teamMap.get(team).add(player); + } + + for (var team : teamMap.keySet()) { + var teamPlayers = teamMap.get(team); + var teamReport = new PublicReportEntry(102).add(team); + reportConsumer.accept(teamReport); + for (var player : teamPlayers) { + playerFinalReport(player); + } + } + } + + private void playerFinalReport(Player player) { + var playerEntities = game.getInGameObjects().stream() + .filter(e -> e.getOwnerId() == player.getId()) + .filter(Entity.class::isInstance) + .map(Entity.class::cast) + .toList(); + + reportConsumer.accept(new PublicReportEntry(103).add(new PlayerNameReportEntry(player).reportText()) + .add(playerEntities.size()).indent()); + + for (var entity : playerEntities) { + reportConsumer.accept(new PublicReportEntry(104) + .add(new EntityNameReportEntry(entity).reportText()) + .add(String.format("%.2f%%", (entity).getArmorRemainingPercent() * 100)) + .add(String.format("%.2f%%", (entity).getInternalRemainingPercent() * 100)) + .add(entity.getCrew().getName()) + .add(entity.getCrew().getHits()) + .indent(2)); + } + } +} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/component/Formation.java b/MekHQ/src/mekhq/campaign/autoresolve/component/Formation.java index 2483c1bcf6b..f021389d52f 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/component/Formation.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/component/Formation.java @@ -21,6 +21,7 @@ import megamek.ai.utility.Memory; import megamek.common.Entity; +import megamek.common.ToHitData; import megamek.common.alphaStrike.ASDamageVector; import megamek.common.alphaStrike.ASRange; import megamek.common.strategicBattleSystems.SBFFormation; @@ -105,7 +106,6 @@ public boolean hadHighStressEpisode() { return highStressEpisode; } - /** * Checks if the formation is crippled. Rules as described on Interstellar Operations BETA pg 242 - Crippling Damage * @return true in case it is crippled @@ -170,7 +170,7 @@ public int getTmm() { @Override public int getSkill() { if (getUnits().isEmpty()) { - return 0; + return ToHitData.AUTOMATIC_FAIL; } return getUnits().stream().mapToInt(SBFUnit::getSkill).sum() / getUnits().size(); } @@ -178,12 +178,11 @@ public int getSkill() { @Override public int getTactics() { if (getUnits().isEmpty()) { - return 0; + return ToHitData.AUTOMATIC_FAIL; } var movement = getMovement(); var skill = getSkill(); - var tactics = Math.max(0, 10 - movement + skill - 4); - return tactics; + return Math.max(0, 6 - movement + skill); } @Override diff --git a/MekHQ/src/mekhq/campaign/autoresolve/converter/SetupForces.java b/MekHQ/src/mekhq/campaign/autoresolve/converter/SetupForces.java index 72a9f7b82f8..aeb1c984afe 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/converter/SetupForces.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/converter/SetupForces.java @@ -153,7 +153,7 @@ private void setupBots(SimulationContext game) { */ private Player getCleanPlayer() { var campaignPlayer = campaign.getPlayer(); - var player = new Player(campaignPlayer.getId(), campaignPlayer.getName()); + var player = new Player(campaignPlayer.getId(), campaign.getName()); player.setCamouflage(campaign.getCamouflage().clone()); player.setColour(campaign.getColour()); player.setStartingPos(scenario.getStartingPos()); diff --git a/MekHQ/src/mekhq/utilities/Internationalization.java b/MekHQ/src/mekhq/utilities/Internationalization.java index 057b3b72350..564b6e991b9 100644 --- a/MekHQ/src/mekhq/utilities/Internationalization.java +++ b/MekHQ/src/mekhq/utilities/Internationalization.java @@ -21,7 +21,13 @@ import mekhq.MekHQ; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.text.MessageFormat; +import java.util.Locale; +import java.util.PropertyResourceBundle; import java.util.ResourceBundle; import java.util.concurrent.ConcurrentHashMap; @@ -43,9 +49,22 @@ public static Internationalization getInstance() { return instance; } + private static class UTF8Control extends ResourceBundle.Control { + @Override + public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) + throws IOException { + // The below is one approach; there are multiple ways to do this + String resourceName = toResourceName(toBundleName(baseName, locale), "properties"); + try (InputStream is = loader.getResourceAsStream(resourceName); + InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) { + return new PropertyResourceBundle(isr); + } + } + } + ResourceBundle getResourceBundle(String bundleName) { return resourceBundles.computeIfAbsent(bundleName, k -> - ResourceBundle.getBundle(PREFIX + bundleName, MekHQ.getMHQOptions().getLocale())); + ResourceBundle.getBundle(PREFIX + bundleName, MekHQ.getMHQOptions().getLocale(), new UTF8Control())); } /** From 67cb1d3c7730fe6d51e21cefea336d6945808055 Mon Sep 17 00:00:00 2001 From: Scoppio Date: Sat, 4 Jan 2025 18:40:07 -0300 Subject: [PATCH 2/6] feat: moves ACAR to MM, changes SetupForces to be an extension of SetupForces from an abstract class --- ...esolveBehaviorSettingsDialog_en.properties | 4 + .../resources/AutoResolveMethod.properties | 26 +- .../resources/AutoResolveMethod_en.properties | 18 + .../resources/mekhq/resources/GUI.properties | 1 + .../resources/acs-report-messages.properties | 115 --- .../acs-report-messages_en.properties | 0 .../mekhq/resources/messages.properties | 1 - MekHQ/src/mekhq/MekHQ.java | 37 +- .../campaign/ResolveScenarioTracker.java | 2 +- .../autoresolve/AutoResolveMethod.java | 29 +- .../mekhq/campaign/autoresolve/Resolver.java | 73 -- .../{converter => }/SetupForces.java | 32 +- .../autoresolve/acar/SimulatedClient.java | 98 --- .../autoresolve/acar/SimulationContext.java | 700 ------------------ .../autoresolve/acar/SimulationManager.java | 266 ------- .../autoresolve/acar/SimulationOptions.java | 99 --- .../acar/action/AbstractAttackAction.java | 46 -- .../autoresolve/acar/action/Action.java | 51 -- .../acar/action/ActionHandler.java | 71 -- .../autoresolve/acar/action/AttackAction.java | 29 - .../acar/action/AttackToHitData.java | 142 ---- .../acar/action/EngagementControlAction.java | 68 -- .../action/EngagementControlToHitData.java | 124 ---- .../acar/action/ManeuverToHitData.java | 69 -- .../acar/action/MoraleCheckAction.java | 54 -- .../acar/action/RecoveringNerveAction.java | 53 -- .../RecoveringNerveActionToHitData.java | 56 -- .../acar/action/StandardUnitAttack.java | 102 --- .../acar/action/WithdrawAction.java | 52 -- .../acar/action/WithdrawToHitData.java | 72 -- .../acar/handler/AbstractActionHandler.java | 56 -- .../EngagementAndControlActionHandler.java | 142 ---- .../handler/MoraleCheckActionHandler.java | 73 -- .../handler/RecoveringNerveActionHandler.java | 70 -- .../handler/StandardUnitAttackHandler.java | 275 ------- .../acar/handler/WithdrawActionHandler.java | 101 --- .../acar/manager/ActionsProcessor.java | 65 -- .../acar/manager/InitiativeHelper.java | 206 ------ .../acar/manager/PhaseEndManager.java | 92 --- .../acar/manager/PhasePreparationManager.java | 60 -- .../acar/manager/SimulationManagerHelper.java | 41 - .../acar/manager/VictoryHelper.java | 98 --- .../acar/phase/DeploymentPhase.java | 39 - .../autoresolve/acar/phase/EndPhase.java | 146 ---- .../autoresolve/acar/phase/FiringPhase.java | 224 ------ .../acar/phase/InitiativePhase.java | 39 - .../autoresolve/acar/phase/MovementPhase.java | 171 ----- .../autoresolve/acar/phase/PhaseHandler.java | 58 -- .../acar/phase/StartingScenarioPhase.java | 44 -- .../autoresolve/acar/phase/VictoryPhase.java | 87 --- .../acar/report/AttackReporter.java | 115 --- .../acar/report/EndPhaseReporter.java | 64 -- .../report/EngagementAndControlReporter.java | 75 -- .../acar/report/EntityNameReportEntry.java | 50 -- .../acar/report/FormationReportEntry.java | 47 -- .../acar/report/HtmlGameLogger.java | 114 --- .../acar/report/MoraleReporter.java | 70 -- .../acar/report/PlayerNameReportEntry.java | 50 -- .../acar/report/PublicReportEntry.java | 153 ---- .../report/RecoveringNerveActionReporter.java | 63 -- .../acar/report/ReportMessages.java | 84 --- .../acar/report/RollReportEntry.java | 66 -- .../report/StartingScenarioPhaseReporter.java | 90 --- .../acar/report/UnitReportEntry.java | 58 -- .../acar/report/VictoryPhaseReporter.java | 126 ---- .../acar/report/WithdrawReporter.java | 71 -- .../autoresolve/component/AcTurn.java | 40 - .../component/EngagementControl.java | 28 - .../autoresolve/component/Formation.java | 208 ------ .../autoresolve/component/FormationTurn.java | 47 -- .../converter/BalancedConsolidateForces.java | 147 ---- .../converter/ConsolidateForces.java | 108 --- .../converter/ForceToFormationConverter.java | 105 --- .../event/AutoResolveConcludedEvent.java | 172 ----- .../universe/SocioIndustrialData.java | 2 +- MekHQ/src/mekhq/gui/BriefingTab.java | 12 +- .../gui/dialog/AutoResolveChanceDialog.java | 20 +- ...AutoResolveBehaviorSettingsHelpDialog.java | 9 +- .../AutoResolveSimulationLogDialog.java | 20 - .../mekhq/utilities/Internationalization.java | 114 --- .../MHQInternationalization.java} | 20 +- .../campaign/autoresolve/ResolverTest.java | 11 +- 82 files changed, 119 insertions(+), 6817 deletions(-) create mode 100644 MekHQ/resources/mekhq/resources/AutoResolveBehaviorSettingsDialog_en.properties create mode 100644 MekHQ/resources/mekhq/resources/AutoResolveMethod_en.properties delete mode 100644 MekHQ/resources/mekhq/resources/acs-report-messages.properties delete mode 100644 MekHQ/resources/mekhq/resources/acs-report-messages_en.properties delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/Resolver.java rename MekHQ/src/mekhq/campaign/autoresolve/{converter => }/SetupForces.java (95%) delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/SimulatedClient.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/SimulationContext.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/SimulationManager.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/SimulationOptions.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/AbstractAttackAction.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/Action.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/ActionHandler.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/AttackAction.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/AttackToHitData.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/EngagementControlAction.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/EngagementControlToHitData.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/ManeuverToHitData.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/MoraleCheckAction.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/RecoveringNerveAction.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/RecoveringNerveActionToHitData.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/StandardUnitAttack.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/WithdrawAction.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/action/WithdrawToHitData.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/handler/AbstractActionHandler.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/handler/EngagementAndControlActionHandler.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/handler/MoraleCheckActionHandler.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/handler/RecoveringNerveActionHandler.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/handler/StandardUnitAttackHandler.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/handler/WithdrawActionHandler.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/manager/ActionsProcessor.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/manager/InitiativeHelper.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/manager/PhaseEndManager.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/manager/PhasePreparationManager.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/manager/SimulationManagerHelper.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/manager/VictoryHelper.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/phase/DeploymentPhase.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/phase/EndPhase.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/phase/FiringPhase.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/phase/InitiativePhase.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/phase/MovementPhase.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/phase/PhaseHandler.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/phase/StartingScenarioPhase.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/phase/VictoryPhase.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/AttackReporter.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/EndPhaseReporter.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/EngagementAndControlReporter.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/EntityNameReportEntry.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/FormationReportEntry.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/HtmlGameLogger.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/MoraleReporter.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/PlayerNameReportEntry.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/PublicReportEntry.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/RecoveringNerveActionReporter.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/ReportMessages.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/RollReportEntry.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/StartingScenarioPhaseReporter.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/UnitReportEntry.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/VictoryPhaseReporter.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/acar/report/WithdrawReporter.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/component/AcTurn.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/component/EngagementControl.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/component/Formation.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/component/FormationTurn.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/converter/BalancedConsolidateForces.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/converter/ConsolidateForces.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/converter/ForceToFormationConverter.java delete mode 100644 MekHQ/src/mekhq/campaign/autoresolve/event/AutoResolveConcludedEvent.java delete mode 100644 MekHQ/src/mekhq/gui/dialog/helpDialogs/AutoResolveSimulationLogDialog.java delete mode 100644 MekHQ/src/mekhq/utilities/Internationalization.java rename MekHQ/src/mekhq/{campaign/autoresolve/acar/report/ReportHeader.java => utilities/MHQInternationalization.java} (58%) diff --git a/MekHQ/resources/mekhq/resources/AutoResolveBehaviorSettingsDialog_en.properties b/MekHQ/resources/mekhq/resources/AutoResolveBehaviorSettingsDialog_en.properties new file mode 100644 index 00000000000..bf4ced59a46 --- /dev/null +++ b/MekHQ/resources/mekhq/resources/AutoResolveBehaviorSettingsDialog_en.properties @@ -0,0 +1,4 @@ +AutoResolveBehaviorSettingsDialog.title=Auto Resolve Help +AutoResolveBehaviorSettingsDialog.autoResolveHelpPath=docs/help/en/AutoResolve.html +AutoResolveBehaviorSettingsDialog.help=Auto Resolve Help +AutoResolveBehaviorSettingsDialog.helpTooltip=Open the Auto Resolve Help documentation in a new window \ No newline at end of file diff --git a/MekHQ/resources/mekhq/resources/AutoResolveMethod.properties b/MekHQ/resources/mekhq/resources/AutoResolveMethod.properties index 218aa6eed6e..1e22369405c 100644 --- a/MekHQ/resources/mekhq/resources/AutoResolveMethod.properties +++ b/MekHQ/resources/mekhq/resources/AutoResolveMethod.properties @@ -1,21 +1,15 @@ +# Copyright (c) 2025 - The MegaMek Team. All Rights Reserved. # -# Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. -# -# This file is part of MekHQ. -# -# MekHQ is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# MekHQ is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with MekHQ. If not, see . +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. # +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + AutoResolveMethod.PRINCESS.text=Princess AutoResolveMethod.PRINCESS.toolTipText=Princess plays the game for you. AutoResolveMethod.ABSTRACT_COMBAT.text=Abstract Combat Auto Resolution diff --git a/MekHQ/resources/mekhq/resources/AutoResolveMethod_en.properties b/MekHQ/resources/mekhq/resources/AutoResolveMethod_en.properties new file mode 100644 index 00000000000..1e22369405c --- /dev/null +++ b/MekHQ/resources/mekhq/resources/AutoResolveMethod_en.properties @@ -0,0 +1,18 @@ +# Copyright (c) 2025 - The MegaMek Team. All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +AutoResolveMethod.PRINCESS.text=Princess +AutoResolveMethod.PRINCESS.toolTipText=Princess plays the game for you. +AutoResolveMethod.ABSTRACT_COMBAT.text=Abstract Combat Auto Resolution +AutoResolveMethod.ABSTRACT_COMBAT.toolTipText=ACAR, a fast simulation using a subset of the abstract combat system rules +AutoResolveMethod.promptForAutoResolveMethod.text=Select the method to use for auto resolving the scenario. +AutoResolveMethod.promptForAutoResolveMethod.title=Auto Resolve Method diff --git a/MekHQ/resources/mekhq/resources/GUI.properties b/MekHQ/resources/mekhq/resources/GUI.properties index 1cc6ced859b..6e4330de7c5 100644 --- a/MekHQ/resources/mekhq/resources/GUI.properties +++ b/MekHQ/resources/mekhq/resources/GUI.properties @@ -1431,6 +1431,7 @@ UnitMarketPane.PurchasedUnitBlackMarketSwindled.finances=Purchased %s (lost on b AutoResolveMethod.dialog.name=Auto Resolve Chance Dialog AutoResolveMethod.dialog.title=Calculating scenario outcome AutoResolveMethod.text=Simulating combat... + # Progress gag text AutoResolveMethod.progress.0=Loading scenario... AutoResolveMethod.progress.1=Checking mek alignment protocols... diff --git a/MekHQ/resources/mekhq/resources/acs-report-messages.properties b/MekHQ/resources/mekhq/resources/acs-report-messages.properties deleted file mode 100644 index 7dcdd4822b5..00000000000 --- a/MekHQ/resources/mekhq/resources/acs-report-messages.properties +++ /dev/null @@ -1,115 +0,0 @@ -# -# Copyright (c) 2020-2024 - The MegaMek Team. All Rights Reserved. -# -# This file is part of MekHQ. -# -# MekHQ is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# MekHQ is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with MekHQ. If not, see . -# -# -# Copyright (c) 2020-2024 - The MegaMek Team. All Rights Reserved. -# -# This file is part of MekHQ. -# -# MekHQ is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# MekHQ is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with MekHQ. If not, see . -# -100=

Abstract Combat Auto Resolution

- -101=

● Starting Scenario Phase

-102=Team {0} Formations: -103={0} has {1} units. -104={0}, Armor: {1}%, Structure: {2}%, Crew: {3}, Hits: {4} - -999= -1000=

○ Initiative Phase for Round {0}

-1005=

○ Initiative Phase for Start of Game Deployment

-1015=▻ {0} has {1} initiative bonus. -1020=▻ The general turn order is: -1040=

※ The turn order is:

-1045=▻ {0}({1}), rolled {2} -1060=

⁜ Future Deployment

-1065=

▶ Round: #{0}

-1066={0} ({1}), Start: {2} -1067={0}[Team {1}] -1200=
-1230={0} -1231={0} ({1}) {2} -1242=no effect -9999=

● End of the Game

-2020={0} rolls a {1}. - -2201=

◔ Engagement and Control Phase

-2200={0} engages {1} under {2} conditions. -2203=needs {0}+ to control engagement. -2202={0} rolled {1}. -2204={0} gains engagement control! -2205={0} fails to gain engagement control. - - -2002=

◑ Firing Phase

-2001={0} attacks {1}. -3100={0} received {1} damage, {2} armor left -2010=The attack cannot succeed: {0} -2003=Needed {0}+ to hit 〈{1}〉. -2012=The attack misses. -2013=The attack hits! -3090=High Stress episode! -3091=It's crippled! -3092=☠ It's destroyed! -3094={0} took {1} damage to its targeting system. -3095=Rolling for internal damage. -3096={0} took {1} damage to its weapons system. -3097=Took no internal damage. - -3299=

◕ End Phase

-3298=

Destroyed Units

-3330={0} attempts to withdraw under {1} conditions. -3331=Needs {0}+ to successfully withdraw 〈{1}〉. -3332={0} rolled {1} for withdrawal. -3333=The withdrawal is successful! -3334=The withdrawal attempt fails. -3335={0} has has succumbed from his wounds ☠ -3336={0} is still alive ({1} hits) ⛑ - -3337={0} has been devastated, there is nothing left of it. -3338={0} was destroyed by the pilot ejection. -3339={0} was destroyed after being pushed off the combat envelope. -3340={0} was captured by enemy forces. -3341={0} has left the battlefield in full retreat. -3342={0} never joined the battle. -3343={0} was destroyed, it is salvageable. -3344={0} was destroyed by surprise, it is salvageable. - -4500={0} attempts a morale check. Need {1} to succeed. -4501={0} rolled {1}. -4502={0} holds steady and does not lose morale. -4503={0} fails its morale check! Morale worsens from {1} to {2}. - -5000=

● End of Combat

-5002=Team {0} Report: -5003={0} has {1} units remaining. -5004={0}, Armor remaining: {1}%, Structure remaining: {2}%, Crew: {3}, Hits: {4} -5005={0} - Armor remaining: {1}%, Structure remaining: {2}% -5006={0} has {1} units destroyed. -5007={0} has {1} units retreating. diff --git a/MekHQ/resources/mekhq/resources/acs-report-messages_en.properties b/MekHQ/resources/mekhq/resources/acs-report-messages_en.properties deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/MekHQ/resources/mekhq/resources/messages.properties b/MekHQ/resources/mekhq/resources/messages.properties index 82b1a20875b..95051803d77 100644 --- a/MekHQ/resources/mekhq/resources/messages.properties +++ b/MekHQ/resources/mekhq/resources/messages.properties @@ -61,7 +61,6 @@ acar.formation_morale=Morale acar.jump_modifier=Movement modifier acar.withdraw.crippled=Crippled AutoResolveDialog.title=Auto Resolve Battle -AutoResolveSimulationLogDialog.title=Auto Resolve Report AutoResolveDialog.messageFailedCalc=Commander, we were unable to simulate any combat scenarios. Do you want to proceed? AutoResolveDialog.messageSimulated=Commander, we ran {0} simulated combat scenarios and our forces came out victorious in {1}, lost {2}, and drew {3} times. This gives us a {4}% chance of victory. Do you want to proceed? AutoResolveDialog.message.victory=Your forces won the scenario. Did your side control the battlefield at the end of the scenario? diff --git a/MekHQ/src/mekhq/MekHQ.java b/MekHQ/src/mekhq/MekHQ.java index 6d5a112f632..b436aba06d0 100644 --- a/MekHQ/src/mekhq/MekHQ.java +++ b/MekHQ/src/mekhq/MekHQ.java @@ -27,6 +27,7 @@ import megamek.SuiteConstants; import megamek.client.Client; import megamek.client.bot.princess.BehaviorSettings; +import megamek.client.ui.dialogs.AutoResolveSimulationLogDialog; import megamek.client.ui.preferences.PreferencesNode; import megamek.client.ui.preferences.SuitePreferences; import megamek.client.ui.swing.GUIPreferences; @@ -34,6 +35,10 @@ import megamek.client.ui.swing.gameConnectionDialogs.HostDialog; import megamek.client.ui.swing.util.UIUtil; 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.*; import megamek.common.net.marshalling.SanityInputFilter; import megamek.logging.MMLogger; @@ -43,10 +48,7 @@ import mekhq.campaign.Campaign; import mekhq.campaign.CampaignController; import mekhq.campaign.ResolveScenarioTracker; -import mekhq.campaign.autoresolve.Resolver; -import mekhq.campaign.autoresolve.acar.SimulatedClient; -import mekhq.campaign.autoresolve.acar.SimulationOptions; -import mekhq.campaign.autoresolve.event.AutoResolveConcludedEvent; +import mekhq.campaign.autoresolve.SetupForces; import mekhq.campaign.handler.PostScenarioDialogHandler; import mekhq.campaign.handler.XPHandler; import mekhq.campaign.mission.AtBScenario; @@ -58,13 +60,13 @@ import mekhq.gui.dialog.AutoResolveChanceDialog; import mekhq.gui.dialog.ChooseMulFilesDialog; import mekhq.gui.dialog.ResolveScenarioWizardDialog; -import mekhq.gui.dialog.helpDialogs.AutoResolveSimulationLogDialog; + import mekhq.gui.panels.StartupScreenPanel; import mekhq.gui.preferences.StringPreference; import mekhq.gui.utilities.ObservableString; import mekhq.service.AutosaveService; import mekhq.service.IAutosaveService; -import mekhq.utilities.Internationalization; +import mekhq.utilities.MHQInternationalization; import javax.swing.*; import javax.swing.text.DefaultEditorKit; @@ -535,8 +537,8 @@ public void gameVictory(PostGameResolution gve) { try { boolean control = yourSideControlsTheBattlefieldDialogAsk( - Internationalization.getText("ResolveDialog.control.message"), - Internationalization.getText("ResolveDialog.control.title")); + MHQInternationalization.getText("ResolveDialog.control.message"), + MHQInternationalization.getText("ResolveDialog.control.title")); ResolveScenarioTracker tracker = new ResolveScenarioTracker(currentScenario, getCampaign(), control); tracker.setClient(gameThread.getClient()); tracker.setEvent(gve); @@ -567,8 +569,8 @@ public void resolveScenario(Scenario selectedScenario) { return; } boolean control = yourSideControlsTheBattlefieldDialogAsk( - Internationalization.getText("ResolveDialog.control.message"), - Internationalization.getText("ResolveDialog.control.title")); + MHQInternationalization.getText("ResolveDialog.control.message"), + MHQInternationalization.getText("ResolveDialog.control.title")); ResolveScenarioTracker tracker = new ResolveScenarioTracker(selectedScenario, getCampaign(), control); @@ -656,7 +658,7 @@ public void startAutoResolve(AtBScenario scenario, List units) { } } - var event = new Resolver(getCampaign(), units, scenario, new SimulationOptions(getCampaign().getGameOptions())) + var event = new Resolver(new SetupForces(getCampaign(), units, scenario),new SimulationOptions(getCampaign().getGameOptions())) .resolveSimulation(); var autoResolveBattleReport = new AutoResolveSimulationLogDialog(getCampaigngui().getFrame(), event.getLogFile()); @@ -673,15 +675,14 @@ public void startAutoResolve(AtBScenario scenario, List units) { public void autoResolveConcluded(AutoResolveConcludedEvent autoResolveConcludedEvent) { try { String message = autoResolveConcludedEvent.controlledScenario() ? - Internationalization.getText("AutoResolveDialog.message.victory") : - Internationalization.getText("AutoResolveDialog.message.defeat"); + MHQInternationalization.getText("AutoResolveDialog.message.victory") : + MHQInternationalization.getText("AutoResolveDialog.message.defeat"); String title = autoResolveConcludedEvent.controlledScenario() ? - Internationalization.getText("AutoResolveDialog.victory") : - Internationalization.getText("AutoResolveDialog.defeat"); + MHQInternationalization.getText("AutoResolveDialog.victory") : + MHQInternationalization.getText("AutoResolveDialog.defeat"); boolean control = yourSideControlsTheBattlefieldDialogAsk(message, title); - var scenario = autoResolveConcludedEvent.getScenario(); - ResolveScenarioTracker tracker = new ResolveScenarioTracker(scenario, getCampaign(), control); + ResolveScenarioTracker tracker = new ResolveScenarioTracker(currentScenario, getCampaign(), control); tracker.setClient(new SimulatedClient(autoResolveConcludedEvent.getGame())); tracker.setEvent(autoResolveConcludedEvent); tracker.processGame(); @@ -697,7 +698,7 @@ public void autoResolveConcluded(AutoResolveConcludedEvent autoResolveConcludedE } return; } - PostScenarioDialogHandler.handle(campaignGUI, getCampaign(), scenario, tracker, + PostScenarioDialogHandler.handle(campaignGUI, getCampaign(), currentScenario, tracker, autoResolveConcludedEvent.controlledScenario()); } catch (Exception ex) { logger.error("Error during auto resolve concluded", ex); diff --git a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java index 1f9ba009971..40b2031f033 100644 --- a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java +++ b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java @@ -30,7 +30,7 @@ import megamek.logging.MMLogger; import mekhq.MekHQ; import mekhq.Utilities; -import mekhq.campaign.autoresolve.acar.SimulatedClient; +import megamek.common.autoresolve.acar.SimulatedClient; import mekhq.campaign.event.PersonBattleFinishedEvent; import mekhq.campaign.finances.Money; import mekhq.campaign.finances.enums.TransactionType; diff --git a/MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java b/MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java index 03ac3e78344..a269e21ca1b 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java @@ -1,25 +1,22 @@ /* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2025 - The MegaMek Team. All Rights Reserved. * - * This file is part of MekHQ. + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . */ package mekhq.campaign.autoresolve; -import mekhq.utilities.Internationalization; +import megamek.common.internationalization.Internationalization; +import mekhq.utilities.MHQInternationalization; import java.util.Optional; @@ -34,8 +31,8 @@ public enum AutoResolveMethod { private final String toolTipText; AutoResolveMethod(final String name, final String toolTipText) { - this.name = Internationalization.getTextAt("AutoResolveMethod", name); - this.toolTipText = Internationalization.getTextAt("AutoResolveMethod", toolTipText); + this.name = MHQInternationalization.getText(name); + this.toolTipText = Internationalization.getText(toolTipText); } public String getToolTipText() { diff --git a/MekHQ/src/mekhq/campaign/autoresolve/Resolver.java b/MekHQ/src/mekhq/campaign/autoresolve/Resolver.java deleted file mode 100644 index f3148d8493b..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/Resolver.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ -package mekhq.campaign.autoresolve; - -import megamek.common.options.AbstractOptions; -import mekhq.campaign.Campaign; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.SimulationOptions; -import mekhq.campaign.autoresolve.acar.phase.*; -import mekhq.campaign.autoresolve.converter.SetupForces; -import mekhq.campaign.autoresolve.event.AutoResolveConcludedEvent; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.mission.AtBScenario; -import mekhq.campaign.unit.Unit; - -import java.util.List; -import java.util.function.Consumer; - - - -/** - * @author Luana Coppio - */ -public class Resolver { - - private final AtBScenario scenario; - private final SimulationOptions options; - private final SetupForces setupForces; - - public Resolver(Campaign campaign, - List units, - AtBScenario scenario, - AbstractOptions gameOptions) { - - this.scenario = scenario; - this.options = new SimulationOptions(gameOptions); - this.setupForces = new SetupForces(campaign, units, scenario); - } - - public AutoResolveConcludedEvent resolveSimulation() { - SimulationContext context = new SimulationContext(scenario, options, setupForces); - SimulationManager simulationManager = new SimulationManager(context); - initializeGameManager(simulationManager); - simulationManager.execute(); - return simulationManager.getConclusionEvent(); - } - - private void initializeGameManager(SimulationManager simulationManager) { - simulationManager.addPhaseHandler(new StartingScenarioPhase(simulationManager)); - simulationManager.addPhaseHandler(new InitiativePhase(simulationManager)); - simulationManager.addPhaseHandler(new DeploymentPhase(simulationManager)); - simulationManager.addPhaseHandler(new MovementPhase(simulationManager)); - simulationManager.addPhaseHandler(new FiringPhase(simulationManager)); - simulationManager.addPhaseHandler(new EndPhase(simulationManager)); - simulationManager.addPhaseHandler(new VictoryPhase(simulationManager)); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/converter/SetupForces.java b/MekHQ/src/mekhq/campaign/autoresolve/SetupForces.java similarity index 95% rename from MekHQ/src/mekhq/campaign/autoresolve/converter/SetupForces.java rename to MekHQ/src/mekhq/campaign/autoresolve/SetupForces.java index aeb1c984afe..6a3c63fa984 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/converter/SetupForces.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/SetupForces.java @@ -1,33 +1,31 @@ /* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2024-2025 - The MegaMek Team. All Rights Reserved. * - * This file is part of MekHQ. + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . */ -package mekhq.campaign.autoresolve.converter; +package mekhq.campaign.autoresolve; import io.sentry.Sentry; import megamek.common.*; import megamek.common.alphaStrike.conversion.ASConverter; +import megamek.common.autoresolve.acar.SimulationContext; +import megamek.common.autoresolve.converter.ConsolidateForces; +import megamek.common.autoresolve.converter.ForceToFormationConverter; import megamek.common.force.Forces; import megamek.common.options.OptionsConstants; import megamek.common.planetaryconditions.PlanetaryConditions; import megamek.logging.MMLogger; import mekhq.campaign.Campaign; -import mekhq.campaign.autoresolve.acar.SimulationContext; import mekhq.campaign.copy.CrewRefBreak; import mekhq.campaign.mission.AtBDynamicScenario; import mekhq.campaign.mission.AtBScenario; @@ -44,7 +42,7 @@ /** * @author Luana Coppio */ -public class SetupForces { +public class SetupForces extends megamek.common.autoresolve.converter.SetupForces { private static final MMLogger logger = MMLogger.create(SetupForces.class); private final Campaign campaign; @@ -61,7 +59,7 @@ public SetupForces(Campaign campaign, List units, AtBScenario scenario) { * Create the forces for the game object, using the campaign, units and scenario * @param game The game object to setup the forces in */ - public void createForcesOnGame(SimulationContext game) { + public void createForcesOnSimulation(SimulationContext game) { setupPlayer(game); setupBots(game); ConsolidateForces.consolidateForces(game); diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/SimulatedClient.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/SimulatedClient.java deleted file mode 100644 index 6f87c1f174d..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/SimulatedClient.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar; - -import megamek.client.AbstractClient; -import megamek.client.IClient; -import megamek.common.IGame; -import megamek.common.Player; - -import java.util.List; -import java.util.Map; - -public class SimulatedClient implements IClient { - - private final IGame game; - private final Player localPlayer; - - public SimulatedClient(IGame game) { - this.game = game; - this.localPlayer = game.getPlayer(0); - } - - @Override - public IGame getGame() { - return game; - } - - @Override - public int getLocalPlayerNumber() { - return localPlayer.getId(); - } - - @Override - public Player getLocalPlayer() { - return localPlayer; - } - - - // The following methods are not used in the context of the Abstract Combat Auto Resolve - @Override - public boolean connect() { - return false; - } - - @Override - public String getName() { - return ""; - } - - @Override - public int getPort() { - return 0; - } - - @Override - public String getHost() { - return ""; - } - - @Override - public boolean isMyTurn() { - return false; - } - - @Override - public void setLocalPlayerNumber(int localPlayerNumber) {} - - @Override - public Map getBots() { - return Map.of(); - } - - @Override - public void sendDone(boolean done) {} - - @Override - public void sendChat(String message) {} - - @Override - public void die() {} -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/SimulationContext.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/SimulationContext.java deleted file mode 100644 index 8da8a7075bc..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/SimulationContext.java +++ /dev/null @@ -1,700 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ -package mekhq.campaign.autoresolve.acar; - - -import megamek.common.*; -import megamek.common.actions.EntityAction; -import megamek.common.annotations.Nullable; -import megamek.common.enums.GamePhase; -import megamek.common.enums.SkillLevel; -import megamek.common.event.GameEvent; -import megamek.common.event.GameListener; -import megamek.common.force.Forces; -import megamek.logging.MMLogger; -import megamek.server.scriptedevent.TriggeredEvent; -import mekhq.campaign.autoresolve.acar.action.Action; -import mekhq.campaign.autoresolve.acar.action.ActionHandler; -import mekhq.campaign.autoresolve.acar.report.PublicReportEntry; -import mekhq.campaign.autoresolve.component.AcTurn; -import mekhq.campaign.autoresolve.component.Formation; -import mekhq.campaign.autoresolve.component.FormationTurn; -import mekhq.campaign.autoresolve.converter.SetupForces; -import mekhq.campaign.mission.AtBScenario; -import org.apache.commons.lang3.NotImplementedException; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -/** - * @author Luana Coppio - */ -public class SimulationContext implements IGame { - - private static final MMLogger logger = MMLogger.create(SimulationContext.class); - - private final SimulationOptions options; - private final AtBScenario scenario; - - /** - * Objectives that must be considered during the game - */ - private static final int AWAITING_FIRST_TURN = -1; - private final List pendingActions = new ArrayList<>(); - - /** - * Game Phase and rules - */ - private GamePhase phase = GamePhase.UNKNOWN; - private GamePhase lastPhase = GamePhase.UNKNOWN; - - private final Map playerSkillLevels = new HashMap<>(); - private int lastEntityId; - /** - * Report and turnlist - */ - private final List turnList = new ArrayList<>(); - protected final ConcurrentHashMap inGameObjects = new ConcurrentHashMap<>(); - protected final ConcurrentHashMap players = new ConcurrentHashMap<>(); - protected final List teams = new ArrayList<>(); - - protected Forces forces = new Forces(this); - private final Map> deploymentTable = new HashMap<>(); - protected int currentRound = -1; - protected int turnIndex = AWAITING_FIRST_TURN; - - /** - * Tools for the game - */ - private final List actionHandlers = new ArrayList<>(); - - /** - * Contains all units that have left the game by any means. - */ - private final Vector graveyard = new Vector<>(); - - public SimulationContext(AtBScenario scenario, SimulationOptions gameOptions, SetupForces setupForces) { - this.options = gameOptions; - this.scenario = scenario; - setBoard(0, new Board()); - setupForces.createForcesOnGame(this); - } - - public AtBScenario getScenario() { - return scenario; - } - - public void addUnit(InGameObject unit) { - int id = unit.getId(); - if (inGameObjects.containsKey(id) || isOutOfGame(id) || (Entity.NONE == id)) { - id = getNextEntityId(); - unit.setId(id); - } - inGameObjects.put(id, unit); - } - - /** @return The TW Units (Entity) currently in the game. */ - public List inGameTWEntities() { - return filterToEntity(inGameObjects.values()); - } - - private List filterToEntity(Collection objects) { - return objects.stream().filter(Entity.class::isInstance).map(o -> (Entity) o).toList(); - } - - public List deployableInGameObjects() { - return getInGameObjects().stream() - .filter(Deployable.class::isInstance) - .map(Deployable.class::cast) - .collect(Collectors.toList()); - } - - public int getNoOfEntities() { - return inGameTWEntities().size(); - } - - public int getSelectedEntityCount(EntitySelector selector) { - int retVal = 0; - - // If no selector was supplied, return the count of all game entities. - if (null == selector) { - retVal = getNoOfEntities(); - } - - // Otherwise, count the entities that meet the selection criteria. - else { - for (Entity entity : inGameTWEntities()) { - if (selector.accept(entity)) { - retVal++; - } - } - - } // End use-selector - - // Return the number of selected entities. - return retVal; - } - - public SkillLevel getPlayerSkill(int playerId) { - return playerSkillLevels.getOrDefault(playerId, SkillLevel.ULTRA_GREEN); - } - - @Override - public int getNextEntityId() { - return inGameObjects.isEmpty() ? 0 : Collections.max(inGameObjects.keySet()) + 1; - } - - /** @return The entity with the given id number, if any. */ - public Optional getEntity(final int id) { - InGameObject possibleEntity = inGameObjects.get(id); - if (possibleEntity instanceof Entity) { - return Optional.of((Entity) possibleEntity); - } - return Optional.empty(); - } - - public void addEntity(Entity entity) { - int id = entity.getId(); - if (isIdUsed(id)) { - id = getNextEntityId(); - entity.setId(id); - } - inGameObjects.put(id, entity); - if (id > lastEntityId) { - lastEntityId = id; - } - - if (entity instanceof Mek mek) { - mek.setAutoEject(true); - mek.setCondEjectAmmo(!entity.hasCase() && !entity.hasCASEII()); - mek.setCondEjectEngine(true); - mek.setCondEjectCTDest(true); - mek.setCondEjectHeadshot(true); - } - - entity.setInitialBV(entity.calculateBattleValue(false, false)); - } - - public boolean isOutOfGame(int id) { - for (Entity entity : graveyard) { - if (entity.getId() == id) { - return true; - } - } - - return false; - } - - - private boolean isIdUsed(int id) { - return inGameObjects.containsKey(id) || isOutOfGame(id); - } - - @Override - public List getTurnsList() { - return Collections.unmodifiableList(turnList); - } - - - @Override - public SimulationOptions getOptions() { - if (options != null) { - return options; - } - return SimulationOptions.EMPTY; - } - - @Override - public GamePhase getPhase() { - return phase; - } - - public void addActionHandler(ActionHandler handler) { - if (actionHandlers.contains(handler)) { - logger.error("Tried to re-add action handler {}!", handler); - } else { - actionHandlers.add(handler); - } - } - - @Override - public AcTurn getTurn() { - if ((turnIndex < 0) || (turnIndex >= turnList.size())) { - return null; - } - return turnList.get(turnIndex); - } - - public Optional getCurrentTurn() { - if ((turnIndex < 0) || (turnIndex >= turnList.size())) { - return Optional.empty(); - } - return Optional.of(turnList.get(turnIndex)); - } - - @Override - public boolean hasMoreTurns() { - return getTurnsList().size() > turnIndex + 1; - } - - public void setTurns(List turns) { - this.turnList.clear(); - this.turnList.addAll(turns); - } - - @Override - public void setPhase(GamePhase phase) { - this.phase = phase; - } - - @Override - public void setLastPhase(GamePhase lastPhase) { - this.lastPhase = this.phase; - } - - @Override - public void receivePhase(GamePhase phase) { - setLastPhase(this.phase); - setPhase(phase); - } - - - @Override - public boolean isCurrentPhasePlayable() { - return true; - } - - - @Override - public void setPlayer(int id, Player player) { - player.setGame(this); - players.put(id, player); - setupTeams(); - } - - @Override - public void removePlayer(int id) { - // not implemented - } - - @Override - public void setupTeams() { - Vector initTeams = new Vector<>(); - - // Now, go through all the teams, and add the appropriate player - for (int t = Player.TEAM_NONE + 1; t < Player.TEAM_NAMES.length; t++) { - Team newTeam = null; - for (Player player : getPlayersList()) { - if (player.getTeam() == t) { - if (newTeam == null) { - newTeam = new Team(t); - } - newTeam.addPlayer(player); - } - } - - if (newTeam != null) { - initTeams.addElement(newTeam); - } - } - - for (Team newTeam : initTeams) { - for (Team oldTeam : teams) { - if (newTeam.equals(oldTeam)) { - newTeam.setInitiative(oldTeam.getInitiative()); - } - } - } - - // Carry over faction settings - for (Team newTeam : initTeams) { - for (Team oldTeam : teams) { - if (newTeam.equals(oldTeam)) { - newTeam.setFaction(oldTeam.getFaction()); - } - } - } - - teams.clear(); - teams.addAll(initTeams); - } - - @Override - public void replaceUnits(List units) { - throw new NotImplementedException("Not implemented"); - } - - @Override - public List getGraveyard() { - List destroyed = new ArrayList<>(); - for (Entity entity : this.graveyard) { - if (entity.getRemovalCondition() != IEntityRemovalConditions.REMOVE_IN_RETREAT) { - destroyed.add(entity); - } - } - - return destroyed; - } - - public List getRetreatingUnits() { - return this.graveyard.stream() - .filter(entity -> entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_IN_RETREAT) - .toList(); - } - - public int getLiveDeployedEntitiesOwnedBy(Player player) { - var res = getActiveFormations(player).stream() - .filter(Formation::isDeployed) - .count(); - - return (int) res; - } - - @Override - public ReportEntry getNewReport(int messageId) { - return new PublicReportEntry(messageId); - } - - @Override - public List scriptedEvents() { - return List.of(); - } - - public boolean gameTimerIsExpired() { - return getRoundCount() >= 1000; - } - - private int getRoundCount() { - return currentRound; - } - - public List getActionHandlers() { - return actionHandlers; - } - - public Optional changeToNextTurn() { - turnIndex++; - return getCurrentTurn(); - } - - public boolean hasEligibleFormation(FormationTurn turn) { - return (turn != null) && getActiveFormations().stream().anyMatch(f -> turn.isValidEntity(f, this)); - } - - /** - * Returns the formation of the given ID, if one can be found. - * - * @param formationID the ID to look for - * @return The formation or an empty Optional - */ - public Optional getFormation(int formationID) { - Optional unit = getInGameObject(formationID); - if (unit.isPresent() && unit.get() instanceof Formation formation) { - return Optional.of(formation); - } else { - return Optional.empty(); - } - } - - public GamePhase getLastPhase() { - return lastPhase; - } - - // check current turn, phase, formation - private boolean isEligibleForAction(Formation formation) { - return (getTurn() instanceof FormationTurn) - && getTurn().isValidEntity(formation, this); - } - - /** - * Returns the list of formations that are in the game's InGameObject list, i.e. - * that aren't destroyed - * or otherwise removed from play. - * - * @return The currently active formations - */ - public List getActiveFormations() { - return getInGameObjects().stream() - .filter(u -> u instanceof Formation) - .map(u -> (Formation) u) - .toList(); - } - - public List getActiveFormations(Player player) { - return getActiveFormations().stream() - .filter(f -> f.getOwnerId() == player.getId()) - .toList(); - } - - public void addUnitToGraveyard(Entity entity) { - if (!inGameObjects.containsKey(entity.getId())) { - logger.error("Tried to add entity {} to graveyard, but it's not in the game!", entity); - return; - } - removeEntity(entity); - graveyard.add(entity); - } - - public void setPlayerSkillLevel(int playerId, SkillLevel averageSkillLevel) { - playerSkillLevels.put(playerId, averageSkillLevel); - } - - public Player getLocalPlayer() { - return getPlayer(0); - } - - @Override - public synchronized Forces getForces() { - return forces; - } - - @Override - public Player getPlayer(int id) { - var player = players.get(id); - if (player == null) { - throw new IllegalArgumentException("No player with ID " + id + " found."); - } - return player; - } - - @Override - public void addPlayer(int id, Player player) { - players.put(id, player); - player.setGame(this); - setupTeams(); - } - - @Override - public List getPlayersList() { - return new ArrayList<>(players.values()); - } - - @Override - public int getNoOfPlayers() { - return players.size(); - } - - @Override - public List getTeams() { - return new ArrayList<>(teams); - } - - @Override - public int getNoOfTeams() { - return teams.size(); - } - - @Override - public List getInGameObjects() { - return new ArrayList<>(inGameObjects.values()); - } - - public void removeFormation(Formation formation) { - inGameObjects.remove(formation.getId()); - } - - public void removeEntity(Entity entity) { - inGameObjects.remove(entity.getId()); - } - - @Override - public void addGameListener(GameListener listener) {} - - @Override - public void removeGameListener(GameListener listener) {} - - @Override - public boolean isForceVictory() { - return false; - } - - @Override - public void fireGameEvent(GameEvent event) {} - - @Override - public void receiveBoard(int boardId, Board board) {} - - @Override - public void receiveBoards(Map boards) {} - - @Override - public void setBoard(int boardId, Board board) {} - - @Override - public Map getBoards() { - return Map.of(); - } - - @Override - public int getCurrentRound() { - return currentRound; - } - - @Override - public void setCurrentRound(int currentRound) { - this.currentRound = currentRound; - } - - /** - * Empties the list of pending EntityActions completely. - * - * @see #getActionsVector() - */ - public void clearActions() { - pendingActions.clear(); - } - - /** - * Removes all pending EntityActions by the InGameObject (Entity, unit) of the - * given ID from the list - * of pending actions. - */ - public void removeActionsFor(int id) { - pendingActions.removeIf(action -> action.getEntityId() == id); - } - - /** - * Remove the given EntityAction from the list of pending actions. - */ - public void removeAction(Action action) { - pendingActions.remove(action); - } - - /** - * Returns the pending EntityActions. Do not use to modify the actions; Arlith - * said: I will be - * angry. >:[ - */ - public List getActionsVector() { - return Collections.unmodifiableList(pendingActions); - } - - /** - * Adds the specified action to the list of pending EntityActions for this phase - * and fires a GameNewActionEvent. - */ - public void addAction(Action action) { - pendingActions.add(action); - } - - /** - * Clears and re-calculates the deployment table, i.e. assembles all - * units/objects in the game - * that are undeployed (that includes returning units or reinforcements) - * together with the game - * round that they are supposed to deploy on. This method can be called at any - * time in the game - * and will assemble deployment according to the present game state. - */ - public void setupDeployment() { - deploymentTable.clear(); - for (Deployable unit : deployableInGameObjects()) { - if (!unit.isDeployed()) { - deploymentTable.computeIfAbsent(unit.getDeployRound(), k -> new ArrayList<>()).add(unit); - } - } - } - - public int lastDeploymentRound() { - return deploymentTable.isEmpty() ? -1 : Collections.max(deploymentTable.keySet()); - } - - public boolean isDeploymentComplete() { - return lastDeploymentRound() < currentRound; - } - - /** - * Check to see if we should deploy this round - */ - public boolean shouldDeployThisRound() { - return shouldDeployForRound(currentRound); - } - - public boolean shouldDeployForRound(int round) { - return deploymentTable.containsKey(round); - } - - /** - * Clear this round from this list of entities to deploy - */ - public void clearDeploymentThisRound() { - deploymentTable.remove(currentRound); - } - - /** - * Resets the turn index to {@link #AWAITING_FIRST_TURN} - */ - public void resetTurnIndex() { - turnIndex = AWAITING_FIRST_TURN; - } - - @Override - public int getTurnIndex() { - return turnIndex; - } - - @Override - public synchronized void setForces(Forces fs) { - forces = fs; - forces.setGame(this); - } - - @Override - public void incrementCurrentRound() { - currentRound++; - } - - /** - * Sets the turn index to the given value. - * - * @param turnIndex the new turn index - */ - protected void setTurnIndex(int turnIndex) { - this.turnIndex = turnIndex; - } - - public boolean hasBoardLocation(@Nullable BoardLocation boardLocation) { - return hasBoardLocation(boardLocation.coords(), boardLocation.boardId()); - } - - public boolean hasBoardLocation(Coords coords, int boardId) { - return hasBoard(boardId) && getBoard(boardId).contains(coords); - } - - public boolean hasBoard(@Nullable BoardLocation boardLocation) { - return (boardLocation != null) && hasBoard(boardLocation.boardId()); - } - - public boolean hasBoard(int boardId) { - return true; - } - - - /** - * Resets this game, i.e. prepares it for a return to the lobby. - */ - public void reset() { - clearActions(); - inGameObjects.clear(); - turnIndex = AWAITING_FIRST_TURN; - currentRound = -1; - forces = new Forces(this); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/SimulationManager.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/SimulationManager.java deleted file mode 100644 index 90eb50e6bff..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/SimulationManager.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar; - -import megamek.common.IGame; -import megamek.common.Player; -import megamek.common.ReportEntry; -import megamek.common.TurnOrdered; -import megamek.common.enums.GamePhase; -import megamek.common.net.packets.Packet; -import megamek.common.preference.PreferenceManager; -import megamek.logging.MMLogger; -import megamek.server.AbstractGameManager; -import megamek.server.Server; -import megamek.server.commands.ServerCommand; -import megamek.server.victory.VictoryResult; -import mekhq.campaign.autoresolve.acar.action.*; -import mekhq.campaign.autoresolve.acar.manager.*; -import mekhq.campaign.autoresolve.acar.phase.PhaseHandler; -import mekhq.campaign.autoresolve.acar.report.HtmlGameLogger; -import mekhq.campaign.autoresolve.acar.report.PublicReportEntry; -import mekhq.campaign.autoresolve.component.Formation; -import mekhq.campaign.autoresolve.event.AutoResolveConcludedEvent; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class SimulationManager extends AbstractGameManager { - private static final MMLogger logger = MMLogger.create(SimulationManager.class); - private final HtmlGameLogger gameLogger = HtmlGameLogger - .create(PreferenceManager.getClientPreferences().getAutoResolveGameLogFilename()); - - private final List pendingReports = new ArrayList<>(); - private final List phaseHandlers = new ArrayList<>(); - private final PhaseEndManager phaseEndManager = new PhaseEndManager(this); - private final PhasePreparationManager phasePreparationManager = new PhasePreparationManager(this); - private final ActionsProcessor actionsProcessor = new ActionsProcessor(this); - private final InitiativeHelper initiativeHelper = new InitiativeHelper(this); - private final VictoryHelper victoryHelper = new VictoryHelper(this); - private final SimulationContext simulationContext; - - public SimulationManager(SimulationContext simulationContext) { - this.simulationContext = simulationContext; - } - - public void execute() { - changePhase(GamePhase.STARTING_SCENARIO); - while (!simulationContext.getPhase().equals(GamePhase.VICTORY)) { - changePhase(GamePhase.INITIATIVE); - } - } - - public void addPhaseHandler(PhaseHandler phaseHandler) { - phaseHandlers.add(phaseHandler); - } - - public AutoResolveConcludedEvent getConclusionEvent() { - return new AutoResolveConcludedEvent( - getGame(), - getCurrentVictoryResult(), - gameLogger.getLogFile() - ); - } - - @Override - protected void endCurrentPhase() { - logger.debug("Ending phase {}", getGame().getPhase()); - phaseEndManager.managePhase(); - } - - @Override - public void prepareForCurrentPhase() { - logger.debug("Preparing phase {}", getGame().getPhase()); - phasePreparationManager.managePhase(); - } - - @Override - public void executeCurrentPhase() { - logger.debug("Executing phase {}", getGame().getPhase()); - phaseHandlers.forEach(PhaseHandler::execute); - endCurrentPhase(); - } - - /** - * Called at the beginning of certain phases to make every player ready. - */ - public void resetPlayersDone() { - for (Player player : getGame().getPlayersList()) { - player.setDone(false); - } - } - - public void resetFormationsDone() { - for (var formation : getGame().getActiveFormations()) { - formation.setDone(false); - } - } - - public void resetFormations() { - for (var formation : getGame().getActiveFormations()) { - formation.reset(); - } - } - - /** - * Rolls initiative for all teams. - */ - public void rollInitiative() { - TurnOrdered.rollInitiative(getGame().getTeams(), false); - } - - /** - * Returns the victory result. - */ - public VictoryResult getCurrentVictoryResult() { - return victoryHelper.getVictoryResult(); - } - - public boolean isVictory() { - return getCurrentVictoryResult().isVictory(); - } - - @Override - public SimulationContext getGame() { - return simulationContext; - } - - @Override - public void setGame(IGame game) { - throw new UnsupportedOperationException("Cannot set game in SimulationManager"); - } - - public InitiativeHelper getInitiativeHelper() { - return initiativeHelper; - } - - public ActionsProcessor getActionsProcessor() { - return actionsProcessor; - } - - public void addMoraleCheck(MoraleCheckAction acsMoraleCheckAction, Formation formation) { - getGame().addAction(acsMoraleCheckAction); - formation.setDone(true); - } - - public void addAttack(List actions, Formation formation) { - actions.forEach(getGame()::addAction); - formation.setDone(true); - } - - public void addNerveRecovery(RecoveringNerveAction recoveringNerveAction) { - getGame().addAction(recoveringNerveAction); - } - - public void addWithdraw(WithdrawAction acsWithdrawAction) { - getGame().addAction(acsWithdrawAction); - } - - public void addEngagementControl(EngagementControlAction action, Formation formation) { - getGame().addAction(action); - formation.setDone(true); - } - - public void flushPendingReports() { - pendingReports.forEach(r -> gameLogger.add(r.text())); - pendingReports.clear(); - } - - @Override - protected void sendPhaseChange() { - // DO NOTHING - } - - @Override - public List getCommandList(Server server) { - return Collections.emptyList(); - } - - @Override - public void addReport(ReportEntry r) { - if (r instanceof PublicReportEntry publicReportEntry) { - pendingReports.add(publicReportEntry); - } else { - pendingReports.add(new PublicReportEntry(999).add(r.text())); - } - } - - @Override - public void calculatePlayerInitialCounts() { - for (Player player : getGame().getPlayersList()) { - player.setInitialEntityCount(Math.toIntExact(getGame().getActiveFormations(player).stream() - .filter(entity -> !entity.isRouted()).count())); - getGame().getActiveFormations(player).stream().map(Formation::getPointValue).reduce(Integer::sum) - .ifPresent(player::setInitialBV); - } - } - - @Override - public void requestTeamChangeForPlayer(int teamID, Player player) { - // DO NOTHING - } - - @Override - public void removeAllEntitiesOwnedBy(Player player) { - // DO NOTHING - } - - @Override - public void resetGame() { - // DO NOTHING - } - - // not to be implemented methods - @Override - public void disconnect(Player player) { - // DO NOTHING - } - - @Override - public void sendCurrentInfo(int connId) { - // DO NOTHING - } - - @Override - public void handleCfrPacket(Server.ReceivedPacket rp) { - // DO NOTHING - } - - @Override - public void requestGameMaster(Player player) { - // DO NOTHING - } - - @Override - public void requestTeamChange(int teamId, Player player) { - // DO NOTHING - } - - @Override - public void send(Packet packet) { - // DO NOTHING - } - - @Override - public void send(int connId, Packet p) { - // DO NOTHING - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/SimulationOptions.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/SimulationOptions.java deleted file mode 100644 index 2ea7f6048f9..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/SimulationOptions.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar; - -import megamek.common.options.AbstractOptions; -import megamek.common.options.AbstractOptionsInfo; -import megamek.common.options.OptionsConstants; - -/** - * @author Luana Coppio - */ -public class SimulationOptions extends AbstractOptions { - - public static final SimulationOptions EMPTY = empty(); - - public static SimulationOptions empty() { - return new SimulationOptions(null); - } - - public SimulationOptions(AbstractOptions abstractOptions) { - if (abstractOptions != null) { - this.optionsHash.putAll(abstractOptions.getOptionMap()); - } - } - - @Override - protected void initialize() { - // do nothing - } - - @Override - protected AbstractOptionsInfo getOptionsInfoImp() { - return SimulationOptionsInfo.instance; - } - - private static class SimulationOptionsInfo extends AbstractOptionsInfo { - private static final AbstractOptionsInfo instance = new SimulationOptionsInfo(); - - protected SimulationOptionsInfo() { - super("SimulationOptions"); - } - } - - - @Override - public int count() { - return optionsHash.size(); - } - - @Override - public int count(String groupKey) { - return optionsHash.size(); - } - - @Override - public int intOption(String name) { - var option = this.getOption(name); - - if (option != null) { - option.intValue(); - } - return 0; - } - - @Override - public boolean booleanOption(String name) { - var option = this.getOption(name); - - if (option != null) { - option.booleanValue(); - } - - return switch (name) { - case OptionsConstants.VICTORY_USE_BV_DESTROYED, - OptionsConstants.VICTORY_USE_BV_RATIO, - OptionsConstants.VICTORY_USE_KILL_COUNT, - OptionsConstants.VICTORY_COMMANDER_KILLED -> false; - default -> true; - }; - } - -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/AbstractAttackAction.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/AbstractAttackAction.java deleted file mode 100644 index 5053f710f45..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/AbstractAttackAction.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -public abstract class AbstractAttackAction implements AttackAction { - - private final int entityId; - private final int targetId; - - public AbstractAttackAction(int entityId, int targetId) { - this.entityId = entityId; - this.targetId = targetId; - } - - @Override - public int getEntityId() { - return entityId; - } - - @Override - public int getTargetId() { - return targetId; - } - - @Override - public String toString() { - return "[" + getClass().getSimpleName() + "]: Unit ID " + entityId + "; Target ID " + targetId; - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/Action.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/Action.java deleted file mode 100644 index 38cc539d79d..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/Action.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -import megamek.common.IGame; -import megamek.common.actions.EntityAction; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.acar.SimulationManager; - -/** - * @author Luana Coppio - */ -public interface Action extends EntityAction { - - /** - * @return A handler that will process this action as an extension of the SBFGameManager - */ - ActionHandler getHandler(SimulationManager gameManager); - - /** - * Validates the data of this action. Validation should not check game rule details, only if the action - * can be handled without running into missing or false data (NullPointerExceptions). Errors should - * be logged. The action will typically be ignored and removed if validation fails. - * - * @param context The simulation context - * @return true when this action is valid, false otherwise - */ - boolean isDataValid(SimulationContext context); - - default boolean isInvalid(SimulationContext context) { - return !isDataValid(context); - } - -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/ActionHandler.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/ActionHandler.java deleted file mode 100644 index edf7ab53f47..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/ActionHandler.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -import mekhq.campaign.autoresolve.acar.manager.SimulationManagerHelper; - -public interface ActionHandler extends SimulationManagerHelper { - - /** - * @return True when this handler should be called at the present state of the SBFGame (e.g. the phase). - * In that case, {@link #handle()} should be called. - */ - boolean cares(); - - /** - * Handles the action, e.g. attack with everything that's necessary, such as adding a report and sending - * changes to the Clients. When the handler has finished handling the action and is no longer needed, - * it must call {@link #setFinished()} to mark itself as a candidate for removal. - */ - default void handle() { - if (isActionValid()) { - execute(); - } - setFinished(); - } - - default boolean isActionValid() { - var action = getAction(); - return action.isDataValid(game()); - } - - void setFinished(); - - /** - * Executes the action - * Here I use "orElseThrow" in most optionals inside the implementations - * because those optionals were already checked before in the Action#isDataValid(Object) call, - * and if any of them that should not be empty is empty then it is checked first in the action itself. - */ - void execute(); - - /** - * If it returns true, it must be removed from the list of active handlers - * (handling this action is finished entirely). If it returns false, it must remain. - * - * @return False when this handler must remain active after doing its present handling, true otherwise - */ - boolean isFinished(); - - /** - * @return The EntityAction that this handler is executing. - */ - Action getAction(); -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/AttackAction.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/AttackAction.java deleted file mode 100644 index 8c84b86551b..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/AttackAction.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -public interface AttackAction extends Action { - - /** - * @return The game ID of the target of the attack - */ - int getTargetId(); - -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/AttackToHitData.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/AttackToHitData.java deleted file mode 100644 index c708a6a371d..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/AttackToHitData.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -import megamek.common.InGameObject; -import megamek.common.TargetRoll; -import megamek.common.strategicBattleSystems.SBFUnit; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.component.Formation; -import mekhq.utilities.Internationalization; - -import java.util.List; - -public class AttackToHitData extends TargetRoll { - - public AttackToHitData(int value, String desc) { - super(value, desc); - } - - public static AttackToHitData compileToHit(SimulationContext game, StandardUnitAttack attack) { - if (!attack.isDataValid(game)) { - return new AttackToHitData(TargetRoll.IMPOSSIBLE, Internationalization.getText("acar.invalid_attack")); - } - - var attackingFormation = game.getFormation(attack.getEntityId()).orElseThrow(); - var unit = attackingFormation.getUnits().get(attack.getUnitNumber()); - var toHit = new AttackToHitData(attackingFormation.getSkill(), Internationalization.getText("acar.skill")); - - processCriticalDamage(toHit, attackingFormation, attack); - processRange(toHit, attack); - processCombatUnit(toHit, unit); - processTMM(toHit, game, attack); - processJUMP(toHit, game, attack); - processMorale(toHit, game, attack); - processSecondaryTarget(toHit, game, attack); - return toHit; - } - - private static void processCriticalDamage(AttackToHitData toHit, Formation formation, StandardUnitAttack attack) { - SBFUnit combatUnit = formation.getUnits().get(attack.getUnitNumber()); - if (combatUnit.getTargetingCrits() > 0) { - toHit.addModifier(combatUnit.getTargetingCrits(), Internationalization.getText("acar.critical_target_damage")); - } - } - - private static void processCombatUnit(AttackToHitData toHit, SBFUnit unit) { - switch (unit.getSkill()) { - case 7 -> toHit.addModifier(+4, Internationalization.getText("acar.skill_7")); - case 6 -> toHit.addModifier(+3, Internationalization.getText("acar.skill_6")); - case 5 -> toHit.addModifier(+2, Internationalization.getText("acar.skill_5")); - case 4 -> toHit.addModifier(+1, Internationalization.getText("acar.skill_4")); - case 3 -> toHit.addModifier(0, Internationalization.getText("acar.skill_3")); - case 2 -> toHit.addModifier(-1, Internationalization.getText("acar.skill_2")); - case 1 -> toHit.addModifier(-2, Internationalization.getText("acar.skill_1")); - case 0 -> toHit.addModifier(-3, Internationalization.getText("acar.skill_0")); - default -> toHit.addModifier(TargetRoll.IMPOSSIBLE, Internationalization.getText("acar.invalid_skill")); - } - } - - private static void processRange(AttackToHitData toHit, StandardUnitAttack attack) { - var range = attack.getRange(); - switch (range) { - case SHORT -> toHit.addModifier(-1, Internationalization.getText( "acar.short_range")); - case MEDIUM -> toHit.addModifier(+2, Internationalization.getText("acar.medium_range")); - case LONG -> toHit.addModifier(+4, Internationalization.getText("acar.long_range")); - case EXTREME -> toHit.addModifier(TargetRoll.IMPOSSIBLE, Internationalization.getText( "acar.extreme_range")); - } - } - - private static void processTMM(AttackToHitData toHit, SimulationContext game, StandardUnitAttack attack) { - var target = game.getFormation(attack.getTargetId()).orElseThrow(); - if (target.getTmm() > 0) { - toHit.addModifier(target.getTmm(), Internationalization.getText( "acar.TMM")); - } - } - - private static void processJUMP(AttackToHitData toHit, SimulationContext game, StandardUnitAttack attack) { - var attacker = game.getFormation(attack.getEntityId()).orElseThrow(); - var target = game.getFormation(attack.getTargetId()).orElseThrow(); - if (attacker.getJumpUsedThisTurn() > 0) { - toHit.addModifier(attacker.getJumpUsedThisTurn(), Internationalization.getText("acar.attacker_JUMP")); - } - if (target.getJumpUsedThisTurn() > 0) { - toHit.addModifier(attacker.getJumpUsedThisTurn(), Internationalization.getText("acar.target_JUMP")); - } - } - - private static void processMorale(AttackToHitData toHit, SimulationContext game, StandardUnitAttack attack) { - var target = game.getFormation(attack.getTargetId()).orElseThrow(); - switch (target.moraleStatus()) { - case SHAKEN -> toHit.addModifier(1, Internationalization.getText("acar.shaken")); - case UNSTEADY -> toHit.addModifier(2, Internationalization.getText("acar.unsteady")); - case BROKEN -> toHit.addModifier(3, Internationalization.getText("acar.broken")); - case ROUTED -> toHit.addModifier(4, Internationalization.getText("acar.routed")); - } - } - - private static void processSecondaryTarget(AttackToHitData toHit, SimulationContext game, StandardUnitAttack attack) { - var attacker = game.getFormation(attack.getEntityId()).orElseThrow(); - if (targetsOfFormation(attacker, game).size() > 2) { - toHit.addModifier(TargetRoll.IMPOSSIBLE, Internationalization.getText("acar.more_than_two_targets")); - } else if (targetsOfFormation(attacker, game).size() == 2) { - toHit.addModifier(+1, Internationalization.getText("acar.two_targets")); - } - } - - /** - * Returns a list of target IDs of all the targets of all attacks that the attacker of the given - * attack is performing this round. The result can be empty (the unit isn't attacking anything or - * it is not the firing phase), it can have one or two entries. - * - * @param unit The attacker to check attacks for - * @param game The game - * @return A list of all target IDs - */ - public static List targetsOfFormation(InGameObject unit, SimulationContext game) { - return game.getActionsVector().stream() - .filter(a -> a.getEntityId() == unit.getId()) - .filter(AttackAction.class::isInstance) - .map(AttackAction.class::cast) - .map(AttackAction::getTargetId) - .distinct() - .toList(); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/EngagementControlAction.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/EngagementControlAction.java deleted file mode 100644 index 8aaabf32cff..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/EngagementControlAction.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -import megamek.common.IGame; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.handler.EngagementAndControlActionHandler; -import mekhq.campaign.autoresolve.component.EngagementControl; - -public class EngagementControlAction implements Action { - - private final int formationId; - private final int targetFormationId; - private final EngagementControl engagementControl; - - public EngagementControlAction(int formationId, int targetFormationId, EngagementControl engagementControl) { - this.formationId = formationId; - this.targetFormationId = targetFormationId; - this.engagementControl = engagementControl; - } - - @Override - public int getEntityId() { - return formationId; - } - - @Override - public ActionHandler getHandler(SimulationManager gameManager) { - return new EngagementAndControlActionHandler(this, gameManager); - } - - @Override - public boolean isDataValid(SimulationContext context) { - return (context.getFormation(formationId).isPresent() - && context.getFormation(targetFormationId).isPresent()); - } - - public EngagementControl getEngagementControl() { - return engagementControl; - } - - public int getTargetFormationId() { - return targetFormationId; - } - - @Override - public String toString() { - return "[EngagementControlAction]: ID: " + formationId + "; engagementControl: " + engagementControl; - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/EngagementControlToHitData.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/EngagementControlToHitData.java deleted file mode 100644 index 6b0691ba544..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/EngagementControlToHitData.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -import megamek.common.TargetRoll; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.component.Formation; -import mekhq.utilities.Internationalization; - -public class EngagementControlToHitData extends TargetRoll { - - public EngagementControlToHitData(int value, String desc) { - super(value, desc); - } - - public static EngagementControlToHitData compileToHit(SimulationContext game, EngagementControlAction engagementControl) { - if (engagementControl.isInvalid(game)) { - return new EngagementControlToHitData(TargetRoll.IMPOSSIBLE, Internationalization.getText("acar.invalid_engagement_control")); - } - var attackingFormationOpt = game.getFormation(engagementControl.getEntityId()); - if (attackingFormationOpt.isEmpty()) { - return new EngagementControlToHitData(TargetRoll.IMPOSSIBLE, Internationalization.getText("acar.invalid_attacking_formation")); - } - - var attackingFormation = attackingFormationOpt.get(); - var toHit = new EngagementControlToHitData(attackingFormation.getTactics(), Internationalization.getText("acar.formation_tactics")); - processFormationModifiers(toHit, game, engagementControl); - processMorale(toHit, game, engagementControl); - processEngagementAndControlChosen(toHit, game, engagementControl); - processSizeDifference(toHit, game, engagementControl); - processPlayerSkill(toHit, game, attackingFormation); - return toHit; - } - - private static void processEngagementAndControlChosen( - EngagementControlToHitData toHit, SimulationContext game, EngagementControlAction engagementControl) { - switch (engagementControl.getEngagementControl()) { - case FORCED_ENGAGEMENT: - toHit.addModifier(-3, Internationalization.getText("acar.force_engagement")); - break; - case EVADE: - toHit.addModifier(-3, Internationalization.getText("acar.evade")); - break; - case OVERRUN: - processSizeDifference(toHit, game, engagementControl); - break; - default: - break; - } - } - - private static void processPlayerSkill(EngagementControlToHitData toHit, SimulationContext game, Formation formation) { - switch (game.getPlayerSkill(formation.getOwnerId())) { - case NONE -> toHit.addModifier(4, Internationalization.getText("acar.skill_7")); - case ULTRA_GREEN -> toHit.addModifier(3, Internationalization.getText("acar.skill_6")); - case GREEN -> toHit.addModifier(2, Internationalization.getText("acar.skill_5")); - case REGULAR -> toHit.addModifier(1, Internationalization.getText("acar.skill_4")); - case VETERAN -> toHit.addModifier(0, Internationalization.getText("acar.skill_3")); - case ELITE -> toHit.addModifier(-1, Internationalization.getText("acar.skill_2")); - case HEROIC -> toHit.addModifier(-2, Internationalization.getText("acar.skill_1")); - case LEGENDARY -> toHit.addModifier(-3, Internationalization.getText("acar.skill_0")); - } - } - - private static void processFormationModifiers( - EngagementControlToHitData toHit, SimulationContext game, EngagementControlAction engagementControl) { - var formationOpt = game.getFormation(engagementControl.getEntityId()); - if (formationOpt.isEmpty()) { - return; - } - var formation = formationOpt.get(); - - var formationIsInfantryOnly = formation.isInfantry(); - var formationIsVehicleOnly = formation.isVehicle(); - - if (formationIsInfantryOnly) { - toHit.addModifier(2, Internationalization.getText("acar.formation_is_infantry_only")); - } - if (formationIsVehicleOnly) { - toHit.addModifier(1, Internationalization.getText("acar.formation_is_vehicle_only")); - } - } - - private static void processSizeDifference( - EngagementControlToHitData toHit, SimulationContext game, EngagementControlAction engagementControl) { - var attackerOpt = game.getFormation(engagementControl.getEntityId()); - var targetOpt = game.getFormation(engagementControl.getTargetFormationId()); - if (attackerOpt.isEmpty() || targetOpt.isEmpty()) { - return; - } - int sizeDifference = attackerOpt.get().getSize() - targetOpt.get().getSize(); - toHit.addModifier(sizeDifference, Internationalization.getText("acar.size_difference")); - } - - private static void processMorale(EngagementControlToHitData toHit, SimulationContext game, EngagementControlAction engagementControl) { - var targetOpt = game.getFormation(engagementControl.getTargetFormationId()); - if (targetOpt.isEmpty()) { - return; - } - switch (targetOpt.get().moraleStatus()) { - case SHAKEN -> toHit.addModifier(+1, Internationalization.getText("acar.shaken_morale")); - case UNSTEADY -> toHit.addModifier(+2, Internationalization.getText("acar.unsteady_morale")); - case BROKEN -> toHit.addModifier(+3, Internationalization.getText("acar.broken_morale")); - case ROUTED -> toHit.addModifier(TargetRoll.AUTOMATIC_FAIL, Internationalization.getText("acar.routed_morale")); - } - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/ManeuverToHitData.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/ManeuverToHitData.java deleted file mode 100644 index 8fbc0be1338..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/ManeuverToHitData.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -import megamek.common.TargetRoll; -import mekhq.campaign.autoresolve.component.EngagementControl; -import mekhq.campaign.autoresolve.component.Formation; -import mekhq.utilities.Internationalization; - -public class ManeuverToHitData extends TargetRoll { - - public ManeuverToHitData(int value, String desc) { - super(value, desc); - } - - public static ManeuverToHitData compileToHit(Formation formation) { - var toHit = new ManeuverToHitData(formation.getTactics(), Internationalization.getText("acar.formation_tactics")); - processFormationModifiers(toHit, formation); - processCombatUnit(toHit, formation); - return toHit; - } - - private static void processFormationModifiers(ManeuverToHitData toHit, Formation formation) { - if (formation.getEngagementControl() == EngagementControl.FORCED_ENGAGEMENT) { - toHit.addModifier(1, Internationalization.getText("acar.forced_engagement")); - } - if (formation.isAerospace()) { - toHit.addModifier(2, Internationalization.getText("acar.aerospace_formation")); - } - } - - private static void processCombatUnit(ManeuverToHitData toHit, Formation formation) { - switch (formation.getSkill()) { - case 7 -> toHit.addModifier(+4, Internationalization.getText("acar.skill_7")); - case 6 -> toHit.addModifier(+3, Internationalization.getText("acar.skill_6")); - case 5 -> toHit.addModifier(+2, Internationalization.getText("acar.skill_5")); - case 4 -> toHit.addModifier(+1, Internationalization.getText("acar.skill_4")); - case 3 -> toHit.addModifier(0, Internationalization.getText("acar.skill_3")); - case 2 -> toHit.addModifier(-1, Internationalization.getText("acar.skill_2")); - case 1 -> toHit.addModifier(-2, Internationalization.getText("acar.skill_1")); - case 0 -> toHit.addModifier(-3, Internationalization.getText("acar.skill_0")); - default -> toHit.addModifier(TargetRoll.IMPOSSIBLE, Internationalization.getText("acar.invalid_skill")); - } - - switch (formation.moraleStatus()) { - case SHAKEN -> toHit.addModifier(+0, Internationalization.getText("acar.shaken_morale")); - case UNSTEADY -> toHit.addModifier(+1, Internationalization.getText("acar.unsteady_morale")); - case BROKEN -> toHit.addModifier(+2, Internationalization.getText("acar.broken_morale")); - case ROUTED -> toHit.addModifier(+2, Internationalization.getText("acar.routed_morale")); - } - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/MoraleCheckAction.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/MoraleCheckAction.java deleted file mode 100644 index 6e13eb6cf93..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/MoraleCheckAction.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -import megamek.common.IGame; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.handler.MoraleCheckActionHandler; - -public class MoraleCheckAction implements Action { - - private final int formationId; - - public MoraleCheckAction(int formationId) { - this.formationId = formationId; - } - - @Override - public int getEntityId() { - return formationId; - } - - @Override - public ActionHandler getHandler(SimulationManager gameManager) { - return new MoraleCheckActionHandler(this, gameManager); - } - - @Override - public boolean isDataValid(SimulationContext context) { - return context.getFormation(formationId).isPresent(); - } - - @Override - public String toString() { - return "[MoraleCheckAction]: ID: " + formationId; - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/RecoveringNerveAction.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/RecoveringNerveAction.java deleted file mode 100644 index a2b6a4dd8f8..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/RecoveringNerveAction.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.handler.RecoveringNerveActionHandler; - -public class RecoveringNerveAction implements Action { - - private final int formationId; - - public RecoveringNerveAction(int formationId) { - this.formationId = formationId; - } - - @Override - public int getEntityId() { - return formationId; - } - - @Override - public ActionHandler getHandler(SimulationManager gameManager) { - return new RecoveringNerveActionHandler(this, gameManager); - } - - @Override - public boolean isDataValid(SimulationContext context) { - return context.getFormation(formationId).isPresent(); - } - - @Override - public String toString() { - return "[RecoveringNerveAction]: ID: " + formationId; - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/RecoveringNerveActionToHitData.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/RecoveringNerveActionToHitData.java deleted file mode 100644 index 17ce016e3b6..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/RecoveringNerveActionToHitData.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -import megamek.common.TargetRoll; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.component.Formation; -import mekhq.utilities.Internationalization; - -public class RecoveringNerveActionToHitData extends TargetRoll { - - public RecoveringNerveActionToHitData(int value, String desc) { - super(value, desc); - } - - public static RecoveringNerveActionToHitData compileToHit(SimulationContext game, RecoveringNerveAction recoveringNerveAction) { - if (recoveringNerveAction.isInvalid(game)) { - return new RecoveringNerveActionToHitData(TargetRoll.IMPOSSIBLE, Internationalization.getText("acar.invalid_nerve_recovering")); - } - var formation = game.getFormation(recoveringNerveAction.getEntityId()).orElseThrow(); - RecoveringNerveActionToHitData toHit = new RecoveringNerveActionToHitData(3 + formation.getSkill(), Internationalization.getText("acar.formation_morale")); - processSkill(toHit, formation); - return toHit; - } - - private static void processSkill(RecoveringNerveActionToHitData toHit, Formation formation) { - switch (formation.getSkill()) { - case 7 -> toHit.addModifier(+2, Internationalization.getText("acar.skill_7")); - case 6 -> toHit.addModifier(+1, Internationalization.getText("acar.skill_6")); - case 5 -> toHit.addModifier(0, Internationalization.getText("acar.skill_5")); - case 4 -> toHit.addModifier(-1, Internationalization.getText("acar.skill_4")); - case 3 -> toHit.addModifier(-2, Internationalization.getText("acar.skill_3")); - case 2 -> toHit.addModifier(-3, Internationalization.getText("acar.skill_2")); - case 1 -> toHit.addModifier(-4, Internationalization.getText("acar.skill_1")); - case 0 -> toHit.addModifier(-5, Internationalization.getText("acar.skill_0")); - default -> toHit.addModifier(TargetRoll.IMPOSSIBLE, Internationalization.getText("acar.invalid_skill")); - } - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/StandardUnitAttack.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/StandardUnitAttack.java deleted file mode 100644 index 457220cb845..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/StandardUnitAttack.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -import megamek.common.alphaStrike.ASRange; -import megamek.logging.MMLogger; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.handler.StandardUnitAttackHandler; -import mekhq.campaign.autoresolve.component.Formation; - -import java.util.Optional; - -public class StandardUnitAttack extends AbstractAttackAction { - private static final MMLogger logger = MMLogger.create(StandardUnitAttack.class); - - private final int unitNumber; - private final ASRange range; - private final ManeuverResult maneuverResult; - - public enum ManeuverResult { - SUCCESS, - FAILURE, - DRAW - } - - /** - * Creates a standard attack of an SBF Unit on another formation. - * The unit number identifies the SBF Unit making the attack, i.e. 1 for the - * first of the formation's units, - * 2 for the second etc. - * - * @param formationId The attacker's ID - * @param unitNumber The number of the attacking SBF Unit inside the formation - * @param targetId The target's ID - */ - public StandardUnitAttack(int formationId, int unitNumber, int targetId, ASRange range, ManeuverResult maneuverResult) { - super(formationId, targetId); - this.unitNumber = unitNumber; - this.range = range; - this.maneuverResult = maneuverResult; - } - - /** - * Returns the index of the SBF Unit inside the formation, i.e. 0 for the first of - * the formation's units, 1 for the second, 3 for the third etc. - * - * @return The unit index number within the formation - */ - public int getUnitNumber() { - return unitNumber; - } - - public ASRange getRange() { - return range; - } - - public ManeuverResult getManeuverResult() { - return maneuverResult; - } - - @Override - public ActionHandler getHandler(SimulationManager gameManager) { - return new StandardUnitAttackHandler(this, gameManager); - } - - @Override - public boolean isDataValid(SimulationContext context) { - Optional possibleAttacker = context.getFormation(getEntityId()); - Optional possibleTarget = context.getFormation(getTargetId()); - if (getEntityId() == getTargetId()) { - logger.warn("Formations cannot attack themselves! {}", this); - return false; - } else if (possibleAttacker.isEmpty() || possibleTarget.isEmpty()) { - return false; - } else if ((getUnitNumber() >= possibleAttacker.get().getUnits().size()) - || (getUnitNumber() < 0)) { - return false; - } else if (possibleTarget.get().getUnits().isEmpty()) { - return false; - } - - return true; - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/WithdrawAction.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/WithdrawAction.java deleted file mode 100644 index 26915ba912e..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/WithdrawAction.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.handler.WithdrawActionHandler; - -public class WithdrawAction implements Action { - private final int formationId; - - public WithdrawAction(int formationId) { - this.formationId = formationId; - } - - @Override - public int getEntityId() { - return formationId; - } - - @Override - public ActionHandler getHandler(SimulationManager gameManager) { - return new WithdrawActionHandler(this, gameManager); - } - - @Override - public boolean isDataValid(SimulationContext context) { - return context.getFormation(getEntityId()).isPresent(); - } - - @Override - public String toString() { - return "[WithdrawAction]: ID: " + formationId; - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/WithdrawToHitData.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/action/WithdrawToHitData.java deleted file mode 100644 index bbf5e850739..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/action/WithdrawToHitData.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.action; - -import megamek.common.TargetRoll; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.component.Formation; -import mekhq.utilities.Internationalization; - -public class WithdrawToHitData extends TargetRoll { - - public WithdrawToHitData(int value, String desc) { - super(value, desc); - } - - public static WithdrawToHitData compileToHit(SimulationContext game, Formation formation) { - var toHit = new WithdrawToHitData(formation.getTactics(), Internationalization.getText("acar.formation_tactics")); - processFormationModifiers(toHit, formation); - processJumpModifiers(toHit, formation); - processMorale(toHit, formation); - processIsCrippled(toHit, formation); - return toHit; - } - - private static void processIsCrippled(WithdrawToHitData toHit, Formation formation) { - if (formation.isCrippled()) { - toHit.addModifier(1, Internationalization.getText("acar.withdraw.crippled")); - } - } - - private static void processJumpModifiers(WithdrawToHitData toHit, Formation formation) { - toHit.addModifier( 2 - formation.getJumpMove(), Internationalization.getText("acar.jump_modifier")); - } - - private static void processFormationModifiers(WithdrawToHitData toHit, Formation formation) { - var formationIsInfantryOnly = formation.isInfantry(); - var formationIsVehicleOnly = formation.isVehicle(); - - if (formationIsInfantryOnly) { - toHit.addModifier(2, Internationalization.getText("acar.formation_is_infantry_only")); - } - if (formationIsVehicleOnly) { - toHit.addModifier(1, Internationalization.getText("acar.formation_is_vehicle_only")); - } - } - - private static void processMorale(WithdrawToHitData toHit, Formation formation) { - switch (formation.moraleStatus()) { - case SHAKEN -> toHit.addModifier(+1, Internationalization.getText("acar.shaken_morale")); - case UNSTEADY -> toHit.addModifier(+2, Internationalization.getText("acar.unsteady_morale")); - case BROKEN -> toHit.addModifier(+3, Internationalization.getText("acar.broken_morale")); - case ROUTED -> toHit.addModifier(TargetRoll.AUTOMATIC_FAIL, Internationalization.getText("acar.routed_morale")); - } - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/AbstractActionHandler.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/AbstractActionHandler.java deleted file mode 100644 index f76ecb51c1d..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/AbstractActionHandler.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.handler; - -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.action.Action; -import mekhq.campaign.autoresolve.acar.action.ActionHandler; - -public abstract class AbstractActionHandler implements ActionHandler { - - private final Action action; - private final SimulationManager gameManager; - private boolean isFinished = false; - - public AbstractActionHandler(Action action, SimulationManager gameManager) { - this.action = action; - this.gameManager = gameManager; - } - - @Override - public boolean isFinished() { - return isFinished; - } - - @Override - public void setFinished() { - isFinished = true; - } - - @Override - public Action getAction() { - return action; - } - - @Override - public SimulationManager simulationManager() { - return gameManager; - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/EngagementAndControlActionHandler.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/EngagementAndControlActionHandler.java deleted file mode 100644 index a05fb847ed7..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/EngagementAndControlActionHandler.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.handler; - -import megamek.common.Compute; -import megamek.common.Roll; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.action.EngagementControlAction; -import mekhq.campaign.autoresolve.acar.action.EngagementControlToHitData; -import mekhq.campaign.autoresolve.acar.report.EngagementAndControlReporter; -import mekhq.campaign.autoresolve.component.EngagementControl; - -import java.util.Map; - -public class EngagementAndControlActionHandler extends AbstractActionHandler { - - private final EngagementAndControlReporter reporter; - - public EngagementAndControlActionHandler(EngagementControlAction action, SimulationManager gameManager) { - super(action, gameManager); - this.reporter = new EngagementAndControlReporter(gameManager.getGame(), this::addReport); - } - - @Override - public boolean cares() { - return game().getPhase().isMovement(); - } - - /** - * Perform the engagement control action - * This changes a little bit the "status quo" during the round, making units do or take less damage or evade specifically one unit - */ - @Override - public void execute() { - // WARNING: THIS IS NOT UP TO RULES AS WRITTEN - EngagementControlAction engagementControl = (EngagementControlAction) getAction(); - - var attackerOpt = game().getFormation(engagementControl.getEntityId()); - var targetOpt = game().getFormation(engagementControl.getTargetFormationId()); - var attacker = attackerOpt.orElseThrow(); - - if (engagementControl.getEngagementControl().equals(EngagementControl.NONE)) { - attacker.setEngagementControl(EngagementControl.NONE); - return; - } - - var target = targetOpt.orElseThrow(); - // Compute To-Hit - var toHit = EngagementControlToHitData.compileToHit(game(), engagementControl); - // Compute defender To-Hit as if roles reversed but same control - var reverseAction = new EngagementControlAction(target.getId(), attacker.getId(), engagementControl.getEngagementControl()); - var toHitDefender = EngagementControlToHitData.compileToHit(game(), reverseAction); - - - // Report the engagement start - reporter.reportEngagementStart(attacker, target, engagementControl.getEngagementControl()); - - // Report attacker to-hit - reporter.reportAttackerToHitValue(toHit.getValue()); - - Roll attackerRoll = Compute.rollD6(2); - Roll defenderRoll = Compute.rollD6(2); - - // Report rolls - reporter.reportAttackerRoll(attacker, attackerRoll); - reporter.reportDefenderRoll(target, defenderRoll); - - var engagements = attacker.getMemory().getMemories("engagementControl"); - var targetEngagements = target.getMemory().getMemories("engagementControl"); - - var attackerDelta = attackerRoll.getMarginOfSuccess(toHit); - var defenderDelta = defenderRoll.getMarginOfSuccess(toHitDefender); - - attacker.setEngagementControl(engagementControl.getEngagementControl()); - attacker.setEngagementControlFailed(true); - - if (attackerDelta > defenderDelta) { - attacker.setEngagementControlFailed(false); - reporter.reportAttackerWin(attacker); - - switch (engagementControl.getEngagementControl()) { - case NONE: - attacker.setEngagementControl(EngagementControl.NONE); - break; - case FORCED_ENGAGEMENT: - case EVADE: - case OVERRUN: - case STANDARD: - attacker.setTargetFormationId(target.getId()); - target.setEngagementControl(engagementControl.getEngagementControl()); - // Adding memory, so the unit can remember that it is engaged with the target - engagements.add(Map.of( - "targetFormationId", attacker.getId(), - "wonEngagementControl", false, - "attacker", true, - "engagementControl", engagementControl.getEngagementControl() - )); - // Adding memory, so the unit can remember that it is engaged with the attacker - targetEngagements.add(Map.of( - "targetFormationId", attacker.getId(), - "wonEngagementControl", false, - "attacker", false, - "engagementControl", engagementControl.getEngagementControl() - )); - } - } else { - // Attacker loses - reporter.reportAttackerLose(attacker); - // Adding memory, so the unit can remember that it is engaged with the target - engagements.add(Map.of( - "targetFormationId", attacker.getId(), - "wonEngagementControl", false, - "attacker", true, - "engagementControl", engagementControl.getEngagementControl() - )); - // Adding memory, so the unit can remember that it is engaged with the attacker - targetEngagements.add(Map.of( - "targetFormationId", attacker.getId(), - "wonEngagementControl", false, - "attacker", false, - "engagementControl", engagementControl.getEngagementControl() - )); - } - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/MoraleCheckActionHandler.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/MoraleCheckActionHandler.java deleted file mode 100644 index 2ea40e26bf5..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/MoraleCheckActionHandler.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.handler; - -import megamek.common.Compute; -import megamek.common.Roll; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.action.MoraleCheckAction; -import mekhq.campaign.autoresolve.acar.action.RecoveringNerveAction; -import mekhq.campaign.autoresolve.acar.action.RecoveringNerveActionToHitData; -import mekhq.campaign.autoresolve.acar.report.MoraleReporter; -import mekhq.campaign.autoresolve.component.Formation; - -public class MoraleCheckActionHandler extends AbstractActionHandler { - - private final MoraleReporter reporter; - - public MoraleCheckActionHandler(MoraleCheckAction action, SimulationManager gameManager) { - super(action, gameManager); - this.reporter = new MoraleReporter(gameManager.getGame(), this::addReport); - } - - @Override - public boolean cares() { - return game().getPhase().isEnd(); - } - - @Override - public void execute() { - MoraleCheckAction moraleCheckAction = (MoraleCheckAction) getAction(); - - var demoralizedOpt = game().getFormation(moraleCheckAction.getEntityId()); - var demoralizedFormation = demoralizedOpt.orElseThrow(); - var toHit = RecoveringNerveActionToHitData.compileToHit(game(), new RecoveringNerveAction(demoralizedFormation.getId())); - Roll moraleCheck = Compute.rollD6(2); - - reporter.reportMoraleCheckStart(demoralizedFormation, toHit.getValue()); - reporter.reportMoraleCheckRoll(demoralizedFormation, moraleCheck); - - if (moraleCheck.isTargetRollSuccess(toHit)) { - // Success - no morale worsening - reporter.reportMoraleCheckSuccess(demoralizedFormation); - } else { - // Failure - morale worsens - var oldMorale = demoralizedFormation.moraleStatus(); - if (Formation.MoraleStatus.values().length == oldMorale.ordinal() + 1) { - demoralizedFormation.setMoraleStatus(Formation.MoraleStatus.ROUTED); - reporter.reportMoraleCheckFailure(demoralizedFormation, oldMorale, Formation.MoraleStatus.ROUTED); - } else { - var newMorale = Formation.MoraleStatus.values()[oldMorale.ordinal() + 1]; - demoralizedFormation.setMoraleStatus(newMorale); - reporter.reportMoraleCheckFailure(demoralizedFormation, oldMorale, newMorale); - } - } - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/RecoveringNerveActionHandler.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/RecoveringNerveActionHandler.java deleted file mode 100644 index 1fadda7d408..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/RecoveringNerveActionHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.handler; - -import megamek.common.Compute; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.action.RecoveringNerveAction; -import mekhq.campaign.autoresolve.acar.action.RecoveringNerveActionToHitData; -import mekhq.campaign.autoresolve.acar.report.RecoveringNerveActionReporter; -import mekhq.campaign.autoresolve.component.Formation; - -public class RecoveringNerveActionHandler extends AbstractActionHandler { - - private final RecoveringNerveActionReporter report; - - public RecoveringNerveActionHandler(RecoveringNerveAction action, SimulationManager gameManager) { - super(action, gameManager); - this.report = new RecoveringNerveActionReporter(game(), this::addReport); - } - - @Override - public boolean cares() { - return game().getPhase().isEnd(); - } - - - @Override - public void execute() { - var recoveringNerveAction = (RecoveringNerveAction) getAction(); - - var formationOpt = game().getFormation(recoveringNerveAction.getEntityId()); - - var formation = formationOpt.orElseThrow(); - if (formation.moraleStatus().ordinal() == 0) { - return; - } - - report.reportRecoveringNerveStart(formation); - var toHit = RecoveringNerveActionToHitData.compileToHit(game(), recoveringNerveAction); - report.reportToHitValue(toHit.getValue()); - var roll = Compute.rollD6(2); - if (!roll.isTargetRollSuccess(toHit)) { - report.reportSuccessRoll(roll); - var newMoraleStatus = Formation.MoraleStatus.values()[formation.moraleStatus().ordinal() -1]; - formation.setMoraleStatus(newMoraleStatus); - report.reportMoraleStatusChange(newMoraleStatus); - } else { - report.reportFailureRoll(roll); - } - - - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/StandardUnitAttackHandler.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/StandardUnitAttackHandler.java deleted file mode 100644 index 6d4e145e998..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/StandardUnitAttackHandler.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.handler; - -import megamek.codeUtilities.ObjectUtility; -import megamek.common.Compute; -import megamek.common.Entity; -import megamek.common.IEntityRemovalConditions; -import megamek.common.Roll; -import megamek.common.alphaStrike.AlphaStrikeElement; -import megamek.common.strategicBattleSystems.SBFUnit; -import megamek.common.util.weightedMaps.WeightedDoubleMap; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.action.AttackToHitData; -import mekhq.campaign.autoresolve.acar.action.StandardUnitAttack; -import mekhq.campaign.autoresolve.acar.report.AttackReporter; -import mekhq.campaign.autoresolve.component.EngagementControl; -import mekhq.campaign.autoresolve.component.Formation; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -public class StandardUnitAttackHandler extends AbstractActionHandler { - - private final AttackReporter reporter; - - public StandardUnitAttackHandler(StandardUnitAttack action, SimulationManager gameManager) { - super(action, gameManager); - this.reporter = new AttackReporter(game(), this::addReport); - } - - @Override - public boolean cares() { - return game().getPhase().isFiring(); - } - - @Override - public void execute() { - var attack = (StandardUnitAttack) getAction(); - var attackerOpt = game().getFormation(attack.getEntityId()); - var targetOpt = game().getFormation(attack.getTargetId()); - var attacker = attackerOpt.orElseThrow(); - var target = targetOpt.orElseThrow(); - - // Using simplified damage as of Interstellar Operations (BETA) page 241 - var targetUnitOpt = ObjectUtility.getRandomItemSafe(target.getUnits()); - var targetUnit = targetUnitOpt.orElseThrow(); - resolveAttack(attacker, attack, target, targetUnit); - } - - private void resolveAttack(Formation attacker, StandardUnitAttack attack, Formation target, SBFUnit targetUnit) { - var attackingUnit = attacker.getUnits().get(attack.getUnitNumber()); - var toHit = AttackToHitData.compileToHit(game(), attack); - - // Start of attack report - reporter.reportAttackStart(attacker, attack.getUnitNumber(), target); - - if (toHit.cannotSucceed()) { - reporter.reportCannotSucceed(toHit.getDesc()); - } else { - reporter.reportToHitValue(toHit); - Roll roll = Compute.rollD6(2); - reporter.reportAttackRoll(roll, attacker); - - if (roll.getIntValue() < toHit.getValue()) { - reporter.reportAttackMiss(); - } else { - reporter.reportAttackHit(); - var damage = calculateDamage(attacker, attack, attackingUnit, target); - - - applyDamage(target, targetUnit, damage, attackingUnit); - } - } - } - - private void applyDamage(Formation target, SBFUnit targetUnit, int[] damage, SBFUnit attackingUnit) { - var elements = new ArrayList<>(targetUnit.getElements()); - - var totalDamageApplied = 0; - // distribute the damage between elements - for (int dmg : damage) { - if (dmg == 0) { - continue; - } - - // shuffle so the damage is applied to a random element sequence. - Collections.shuffle(elements); - - // a single element can only cause damage to a single element at a time - // so here we try to hit one, if it has armor we apply damage to its armor, if - // the armor is 0 and it has structure and there is damage left we hit its structure, - // we then stop here. - for (AlphaStrikeElement element : elements) { - var elementStructure = element.getCurrentStructure(); - if (elementStructure == 0) { - continue; - } - - var elementArmor = element.getCurrentArmor(); - if (elementArmor > 0) { - var elementDamage = Math.min(dmg, elementArmor); - element.setCurrentArmor(elementArmor - elementDamage); - dmg -= elementDamage; - totalDamageApplied += elementDamage; - } - if (elementStructure > 0 && elementArmor == 0) { - var elementDamage = Math.min(dmg, elementStructure); - element.setCurrentStructure(elementStructure - elementDamage); - totalDamageApplied += elementDamage; - } - // if damage was applied to an element, we stop here and move to the next damage attack thingy - // a single attack cannot deal damage to more than one element at a time - if (totalDamageApplied > 0) { - break; - } - } - } - - targetUnit.setCurrentArmor(targetUnit.getCurrentArmor() - totalDamageApplied); - - reporter.reportDamageDealt(targetUnit, totalDamageApplied, targetUnit.getCurrentArmor()); - - if (targetUnit.getCurrentArmor() * 2 <= targetUnit.getCurrentArmor()) { - target.setHighStressEpisode(); - reporter.reportStressEpisode(); - } - - if (target.isCrippled() && targetUnit.getCurrentArmor() > 0) { - reporter.reportStressEpisode(); - target.setHighStressEpisode(); - reporter.reportUnitCrippled(); - } - - if (targetUnit.getCurrentArmor() == 0) { - // Destroyed - reporter.reportUnitDestroyed(); - target.setHighStressEpisode(); - countKill(attackingUnit, targetUnit); - } else { - // Check for critical hits if armor is now less than half original - if (targetUnit.getCurrentArmor() * 2 < targetUnit.getArmor()) { - reporter.reportCriticalCheck(); - var critRoll = Compute.rollD6(2); - var criticalRollResult = critRoll.getIntValue(); - handleCrits(target, targetUnit, criticalRollResult, attackingUnit); - } - } - } - - private int[] calculateDamage(Formation attacker, StandardUnitAttack attack, - SBFUnit attackingUnit, Formation target) { - int bonusDamage = 0; - if (attack.getManeuverResult().equals(StandardUnitAttack.ManeuverResult.SUCCESS)) { - bonusDamage += 1; - } - - var damage = attackingUnit.getElements().stream().mapToInt(e -> e.getStandardDamage().getDamage(attack.getRange()).damage).toArray(); - return processDamageByEngagementControl(attacker, target, bonusDamage, damage); - } - - private void handleCrits(Formation target, SBFUnit targetUnit, int criticalRollResult, SBFUnit attackingUnit) { - switch (criticalRollResult) { - case 2, 3, 4 -> reporter.reportNoCrit(); - case 5, 6, 7 -> { - targetUnit.addTargetingCrit(); - reporter.reportTargetingCrit(targetUnit); - } - case 8, 9 -> { - targetUnit.addDamageCrit(); - reporter.reportDamageCrit(targetUnit); - } - case 10, 11 -> { - targetUnit.addTargetingCrit(); - targetUnit.addDamageCrit(); - reporter.reportTargetingCrit(targetUnit); - reporter.reportDamageCrit(targetUnit); - } - default -> { - countKill(attackingUnit, targetUnit); - targetUnit.setCurrentArmor(0); - target.setHighStressEpisode(); - reporter.reportUnitDestroyed(); - } - } - } - - private void countKill(SBFUnit attackingUnit, SBFUnit targetUnit) { - var killers = attackingUnit.getElements().stream().map(AlphaStrikeElement::getId) - .map(e -> simulationManager().getGame().getEntity(e)).filter(Optional::isPresent).map(Optional::get).toList(); - var targets = targetUnit.getElements().stream().map(AlphaStrikeElement::getId) - .map(e -> simulationManager().getGame().getEntity(e)).filter(Optional::isPresent).map(Optional::get).toList(); - for (var target : targets) { - ObjectUtility.getRandomItemSafe(killers).ifPresent(e -> e.addKill(target)); - } - } - - private int[] processDamageByEngagementControl(Formation attacker, Formation target, int bonusDamage, int[] damage) { - var engagementControlMemories = attacker.getMemory().getMemories("engagementControl"); - var engagement = engagementControlMemories - .stream() - .filter(f -> f.getOrDefault("targetFormationId", Entity.NONE).equals(target.getId())) - .findFirst(); // there should be only one engagement control memory for this target - - if (damage.length > 0) { - damage[0] += bonusDamage; - } - - if (engagement.isEmpty()) { - return damage; - } - - var isAttacker = (boolean) engagement.get().getOrDefault("attacker", false); - - if (!isAttacker) { - return damage; - } - - var wonEngagement = (boolean) engagement.get().getOrDefault("wonEngagementControl", false); - var engagementControl = (EngagementControl) engagement.get().getOrDefault("engagementControl", EngagementControl.NONE); - if (wonEngagement) { - return processDamageEngagementControlVictory(engagementControl, damage); - } - return processDamageEngagementControlDefeat(engagementControl, damage); - } - - private int[] processDamageEngagementControlDefeat(EngagementControl engagementControl, int[] damage) { - if (engagementControl == EngagementControl.EVADE) { - for (int i = 0; i < damage.length; i++) { - damage[i] = (int) ((double) damage[i] * 0.5); - } - } - return damage; - } - - private int[] processDamageEngagementControlVictory(EngagementControl engagementControl, int[] damage) { - return switch(engagementControl) { - case OVERRUN -> { - for (var i = 0; i < damage.length; i++) { - damage[i] = (int) ((double) damage[i] * 0.25); - } - yield damage; - } - case FORCED_ENGAGEMENT -> { - for (var i = 0; i < damage.length; i++) { - damage[i] = (int) ((double) damage[i] * 0.5); - } - yield damage; - } - default -> damage; - }; - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/WithdrawActionHandler.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/WithdrawActionHandler.java deleted file mode 100644 index 95d1abb0e3e..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/handler/WithdrawActionHandler.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.handler; - -import megamek.common.Compute; -import megamek.common.IEntityRemovalConditions; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.action.WithdrawAction; -import mekhq.campaign.autoresolve.acar.action.WithdrawToHitData; -import mekhq.campaign.autoresolve.acar.report.WithdrawReporter; -import mekhq.campaign.unit.damage.DamageApplierChooser; - -import java.util.concurrent.atomic.AtomicInteger; - -public class WithdrawActionHandler extends AbstractActionHandler { - - private final WithdrawReporter reporter; - - public WithdrawActionHandler(WithdrawAction action, SimulationManager gameManager) { - super(action, gameManager); - this.reporter = new WithdrawReporter(gameManager.getGame(), this::addReport); - } - - - @Override - public boolean cares() { - return game().getPhase().isEnd(); - } - - /** - * This is not up to rules as written, the intention was to create a play experience that is more challenging and engaging. - * The rules as written allow for a very simple withdraw mechanic that in this situation is very easy to exploit and would - * create too many games which result in no losses. - */ - @Override - public void execute() { - var withdraw = (WithdrawAction) getAction(); - var withdrawOpt = game().getFormation(withdraw.getEntityId()); - - if (withdrawOpt.isEmpty()) { - return; - } - - var withdrawFormation = withdrawOpt.get(); - var toHit = WithdrawToHitData.compileToHit(game(), withdrawFormation); - var withdrawRoll = Compute.rollD6(2); - - // Reporting the start of the withdrawal attempt - reporter.reportStartWithdraw(withdrawFormation, toHit); - // Reporting the roll - reporter.reportWithdrawRoll(withdrawFormation, withdrawRoll); - - if (withdrawRoll.isTargetRollSuccess(toHit)) { - // successful withdraw - withdrawFormation.setDeployed(false); - AtomicInteger unitWithdrawn = new AtomicInteger(); - for (var unit : withdrawFormation.getUnits()) { - for (var element : unit.getElements()) { - game().getEntity(element.getId()).ifPresent(entity -> { - // only withdraw live units - if (entity.getRemovalCondition() != IEntityRemovalConditions.REMOVE_IN_RETREAT && - entity.getRemovalCondition() != IEntityRemovalConditions.REMOVE_DEVASTATED && - entity.getRemovalCondition() != IEntityRemovalConditions.REMOVE_SALVAGEABLE && - entity.getRemovalCondition() != IEntityRemovalConditions.REMOVE_PUSHED && - entity.getRemovalCondition() != IEntityRemovalConditions.REMOVE_CAPTURED && - entity.getRemovalCondition() != IEntityRemovalConditions.REMOVE_EJECTED && - entity.getRemovalCondition() != IEntityRemovalConditions.REMOVE_NEVER_JOINED) - { - entity.setRemovalCondition(IEntityRemovalConditions.REMOVE_IN_RETREAT); - unitWithdrawn.getAndIncrement(); - } - game().addUnitToGraveyard(entity); - }); - } - } - if (unitWithdrawn.get() > 0) { - reporter.reportSuccessfulWithdraw(); - } - game().removeFormation(withdrawFormation); - } else { - reporter.reportFailedWithdraw(); - } - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/ActionsProcessor.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/ActionsProcessor.java deleted file mode 100644 index 645bf7dae36..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/ActionsProcessor.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.manager; - -import megamek.common.actions.EntityAction; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.action.*; - -public record ActionsProcessor(SimulationManager simulationManager) implements SimulationManagerHelper { - - public void handleActions() { - addNewHandlers(); - processHandlers(); - removeFinishedHandlers(); - } - - /** - * Add new handlers to the game - * Every new type of action has to have its handler registered here, otherwise it won't be processed - */ - private void addNewHandlers() { - for (EntityAction action : game().getActionsVector()) { - if (action instanceof AttackAction attack && attack.getHandler(simulationManager) != null) { - game().addActionHandler(attack.getHandler(simulationManager)); - } else if (action instanceof EngagementControlAction engagementControl && engagementControl.getHandler(simulationManager) != null) { - game().addActionHandler(engagementControl.getHandler(simulationManager)); - } else if (action instanceof WithdrawAction withdraw && withdraw.getHandler(simulationManager) != null) { - game().addActionHandler(withdraw.getHandler(simulationManager)); - } else if (action instanceof RecoveringNerveAction recoveringNerve && recoveringNerve.getHandler(simulationManager) != null) { - game().addActionHandler(recoveringNerve.getHandler(simulationManager)); - } else if (action instanceof MoraleCheckAction moraleCheck && moraleCheck.getHandler(simulationManager) != null) { - game().addActionHandler(moraleCheck.getHandler(simulationManager)); - } - } - } - - private void processHandlers() { - for (ActionHandler handler : game().getActionHandlers()) { - if (handler.cares()) { - handler.handle(); - } - } - } - - private void removeFinishedHandlers() { - game().getActionHandlers().removeIf(ActionHandler::isFinished); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/InitiativeHelper.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/InitiativeHelper.java deleted file mode 100644 index da6779a4703..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/InitiativeHelper.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.manager; - -import megamek.common.*; -import megamek.common.enums.GamePhase; -import megamek.common.strategicBattleSystems.SBFFormation; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.report.FormationReportEntry; -import mekhq.campaign.autoresolve.acar.report.PlayerNameReportEntry; -import mekhq.campaign.autoresolve.acar.report.PublicReportEntry; -import mekhq.campaign.autoresolve.acar.report.ReportHeader; -import mekhq.campaign.autoresolve.component.AcTurn; -import mekhq.campaign.autoresolve.component.Formation; -import mekhq.campaign.autoresolve.component.FormationTurn; - -import java.util.*; -import java.util.stream.Collectors; - -/** - * @author Luana Coppio - */ -public record InitiativeHelper(SimulationManager simulationManager) implements SimulationManagerHelper { - - /** - * Determines the turn order for a given phase, setting the game's turn list and sending it to the - * Clients. Also resets the turn index. - * - * @param phase The phase to find the turns for - * @see AbstractGame#resetTurnIndex() - */ - void determineTurnOrder(GamePhase phase) { - final List turns; - if (phase.isFiring() || phase.isMovement()) { - turns = game().getInGameObjects().stream() - .filter(Formation.class::isInstance) - .map(Formation.class::cast) - .filter(Formation::isDeployed) - .filter(unit -> unit.isEligibleForPhase(phase)) - .map(InGameObject::getOwnerId) - .map(FormationTurn::new) - .sorted(Comparator.comparing(t -> game().getPlayer(t.playerId()).getInitiative())) - .collect(Collectors.toList()); - } else if (phase.isDeployment()) { - // Deployment phase: sort by initiative - turns = game().getInGameObjects().stream() - .filter(Formation.class::isInstance) - .map(Formation.class::cast) - .filter(unit -> !unit.isDeployed()) - .map(InGameObject::getOwnerId) - .map(FormationTurn::new) - .sorted(Comparator.comparing(t -> game().getPlayer(t.playerId()).getInitiative())) - .collect(Collectors.toList()); - - } else { - // As a fallback, provide unsorted turns - turns = game().getInGameObjects().stream() - .filter(Formation.class::isInstance) - .map(Formation.class::cast) - .filter(SBFFormation::isDeployed) - .filter(unit -> unit.isEligibleForPhase(phase)) - .map(InGameObject::getOwnerId) - .map(FormationTurn::new) - .collect(Collectors.toList()); - - // Now, assemble formations and sort by initiative and relative formation count - Map unitCountsByPlayer = game().getInGameObjects().stream() - .filter(Formation.class::isInstance) - .map(Formation.class::cast) - .filter(SBFFormation::isDeployed) - .filter(unit -> unit.isEligibleForPhase(phase)) - .collect(Collectors.groupingBy(InGameObject::getOwnerId, Collectors.counting())); - - if (!unitCountsByPlayer.isEmpty()) { - final long lowestUnitCount = Collections.min(unitCountsByPlayer.values()); - - int playerWithLowestUnitCount = unitCountsByPlayer.entrySet().stream() - .filter(e -> e.getValue() == lowestUnitCount) - .map(Map.Entry::getKey) - .findAny().orElse(Player.PLAYER_NONE); - - List playersByInitiative = new ArrayList<>(unitCountsByPlayer.keySet()); - playersByInitiative.sort(Comparator.comparing(id -> game().getPlayer(id).getInitiative())); - - if ((playerWithLowestUnitCount != Player.PLAYER_NONE) && (lowestUnitCount > 0)) { - List sortedTurns = new ArrayList<>(); - for (int initCycle = 0; initCycle < lowestUnitCount; initCycle++) { - long currentLowestUnitCount = Collections.min(unitCountsByPlayer.values()); - for (int playerId : playersByInitiative) { - long unitsToMove = unitCountsByPlayer.get(playerId) / currentLowestUnitCount; - long remainingUnits = unitCountsByPlayer.get(playerId); - unitsToMove = Math.min(unitsToMove, remainingUnits); - for (int i = 0; i < unitsToMove; i++) { - sortedTurns.add(new FormationTurn(playerId)); - } - unitCountsByPlayer.put(playerId, remainingUnits - unitsToMove); - } - } - // When here, sorting has been successful; replace the unsorted turns - turns.clear(); - turns.addAll(sortedTurns); - } - } - } - - game().setTurns(turns); - game().resetTurnIndex(); - } - - public void writeInitiativeReport() { - writeHeader(); - writeInitiativeRolls(); - writeTurnOrder(); - writeFutureDeployment(); - } - - private void writeTurnOrder() { - addReport(new PublicReportEntry(1020)); - - for (var turn : game().getTurnsList()) { - Player player = game().getPlayer(turn.playerId()); - addReport(new PlayerNameReportEntry(player).indent().addNL()); - } - - } - - private void writeFutureDeployment() { - // remaining deployments - Comparator comp = Comparator.comparingInt(Deployable::getDeployRound); - List futureDeployments = game().getInGameObjects().stream() - .filter(Formation.class::isInstance) - .map(Deployable.class::cast) - .filter(unit -> !unit.isDeployed()) - .sorted(comp) - .toList(); - - if (!futureDeployments.isEmpty()) { - addReport(new PublicReportEntry(1060)); - int round = -1; - for (Deployable deployable : futureDeployments) { - if (round != deployable.getDeployRound()) { - round = deployable.getDeployRound(); - addReport(new PublicReportEntry(1065).add(round)); - } - - var r = new PublicReportEntry(1066) - .add(new FormationReportEntry((Formation) deployable, game()).text()) - .add(((InGameObject) deployable).getId()) - .add(deployable.getDeployRound()) - .indent(); - addReport(r); - } - } - } - - private void writeInitiativeRolls() { - for (Team team : game().getTeams()) { - // Teams with no active players can be ignored - if (team.isObserverTeam()) { - continue; - } - addReport(new PublicReportEntry(1015).add(Player.TEAM_NAMES[team.getId()]) - .add(team.getInitiative().toString())); - - // Multiple players. List the team, then break it down. - for (Player player : team.nonObserverPlayers()) { - addReport(new PublicReportEntry(2020) - .indent() - .add(player.getName()) - .add(player.getInitiative().toString()) - ); - } - } - } - - private void writeHeader() { - addReport(new ReportHeader(1200)); - if (game().getLastPhase().isDeployment() || game().isDeploymentComplete() - || !game().shouldDeployThisRound()) { - addReport(new ReportHeader(1000).add(game().getCurrentRound())); - } else { - if (game().getCurrentRound() == 0) { - addReport(new ReportHeader(1005)); - } else { - addReport(new ReportHeader(1000).add(game().getCurrentRound())); - } - } - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/PhaseEndManager.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/PhaseEndManager.java deleted file mode 100644 index e049c3b2b80..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/PhaseEndManager.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.manager; - -import megamek.common.enums.GamePhase; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.report.PublicReportEntry; - -public record PhaseEndManager(SimulationManager simulationManager) implements SimulationManagerHelper { - - public void managePhase() { - switch (simulationManager.getGame().getPhase()) { - case INITIATIVE: - simulationManager.addReport(new PublicReportEntry(999)); - simulationManager.getGame().setupDeployment(); - simulationManager.resetFormations(); - simulationManager.flushPendingReports(); - if (simulationManager.getGame().shouldDeployThisRound()) { - simulationManager.changePhase(GamePhase.DEPLOYMENT); - } else { - simulationManager.changePhase(GamePhase.SBF_DETECTION); - } - break; - case DEPLOYMENT: - simulationManager.addReport(new PublicReportEntry(999)); - simulationManager.getGame().clearDeploymentThisRound(); - phaseCleanup(); - simulationManager.changePhase(GamePhase.SBF_DETECTION); - break; - case SBF_DETECTION: - simulationManager.getActionsProcessor().handleActions(); - phaseCleanup(); - simulationManager.changePhase(GamePhase.MOVEMENT); - break; - case MOVEMENT: - simulationManager.addReport(new PublicReportEntry(999)); - simulationManager.addReport(new PublicReportEntry(2201)); - simulationManager.getActionsProcessor().handleActions(); - phaseCleanup(); - simulationManager.changePhase(GamePhase.FIRING); - break; - case FIRING: - simulationManager.addReport(new PublicReportEntry(999)); - simulationManager.addReport(new PublicReportEntry(2002)); - simulationManager.getActionsProcessor().handleActions(); - phaseCleanup(); - simulationManager.changePhase(GamePhase.END); - break; - case END: - simulationManager.getActionsProcessor().handleActions(); - endPhaseCleanup(); - if (simulationManager.isVictory()) { - simulationManager.changePhase(GamePhase.VICTORY); - } - break; - case VICTORY: - endPhaseCleanup(); - case STARTING_SCENARIO: - default: - break; - } - } - - private void phaseCleanup() { - simulationManager.resetPlayersDone(); - simulationManager.resetFormationsDone(); - simulationManager.flushPendingReports(); - } - private void endPhaseCleanup() { - simulationManager.resetPlayersDone(); - simulationManager.resetFormations(); - simulationManager.flushPendingReports(); - } - -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/PhasePreparationManager.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/PhasePreparationManager.java deleted file mode 100644 index 070ee3d8bd6..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/PhasePreparationManager.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.manager; - -import mekhq.campaign.autoresolve.acar.SimulationManager; - -public record PhasePreparationManager(SimulationManager simulationManager) implements SimulationManagerHelper { - - public void managePhase() { - clearActions(); - switch (game().getPhase()) { - case DEPLOYMENT: - case SBF_DETECTION: - case MOVEMENT: - case FIRING: - resetEntityPhase(); - simulationManager.getInitiativeHelper().determineTurnOrder(game().getPhase()); - case INITIATIVE: - clearActions(); - break; - case END: - case VICTORY: - default: - clearReports(); - clearActions(); - break; - } - } - - public void resetEntityPhase() { - for (var formation : game().getActiveFormations()) { - formation.setDone(false); - } - } - - private void clearActions() { - game().clearActions(); - } - - private void clearReports() { - simulationManager.flushPendingReports(); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/SimulationManagerHelper.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/SimulationManagerHelper.java deleted file mode 100644 index 04166a97d2b..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/SimulationManagerHelper.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.manager; - -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.report.PublicReportEntry; - -/** - * Helper interface for classes that need to interact with the {@link SimulationManager}. - * @author Luana Coppio - */ -public interface SimulationManagerHelper { - - SimulationManager simulationManager(); - - default SimulationContext game() { - return simulationManager().getGame(); - } - - default void addReport(PublicReportEntry reportEntry) { - simulationManager().addReport(reportEntry); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/VictoryHelper.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/VictoryHelper.java deleted file mode 100644 index 9d6542d83df..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/manager/VictoryHelper.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.manager; - -import megamek.common.Player; -import megamek.server.victory.VictoryResult; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.acar.SimulationManager; - -public class VictoryHelper { - - private final SimulationManager manager; - private VictoryResult result; - - public VictoryHelper(SimulationManager manager) { - this.manager = manager; - } - - public VictoryResult getVictoryResult() { - if (this.result == null || !this.result.isVictory()) { - this.result = new BattlefieldControlVictory().checkVictory(manager.getGame()); - } - return this.result; - } - - public static class BattlefieldControlVictory { - - public VictoryResult checkVictory(SimulationContext context) { - // check all players/teams for aliveness - int playersAlive = 0; - Player lastPlayer = null; - boolean oneTeamAlive = false; - int lastTeam = Player.TEAM_NONE; - boolean unteamedAlive = false; - for (Player player : context.getPlayersList()) { - int team = player.getTeam(); - if (context.getLiveDeployedEntitiesOwnedBy(player) <= 0) { - continue; - } - // we found a live one! - playersAlive++; - lastPlayer = player; - // check team - if (team == Player.TEAM_NONE) { - unteamedAlive = true; - } else if (lastTeam == Player.TEAM_NONE) { - // possibly only one team alive - oneTeamAlive = true; - lastTeam = team; - } else if (team != lastTeam) { - // more than one team alive - oneTeamAlive = false; - lastTeam = team; - } - } - - // check if there's one player alive - if (playersAlive < 1) { - return VictoryResult.drawResult(); - } else if (playersAlive == 1) { - if (lastPlayer.getTeam() == Player.TEAM_NONE) { - // individual victory - return new VictoryResult(true, lastPlayer.getId(), Player.TEAM_NONE); - } - } - // did we only find one live team? - if (oneTeamAlive && !unteamedAlive) { - // team victory - return new VictoryResult(true, Player.PLAYER_NONE, lastTeam); - } - - if (context.gameTimerIsExpired()) { - // draw - return VictoryResult.drawResult(); - } - - return VictoryResult.noResult(); - } - } - -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/DeploymentPhase.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/DeploymentPhase.java deleted file mode 100644 index 9859c67765a..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/DeploymentPhase.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.phase; - -import megamek.common.enums.GamePhase; -import mekhq.campaign.autoresolve.acar.SimulationManager; - -public class DeploymentPhase extends PhaseHandler { - - public DeploymentPhase(SimulationManager simulationManager) { - super(simulationManager, GamePhase.DEPLOYMENT); - } - - @Override - protected void executePhase() { - // Automatically deploy all formations that are set to deploy this round - getSimulationManager().getGame().getActiveFormations().stream() - .filter( f-> !f.isDeployed()) - .filter( f-> f.getDeployRound() == getSimulationManager().getGame().getCurrentRound()) - .forEach( f-> f.setDeployed(true)); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/EndPhase.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/EndPhase.java deleted file mode 100644 index 005d7cc11ca..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/EndPhase.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.phase; -import megamek.ai.utility.Memory; -import megamek.common.Compute; -import megamek.common.IEntityRemovalConditions; -import megamek.common.enums.GamePhase; -import megamek.common.strategicBattleSystems.SBFUnit; -import megamek.common.util.weightedMaps.WeightedDoubleMap; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.action.MoraleCheckAction; -import mekhq.campaign.autoresolve.acar.action.RecoveringNerveAction; -import mekhq.campaign.autoresolve.acar.action.WithdrawAction; -import mekhq.campaign.autoresolve.acar.report.EndPhaseReporter; -import mekhq.campaign.autoresolve.component.Formation; -import mekhq.campaign.unit.damage.DamageApplierChooser; - - -import java.util.List; - -public class EndPhase extends PhaseHandler { - - private final EndPhaseReporter reporter; - - public EndPhase(SimulationManager simulationManager) { - super(simulationManager, GamePhase.END); - reporter = new EndPhaseReporter(simulationManager.getGame(), simulationManager::addReport); - } - - @Override - protected void executePhase() { - reporter.endPhaseHeader(); - checkUnitDestruction(); - checkMorale(); - checkWithdrawingForces(); - checkRecoveringNerves(); - forgetEverything(); - } - - private void checkUnitDestruction() { - var areThereUnitsToDestroy = getContext().getActiveFormations().stream() - .flatMap(f -> f.getUnits().stream()).anyMatch(u -> u.getCurrentArmor() <= 0); - if (areThereUnitsToDestroy) { - reporter.destroyedUnitsHeader(); - } - var allFormations = getContext().getActiveFormations(); - for (var formation : allFormations) { - var destroyedUnits = formation.getUnits().stream() - .filter(u -> u.getCurrentArmor() <= 0) - .toList(); - if (!destroyedUnits.isEmpty()) { - destroyUnits(formation, destroyedUnits); - } - } - } - - private void checkWithdrawingForces() { - if (getSimulationManager().isVictory()) { - // If the game is over, no need to withdraw - return; - } - var forcedWithdrawingUnits = getSimulationManager().getGame().getActiveFormations().stream() - .filter(f -> f.moraleStatus() == Formation.MoraleStatus.ROUTED || f.isCrippled()) - .toList(); - - for (var formation : forcedWithdrawingUnits) { - getSimulationManager().addWithdraw(new WithdrawAction(formation.getId())); - } - } - - private void checkMorale() { - var formationNeedsMoraleCheck = getSimulationManager().getGame().getActiveFormations().stream() - .filter(Formation::hadHighStressEpisode) - .toList(); - - for (var formation : formationNeedsMoraleCheck) { - getSimulationManager().addMoraleCheck(new MoraleCheckAction(formation.getId()), formation); - } - } - - private void checkRecoveringNerves() { - var recoveringNerves = getSimulationManager().getGame().getActiveFormations().stream() - .filter(f -> f.moraleStatus().ordinal() > Formation.MoraleStatus.NORMAL.ordinal()) - .toList(); - - for (var formation : recoveringNerves) { - getSimulationManager().addNerveRecovery(new RecoveringNerveAction(formation.getId())); - } - } - - private void forgetEverything() { - var formations = getSimulationManager().getGame().getActiveFormations(); - formations.stream().map(Formation::getMemory).forEach(Memory::clear); - formations.forEach(Formation::reset); - } - - private static final WeightedDoubleMap REMOVAL_CONDITIONS_TABLE = WeightedDoubleMap.of( - IEntityRemovalConditions.REMOVE_SALVAGEABLE, 2, - IEntityRemovalConditions.REMOVE_DEVASTATED, 5, - IEntityRemovalConditions.REMOVE_EJECTED, 10 - ); - - private static final WeightedDoubleMap REMOVAL_CONDITIONS_TABLE_NO_EJECTION = WeightedDoubleMap.of( - IEntityRemovalConditions.REMOVE_SALVAGEABLE, 3, - IEntityRemovalConditions.REMOVE_DEVASTATED, 7 - ); - - public void destroyUnits(Formation formation, List destroyedUnits) { - for (var unit : destroyedUnits) { - for (var element : unit.getElements()) { - var entityOpt = getContext().getEntity(element.getId()); - if (entityOpt.isPresent()) { - var entity = entityOpt.get(); - var removalConditionTable = entity.isEjectionPossible() ? - REMOVAL_CONDITIONS_TABLE : REMOVAL_CONDITIONS_TABLE_NO_EJECTION; - entity.setRemovalCondition(removalConditionTable.randomItem()); - - reporter.reportUnitDestroyed(entity); - getContext().addUnitToGraveyard(entity); - } - } - - formation.removeUnit(unit); - if (formation.getUnits().isEmpty()) { - getContext().removeFormation(formation); - } - } - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/FiringPhase.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/FiringPhase.java deleted file mode 100644 index 8ffbea45858..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/FiringPhase.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.phase; - -import megamek.common.Compute; -import megamek.common.Entity; -import megamek.common.alphaStrike.ASRange; -import megamek.common.enums.GamePhase; -import megamek.common.util.weightedMaps.WeightedDoubleMap; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.action.Action; -import mekhq.campaign.autoresolve.acar.action.ManeuverToHitData; -import mekhq.campaign.autoresolve.acar.action.StandardUnitAttack; -import mekhq.campaign.autoresolve.component.EngagementControl; -import mekhq.campaign.autoresolve.component.Formation; -import mekhq.campaign.autoresolve.component.FormationTurn; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -public class FiringPhase extends PhaseHandler { - - public FiringPhase(SimulationManager gameManager) { - super(gameManager, GamePhase.FIRING); - } - - @Override - protected void executePhase() { - while (getSimulationManager().getGame().hasMoreTurns()) { - - var optTurn = getSimulationManager().getGame().changeToNextTurn(); - - if (optTurn.isEmpty()) { - break; - } - var turn = optTurn.get(); - - - if (turn instanceof FormationTurn formationTurn) { - var player = getSimulationManager().getGame().getPlayer(formationTurn.playerId()); - - getSimulationManager().getGame().getActiveFormations(player) - .stream() - .filter(f -> f.isEligibleForPhase(getSimulationManager().getGame().getPhase())) // only eligible formations - .findAny() - .map(this::attack) - .stream() - .flatMap(Collection::stream) - .forEach(this::standardUnitAttacks); // add engage and control action - } - } - } - - private void standardUnitAttacks(AttackRecord attackRecord) { - var actingFormation = attackRecord.actingFormation; - var target = attackRecord.target; - - var attacks = new ArrayList(); - - var actingFormationToHit = ManeuverToHitData.compileToHit(actingFormation); - var targetFormationToHit = ManeuverToHitData.compileToHit(target); - - ASRange range = ASRange.LONG; - StandardUnitAttack.ManeuverResult manueverModifier = StandardUnitAttack.ManeuverResult.DRAW; - if (!actingFormation.isRangeSet(target.getId())) { - var actingFormationMos = Compute.rollD6(2).getMarginOfSuccess(actingFormationToHit); - var targetFormationMos = Compute.rollD6(2).getMarginOfSuccess(targetFormationToHit); - - if (actingFormationMos > targetFormationMos) { - if (EngagementControl.OVERRUN.equals(actingFormation.getEngagementControl()) - && !actingFormation.isEngagementControlFailed()) { - range = ASRange.SHORT; - } else { - range = WeightedDoubleMap.of( - ASRange.LONG, actingFormation.getStdDamage().L.damage, - ASRange.MEDIUM, actingFormation.getStdDamage().M.damage, - ASRange.SHORT, actingFormation.getStdDamage().S.damage - ).randomItem(); - - } - manueverModifier = StandardUnitAttack.ManeuverResult.SUCCESS; - target.setRange(actingFormation.getId(), range); - } else if (actingFormationMos < targetFormationMos) { - range = WeightedDoubleMap.of( - ASRange.LONG, target.getStdDamage().L.damage, - ASRange.MEDIUM, target.getStdDamage().M.damage, - ASRange.SHORT, target.getStdDamage().S.damage - ).randomItem(); - manueverModifier = StandardUnitAttack.ManeuverResult.FAILURE; - target.setRange(actingFormation.getId(), range); - } - - actingFormation.setRange(target.getId(), range); - } - - range = actingFormation.getRange(target.getId()); - - var maxAttacksOnFailure = Math.max( - Math.round(actingFormation.getUnits().size() / 2.0), - Math.min(actingFormation.getUnits().size(), 1)); - var maxAttacksNormally = actingFormation.getUnits().size(); - - var maxAttacks = manueverModifier == StandardUnitAttack.ManeuverResult.FAILURE ? maxAttacksOnFailure : maxAttacksNormally; - - for (int i = 0; (i < maxAttacks) && (i < attackRecord.attackingUnits.size()); i++) { - var unitIndex = attackRecord.attackingUnits.get(i); - var attack = new StandardUnitAttack(actingFormation.getId(), unitIndex, target.getId(), range, manueverModifier); - attacks.add(attack); - } - - getSimulationManager().addAttack(attacks, actingFormation); - } - - private record AttackRecord(Formation actingFormation, Formation target, List attackingUnits) { } - - private List attack(Formation actingFormation) { - var target = this.selectTarget(actingFormation); - - List unitIds = new ArrayList<>(); - for (int i = 0; i < actingFormation.getUnits().size(); i++) { - unitIds.add(i); - } - - var ret = new ArrayList(); - - if (target.size() == 2 && unitIds.size() > 1) { - var sizeOfGroupA = Compute.randomInt(unitIds.size()); - var sizeOfGroupB = unitIds.size() - sizeOfGroupA; - - Collections.shuffle(unitIds); - - var groupA = unitIds.subList(0, sizeOfGroupA); - var groupB = unitIds.subList(sizeOfGroupA, unitIds.size()); - - if (sizeOfGroupA > 0) { - ret.add(new AttackRecord(actingFormation, target.get(0), groupA)); - } - if (sizeOfGroupB > 0) { - ret.add(new AttackRecord(actingFormation, target.get(1), groupB)); - } - } else if (target.size() == 1) { - ret.add(new AttackRecord(actingFormation, target.get(0), unitIds)); - } - - return ret; - } - - private List selectTarget(Formation actingFormation) { - var game = getSimulationManager().getGame(); - var player = game.getPlayer(actingFormation.getOwnerId()); - var canBeTargets = getSimulationManager().getGame().getActiveFormations().stream() - .filter(Formation::isDeployed) - .filter(f -> game.getPlayer(f.getOwnerId()).isEnemyOf(player)) - .filter(f -> f.getId() != actingFormation.getTargetFormationId()) - .filter( f -> !formationWasOverrunByFormation(actingFormation, f)) - .filter(f -> !formationWasEvadedByFormation(actingFormation, f)) - .collect(Collectors.toList()); - Collections.shuffle(canBeTargets); - - var mandatoryTarget = game.getFormation(actingFormation.getTargetFormationId()); - mandatoryTarget.ifPresent(formation -> canBeTargets.add(0, formation)); - - if (canBeTargets.isEmpty()) { - return List.of(); - } - - if (canBeTargets.size() == 1) { - return canBeTargets; - } - - if (formationWasForcedByFormation(actingFormation, canBeTargets.get(0))) { - return List.of(canBeTargets.get(0)); - } - - var targetCount = Compute.randomInt(2) + 1; - return canBeTargets.subList(0, targetCount); - } - - private boolean formationWasOverrunByFormation(Formation actingFormation, Formation target) { - return engagementAndControlResult(actingFormation, target, EngagementControl.OVERRUN); - } - - private boolean formationWasEvadedByFormation(Formation actingFormation, Formation target) { - return engagementAndControlResult(actingFormation, target, EngagementControl.EVADE); - } - - private boolean formationWasForcedByFormation(Formation actingFormation, Formation target) { - return engagementAndControlResult(actingFormation, target, EngagementControl.FORCED_ENGAGEMENT); - } - - private boolean engagementAndControlResult(Formation actingFormation, Formation target, EngagementControl engagementControl) { - var engagementControlMemories = actingFormation.getMemory().getMemories("engagementControl"); - var engagement = engagementControlMemories.stream().filter(f -> f.getOrDefault("targetFormationId", Entity.NONE).equals(target.getId())) - .findFirst(); // there should be only one engagement control memory for this target - if (engagement.isEmpty()) { - return false; - } - - var isAttacker = (boolean) engagement.get().getOrDefault("attacker", false); - var lostEngagementControl = (boolean) engagement.get().getOrDefault("wonEngagementControl", false); - var enc = (EngagementControl) engagement.get().get("engagementControl"); - return enc.equals(engagementControl) && lostEngagementControl && !isAttacker; - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/InitiativePhase.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/InitiativePhase.java deleted file mode 100644 index 0056a28039d..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/InitiativePhase.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.phase; - -import megamek.common.enums.GamePhase; -import mekhq.campaign.autoresolve.acar.SimulationManager; - -public class InitiativePhase extends PhaseHandler { - - public InitiativePhase(SimulationManager gameManager) { - super(gameManager, GamePhase.INITIATIVE); - } - - @Override - protected void executePhase() { - getSimulationManager().calculatePlayerInitialCounts(); - getSimulationManager().resetPlayersDone(); - getSimulationManager().rollInitiative(); - getSimulationManager().incrementAndSendGameRound(); - getSimulationManager().getInitiativeHelper().writeInitiativeReport(); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/MovementPhase.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/MovementPhase.java deleted file mode 100644 index af008cb3a62..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/MovementPhase.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.phase; - -import megamek.common.Entity; -import megamek.common.alphaStrike.ASRange; -import megamek.common.enums.GamePhase; -import megamek.common.strategicBattleSystems.SBFFormation; -import megamek.common.util.weightedMaps.WeightedDoubleMap; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.action.EngagementControlAction; -import mekhq.campaign.autoresolve.component.EngagementControl; -import mekhq.campaign.autoresolve.component.Formation; -import mekhq.campaign.autoresolve.component.FormationTurn; - -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -public class MovementPhase extends PhaseHandler { - - private static final WeightedDoubleMap normal = WeightedDoubleMap.of( - EngagementControl.FORCED_ENGAGEMENT, 1.0, - EngagementControl.EVADE, 0.0, - EngagementControl.STANDARD, 1.0, - EngagementControl.OVERRUN, 0.5, - EngagementControl.NONE, 0.0 - ); - - private static final WeightedDoubleMap unsteady = WeightedDoubleMap.of( - EngagementControl.FORCED_ENGAGEMENT, 0.5, - EngagementControl.EVADE, 0.02, - EngagementControl.STANDARD, 1.0, - EngagementControl.OVERRUN, 0.1, - EngagementControl.NONE, 0.01 - ); - - private static final WeightedDoubleMap shaken = WeightedDoubleMap.of( - EngagementControl.FORCED_ENGAGEMENT, 0.2, - EngagementControl.EVADE, 0.1, - EngagementControl.STANDARD, 0.8, - EngagementControl.OVERRUN, 0.05, - EngagementControl.NONE, 0.01 - ); - - private static final WeightedDoubleMap broken = WeightedDoubleMap.of( - EngagementControl.FORCED_ENGAGEMENT, 0.05, - EngagementControl.EVADE, 1.0, - EngagementControl.STANDARD, 0.5, - EngagementControl.OVERRUN, 0.05, - EngagementControl.NONE, 0.3 - ); - - private static final WeightedDoubleMap routed = WeightedDoubleMap.of( - EngagementControl.NONE, 1.0 - ); - - private static final Map> engagementControlOptions = Map.of( - Formation.MoraleStatus.NORMAL, normal, - Formation.MoraleStatus.UNSTEADY, unsteady, - Formation.MoraleStatus.SHAKEN, shaken, - Formation.MoraleStatus.BROKEN, broken, - Formation.MoraleStatus.ROUTED, routed - ); - - public MovementPhase(SimulationManager gameManager) { - super(gameManager, GamePhase.MOVEMENT); - } - - @Override - protected void executePhase() { - - while (getSimulationManager().getGame().hasMoreTurns()) { - - var optTurn = getSimulationManager().getGame().changeToNextTurn(); - if (optTurn.isEmpty()) { - break; - } - var turn = optTurn.get(); - - if (turn instanceof FormationTurn formationTurn) { - var player = getSimulationManager().getGame().getPlayer(formationTurn.playerId()); - getSimulationManager().getGame().getActiveFormations(player) - .stream() - .filter(f -> f.isEligibleForPhase(getSimulationManager().getGame().getPhase())) // only eligible formations - .findAny() - .map(this::engage) - .filter(Optional::isPresent) - .map(Optional::get) - .ifPresent(this::engagementAndControl); - } - } - } - - private Optional engage(Formation activeFormation) { - var target = this.selectTarget(activeFormation); - return target.map(sbfFormation -> new EngagementControlRecord(activeFormation, sbfFormation)); - } - - private record EngagementControlRecord(Formation actingFormation, Formation target) { } - - private static final Map engagementAndControlExclusions = Map.of( - 0, new EngagementControl[] { EngagementControl.NONE }, - 1, new EngagementControl[] { EngagementControl.FORCED_ENGAGEMENT }, - 2, new EngagementControl[] { EngagementControl.OVERRUN }, - 3, new EngagementControl[] { EngagementControl.OVERRUN, EngagementControl.FORCED_ENGAGEMENT }, - 4, new EngagementControl[] { EngagementControl.STANDARD, EngagementControl.OVERRUN, EngagementControl.FORCED_ENGAGEMENT }, - 5, new EngagementControl[] { EngagementControl.STANDARD, EngagementControl.OVERRUN, EngagementControl.FORCED_ENGAGEMENT } - ); - - private static final EngagementControl[] EMPTY_EAC = new EngagementControl[0]; - - private void engagementAndControl(EngagementControlRecord engagement) { - var eco = engagementControlOptions.get(engagement.target.moraleStatus()); - var engagementControlExclusion = 0; - - if (engagement.actingFormation.getStdDamage().usesDamage(ASRange.LONG)) { - engagementControlExclusion += 1; - } - if (engagement.actingFormation.getStdDamage().getDamage(ASRange.SHORT).damage < 2) { - engagementControlExclusion += 2; - } - if (engagement.actingFormation.isCrippled()) { - engagementControlExclusion += 2; - } - - var engagementControl = eco.randomOptionalItem(engagementAndControlExclusions.getOrDefault(engagementControlExclusion, EMPTY_EAC)); - - getSimulationManager().addEngagementControl( - new EngagementControlAction( - engagement.actingFormation.getId(), - engagement.target.getId(), - engagementControl.orElse(EngagementControl.NONE)), - engagement.actingFormation); - } - - private Optional selectTarget(Formation actingFormation) { - var game = getSimulationManager().getGame(); - var player = game.getPlayer(actingFormation.getOwnerId()); - var canBeTargets = getSimulationManager().getGame().getActiveFormations().stream() - .filter(f -> actingFormation.getTargetFormationId() == Entity.NONE || f.getId() == actingFormation.getTargetFormationId()) - .filter(SBFFormation::isDeployed) - .filter(f -> f.isGround() == actingFormation.isGround()) - .filter(f -> game.getPlayer(f.getOwnerId()).isEnemyOf(player)) - .collect(Collectors.toList()); - Collections.shuffle(canBeTargets); - - if (canBeTargets.isEmpty()) { - return Optional.empty(); - } - return Optional.of(canBeTargets.get(0)); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/PhaseHandler.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/PhaseHandler.java deleted file mode 100644 index 7ec93923c1b..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/PhaseHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.phase; - -import megamek.common.enums.GamePhase; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.acar.SimulationManager; - -/** - * @author Luana Coppio - */ -public abstract class PhaseHandler { - - private final GamePhase phase; - private final SimulationManager simulationManager; - - public PhaseHandler(SimulationManager simulationManager, GamePhase phase) { - this.phase = phase; - this.simulationManager = simulationManager; - } - - private boolean isPhase(GamePhase phase) { - return this.phase == phase; - } - - protected SimulationManager getSimulationManager() { - return simulationManager; - } - - protected SimulationContext getContext() { - return getSimulationManager().getGame(); - } - - public void execute() { - if (isPhase(getSimulationManager().getGame().getPhase())) { - executePhase(); - } - } - - protected abstract void executePhase(); -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/StartingScenarioPhase.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/StartingScenarioPhase.java deleted file mode 100644 index bb5805b82d4..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/StartingScenarioPhase.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.phase; - -import megamek.common.enums.GamePhase; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.report.StartingScenarioPhaseReporter; - -public class StartingScenarioPhase extends PhaseHandler { - - public StartingScenarioPhase(SimulationManager gameManager) { - super(gameManager, GamePhase.STARTING_SCENARIO); - } - - @Override - protected void executePhase() { - getSimulationManager().resetPlayersDone(); - getSimulationManager().resetFormations(); - getSimulationManager().calculatePlayerInitialCounts(); - getSimulationManager().getGame().setupTeams(); - getSimulationManager().getGame().setupDeployment(); - - var reporter = new StartingScenarioPhaseReporter(getSimulationManager().getGame(), getSimulationManager()::addReport); - reporter.startingScenarioHeader(); - reporter.formationsSetup(getSimulationManager()); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/VictoryPhase.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/VictoryPhase.java deleted file mode 100644 index 06f4bee4abf..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/phase/VictoryPhase.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.phase; - -import megamek.common.Entity; -import megamek.common.enums.GamePhase; -import megamek.common.strategicBattleSystems.SBFUnit; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.acar.SimulationManager; -import mekhq.campaign.autoresolve.acar.report.VictoryPhaseReporter; -import mekhq.campaign.autoresolve.component.Formation; -import mekhq.campaign.unit.damage.DamageApplierChooser; -import mekhq.campaign.unit.damage.EntityFinalState; - -public class VictoryPhase extends PhaseHandler { - - private final VictoryPhaseReporter victoryPhaseReporter; - - public VictoryPhase(SimulationManager gameManager) { - super(gameManager, GamePhase.VICTORY); - victoryPhaseReporter = new VictoryPhaseReporter(gameManager.getGame(), gameManager::addReport); - } - - @Override - protected void executePhase() { - applyDamageToRemainingUnits(getContext()); - victoryPhaseReporter.victoryHeader(); - victoryPhaseReporter.victoryResult(getSimulationManager()); - } - - private static void applyDamageToRemainingUnits(SimulationContext context) { - for (var formation : context.getActiveFormations()) { - applyDamageToEntitiesFromFormation(context, formation); - } - - for (var inGameObject : context.getGraveyard()) { - if (inGameObject instanceof Entity entity) { - DamageApplierChooser.damageRemovedEntity(entity, entity.getRemovalCondition()); - } - } - } - - private static void applyDamageToEntitiesFromFormation(SimulationContext context, Formation formation) { - for ( var unit : formation.getUnits()) { - if (unit.getCurrentArmor() < unit.getArmor()) { - applyDamageToEntitiesFromUnit(context, unit); - } - } - } - - private static void applyDamageToEntitiesFromUnit(SimulationContext context, SBFUnit unit) { - for (var element : unit.getElements()) { - var entityOpt = context.getEntity(element.getId()); - if (entityOpt.isPresent()) { - var entity = entityOpt.get(); - applyDamageToEntityFromUnit(unit, entity); - } - } - } - - private static void applyDamageToEntityFromUnit(SBFUnit unit, Entity entity) { - var percent = (double) unit.getCurrentArmor() / unit.getArmor(); - var crits = Math.min(9, unit.getTargetingCrits() + unit.getMpCrits() + unit.getDamageCrits()); - percent -= percent * (crits / 11.0); - percent = Math.min(0.95, percent); - var totalDamage = (int) ((entity.getTotalArmor() + entity.getTotalInternal()) * (1 - percent)); - DamageApplierChooser.choose(entity, EntityFinalState.CREW_AND_ENTITY_MUST_SURVIVE) - .applyDamageInClusters(totalDamage, 5); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/AttackReporter.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/AttackReporter.java deleted file mode 100644 index 3e24b03a794..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/AttackReporter.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.common.IGame; -import megamek.common.Roll; -import megamek.common.TargetRoll; -import megamek.common.strategicBattleSystems.SBFUnit; -import mekhq.campaign.autoresolve.component.Formation; - -import java.util.function.Consumer; - -import static megamek.client.ui.swing.tooltip.SBFInGameObjectTooltip.ownerColor; - -public class AttackReporter { - - private final IGame game; - private final Consumer reportConsumer; - - public AttackReporter(IGame game, Consumer reportConsumer) { - this.reportConsumer = reportConsumer; - this.game = game; - } - public void reportAttackStart(Formation attacker, int unitNumber, Formation target) { - var report = new PublicReportEntry(2001).noNL(); - report.add(new UnitReportEntry(attacker, unitNumber, ownerColor(attacker, game)).text()); - report.add(new FormationReportEntry(target, game).text()); - reportConsumer.accept(report); - } - - public void reportCannotSucceed(String toHitDesc) { - reportConsumer.accept(new PublicReportEntry(2010).add(toHitDesc)); - } - - public void reportToHitValue(TargetRoll toHitValue) { - // e.g. "Needed X to hit" - reportConsumer.accept(new PublicReportEntry(2003).indent().add(toHitValue.getValue()) - .add(toHitValue.toString())); - } - - public void reportAttackRoll(Roll roll, Formation attacker) { - var report = new PublicReportEntry(2020).indent(); - report.add(new PlayerNameReportEntry(game.getPlayer(attacker.getOwnerId())).text()); - report.add(new RollReportEntry(roll).reportText()); - reportConsumer.accept(report); - } - - public void reportAttackMiss() { - reportConsumer.accept(new PublicReportEntry(2012).indent(2)); - } - - public void reportAttackHit() { - reportConsumer.accept(new PublicReportEntry(2013).indent(2)); - } - - public void reportDamageDealt(SBFUnit targetUnit, int damage, int newArmor) { - reportConsumer.accept(new PublicReportEntry(3100) - .add(targetUnit.getName()) - .add(damage) - .add(newArmor) - .indent(2)); - } - - public void reportStressEpisode() { - reportConsumer.accept(new PublicReportEntry(3090).indent(3)); - } - - public void reportUnitDestroyed() { - reportConsumer.accept(new PublicReportEntry(3092).indent(3)); - } - - public void reportCriticalCheck() { - // Called before rolling criticals - reportConsumer.accept(new PublicReportEntry(3095).indent(3)); - } - - public void reportNoCrit() { - reportConsumer.accept(new PublicReportEntry(3097).indent(3)); - } - - public void reportTargetingCrit(SBFUnit targetUnit) { - reportConsumer.accept(new PublicReportEntry(3094) - .add(targetUnit.getName()) - .add(targetUnit.getTargetingCrits()) - .indent(3)); - } - - public void reportDamageCrit(SBFUnit targetUnit) { - reportConsumer.accept(new PublicReportEntry(3096) - .add(targetUnit.getName()) - .add(targetUnit.getDamageCrits()) - .indent(3)); - } - - public void reportUnitCrippled() { - reportConsumer.accept(new PublicReportEntry(3091).indent(3)); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/EndPhaseReporter.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/EndPhaseReporter.java deleted file mode 100644 index 4ac6acda387..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/EndPhaseReporter.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.common.Entity; -import megamek.common.IEntityRemovalConditions; -import megamek.common.IGame; - -import java.util.Map; -import java.util.function.Consumer; - -public class EndPhaseReporter { - - private final Consumer reportConsumer; - private static final Map reportIdForEachRemovalCondition = Map.of( - IEntityRemovalConditions.REMOVE_DEVASTATED, 3337, - IEntityRemovalConditions.REMOVE_EJECTED, 3338, - IEntityRemovalConditions.REMOVE_PUSHED, 3339, - IEntityRemovalConditions.REMOVE_CAPTURED, 3340, - IEntityRemovalConditions.REMOVE_IN_RETREAT, 3341, - IEntityRemovalConditions.REMOVE_NEVER_JOINED, 3342, - IEntityRemovalConditions.REMOVE_SALVAGEABLE, 3343); - - private static final int MSG_ID_UNIT_DESTROYED_UNKNOWINGLY = 3344; - - public EndPhaseReporter(IGame game, Consumer reportConsumer) { - this.reportConsumer = reportConsumer; - } - - public void endPhaseHeader() { - reportConsumer.accept(new PublicReportEntry(999)); - reportConsumer.accept(new PublicReportEntry(3299)); - } - - public void reportUnitDestroyed(Entity entity) { - var removalCondition = entity.getRemovalCondition(); - var reportId = reportIdForEachRemovalCondition.getOrDefault(removalCondition, MSG_ID_UNIT_DESTROYED_UNKNOWINGLY); - - var r = new PublicReportEntry(reportId) - .add(new EntityNameReportEntry(entity).reportText()); - reportConsumer.accept(r); - } - - public void destroyedUnitsHeader() { - reportConsumer.accept(new PublicReportEntry(3298).indent()); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/EngagementAndControlReporter.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/EngagementAndControlReporter.java deleted file mode 100644 index 940ad035220..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/EngagementAndControlReporter.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.common.IGame; -import megamek.common.Roll; -import mekhq.campaign.autoresolve.component.EngagementControl; -import mekhq.campaign.autoresolve.component.Formation; - -import java.util.function.Consumer; - -public class EngagementAndControlReporter { - private final IGame game; - private final Consumer reportConsumer; - - public EngagementAndControlReporter(IGame game, Consumer reportConsumer) { - this.game = game; - this.reportConsumer = reportConsumer; - } - - public void reportEngagementStart(Formation attacker, Formation target, EngagementControl control) { - PublicReportEntry report = new PublicReportEntry(2200) - .add(new FormationReportEntry(attacker, game).text()) - .add(new FormationReportEntry(target, game).text()) - .add(control.name()); - reportConsumer.accept(report); - } - - public void reportAttackerToHitValue(int toHitValue) { - reportConsumer.accept(new PublicReportEntry(2203).indent().add(toHitValue)); - } - - public void reportAttackerRoll(Formation attacker, Roll attackerRoll) { - PublicReportEntry report = new PublicReportEntry(2202); - report.add(new PlayerNameReportEntry(game.getPlayer(attacker.getOwnerId())).text()); - report.add(new RollReportEntry(attackerRoll).reportText()).indent(); - reportConsumer.accept(report); - } - - public void reportDefenderRoll(Formation target, Roll defenderRoll) { - PublicReportEntry report = new PublicReportEntry(2202).indent(); - report.add(new PlayerNameReportEntry(game.getPlayer(target.getOwnerId())).text()); - report.add(new RollReportEntry(defenderRoll).reportText()); - reportConsumer.accept(report); - } - - public void reportAttackerWin(Formation attacker) { - PublicReportEntry report = new PublicReportEntry(2204).indent() - .add(new PlayerNameReportEntry(game.getPlayer(attacker.getOwnerId())).text()); - reportConsumer.accept(report); - } - - public void reportAttackerLose(Formation attacker) { - reportConsumer.accept(new PublicReportEntry(2205) - .add(new PlayerNameReportEntry(game.getPlayer(attacker.getOwnerId())).text()) - .indent()); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/EntityNameReportEntry.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/EntityNameReportEntry.java deleted file mode 100644 index 34c401f4d45..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/EntityNameReportEntry.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.client.ui.swing.util.PlayerColour; -import megamek.client.ui.swing.util.UIUtil; -import megamek.common.Entity; - -public class EntityNameReportEntry extends PublicReportEntry { - - private final String name; - private final String playerColorHex; - private final String playerTeam; - - public EntityNameReportEntry(Entity entity) { - this(entity.getDisplayName(), entity.getOwner().getColour(), entity.getOwner().getTeam()); - - } - - public EntityNameReportEntry(String name, PlayerColour color, int playerTeam) { - super(0); - this.name = name; - this.playerColorHex = UIUtil.hexColor(color.getColour()); - this.playerTeam = playerTeam + ""; - noNL(); - } - - @Override - protected String reportText() { - return "" + name + "[Team " + playerTeam + "]"; - } - -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/FormationReportEntry.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/FormationReportEntry.java deleted file mode 100644 index 7e0766d213f..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/FormationReportEntry.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.client.ui.swing.tooltip.SBFInGameObjectTooltip; -import megamek.client.ui.swing.util.UIUtil; -import megamek.common.IGame; -import mekhq.campaign.autoresolve.component.Formation; - -public class FormationReportEntry extends PublicReportEntry { - - private final String formationName; - private final String playerColorHex; - - public FormationReportEntry(String formationName, String playerColorHex) { - super(0); - this.formationName = formationName; - this.playerColorHex = playerColorHex; - noNL(); - } - - public FormationReportEntry(Formation formation, IGame game) { - this(formation.getName(), UIUtil.hexColor(SBFInGameObjectTooltip.ownerColor(formation, game))); - } - - @Override - protected String reportText() { - return "" + formationName + ""; - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/HtmlGameLogger.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/HtmlGameLogger.java deleted file mode 100644 index 568d84d1448..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/HtmlGameLogger.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.common.GameLog; -import megamek.common.Report; - -import java.io.File; -import java.time.LocalDateTime; -import java.util.List; - -/** - * @author Luana Coppio - */ -public class HtmlGameLogger { - - private final GameLog gameLog; - - private static class LocalGameLog extends GameLog { - - /** - * Creates GameLog named - * - * @param filename the name of the file - */ - public LocalGameLog(String filename) { - super(filename); - } - - @Override - protected void initialize() { - appendRaw(""" - - - - Simulation Game Log - - - - - - """ - ); - appendRaw("

Log file opened " + LocalDateTime.now() + "

"); - } - - } - - /** - * Creates GameLog named - * - * @param filename the name of the file - */ - private HtmlGameLogger(String filename) { - gameLog = new LocalGameLog(filename); - } - - public static HtmlGameLogger create(String filename) { - return new HtmlGameLogger(filename); - } - - public HtmlGameLogger add(List reports) { - for (var report : reports) { - add(report.text()); - } - return this; - } - - public HtmlGameLogger add(Report report) { - add(report.text()); - return this; - } - - public HtmlGameLogger addRaw(String message) { - gameLog.appendRaw(message); - return this; - } - - public HtmlGameLogger add(String message) { - gameLog.append(message); - return this; - } - - public void close() { - addRaw(""); - } - - public File getLogFile() { - return gameLog.getLogFile(); - } - -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/MoraleReporter.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/MoraleReporter.java deleted file mode 100644 index 1975079ab7c..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/MoraleReporter.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.common.IGame; -import megamek.common.Roll; -import mekhq.campaign.autoresolve.component.Formation; - -import java.util.function.Consumer; - -public class MoraleReporter { - - private final IGame game; - private final Consumer reportConsumer; - - public MoraleReporter(IGame game, Consumer reportConsumer) { - this.game = game; - this.reportConsumer = reportConsumer; - } - - public void reportMoraleCheckStart(Formation formation, int toHitValue) { - // 4500: Start of morale check - var startReport = new PublicReportEntry(4500) - .add(new FormationReportEntry(formation, game).reportText()) - .add(toHitValue); - reportConsumer.accept(startReport); - } - - public void reportMoraleCheckRoll(Formation formation, Roll roll) { - // 4501: Roll result - var rollReport = new PublicReportEntry(4501) - .add(new FormationReportEntry(formation, game).reportText()) - .add(new RollReportEntry(roll).reportText()); - reportConsumer.accept(rollReport); - } - - public void reportMoraleCheckSuccess(Formation formation) { - // 4502: Success - morale does not worsen - var successReport = new PublicReportEntry(4502) - .add(new FormationReportEntry(formation, game).text()); - reportConsumer.accept(successReport); - } - - public void reportMoraleCheckFailure(Formation formation, Formation.MoraleStatus oldStatus, Formation.MoraleStatus newStatus) { - // 4503: Failure - morale worsens - var failReport = new PublicReportEntry(4503) - .add(new FormationReportEntry(formation, game).text()) - .add(oldStatus.name()) - .add(newStatus.name()); - - reportConsumer.accept(failReport); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/PlayerNameReportEntry.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/PlayerNameReportEntry.java deleted file mode 100644 index 3d70315c5d7..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/PlayerNameReportEntry.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.client.ui.swing.util.PlayerColour; -import megamek.client.ui.swing.util.UIUtil; -import megamek.common.Player; - -public class PlayerNameReportEntry extends PublicReportEntry { - - private final String playerName; - private final String playerColorHex; - - public PlayerNameReportEntry(Player player) { - this(player.getName(), player.getColour()); - noNL(); - } - - public PlayerNameReportEntry(String playerName, String playerColorHex) { - super(0); - this.playerName = playerName; - this.playerColorHex = playerColorHex; - } - - public PlayerNameReportEntry(String playerName, PlayerColour color) { - this(playerName, UIUtil.hexColor(color.getColour())); - } - - @Override - protected String reportText() { - return "" + playerName + ""; - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/PublicReportEntry.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/PublicReportEntry.java deleted file mode 100644 index 9ab048a4a44..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/PublicReportEntry.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.common.Report; -import megamek.common.ReportEntry; -import megamek.common.Roll; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - - -public class PublicReportEntry implements ReportEntry { - - record DataEntry(String data, boolean isObscured) implements Serializable { } - - private final int messageId; - private final List data = new ArrayList<>(); - private boolean endLine = true; - private boolean endSpace = false; - private int indentation = 0; - - public PublicReportEntry(int messageId) { - this.messageId = messageId; - } - - /** - * Add the given int to the list of data that will be substituted for the - * <data> tags in the report. The order in which items are added must - * match the order of the tags in the report text. - * - * @param data the int to be substituted - * @return This Report to allow chaining - */ - public PublicReportEntry add(int data) { - return add(String.valueOf(data), true); - } - - /** - * Add the given int to the list of data that will be substituted for the - * <data> tags in the report, and mark it as double-blind sensitive - * information if obscure is true. The order in which items - * are added must match the order of the tags in the report text. - * - * @param data the int to be substituted - * @param obscure boolean indicating whether the data is double-blind - * sensitive - * @return This Report to allow chaining - */ - public PublicReportEntry add(int data, boolean obscure) { - return add(String.valueOf(data), obscure); - } - - /** - * Add the given String to the list of data that will be substituted for the - * <data> tags in the report. The order in which items are added must - * match the order of the tags in the report text. - * - * @param data the String to be substituted - * @return This Report to allow chaining - */ - public PublicReportEntry add(String data) { - return add(data, true); - } - - /** - * Add the given String to the list of data that will be substituted for the - * <data> tags in the report, and mark it as double-blind sensitive - * information if obscure is true. The order in which items - * are added must match the order of the tags in the report text. - * - * @param data the String to be substituted - * @param obscure boolean indicating whether the data is double-blind - * sensitive - * @return This Report to allow chaining - */ - public PublicReportEntry add(String data, boolean obscure) { - this.data.add(new DataEntry(data, obscure)); - return this; - } - - @Override - public final String text() { - return " ".repeat(indentation) + reportText() + lineEnd(); - } - - @Override - public ReportEntry addRoll(Roll roll) { - return this; - } - - /** - * Indent the report. Equivalent to calling {@link #indent(int)} with a - * parameter of 1. - * - * @return This Report to allow chaining - */ - public PublicReportEntry indent() { - return indent(1); - } - - /** - * Indent the report n times. - * - * @param n the number of times to indent the report - * @return This Report to allow chaining - */ - public PublicReportEntry indent(int n) { - indentation += (n * Report.DEFAULT_INDENTATION); - return this; - } - - public PublicReportEntry noNL() { - endLine = false; - return this; - } - - public PublicReportEntry endSpace() { - endSpace = true; - return this; - } - - public PublicReportEntry addNL() { - endLine = true; - return this; - } - - private String lineEnd() { - return (endSpace ? " " : "") + (endLine ? "
" : ""); - } - - protected String reportText() { - return ReportMessages.getString(String.valueOf(messageId), data.stream().map(d -> (Object) d.data).toList()); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/RecoveringNerveActionReporter.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/RecoveringNerveActionReporter.java deleted file mode 100644 index 35d78f31e02..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/RecoveringNerveActionReporter.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.client.ui.swing.util.UIUtil; -import megamek.common.IGame; -import megamek.common.Roll; -import mekhq.campaign.autoresolve.component.Formation; - -import java.util.function.Consumer; - -import static megamek.client.ui.swing.tooltip.SBFInGameObjectTooltip.ownerColor; - -public class RecoveringNerveActionReporter { - - private final IGame game; - private final Consumer reportConsumer; - - public RecoveringNerveActionReporter(IGame game, Consumer reportConsumer) { - this.reportConsumer = reportConsumer; - this.game = game; - } - - public void reportRecoveringNerveStart(Formation formation) { - reportConsumer.accept(new PublicReportEntry(4000) - .add(new FormationReportEntry(formation.generalName(), UIUtil.hexColor(ownerColor(formation, game))).text())); - } - - public void reportToHitValue(int toHitValue) { - reportConsumer.accept(new PublicReportEntry(4001).add(toHitValue).noNL()); - } - - public void reportSuccessRoll(Roll roll) { - var report = new PublicReportEntry(4002).indent().noNL(); - report.add(new RollReportEntry(roll).reportText()); - reportConsumer.accept(report); - } - - public void reportMoraleStatusChange(Formation.MoraleStatus newMoraleStatus) { - reportConsumer.accept(new PublicReportEntry(4003).add(newMoraleStatus.name())); - } - - public void reportFailureRoll(Roll roll) { - reportConsumer.accept(new PublicReportEntry(4004).add(roll.toString())); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/ReportMessages.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/ReportMessages.java deleted file mode 100644 index 48642996697..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/ReportMessages.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.MegaMek; - -import java.text.MessageFormat; -import java.util.List; -import java.util.Locale; -import java.util.MissingResourceException; -import java.util.ResourceBundle; - -public class ReportMessages { - - private static final ResourceBundle RESOURCE_BUNDLE = - ResourceBundle.getBundle("mekhq.resources.acs-report-messages", MegaMek.getMMOptions().getLocale()); - - private static final ResourceBundle EN_RESOURCE_BUNDLE = - ResourceBundle.getBundle("mekhq.resources.acs-report-messages", Locale.ENGLISH); - - private static String getEnString(String key) { - try { - return EN_RESOURCE_BUNDLE.getString(key); - } catch (MissingResourceException e) { - return null; - } - } - - public static String getString(String key) { - try { - return RESOURCE_BUNDLE.getString(key); - } catch (MissingResourceException e) { - return getEnString(key); - } - } - - /** - * Returns the formatted message for the given key in the resource bundle. - * - * @param key the resource name - * @param args the message arguments - * @return the string - */ - public static String getString(String key, Object... args) { - try { - String message = getString(key); - return MessageFormat.format(message, args); - } catch (NullPointerException ex) { - return "[Missing report message " + key + "]"; - } catch (IllegalArgumentException exception) { - return "[Invalid message data for " + key + "]"; - } - } - - /** - * Returns the formatted message for the given key in the resource bundle. - * - * @param key the resource name - * @param data the message arguments - * @return the string - */ - public static String getString(String key, List data) { - return getString(key, data.toArray()); - } - - private ReportMessages() { } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/RollReportEntry.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/RollReportEntry.java deleted file mode 100644 index 6c7198ffe35..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/RollReportEntry.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.client.ui.swing.util.UIUtil; -import megamek.common.Roll; - -import java.util.List; - -public class RollReportEntry extends PublicReportEntry { - - private static final String ONE = "⚀"; - private static final String TWO = "⚁"; - private static final String THREE = "⚂"; - private static final String FOUR = "⚃"; - private static final String FIVE = "⚄"; - private static final String SIX = "⚅"; - - private static final List DICE = List.of(ONE, TWO, THREE, FOUR, FIVE, SIX); - - private final String rollText; - private final int[] dices; - - public RollReportEntry(Roll roll) { - super(0); - rollText = roll.toString(); - var size = roll.getIntValues().length; - dices = new int[size]; - - System.arraycopy(roll.getIntValues(), 0, dices, 0, size); - } - - @Override - protected String reportText() { - var dicesText = new StringBuilder(); - for (int i = 0; i < dices.length; i++) { - var dv = dices[i] - 1; - if (dv < 0 || dv >= DICE.size()) { - dicesText.append(dv); - } else { - dicesText.append(DICE.get(dv)); - } - if (i < dices.length - 1) { - dicesText.append(" + "); - } - } - return UIUtil.spanCSS("dice", dicesText.toString()) + " " + UIUtil.spanCSS("roll", rollText); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/StartingScenarioPhaseReporter.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/StartingScenarioPhaseReporter.java deleted file mode 100644 index bb828e215d8..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/StartingScenarioPhaseReporter.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.common.Entity; -import megamek.common.IGame; -import megamek.common.Player; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.acar.SimulationManager; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.function.Consumer; - -public class StartingScenarioPhaseReporter { - - private final IGame game; - private final Consumer reportConsumer; - - public StartingScenarioPhaseReporter(IGame game, Consumer reportConsumer) { - this.reportConsumer = reportConsumer; - this.game = game; - } - - public void startingScenarioHeader() { - reportConsumer.accept(new PublicReportEntry(100)); - reportConsumer.accept(new PublicReportEntry(101)); - } - - public void formationsSetup(SimulationManager gameManager) { - var players = gameManager.getGame().getPlayersList(); - var teamMap = new HashMap>(); - - for (var player : players) { - var team = player.getTeam(); - if (!teamMap.containsKey(team)) { - teamMap.put(team, new ArrayList<>()); - } - teamMap.get(team).add(player); - } - - for (var team : teamMap.keySet()) { - var teamPlayers = teamMap.get(team); - var teamReport = new PublicReportEntry(102).add(team); - reportConsumer.accept(teamReport); - for (var player : teamPlayers) { - playerFinalReport(player); - } - } - } - - private void playerFinalReport(Player player) { - var playerEntities = game.getInGameObjects().stream() - .filter(e -> e.getOwnerId() == player.getId()) - .filter(Entity.class::isInstance) - .map(Entity.class::cast) - .toList(); - - reportConsumer.accept(new PublicReportEntry(103).add(new PlayerNameReportEntry(player).reportText()) - .add(playerEntities.size()).indent()); - - for (var entity : playerEntities) { - reportConsumer.accept(new PublicReportEntry(104) - .add(new EntityNameReportEntry(entity).reportText()) - .add(String.format("%.2f%%", (entity).getArmorRemainingPercent() * 100)) - .add(String.format("%.2f%%", (entity).getInternalRemainingPercent() * 100)) - .add(entity.getCrew().getName()) - .add(entity.getCrew().getHits()) - .indent(2)); - } - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/UnitReportEntry.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/UnitReportEntry.java deleted file mode 100644 index cccf0c9d665..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/UnitReportEntry.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.client.ui.swing.util.UIUtil; -import mekhq.campaign.autoresolve.component.Formation; - -import java.awt.*; - -public class UnitReportEntry extends PublicReportEntry { - - private final String formationName; - private final String unitName; - private final String playerColorHex; - - public UnitReportEntry(String formationName, String unitName, String playerColorHex) { - super(0); - this.formationName = formationName; - this.unitName = unitName; - this.playerColorHex = playerColorHex; - noNL(); - } - - public UnitReportEntry(Formation formation, int unitIndex, Color color) { - this(formation.generalName(), unitName(formation, unitIndex), UIUtil.hexColor(color)); - } - - @Override - protected String reportText() { - return "" + formationName + "" + ", " - + "" + unitName + ""; - } - - private static String unitName(Formation formation, int unitIndex) { - if ((unitIndex < 0) || (unitIndex > formation.getUnits().size())) { - throw new IllegalArgumentException("Invalid unit index"); - } else { - return formation.getUnits().get(unitIndex).getName(); - } - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/VictoryPhaseReporter.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/VictoryPhaseReporter.java deleted file mode 100644 index 4b6420a5480..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/VictoryPhaseReporter.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.common.Entity; -import megamek.common.IGame; -import megamek.common.Player; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.acar.SimulationManager; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.function.Consumer; - -public class VictoryPhaseReporter { - - private final IGame game; - private final Consumer reportConsumer; - - public VictoryPhaseReporter(IGame game, Consumer reportConsumer) { - this.reportConsumer = reportConsumer; - this.game = game; - } - - public void victoryHeader() { - reportConsumer.accept(new PublicReportEntry(999)); - reportConsumer.accept(new PublicReportEntry(5000)); - } - - public void victoryResult(SimulationManager gameManager) { - var players = gameManager.getGame().getPlayersList(); - var teamMap = new HashMap>(); - - for (var player : players) { - var team = player.getTeam(); - if (!teamMap.containsKey(team)) { - teamMap.put(team, new ArrayList<>()); - } - teamMap.get(team).add(player); - } - - for (var team : teamMap.keySet()) { - var teamPlayers = teamMap.get(team); - var teamReport = new PublicReportEntry(5002).add(team); - reportConsumer.accept(teamReport); - for (var player : teamPlayers) { - playerFinalReport(player); - } - } - } - - private void playerFinalReport(Player player) { - var playerEntities = game.getInGameObjects().stream() - .filter(e -> e.getOwnerId() == player.getId()) - .filter(Entity.class::isInstance) - .map(Entity.class::cast) - .toList(); - - reportConsumer.accept(new PublicReportEntry(5003).add(new PlayerNameReportEntry(player).reportText()) - .add(playerEntities.size()).indent()); - - for (var entity : playerEntities) { - reportConsumer.accept(new PublicReportEntry(5004) - .add(new EntityNameReportEntry(entity).reportText()) - .add(String.format("%.2f%%", (entity).getArmorRemainingPercent() * 100)) - .add(String.format("%.2f%%", (entity).getInternalRemainingPercent() * 100)) - .add(entity.getCrew().getName()) - .add(entity.getCrew().getHits()) - .indent(2)); - } - - var deadEntities = game.getGraveyard().stream() - .filter(e -> e.getOwnerId() == player.getId()) - .filter(Entity.class::isInstance) - .map(Entity.class::cast) - .toList(); - - reportConsumer.accept(new PublicReportEntry(5006).add(new PlayerNameReportEntry(player).reportText()) - .add(deadEntities.size()).indent()); - - for (var entity : deadEntities) { - reportConsumer.accept(new PublicReportEntry(5004) - .add(new EntityNameReportEntry(entity).reportText()) - .add(String.format("%.2f%%", entity.getArmorRemainingPercent() * 100)) - .add(String.format("%.2f%%", entity.getInternalRemainingPercent() * 100)) - .add(entity.getCrew().getName()) - .add(entity.getCrew().getHits()) - .indent(2)); - } - - var retreatingEntities = ((SimulationContext) game).getRetreatingUnits().stream() - .filter(e -> e.getOwnerId() == player.getId()) - .toList(); - - reportConsumer.accept(new PublicReportEntry(5007).add(new PlayerNameReportEntry(player).reportText()) - .add(retreatingEntities.size()).indent()); - - for (var entity : retreatingEntities) { - reportConsumer.accept(new PublicReportEntry(5004) - .add(new EntityNameReportEntry(entity).reportText()) - .add(String.format("%.2f%%", entity.getArmorRemainingPercent() * 100)) - .add(String.format("%.2f%%", entity.getInternalRemainingPercent() * 100)) - .add(entity.getCrew().getName()) - .add(entity.getCrew().getHits()) - .indent(2)); - } - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/WithdrawReporter.java b/MekHQ/src/mekhq/campaign/autoresolve/acar/report/WithdrawReporter.java deleted file mode 100644 index d7f72da5d69..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/WithdrawReporter.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.acar.report; - -import megamek.client.ui.swing.util.UIUtil; -import megamek.common.IGame; -import megamek.common.Roll; -import megamek.common.TargetRoll; -import mekhq.campaign.autoresolve.component.Formation; - -import java.util.function.Consumer; - -import static megamek.client.ui.swing.tooltip.SBFInGameObjectTooltip.ownerColor; - -public class WithdrawReporter { - - private final IGame game; - private final Consumer reportConsumer; - - public WithdrawReporter(IGame game, Consumer reportConsumer) { - this.game = game; - this.reportConsumer = reportConsumer; - } - - public void reportStartWithdraw(Formation withdrawingFormation, TargetRoll toHitData) { - // Formation trying to withdraw - var report = new PublicReportEntry(3330).noNL() - .add(new FormationReportEntry( - withdrawingFormation.generalName(), - UIUtil.hexColor(ownerColor(withdrawingFormation, game)) - ).text()) - .add(withdrawingFormation.moraleStatus().name().toLowerCase()) - .indent(); - reportConsumer.accept(report); - - // To-Hit Value - reportConsumer.accept(new PublicReportEntry(3331).add(toHitData.getValue()).add(toHitData.toString()).indent(2)); - } - - public void reportWithdrawRoll(Formation withdrawingFormation, Roll withdrawRoll) { - var report = new PublicReportEntry(3332).noNL(); - report.add(new PlayerNameReportEntry(game.getPlayer(withdrawingFormation.getOwnerId())).text()); - report.add(new RollReportEntry(withdrawRoll).reportText()).indent(2); - reportConsumer.accept(report); - } - - public void reportSuccessfulWithdraw() { - reportConsumer.accept(new PublicReportEntry(3333).indent(3)); - } - - public void reportFailedWithdraw() { - reportConsumer.accept(new PublicReportEntry(3334).indent(3)); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/component/AcTurn.java b/MekHQ/src/mekhq/campaign/autoresolve/component/AcTurn.java deleted file mode 100644 index 6d65ba91afa..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/component/AcTurn.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.component; - -import megamek.common.AbstractPlayerTurn; -import mekhq.campaign.autoresolve.acar.SimulationContext; - -public abstract class AcTurn extends AbstractPlayerTurn { - - public AcTurn(int playerId) { - super(playerId); - } - - /** - * Returns true when this turn can be played given the current state of the given game. Should - * return false when e.g. no valid formation can be found to move for the player or the player or - * formation of the turn is null (e.g. because it has previously been destroyed). - * - * @param game The game object - * @return True when the turn can be played, false when it should be skipped - */ - public abstract boolean isValid(SimulationContext game); -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/component/EngagementControl.java b/MekHQ/src/mekhq/campaign/autoresolve/component/EngagementControl.java deleted file mode 100644 index 6b262233a7c..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/component/EngagementControl.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.component; - -public enum EngagementControl { - NONE, - STANDARD, - EVADE, - OVERRUN, - FORCED_ENGAGEMENT -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/component/Formation.java b/MekHQ/src/mekhq/campaign/autoresolve/component/Formation.java deleted file mode 100644 index f021389d52f..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/component/Formation.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.component; - -import megamek.ai.utility.Memory; -import megamek.common.Entity; -import megamek.common.ToHitData; -import megamek.common.alphaStrike.ASDamageVector; -import megamek.common.alphaStrike.ASRange; -import megamek.common.strategicBattleSystems.SBFFormation; -import megamek.common.strategicBattleSystems.SBFUnit; - - -public class Formation extends SBFFormation { - - private int targetFormationId = Entity.NONE; - private EngagementControl engagementControl; - private boolean engagementControlFailed; - private boolean unitIsCrippledLatch = false; - private final Memory memory = new Memory(); - private boolean clanFormation = false; - private ASDamageVector stdDamage; - private boolean highStressEpisode = false; - public Memory getMemory() { - return memory; - } - - public EngagementControl getEngagementControl() { - return engagementControl; - } - - public void setEngagementControl(EngagementControl engagementControl) { - this.engagementControl = engagementControl; - } - - public int getTargetFormationId() { - return targetFormationId; - } - - public void setTargetFormationId(int targetFormationId) { - this.targetFormationId = targetFormationId; - } - - public void setIsClan(boolean clanFormation) { - this.clanFormation = clanFormation; - } - - public boolean isClan() { - return this.clanFormation; - } - - public boolean isEngagementControlFailed() { - return engagementControlFailed; - } - - public void setEngagementControlFailed(boolean engagementControlFailed) { - this.engagementControlFailed = engagementControlFailed; - } - - public boolean isRangeSet(int formationId) { - return getMemory().get("range." + formationId).isPresent(); - } - - public ASRange getRange(int formationId) { - return (ASRange) getMemory().get("range." + formationId).orElse(ASRange.LONG); - } - - public void setRange(int formationId, ASRange range) { - this.getMemory().put("range." + formationId, range); - } - - public void setHighStressEpisode() { - highStressEpisode = true; - } - - public void reset() { - targetFormationId = Entity.NONE; - engagementControl = null; - highStressEpisode = false; - getMemory().clear(); - setDone(false); - } - - public int getCurrentMovement() { - return getUnits().stream().mapToInt(u -> Math.max(0, u.getMovement() - u.getMpCrits())).min().orElse(0); - } - - public boolean hadHighStressEpisode() { - return highStressEpisode; - } - - /** - * Checks if the formation is crippled. Rules as described on Interstellar Operations BETA pg 242 - Crippling Damage - * @return true in case it is crippled - */ - public boolean isCrippled() { - if (!unitIsCrippledLatch) { - var halfOfUnitsDoZeroDamage = getUnits().stream() - .filter(u -> !u.getCurrentDamage().hasDamage()) - .count() >= Math.ceil(getUnits().size() / 2.0); - - var hasVeryDamagedMovement = (getCurrentMovement() <= 1) && (getMovement() >= 3); - - var totalUnits = getUnits().size(); - var lessThan20PercentOfArmorOrLess = 0; - for (var units : getUnits()) { - if (((double) units.getCurrentArmor() / units.getArmor()) <= 0.2) { - lessThan20PercentOfArmorOrLess++; - } - } - - var halfOfUnitsHaveTwentyPercentOfArmorOrLess = lessThan20PercentOfArmorOrLess >= Math.ceil(totalUnits / 2.0); - - var halfOfUnitsTookTwoTargetDamageOrMore = getUnits().stream() - .filter(u -> u.getTargetingCrits() >= 2) - .count() >= Math.ceil(getUnits().size() / 2.0); - - // Sets the latch for crippled variable so it is not recalculated - unitIsCrippledLatch = hasVeryDamagedMovement - || halfOfUnitsDoZeroDamage - || halfOfUnitsHaveTwentyPercentOfArmorOrLess - || halfOfUnitsTookTwoTargetDamageOrMore; - } - - return unitIsCrippledLatch; - } - - public void setStdDamage(ASDamageVector stdDamage) { - this.stdDamage = stdDamage; - } - - @Override - public ASDamageVector getStdDamage() { - return stdDamage; - } - - @Override - public int getSize() { - if (getUnits().isEmpty()) { - return 0; - } - return getUnits().stream().mapToInt(SBFUnit::getSize).sum() / getUnits().size(); - } - - @Override - public int getTmm() { - if (getUnits().isEmpty()) { - return 0; - } - return getUnits().stream().mapToInt(SBFUnit::getTmm).min().orElse(0); - } - - @Override - public int getSkill() { - if (getUnits().isEmpty()) { - return ToHitData.AUTOMATIC_FAIL; - } - return getUnits().stream().mapToInt(SBFUnit::getSkill).sum() / getUnits().size(); - } - - @Override - public int getTactics() { - if (getUnits().isEmpty()) { - return ToHitData.AUTOMATIC_FAIL; - } - var movement = getMovement(); - var skill = getSkill(); - return Math.max(0, 6 - movement + skill); - } - - @Override - public int getMovement() { - if (getUnits().isEmpty()) { - return 0; - } - return getUnits().stream().mapToInt(SBFUnit::getMovement).min().orElse(0); - } - - @Override - public int getPointValue() { - return getUnits().stream().mapToInt(SBFUnit::getPointValue).sum(); - } - - @Override - public String toString() { - return "[Formation] " + getName() + ": " + getType() + "; SZ" + getSize() + "; TMM" + getTmm() + "; M" + getMovement() - + "; T" + getTactics() + "; M " + moraleStatus() + "; " + getPointValue() + "@" + getSkill() + "; " + getUnits().size() + " units" - + "; " + getSpecialAbilities().getSpecialsDisplayString(this); - } - -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/component/FormationTurn.java b/MekHQ/src/mekhq/campaign/autoresolve/component/FormationTurn.java deleted file mode 100644 index 69d4c4841a2..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/component/FormationTurn.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.component; - -import megamek.common.IGame; -import megamek.common.InGameObject; -import mekhq.campaign.autoresolve.acar.SimulationContext; - -public class FormationTurn extends AcTurn { - - /** - * Creates a new player turn for an SBF Game. - * - * @param playerId The player who has to take action - */ - public FormationTurn(int playerId) { - super(playerId); - } - - @Override - public boolean isValid(SimulationContext game) { - return (game.getPlayer(playerId()) != null) && game.hasEligibleFormation(this); - } - - @Override - public boolean isValidEntity(InGameObject unit, IGame game) { - return (unit.getOwnerId() == playerId()) && unit instanceof Formation - && ((Formation) unit).isEligibleForPhase(game.getPhase()); - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/converter/BalancedConsolidateForces.java b/MekHQ/src/mekhq/campaign/autoresolve/converter/BalancedConsolidateForces.java deleted file mode 100644 index c0043e6a50a..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/converter/BalancedConsolidateForces.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.converter; - -import java.util.*; -import java.util.stream.Collectors; - -/** - * BalancedConsolidateForces is a helper class that redistribute entities and forces - * in a way to consolidate then into valid forces to build Formations out of them. - * @author Luana Coppio - */ -public class BalancedConsolidateForces { - - public static final int MAX_ENTITIES_IN_SUB_FORCE = 6; - public static final int MAX_ENTITIES_IN_TOP_LEVEL_FORCE = 20; - - public record Container(int uid, int teamId, int[] entities, Container[] subs) { - public boolean isLeaf() { - return subs.length == 0 && entities.length > 0; - } - - public boolean isTop() { - return subs.length > 0 && entities.length == 0; - } - - public String toString() { - return "Container(uid=" + uid + ", team=" + teamId + ", ent=" + Arrays.toString(entities) + ", subs=" + Arrays.toString(subs) + ")"; - } - } - - public record ForceRepresentation(int uid, int teamId, int[] entities, int[] subForces) { - public boolean isLeaf() { - return subForces.length == 0 && entities.length > 0; - } - - public boolean isTop() { - return subForces.length > 0 && entities.length == 0; - } - } - - /** - * Balances the forces by team, tries to ensure that every team has the same number of top level forces, each within the ACS parameters - * of a maximum of 20 entities and 4 sub forces. It also aggregates the entities by team instead of keeping segregated by player. - * See the test cases for examples on how it works. - * @param forces List of Forces to balance - * @return List of Trees representing the balanced forces - */ - public static List balancedLists(List forces) { - Map> entitiesByTeam = new HashMap<>(); - for (ForceRepresentation c : forces) { - entitiesByTeam.computeIfAbsent(c.teamId(), k -> new HashSet<>()).addAll(Arrays.stream(c.entities()).boxed().toList()); - } - - // Find the number of top-level containers for each team - Map numOfEntitiesByTeam = entitiesByTeam.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().size())); - - int maxEntities = numOfEntitiesByTeam.values().stream().max(Integer::compareTo).orElse(0); - int topCount = (int) Math.ceil((double) maxEntities / MAX_ENTITIES_IN_TOP_LEVEL_FORCE); - - Map balancedForces = new HashMap<>(); - - for (int team : entitiesByTeam.keySet()) { - createTopLevelForTeam(balancedForces, team, new ArrayList<>(entitiesByTeam.get(team)), topCount); - } - - return new ArrayList<>(balancedForces.values()); - } - - private static void createTopLevelForTeam(Map balancedForces, int team, List allEntityIds, int topCount) { - int maxId = balancedForces.keySet().stream().max(Integer::compareTo).orElse(0) + 1; - - int maxEntitiesPerTopLevelForce = (int) Math.min(Math.ceil((double) allEntityIds.size() / topCount), MAX_ENTITIES_IN_TOP_LEVEL_FORCE); - - int idx = 0; - - for (int i = 0; i < topCount; i++) { - int end = Math.min(idx + maxEntitiesPerTopLevelForce, allEntityIds.size()); - List subListOfEntityIds = allEntityIds.subList(idx, end); - idx = end; - - List subForces = new ArrayList<>(); - int step = Math.min(subListOfEntityIds.size(), MAX_ENTITIES_IN_SUB_FORCE); - for (int start = 0; start < subListOfEntityIds.size(); start += step) { - var subForceSize = Math.min(subListOfEntityIds.size(), start + step); - Container leaf = new Container( - maxId++, - team, - subListOfEntityIds.subList(start, subForceSize).stream().mapToInt(Integer::intValue).toArray(), - new Container[0]); - subForces.add(leaf); - } - - if (subForces.isEmpty()) { - // no entities? skip creating top-level - break; - } - - var subForcesArray = new Container[subForces.size()]; - for (int k = 0; k < subForcesArray.length; k++) { - subForcesArray[k] = subForces.get(k); - } - - Container top = new Container(maxId++, team, new int[0], subForcesArray); - balancedForces.put(top.uid(), top); - } - } - - /** - * Checks if the forces are balanced, meaning that each team has the same number of top level forces, each within the ACS parameters - * This function isn't used outside of tests, but it can be useful to check if the forces are balanced after consolidation - * @param postBalanceForces The list of forces post balance - * @return True if the forces are balanced, false otherwise - */ - @SuppressWarnings("unused") - public static boolean isBalanced(List postBalanceForces) { - if (postBalanceForces.isEmpty()) { - return false; - } - Map> resMap = new HashMap<>(); - for (Container c : postBalanceForces) { - if (c.isTop()) resMap.computeIfAbsent(c.teamId(), k -> new ArrayList<>()).add(c); - } - - List counts = resMap.values().stream().map(List::size).toList(); - int min = Collections.min(counts), max = Collections.max(counts); - return max - min <= 1; - } -} - diff --git a/MekHQ/src/mekhq/campaign/autoresolve/converter/ConsolidateForces.java b/MekHQ/src/mekhq/campaign/autoresolve/converter/ConsolidateForces.java deleted file mode 100644 index 4303a92b127..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/converter/ConsolidateForces.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.converter; - -import megamek.common.Entity; -import megamek.common.ForceAssignable; -import megamek.common.IGame; -import megamek.common.Player; -import megamek.common.force.Force; -import megamek.common.force.Forces; -import megamek.common.icons.Camouflage; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author Luana Coppio - */ -public class ConsolidateForces { - /** - * Consolidates forces by redistributing entities and sub forces as needed. - * It will balance the forces by team, ensuring that each force has a maximum of 20 entities and 4 sub forces. - * @param game The game to consolidate forces for - */ - public static void consolidateForces(IGame game) { - Forces forces = game.getForces(); - var teamByPlayer = game.getTeamByPlayer(); - var forceNameByPlayer = new HashMap(); - for (var force : forces.getAllForces()) { - if (!forceNameByPlayer.containsKey(force.getOwnerId())) { - forceNameByPlayer.put(force.getOwnerId(), force.getName()); - } - } - var representativeOwnerForForce = new HashMap>(); - for (var force : forces.getAllForces()) { - representativeOwnerForForce.computeIfAbsent(teamByPlayer.get(force.getOwnerId()), k -> new ArrayList<>()).add(game.getPlayer(force.getOwnerId())); - } - - List forceRepresentation = getForceRepresentations(forces, teamByPlayer); - var balancedConsolidateForces = BalancedConsolidateForces.balancedLists(forceRepresentation); - - clearAllForces(forces); - - for (var forceRep : balancedConsolidateForces) { - var player = representativeOwnerForForce.get(forceRep.teamId()).get(0); - var parentForceId = forces.addTopLevelForce( - new Force( - "[Team " + forceRep.teamId() + "] "+ forceNameByPlayer.get(player.getId()) + " Formation", - -1, - new Camouflage(), - player), - player); - for (var subForce : forceRep.subs()) { - var subForceId = forces.addSubForce( - new Force( - "[Team " + forceRep.teamId() + "] " + subForce.uid() + " Unit", - -1, - new Camouflage(), - player), - forces.getForce(parentForceId)); - for (var entityId : subForce.entities()) { - forces.addEntity((Entity) game.getEntityFromAllSources(entityId), subForceId); - } - } - } - } - - private static void clearAllForces(Forces forces) { - // Remove all empty forces and sub forces after consolidation - forces.deleteForces(forces.getAllForces()); - - } - - /** - * Converts the forces into a list of ForceRepresentations. It is an intermediary representation of a force, in a way that makes it very - * lightweight to manipulate and balance. It only contains the representation of the force top-level, and the list of entities in it. - * @param forces The forces to convert - * @param teamByPlayer A map of player IDs to team IDs - * @return A list of ForceRepresentations - */ - private static List getForceRepresentations(Forces forces, Map teamByPlayer) { - List forceRepresentations = new ArrayList<>(); - for (Force force : forces.getTopLevelForces()) { - int[] entityIds = forces.getFullEntities(force).stream().mapToInt(ForceAssignable::getId).toArray(); - forceRepresentations.add(new BalancedConsolidateForces.ForceRepresentation(force.getId(), teamByPlayer.get(force.getOwnerId()), entityIds, new int[0])); - } - return forceRepresentations; - } -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/converter/ForceToFormationConverter.java b/MekHQ/src/mekhq/campaign/autoresolve/converter/ForceToFormationConverter.java deleted file mode 100644 index 76fc44f0001..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/converter/ForceToFormationConverter.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.converter; - -import megamek.common.Entity; -import megamek.common.ForceAssignable; -import megamek.common.Game; -import megamek.common.alphaStrike.ASDamage; -import megamek.common.alphaStrike.ASDamageVector; -import megamek.common.alphaStrike.ASRange; -import megamek.common.alphaStrike.AlphaStrikeElement; -import megamek.common.alphaStrike.conversion.ASConverter; -import megamek.common.force.Force; -import megamek.common.force.Forces; -import megamek.common.strategicBattleSystems.BaseFormationConverter; -import megamek.common.strategicBattleSystems.SBFUnit; -import megamek.common.strategicBattleSystems.SBFUnitConverter; -import megamek.logging.MMLogger; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.autoresolve.component.Formation; - -import java.util.ArrayList; - -public class ForceToFormationConverter extends BaseFormationConverter { - private static final MMLogger logger = MMLogger.create(ForceToFormationConverter.class); - - public ForceToFormationConverter(Force force, SimulationContext game) { - super(force, game, new Formation()); - } - - @Override - public Formation convert() { - Forces forces = game.getForces(); - for (Force subforce : forces.getFullSubForces(force)) { - var thisUnit = new ArrayList(); - for (ForceAssignable entity : forces.getFullEntities(subforce)) { - if (entity instanceof Entity entityCast) { - var element = ASConverter.convertAndKeepRefs(entityCast); - if (element != null) { - thisUnit.add(element); - } else { - var msg = String.format("Could not convert entity %s to AS element", entityCast); - logger.error(msg); - } - } - } - SBFUnit convertedUnit = new SBFUnitConverter(thisUnit, subforce.getName(), report).createSbfUnit(); - formation.addUnit(convertedUnit); - } - formation.setName(force.getName()); - formation.setStdDamage(setStdDamageForFormation(formation)); - for (var unit : formation.getUnits()) { - var health = 0; - for (var element : unit.getElements()) { - health += element.getCurrentArmor() + element.getCurrentStructure(); - } - unit.setArmor(health); - unit.setCurrentArmor(health); - } - return formation; - } - - private ASDamageVector setStdDamageForFormation(Formation formation) { - // Get the list of damage objects from the units in the formation - var damages = formation.getUnits().stream().map(SBFUnit::getDamage).toList(); - var size = damages.size(); - - // Initialize accumulators for the different damage types - var l = 0; - var m = 0; - var s = 0; - - // Sum up the damage values for each type - for (var damage : damages) { - l += damage.getDamage(ASRange.LONG).damage; - m += damage.getDamage(ASRange.MEDIUM).damage; - s += damage.getDamage(ASRange.SHORT).damage; - } - return new ASDamageVector( - new ASDamage(Math.ceil((double) s / size)), - new ASDamage(Math.ceil((double) m / size)), - new ASDamage(Math.ceil((double) l / size)), - null, - size, - true); - } - -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/event/AutoResolveConcludedEvent.java b/MekHQ/src/mekhq/campaign/autoresolve/event/AutoResolveConcludedEvent.java deleted file mode 100644 index 8044697d675..00000000000 --- a/MekHQ/src/mekhq/campaign/autoresolve/event/AutoResolveConcludedEvent.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.autoresolve.event; - -import megamek.common.Entity; -import megamek.common.IEntityRemovalConditions; -import megamek.common.IGame; -import megamek.common.annotations.Nullable; -import megamek.common.event.PostGameResolution; -import megamek.server.victory.VictoryResult; -import mekhq.campaign.autoresolve.acar.SimulationContext; -import mekhq.campaign.mission.AtBScenario; -import org.apache.commons.lang3.builder.ToStringBuilder; - -import java.io.File; -import java.util.Enumeration; -import java.util.Vector; - -/** - * @author Luana Coppio - */ -public class AutoResolveConcludedEvent implements PostGameResolution { - - private final IGame game; - private final boolean controlledScenario; - private final AtBScenario scenario; - private final Vector survived = new Vector<>(); - private final Vector retreated = new Vector<>(); - private final Vector graveyard = new Vector<>(); - private final Vector devastated = new Vector<>(); - private final Vector wrecked = new Vector<>(); - private final VictoryResult victoryResult; - private final File logFile; - - public AutoResolveConcludedEvent(SimulationContext game, VictoryResult victoryResult) { - this(game, victoryResult, null); - } - - public AutoResolveConcludedEvent(SimulationContext game, VictoryResult victoryResult, File logFile) { - this.controlledScenario = (game.getLocalPlayer().getTeam() == victoryResult.getWinningTeam()); - this.victoryResult = victoryResult; - this.game = game; - this.scenario = game.getScenario(); - this.logFile = logFile; - - game.getInGameObjects().stream() - .filter(Entity.class::isInstance) - .map(Entity.class::cast) - .forEach(e -> e.setOwner(game.getPlayer(e.getOwnerId()))); - - game.getGraveyard().stream() - .filter(Entity.class::isInstance) - .map(Entity.class::cast) - .forEach(e -> e.setOwner(game.getPlayer(e.getOwnerId()))); - - game.getInGameObjects().stream() - .filter(Entity.class::isInstance) - .map(Entity.class::cast) - .forEach(survived::addElement); - - game.getGraveyard().stream() - .filter(Entity.class::isInstance) - .map(Entity.class::cast) - .filter(entity -> entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_SALVAGEABLE || - entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_EJECTED) - .forEach(graveyard::addElement); - - game.getGraveyard().stream() - .filter(Entity.class::isInstance) - .map(Entity.class::cast) - .filter(entity -> entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_NEVER_JOINED || - entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_IN_RETREAT || - entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_CAPTURED || - entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_PUSHED) - .forEach(retreated::addElement); - - game.getGraveyard().stream() - .filter(Entity.class::isInstance) - .map(Entity.class::cast) - .filter(entity -> entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_DEVASTATED) - .forEach(devastated::addElement); - - game.getGraveyard().stream() - .filter(Entity.class::isInstance) - .map(Entity.class::cast) - .filter(entity -> entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_DEVASTATED || - entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_EJECTED || - entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_SALVAGEABLE) - .forEach(wrecked::addElement); - } - - public AtBScenario getScenario() { - return scenario; - } - - public VictoryResult getVictoryResult() { - return victoryResult; - } - - public IGame getGame() { - return game; - } - - public boolean controlledScenario() { - return controlledScenario; - } - - @Override - public Enumeration getEntities() { - return survived.elements(); - } - - @Override - public Entity getEntity(int id) { - return (Entity) game.getEntityFromAllSources(id); - } - - @Override - public Enumeration getGraveyardEntities() { - return graveyard.elements(); - } - - @Override - public Enumeration getWreckedEntities() { - return wrecked.elements(); - } - - @Override - public Enumeration getRetreatedEntities() { - return retreated.elements(); - } - - @Override - public Enumeration getDevastatedEntities() { - return devastated.elements(); - } - - @Nullable - public File getLogFile() { - return logFile; - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .append("game", game) - .append("controlledScenario", controlledScenario) - .append("survived", survived) - .append("retreated", retreated) - .append("graveyard", graveyard) - .append("devastated", devastated) - .append("wrecked", wrecked) - .toString(); - } -} diff --git a/MekHQ/src/mekhq/campaign/universe/SocioIndustrialData.java b/MekHQ/src/mekhq/campaign/universe/SocioIndustrialData.java index 8135d07324c..7385749b3da 100644 --- a/MekHQ/src/mekhq/campaign/universe/SocioIndustrialData.java +++ b/MekHQ/src/mekhq/campaign/universe/SocioIndustrialData.java @@ -67,7 +67,7 @@ public String toString() { /** @return the USILR rating as a HTML description */ public String getHTMLDescription() { - // TODO: Internationalization + // TODO: MHQInternationalization // TODO: Some way to encode "advanced" ultra-tech worlds (rating "AA" for technological sophistication) // TODO: Some way to encode "regressed" worlds // Note that rating "E" isn't used in official USILR codes, but we add them for completeness diff --git a/MekHQ/src/mekhq/gui/BriefingTab.java b/MekHQ/src/mekhq/gui/BriefingTab.java index d86e1236191..3131a9e4607 100644 --- a/MekHQ/src/mekhq/gui/BriefingTab.java +++ b/MekHQ/src/mekhq/gui/BriefingTab.java @@ -61,7 +61,7 @@ import mekhq.gui.view.LanceAssignmentView; import mekhq.gui.view.MissionViewPanel; import mekhq.gui.view.ScenarioViewPanel; -import mekhq.utilities.Internationalization; +import mekhq.utilities.MHQInternationalization; import javax.swing.*; import javax.swing.table.TableColumn; @@ -814,15 +814,15 @@ private void promptAutoResolve(Scenario scenario) { // the options for the auto resolve method follow a predefined order, which is the same as the order in the enum // and it uses that to preselect the option that is currently set in the campaign options Object[] options = new Object[]{ - Internationalization.getTextAt("AutoResolveMethod", "AutoResolveMethod.PRINCESS.text"), - Internationalization.getTextAt("AutoResolveMethod", "AutoResolveMethod.ABSTRACT_COMBAT.text"), + MHQInternationalization.getTextAt("AutoResolveMethod", "AutoResolveMethod.PRINCESS.text"), + MHQInternationalization.getTextAt("AutoResolveMethod", "AutoResolveMethod.ABSTRACT_COMBAT.text"), }; var preSelectedOptionIndex = getCampaignOptions().getAutoResolveMethod().ordinal(); var selectedOption = JOptionPane.showOptionDialog(getFrame(), - Internationalization.getTextAt("AutoResolveMethod", "AutoResolveMethod.promptForAutoResolveMethod.text"), - Internationalization.getTextAt("AutoResolveMethod", "AutoResolveMethod.promptForAutoResolveMethod.title"), + MHQInternationalization.getTextAt("megamek.client.AutoResolveMethod", "AutoResolveMethod.promptForAutoResolveMethod.text"), + MHQInternationalization.getTextAt("megamek.client.AutoResolveMethod", "AutoResolveMethod.promptForAutoResolveMethod.title"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[preSelectedOptionIndex]); @@ -830,7 +830,7 @@ private void promptAutoResolve(Scenario scenario) { return; } - AutoResolveMethod autoResolveMethod = AutoResolveMethod.values()[selectedOption]; + var autoResolveMethod = AutoResolveMethod.values()[selectedOption]; if (autoResolveMethod == AutoResolveMethod.PRINCESS) { runPrincessAutoResolve(); diff --git a/MekHQ/src/mekhq/gui/dialog/AutoResolveChanceDialog.java b/MekHQ/src/mekhq/gui/dialog/AutoResolveChanceDialog.java index 6c2d7c706b2..38bb0540c8d 100644 --- a/MekHQ/src/mekhq/gui/dialog/AutoResolveChanceDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/AutoResolveChanceDialog.java @@ -20,16 +20,17 @@ package mekhq.gui.dialog; import megamek.client.ui.swing.util.UIUtil; +import megamek.common.autoresolve.Resolver; +import megamek.common.autoresolve.acar.SimulationOptions; +import megamek.common.autoresolve.event.AutoResolveConcludedEvent; import megamek.logging.MMLogger; import megamek.server.victory.VictoryResult; import mekhq.campaign.Campaign; -import mekhq.campaign.autoresolve.Resolver; -import mekhq.campaign.autoresolve.acar.SimulationOptions; -import mekhq.campaign.autoresolve.event.AutoResolveConcludedEvent; +import mekhq.campaign.autoresolve.SetupForces; import mekhq.campaign.mission.AtBScenario; import mekhq.campaign.unit.Unit; import mekhq.gui.baseComponents.AbstractMHQDialog; -import mekhq.utilities.Internationalization; +import mekhq.utilities.MHQInternationalization; import org.apache.commons.lang3.time.StopWatch; import javax.swing.*; @@ -146,6 +147,7 @@ protected Container createCenterPane() { private JProgressBar createProgressBar() { setProgressBar(new JProgressBar(0, 100)); + getProgressBar().setString(resources.getString("AutoResolveMethod.progress.0")); getProgressBar().setValue(0); getProgressBar().setStringPainted(true); @@ -192,7 +194,7 @@ public void propertyChange(PropertyChangeEvent evt) { progressBar.setValue(progress); var factor = clamp(numberOfSimulations / 25, 3, 99); int i = (int) (factor * ((float) progress / progressBar.getMaximum())); - getProgressBar().setString(Internationalization.getTextAt("GUI", progressText.get(i))); + getProgressBar().setString(resources.getString(progressText.get(i))); } public static int clamp(long value, int min, int max) { @@ -216,14 +218,14 @@ public Task(AutoResolveChanceDialog dialog) { @Override public Integer doInBackground() { String message; - String title = Internationalization.getText("AutoResolveDialog.title"); + String title = resources.getString("AutoResolveDialog.title"); StopWatch stopWatch = new StopWatch(); stopWatch.start(); var simulatedVictories = calculateNumberOfVictories(); stopWatch.stop(); if (simulatedVictories.getRuns() == 0 && simulatedVictories.getRuns() < numberOfSimulations) { - message = Internationalization.getText("AutoResolveDialog.messageFailedCalc"); + message = resources.getString("AutoResolveDialog.messageFailedCalc"); logger.debug("No combat scenarios were simulated, possible error!"); } else { var timePerRun = stopWatch.getTime() / (numberOfSimulations / Runtime.getRuntime().availableProcessors()); @@ -235,7 +237,7 @@ public Integer doInBackground() { timePerRun, stopWatch.toString()); - message = Internationalization.getFormattedText("AutoResolveDialog.messageSimulated", + message = MHQInternationalization.getFormattedText("AutoResolveDialog.messageSimulated", simulatedVictories.getRuns(), simulatedVictories.getVictories(), simulatedVictories.getLosses(), @@ -268,7 +270,7 @@ private SimulationScore calculateNumberOfVictories() { for (int i = 0; i < numberOfSimulations; i++) { futures.add(executor.submit(() -> { var autoResolveConcludedEvent = new Resolver( - campaign, units, scenario, new SimulationOptions(campaign.getGameOptions())) + new SetupForces(campaign, units, scenario), new SimulationOptions(campaign.getGameOptions())) .resolveSimulation(); setProgress(Math.min(100 * runCounter.incrementAndGet() / numberOfSimulations, 100)); return autoResolveConcludedEvent; diff --git a/MekHQ/src/mekhq/gui/dialog/helpDialogs/AutoResolveBehaviorSettingsHelpDialog.java b/MekHQ/src/mekhq/gui/dialog/helpDialogs/AutoResolveBehaviorSettingsHelpDialog.java index 72e19ffbc3f..55312211c47 100644 --- a/MekHQ/src/mekhq/gui/dialog/helpDialogs/AutoResolveBehaviorSettingsHelpDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/helpDialogs/AutoResolveBehaviorSettingsHelpDialog.java @@ -1,6 +1,7 @@ package mekhq.gui.dialog.helpDialogs; import megamek.client.ui.dialogs.helpDialogs.AbstractHelpDialog; +import megamek.common.internationalization.Internationalization; import mekhq.MekHQ; import javax.swing.*; @@ -9,18 +10,14 @@ public class AutoResolveBehaviorSettingsHelpDialog extends AbstractHelpDialog { - private static final ResourceBundle resourceMap = ResourceBundle.getBundle( - "mekhq.resources.AutoResolveBehaviorSettingsDialog", - MekHQ.getMHQOptions().getLocale()); - /** * Creates a new instance of AutoResolveBehaviorSettingsHelpDialog. * This screen opens a help dialog, using the megamek help dialog, which open an HTML file * @param frame parent frame */ public AutoResolveBehaviorSettingsHelpDialog(final JFrame frame) { - super(frame, resourceMap.getString("AutoResolveBehaviorSettingsDialog.title"), - resourceMap.getString("AutoResolveBehaviorSettingsDialog.autoResolveHelpPath")); + super(frame, Internationalization.getText("AutoResolveBehaviorSettingsDialog.title"), + Internationalization.getText("AutoResolveBehaviorSettingsDialog.autoResolveHelpPath")); setMinimumSize(new Dimension(400, 400)); setModalExclusionType(ModalExclusionType.TOOLKIT_EXCLUDE); diff --git a/MekHQ/src/mekhq/gui/dialog/helpDialogs/AutoResolveSimulationLogDialog.java b/MekHQ/src/mekhq/gui/dialog/helpDialogs/AutoResolveSimulationLogDialog.java deleted file mode 100644 index f0b63767545..00000000000 --- a/MekHQ/src/mekhq/gui/dialog/helpDialogs/AutoResolveSimulationLogDialog.java +++ /dev/null @@ -1,20 +0,0 @@ -package mekhq.gui.dialog.helpDialogs; - -import megamek.client.ui.dialogs.helpDialogs.AbstractHelpDialog; -import mekhq.utilities.Internationalization; - -import javax.swing.*; -import java.awt.*; -import java.io.File; - -public class AutoResolveSimulationLogDialog extends AbstractHelpDialog { - - public AutoResolveSimulationLogDialog(final JFrame frame, File logFile) { - super(frame, Internationalization.getText("AutoResolveSimulationLogDialog.title"), - logFile.getAbsolutePath()); - - setMinimumSize(new Dimension(800, 400)); - setModalExclusionType(ModalExclusionType.TOOLKIT_EXCLUDE); - } - -} diff --git a/MekHQ/src/mekhq/utilities/Internationalization.java b/MekHQ/src/mekhq/utilities/Internationalization.java deleted file mode 100644 index 564b6e991b9..00000000000 --- a/MekHQ/src/mekhq/utilities/Internationalization.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.utilities; - -import mekhq.MekHQ; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.text.MessageFormat; -import java.util.Locale; -import java.util.PropertyResourceBundle; -import java.util.ResourceBundle; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Class to handle internationalization (you will find online material on that looking for i18n) - * It makes use of some short names to make it easier to use since it is used in many places - */ -public class Internationalization { - - private static final String PREFIX = "mekhq.resources."; - protected static final String DEFAULT = "messages"; - private final ConcurrentHashMap resourceBundles = new ConcurrentHashMap<>(); - private static final Internationalization instance = new Internationalization(); - - protected Internationalization() { - } - - public static Internationalization getInstance() { - return instance; - } - - private static class UTF8Control extends ResourceBundle.Control { - @Override - public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) - throws IOException { - // The below is one approach; there are multiple ways to do this - String resourceName = toResourceName(toBundleName(baseName, locale), "properties"); - try (InputStream is = loader.getResourceAsStream(resourceName); - InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) { - return new PropertyResourceBundle(isr); - } - } - } - - ResourceBundle getResourceBundle(String bundleName) { - return resourceBundles.computeIfAbsent(bundleName, k -> - ResourceBundle.getBundle(PREFIX + bundleName, MekHQ.getMHQOptions().getLocale(), new UTF8Control())); - } - - /** - * Get a localized string from a specific bundle - * @param bundleName the name of the bundle - * @param key the key of the string - * @return the localized string - */ - public static String getTextAt(String bundleName, String key) { - if (Internationalization.getInstance().getResourceBundle(bundleName).containsKey(key)) { - return Internationalization.getInstance().getResourceBundle(bundleName).getString(key); - } - return "!" + key + "!"; - } - - /** - * Get a localized string from the default bundle - * @param key the key of the string - * @return the localized string - */ - public static String getText(String key) { - return getTextAt(DEFAULT, key); - } - - /** - * Get a formatted localized string from the default bundle - * @param key the key of the string - * @param args the arguments to format the string - * @return the localized string - */ - public static String getFormattedText(String key, Object... args) { - return MessageFormat.format(getFormattedTextAt(DEFAULT, key), args); - } - - /** - * Get a formatted localized string from the default bundle - * @param bundleName the name of the bundle - * @param key the key of the string - * @param args the arguments to format the string - * @return the localized string - */ - public static String getFormattedTextAt(String bundleName, String key, Object... args) { - return MessageFormat.format(getTextAt(bundleName, key), args); - } - - -} diff --git a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/ReportHeader.java b/MekHQ/src/mekhq/utilities/MHQInternationalization.java similarity index 58% rename from MekHQ/src/mekhq/campaign/autoresolve/acar/report/ReportHeader.java rename to MekHQ/src/mekhq/utilities/MHQInternationalization.java index 2ee5e87059c..b45a716fec5 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/acar/report/ReportHeader.java +++ b/MekHQ/src/mekhq/utilities/MHQInternationalization.java @@ -17,18 +17,22 @@ * along with MekHQ. If not, see . */ -package mekhq.campaign.autoresolve.acar.report; +package mekhq.utilities; -import megamek.client.ui.swing.util.UIUtil; -public class ReportHeader extends PublicReportEntry{ +import megamek.common.internationalization.Internationalization; - public ReportHeader(int messageId) { - super(messageId); +/** + * Class to handle MHQInternationalization (you will find online material on that looking for i18n) + * It makes use of some short names to make it easier to use since it is used in many places + */ +public class MHQInternationalization extends Internationalization { + + static { + instance = new MHQInternationalization("mekhq.resources.messages"); } - @Override - protected String reportText() { - return UIUtil.spanCSS("header", super.reportText()); + protected MHQInternationalization(String defaultBundle) { + super(defaultBundle); } } diff --git a/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java b/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java index 8433014d55f..01eac68958d 100644 --- a/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java +++ b/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java @@ -21,14 +21,15 @@ import megamek.client.ui.swing.util.PlayerColour; import megamek.common.*; +import megamek.common.autoresolve.Resolver; +import megamek.common.autoresolve.acar.SimulationOptions; +import megamek.common.autoresolve.event.AutoResolveConcludedEvent; import megamek.common.enums.Gender; import megamek.common.enums.SkillLevel; import megamek.common.icons.Camouflage; import megamek.common.planetaryconditions.*; import mekhq.campaign.Campaign; import mekhq.campaign.RandomSkillPreferences; -import mekhq.campaign.autoresolve.acar.SimulationOptions; -import mekhq.campaign.autoresolve.event.AutoResolveConcludedEvent; import mekhq.campaign.force.Force; import mekhq.campaign.mission.AtBContract; import mekhq.campaign.mission.AtBDynamicScenario; @@ -60,9 +61,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; /** @@ -356,7 +355,7 @@ void autoResolve(Consumer autoResolveConcludedEvent) when(botForce.getTeam()).thenReturn(2); when(botForce.getFullEntityList(any())).thenReturn(entities); - resolver = new Resolver(campaign, units, scenario, SimulationOptions.empty()); + resolver = new Resolver(new SetupForces(campaign, units, scenario), SimulationOptions.empty()); autoResolveConcludedEvent.accept(resolver.resolveSimulation()); } From 4058fa78cedbbe65f3d3d3e8fc35191381b85b61 Mon Sep 17 00:00:00 2001 From: Scoppio Date: Sat, 4 Jan 2025 20:38:28 -0300 Subject: [PATCH 3/6] fix: added autosave before AutoResolve --- MekHQ/src/mekhq/MekHQ.java | 4 +++- MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/MekHQ/src/mekhq/MekHQ.java b/MekHQ/src/mekhq/MekHQ.java index b436aba06d0..48553a3a832 100644 --- a/MekHQ/src/mekhq/MekHQ.java +++ b/MekHQ/src/mekhq/MekHQ.java @@ -645,6 +645,8 @@ public IconPackage getIconPackage() { */ public void startAutoResolve(AtBScenario scenario, List units) { + this.autosaveService.requestBeforeMissionAutosave(getCampaign()); + if (getCampaign().getCampaignOptions().isAutoResolveVictoryChanceEnabled()) { var proceed = AutoResolveChanceDialog .showSimulationProgressDialog( @@ -732,7 +734,7 @@ private void initEventHandlers() { } private static void setLookAndFeel(String themeName) { - final String theme = themeName.isBlank() ? "com.formdev.flatlaf.FlatDarculaLaf" : themeName; + final String theme = themeName.isBlank() || themeName.equals("UITheme") ? "com.formdev.flatlaf.FlatDarculaLaf" : themeName; Runnable runnable = () -> { try { diff --git a/MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java b/MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java index a269e21ca1b..2625e196815 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java @@ -31,8 +31,8 @@ public enum AutoResolveMethod { private final String toolTipText; AutoResolveMethod(final String name, final String toolTipText) { - this.name = MHQInternationalization.getText(name); - this.toolTipText = Internationalization.getText(toolTipText); + this.name = MHQInternationalization.getTextAt("mekhq.resources.AutoResolveMethod", name); + this.toolTipText = Internationalization.getTextAt("mekhq.resources.AutoResolveMethod", toolTipText); } public String getToolTipText() { From c35bcd5574be52767c49a33961c6940d51f62ed7 Mon Sep 17 00:00:00 2001 From: Scoppio Date: Sun, 5 Jan 2025 22:13:08 -0300 Subject: [PATCH 4/6] fix: small change necessary to keep the ACAR working with MHQ --- .../resources/AutoResolveMethod.properties | 18 ---- .../resources/AutoResolveMethod_en.properties | 18 ---- .../resources/mekhq/resources/GUI.properties | 20 ++++- .../mekhq/resources/messages.properties | 52 ----------- MekHQ/src/mekhq/MekHQ.java | 12 +-- .../{SetupForces.java => AtBSetupForces.java} | 12 +-- .../autoresolve/AutoResolveMethod.java | 4 +- .../src/mekhq/campaign/copy/CrewRefBreak.java | 49 ----------- MekHQ/src/mekhq/campaign/copy/RefBreak.java | 35 -------- MekHQ/src/mekhq/gui/BriefingTab.java | 8 +- .../gui/dialog/AutoResolveChanceDialog.java | 14 +-- .../utilities/MHQInternationalization.java | 88 ++++++++++++++++++- .../campaign/autoresolve/ResolverTest.java | 2 +- 13 files changed, 130 insertions(+), 202 deletions(-) delete mode 100644 MekHQ/resources/mekhq/resources/AutoResolveMethod.properties delete mode 100644 MekHQ/resources/mekhq/resources/AutoResolveMethod_en.properties rename MekHQ/src/mekhq/campaign/autoresolve/{SetupForces.java => AtBSetupForces.java} (97%) delete mode 100644 MekHQ/src/mekhq/campaign/copy/CrewRefBreak.java delete mode 100644 MekHQ/src/mekhq/campaign/copy/RefBreak.java diff --git a/MekHQ/resources/mekhq/resources/AutoResolveMethod.properties b/MekHQ/resources/mekhq/resources/AutoResolveMethod.properties deleted file mode 100644 index 1e22369405c..00000000000 --- a/MekHQ/resources/mekhq/resources/AutoResolveMethod.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2025 - The MegaMek Team. All Rights Reserved. -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation; either version 2 of the License, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -# for more details. - -AutoResolveMethod.PRINCESS.text=Princess -AutoResolveMethod.PRINCESS.toolTipText=Princess plays the game for you. -AutoResolveMethod.ABSTRACT_COMBAT.text=Abstract Combat Auto Resolution -AutoResolveMethod.ABSTRACT_COMBAT.toolTipText=ACAR, a fast simulation using a subset of the abstract combat system rules -AutoResolveMethod.promptForAutoResolveMethod.text=Select the method to use for auto resolving the scenario. -AutoResolveMethod.promptForAutoResolveMethod.title=Auto Resolve Method diff --git a/MekHQ/resources/mekhq/resources/AutoResolveMethod_en.properties b/MekHQ/resources/mekhq/resources/AutoResolveMethod_en.properties deleted file mode 100644 index 1e22369405c..00000000000 --- a/MekHQ/resources/mekhq/resources/AutoResolveMethod_en.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2025 - The MegaMek Team. All Rights Reserved. -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation; either version 2 of the License, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -# for more details. - -AutoResolveMethod.PRINCESS.text=Princess -AutoResolveMethod.PRINCESS.toolTipText=Princess plays the game for you. -AutoResolveMethod.ABSTRACT_COMBAT.text=Abstract Combat Auto Resolution -AutoResolveMethod.ABSTRACT_COMBAT.toolTipText=ACAR, a fast simulation using a subset of the abstract combat system rules -AutoResolveMethod.promptForAutoResolveMethod.text=Select the method to use for auto resolving the scenario. -AutoResolveMethod.promptForAutoResolveMethod.title=Auto Resolve Method diff --git a/MekHQ/resources/mekhq/resources/GUI.properties b/MekHQ/resources/mekhq/resources/GUI.properties index 6e4330de7c5..d0a91c48229 100644 --- a/MekHQ/resources/mekhq/resources/GUI.properties +++ b/MekHQ/resources/mekhq/resources/GUI.properties @@ -1532,4 +1532,22 @@ AutoResolveMethod.progress.95=Simulating AI mutiny risk... AutoResolveMethod.progress.96=Cross-referencing mek chassis vulnerabilities... AutoResolveMethod.progress.97=Balancing Lance formations for fairness... AutoResolveMethod.progress.98=Evaluating retreat probabilities... -AutoResolveMethod.progress.99=Finalizing combat scenario results... \ No newline at end of file +AutoResolveMethod.progress.99=Finalizing combat scenario results... + +AutoResolveDialog.title=Auto Resolve Battle +AutoResolveDialog.messageFailedCalc=Commander, we were unable to simulate any combat scenarios. Do you want to proceed? +AutoResolveDialog.messageSimulated=Commander, we ran {0} simulated combat scenarios and our forces came out victorious in {1}, lost {2}, and drew {3} times. This gives us a {4}% chance of victory. Do you want to proceed? +AutoResolveDialog.message.victory=Your forces won the scenario. Did your side control the battlefield at the end of the scenario? +AutoResolveDialog.message.defeat=Your forces lost the scenario. Do you want to declare your side as controlling the battlefield at the end of the scenario? +AutoResolveDialog.victory=Victory! +AutoResolveDialog.defeat=Defeat! +ResolveDialog.control.title=Control of Battlefield? +ResolveDialog.control.message=Did your side control the battlefield at the end of the scenario? + + +AutoResolveMethod.PRINCESS.text=Princess +AutoResolveMethod.PRINCESS.toolTipText=Princess plays the game for you. +AutoResolveMethod.ABSTRACT_COMBAT.text=Abstract Combat Auto Resolution +AutoResolveMethod.ABSTRACT_COMBAT.toolTipText=ACAR, a fast simulation using a subset of the abstract combat system rules +AutoResolveMethod.promptForAutoResolveMethod.text=Select the method to use for auto resolving the scenario. +AutoResolveMethod.promptForAutoResolveMethod.title=Auto Resolve Method \ No newline at end of file diff --git a/MekHQ/resources/mekhq/resources/messages.properties b/MekHQ/resources/mekhq/resources/messages.properties index 95051803d77..31f6dc25cd8 100644 --- a/MekHQ/resources/mekhq/resources/messages.properties +++ b/MekHQ/resources/mekhq/resources/messages.properties @@ -17,55 +17,3 @@ # along with MekHQ. If not, see . # -acar.invalid_attack=Invalid attack -acar.invalid_skill=Invalid skill -acar.skill=Skill -acar.critical_target_damage=Critical Target Damage -acar.skill_7=Wet behind the ears (skill) -acar.skill_6=Really Green (skill) -acar.skill_5=Green (skill) -acar.skill_4=Regular (skill) -acar.skill_3=Veteran (skill) -acar.skill_2=Elite (skill) -acar.skill_1=Heroic (skill) -acar.skill_0=Legendary (skill) -acar.short_range=Short range -acar.medium_range=Medium range -acar.long_range=Long range -acar.extreme_range=Extreme range -acar.TMM=TMM -acar.attacker_JUMP=Attacker JUMP -acar.target_JUMP=Target JUMP -acar.shaken=Shaken (morale) -acar.unsteady=Unsteady (morale) -acar.broken=Broken (morale) -acar.routed=Routed (morale) -acar.more_than_two_targets=Too many targets -acar.two_targets=Two targets -acar.invalid_engagement_control=Invalid engagement and control -acar.invalid_attacking_formation=Invalid attacking formation -acar.formation_tactics=Tactics -acar.force_engagement=Force engagement -acar.evade=Evade -acar.formation_is_infantry_only=Formation is infantry only -acar.formation_is_vehicle_only=Formation is vehicle only -acar.size_difference=Formation size difference -acar.shaken_morale=Shaken morale -acar.unsteady_morale=Unsteady morale -acar.broken_morale=Broken morale -acar.routed_morale=Routed morale -acar.forced_engagement=Forced Engagement -acar.aerospace_formation=Aerospace Formation -acar.invalid_nerve_recovering=Invalid nerve recovering -acar.formation_morale=Morale -acar.jump_modifier=Movement modifier -acar.withdraw.crippled=Crippled -AutoResolveDialog.title=Auto Resolve Battle -AutoResolveDialog.messageFailedCalc=Commander, we were unable to simulate any combat scenarios. Do you want to proceed? -AutoResolveDialog.messageSimulated=Commander, we ran {0} simulated combat scenarios and our forces came out victorious in {1}, lost {2}, and drew {3} times. This gives us a {4}% chance of victory. Do you want to proceed? -AutoResolveDialog.message.victory=Your forces won the scenario. Did your side control the battlefield at the end of the scenario? -AutoResolveDialog.message.defeat=Your forces lost the scenario. Do you want to declare your side as controlling the battlefield at the end of the scenario? -AutoResolveDialog.victory=Victory! -AutoResolveDialog.defeat=Defeat! -ResolveDialog.control.title=Control of Battlefield? -ResolveDialog.control.message=Did your side control the battlefield at the end of the scenario? diff --git a/MekHQ/src/mekhq/MekHQ.java b/MekHQ/src/mekhq/MekHQ.java index 48553a3a832..64acead900d 100644 --- a/MekHQ/src/mekhq/MekHQ.java +++ b/MekHQ/src/mekhq/MekHQ.java @@ -48,7 +48,7 @@ import mekhq.campaign.Campaign; import mekhq.campaign.CampaignController; import mekhq.campaign.ResolveScenarioTracker; -import mekhq.campaign.autoresolve.SetupForces; +import mekhq.campaign.autoresolve.AtBSetupForces; import mekhq.campaign.handler.PostScenarioDialogHandler; import mekhq.campaign.handler.XPHandler; import mekhq.campaign.mission.AtBScenario; @@ -660,21 +660,21 @@ public void startAutoResolve(AtBScenario scenario, List units) { } } - var event = new Resolver(new SetupForces(getCampaign(), units, scenario),new SimulationOptions(getCampaign().getGameOptions())) + var event = new Resolver(new AtBSetupForces(getCampaign(), units, scenario),new SimulationOptions(getCampaign().getGameOptions())) .resolveSimulation(); var autoResolveBattleReport = new AutoResolveSimulationLogDialog(getCampaigngui().getFrame(), event.getLogFile()); autoResolveBattleReport.setModal(true); autoResolveBattleReport.setVisible(true); - autoResolveConcluded(event); + autoResolveConcluded(event, scenario); } /** * This method is called when the auto resolve game is over. * @param autoResolveConcludedEvent The event that contains the results of the auto resolve game. */ - public void autoResolveConcluded(AutoResolveConcludedEvent autoResolveConcludedEvent) { + public void autoResolveConcluded(AutoResolveConcludedEvent autoResolveConcludedEvent, AtBScenario scenario) { try { String message = autoResolveConcludedEvent.controlledScenario() ? MHQInternationalization.getText("AutoResolveDialog.message.victory") : @@ -684,7 +684,7 @@ public void autoResolveConcluded(AutoResolveConcludedEvent autoResolveConcludedE MHQInternationalization.getText("AutoResolveDialog.defeat"); boolean control = yourSideControlsTheBattlefieldDialogAsk(message, title); - ResolveScenarioTracker tracker = new ResolveScenarioTracker(currentScenario, getCampaign(), control); + ResolveScenarioTracker tracker = new ResolveScenarioTracker(scenario, getCampaign(), control); tracker.setClient(new SimulatedClient(autoResolveConcludedEvent.getGame())); tracker.setEvent(autoResolveConcludedEvent); tracker.processGame(); @@ -700,7 +700,7 @@ public void autoResolveConcluded(AutoResolveConcludedEvent autoResolveConcludedE } return; } - PostScenarioDialogHandler.handle(campaignGUI, getCampaign(), currentScenario, tracker, + PostScenarioDialogHandler.handle(campaignGUI, getCampaign(), scenario, tracker, autoResolveConcludedEvent.controlledScenario()); } catch (Exception ex) { logger.error("Error during auto resolve concluded", ex); diff --git a/MekHQ/src/mekhq/campaign/autoresolve/SetupForces.java b/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java similarity index 97% rename from MekHQ/src/mekhq/campaign/autoresolve/SetupForces.java rename to MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java index 6a3c63fa984..cb6e556d70e 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/SetupForces.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java @@ -21,12 +21,13 @@ import megamek.common.autoresolve.acar.SimulationContext; import megamek.common.autoresolve.converter.ConsolidateForces; import megamek.common.autoresolve.converter.ForceToFormationConverter; +import megamek.common.autoresolve.converter.SingleElementConsolidateForces; +import megamek.common.copy.CrewRefBreak; import megamek.common.force.Forces; import megamek.common.options.OptionsConstants; import megamek.common.planetaryconditions.PlanetaryConditions; import megamek.logging.MMLogger; import mekhq.campaign.Campaign; -import mekhq.campaign.copy.CrewRefBreak; import mekhq.campaign.mission.AtBDynamicScenario; import mekhq.campaign.mission.AtBScenario; import mekhq.campaign.mission.BotForce; @@ -42,14 +43,14 @@ /** * @author Luana Coppio */ -public class SetupForces extends megamek.common.autoresolve.converter.SetupForces { - private static final MMLogger logger = MMLogger.create(SetupForces.class); +public class AtBSetupForces extends megamek.common.autoresolve.converter.SetupForces { + private static final MMLogger logger = MMLogger.create(AtBSetupForces.class); private final Campaign campaign; private final List units; private final AtBScenario scenario; - public SetupForces(Campaign campaign, List units, AtBScenario scenario) { + public AtBSetupForces(Campaign campaign, List units, AtBScenario scenario) { this.campaign = campaign; this.units = units; this.scenario = scenario; @@ -62,7 +63,7 @@ public SetupForces(Campaign campaign, List units, AtBScenario scenario) { public void createForcesOnSimulation(SimulationContext game) { setupPlayer(game); setupBots(game); - ConsolidateForces.consolidateForces(game); + ConsolidateForces.consolidateForces(game, new SingleElementConsolidateForces()); convertForcesIntoFormations(game); } @@ -432,7 +433,6 @@ public boolean accept(Entity entity) { || entity.hasQuirk(OptionsConstants.QUIRK_POS_SEARCHLIGHT)); game.getPlayer(entity.getOwnerId()).changeInitialEntityCount(1); - game.getPlayer(entity.getOwnerId()).changeInitialBV(entity.calculateBattleValue()); // Restore forces from MULs or other external sources from the forceString, if // any diff --git a/MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java b/MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java index 2625e196815..a269e21ca1b 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/AutoResolveMethod.java @@ -31,8 +31,8 @@ public enum AutoResolveMethod { private final String toolTipText; AutoResolveMethod(final String name, final String toolTipText) { - this.name = MHQInternationalization.getTextAt("mekhq.resources.AutoResolveMethod", name); - this.toolTipText = Internationalization.getTextAt("mekhq.resources.AutoResolveMethod", toolTipText); + this.name = MHQInternationalization.getText(name); + this.toolTipText = Internationalization.getText(toolTipText); } public String getToolTipText() { diff --git a/MekHQ/src/mekhq/campaign/copy/CrewRefBreak.java b/MekHQ/src/mekhq/campaign/copy/CrewRefBreak.java deleted file mode 100644 index 1cff531b3c2..00000000000 --- a/MekHQ/src/mekhq/campaign/copy/CrewRefBreak.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.copy; - -import megamek.common.Crew; - -/** - * @author Luana Coppio - */ -public class CrewRefBreak implements RefBreak { - - private final Crew originalCrew; - - public CrewRefBreak(Crew originalCrew) { - this.originalCrew = originalCrew; - } - - @Override - public Crew copy() { - var newCrewRef = new Crew(originalCrew.getCrewType(), originalCrew.getName(), originalCrew.getSize(), - originalCrew.getGunnery(), originalCrew.getPiloting(), originalCrew.getGender(), originalCrew.isClanPilot(), - originalCrew.getExtraData()); - - for (int i = 0; i < originalCrew.getCrewType().getCrewSlots(); i++) { - newCrewRef.setExternalIdAsString(originalCrew.getExternalIdAsString(i), i); - newCrewRef.setHits(originalCrew.getHits(i), i); - newCrewRef.setName(originalCrew.getName(i), i); - newCrewRef.setNickname(originalCrew.getNickname(i), i); - } - return newCrewRef; - } -} diff --git a/MekHQ/src/mekhq/campaign/copy/RefBreak.java b/MekHQ/src/mekhq/campaign/copy/RefBreak.java deleted file mode 100644 index 3a0084e804c..00000000000 --- a/MekHQ/src/mekhq/campaign/copy/RefBreak.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.campaign.copy; - -public interface RefBreak { - - /** - * Copy the object, creating a new instance with the same values. - * This is NOT a deep or a perfect copy, it is instead a "serviceable" copy. It allows to create a new instance - * that has NO references to the previous one, so it can run on a different thread or be modified without affecting - * the original object. - * If the object it returns is incomplete for your purposes, you should override this method and complete it or expand the - * implementation. - * @return a new instance of the object with the same values. - */ - T copy(); - -} diff --git a/MekHQ/src/mekhq/gui/BriefingTab.java b/MekHQ/src/mekhq/gui/BriefingTab.java index 3131a9e4607..737c0b9655f 100644 --- a/MekHQ/src/mekhq/gui/BriefingTab.java +++ b/MekHQ/src/mekhq/gui/BriefingTab.java @@ -814,15 +814,15 @@ private void promptAutoResolve(Scenario scenario) { // the options for the auto resolve method follow a predefined order, which is the same as the order in the enum // and it uses that to preselect the option that is currently set in the campaign options Object[] options = new Object[]{ - MHQInternationalization.getTextAt("AutoResolveMethod", "AutoResolveMethod.PRINCESS.text"), - MHQInternationalization.getTextAt("AutoResolveMethod", "AutoResolveMethod.ABSTRACT_COMBAT.text"), + MHQInternationalization.getText("AutoResolveMethod.PRINCESS.text"), + MHQInternationalization.getText("AutoResolveMethod.ABSTRACT_COMBAT.text"), }; var preSelectedOptionIndex = getCampaignOptions().getAutoResolveMethod().ordinal(); var selectedOption = JOptionPane.showOptionDialog(getFrame(), - MHQInternationalization.getTextAt("megamek.client.AutoResolveMethod", "AutoResolveMethod.promptForAutoResolveMethod.text"), - MHQInternationalization.getTextAt("megamek.client.AutoResolveMethod", "AutoResolveMethod.promptForAutoResolveMethod.title"), + MHQInternationalization.getText("AutoResolveMethod.promptForAutoResolveMethod.text"), + MHQInternationalization.getText("AutoResolveMethod.promptForAutoResolveMethod.title"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[preSelectedOptionIndex]); diff --git a/MekHQ/src/mekhq/gui/dialog/AutoResolveChanceDialog.java b/MekHQ/src/mekhq/gui/dialog/AutoResolveChanceDialog.java index 38bb0540c8d..da9833b3bb4 100644 --- a/MekHQ/src/mekhq/gui/dialog/AutoResolveChanceDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/AutoResolveChanceDialog.java @@ -26,7 +26,7 @@ import megamek.logging.MMLogger; import megamek.server.victory.VictoryResult; import mekhq.campaign.Campaign; -import mekhq.campaign.autoresolve.SetupForces; +import mekhq.campaign.autoresolve.AtBSetupForces; import mekhq.campaign.mission.AtBScenario; import mekhq.campaign.unit.Unit; import mekhq.gui.baseComponents.AbstractMHQDialog; @@ -108,8 +108,10 @@ public int getDraws() { public static int showSimulationProgressDialog(JFrame frame, int numberOfSimulations, List units, AtBScenario scenario, Campaign campaign) { var dialog = new AutoResolveChanceDialog(frame, numberOfSimulations, units, scenario, campaign); dialog.initialize(); + dialog.setModal(true); dialog.getTask().execute(); dialog.setVisible(true); + return dialog.getReturnCode(); } @@ -148,7 +150,7 @@ protected Container createCenterPane() { private JProgressBar createProgressBar() { setProgressBar(new JProgressBar(0, 100)); - getProgressBar().setString(resources.getString("AutoResolveMethod.progress.0")); + getProgressBar().setString(MHQInternationalization.getText("AutoResolveMethod.progress.0")); getProgressBar().setValue(0); getProgressBar().setStringPainted(true); getProgressBar().setVisible(true); @@ -194,7 +196,7 @@ public void propertyChange(PropertyChangeEvent evt) { progressBar.setValue(progress); var factor = clamp(numberOfSimulations / 25, 3, 99); int i = (int) (factor * ((float) progress / progressBar.getMaximum())); - getProgressBar().setString(resources.getString(progressText.get(i))); + getProgressBar().setString(MHQInternationalization.getText(progressText.get(i))); } public static int clamp(long value, int min, int max) { @@ -218,14 +220,14 @@ public Task(AutoResolveChanceDialog dialog) { @Override public Integer doInBackground() { String message; - String title = resources.getString("AutoResolveDialog.title"); + String title = MHQInternationalization.getText("AutoResolveDialog.title"); StopWatch stopWatch = new StopWatch(); stopWatch.start(); var simulatedVictories = calculateNumberOfVictories(); stopWatch.stop(); if (simulatedVictories.getRuns() == 0 && simulatedVictories.getRuns() < numberOfSimulations) { - message = resources.getString("AutoResolveDialog.messageFailedCalc"); + message = MHQInternationalization.getText("AutoResolveDialog.messageFailedCalc"); logger.debug("No combat scenarios were simulated, possible error!"); } else { var timePerRun = stopWatch.getTime() / (numberOfSimulations / Runtime.getRuntime().availableProcessors()); @@ -270,7 +272,7 @@ private SimulationScore calculateNumberOfVictories() { for (int i = 0; i < numberOfSimulations; i++) { futures.add(executor.submit(() -> { var autoResolveConcludedEvent = new Resolver( - new SetupForces(campaign, units, scenario), new SimulationOptions(campaign.getGameOptions())) + new AtBSetupForces(campaign, units, scenario), new SimulationOptions(campaign.getGameOptions())) .resolveSimulation(); setProgress(Math.min(100 * runCounter.incrementAndGet() / numberOfSimulations, 100)); return autoResolveConcludedEvent; diff --git a/MekHQ/src/mekhq/utilities/MHQInternationalization.java b/MekHQ/src/mekhq/utilities/MHQInternationalization.java index b45a716fec5..400980bba1a 100644 --- a/MekHQ/src/mekhq/utilities/MHQInternationalization.java +++ b/MekHQ/src/mekhq/utilities/MHQInternationalization.java @@ -20,19 +20,99 @@ package mekhq.utilities; -import megamek.common.internationalization.Internationalization; +import megamek.MegaMek; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; /** * Class to handle MHQInternationalization (you will find online material on that looking for i18n) * It makes use of some short names to make it easier to use since it is used in many places */ -public class MHQInternationalization extends Internationalization { +public class MHQInternationalization { + + private final String defaultBundle; + private final ConcurrentHashMap resourceBundles = new ConcurrentHashMap<>(); + protected static MHQInternationalization instance; static { - instance = new MHQInternationalization("mekhq.resources.messages"); + instance = new MHQInternationalization("mekhq.resources.GUI"); + } + + public static MHQInternationalization getInstance() { + return instance; } protected MHQInternationalization(String defaultBundle) { - super(defaultBundle); + this.defaultBundle = defaultBundle; } + + private static class UTF8Control extends ResourceBundle.Control { + @Override + public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) + throws IOException { + // The below is one approach; there are multiple ways to do this + String resourceName = toResourceName(toBundleName(baseName, locale), "properties"); + try (InputStream is = loader.getResourceAsStream(resourceName); + InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) { + return new PropertyResourceBundle(isr); + } + } + } + + ResourceBundle getResourceBundle(String bundleName) { + return resourceBundles.computeIfAbsent(bundleName, k -> + ResourceBundle.getBundle(bundleName, MegaMek.getMMOptions().getLocale(), new UTF8Control())); + } + + /** + * Get a localized string from a specific bundle + * @param bundleName the name of the bundle + * @param key the key of the string + * @return the localized string + */ + public static String getTextAt(String bundleName, String key) { + if (getInstance().getResourceBundle(bundleName).containsKey(key)) { + return getInstance().getResourceBundle(bundleName).getString(key); + } + return "!" + key + "!"; + } + + /** + * Get a localized string from the default bundle + * @param key the key of the string + * @return the localized string + */ + public static String getText(String key) { + return getTextAt(getInstance().defaultBundle, key); + } + + /** + * Get a formatted localized string from the default bundle + * @param key the key of the string + * @param args the arguments to format the string + * @return the localized string + */ + public static String getFormattedText(String key, Object... args) { + return MessageFormat.format(getFormattedTextAt(getInstance().defaultBundle, key), args); + } + + /** + * Get a formatted localized string from the default bundle + * @param bundleName the name of the bundle + * @param key the key of the string + * @param args the arguments to format the string + * @return the localized string + */ + public static String getFormattedTextAt(String bundleName, String key, Object... args) { + return MessageFormat.format(getTextAt(bundleName, key), args); + } + } diff --git a/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java b/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java index 01eac68958d..25eb894b825 100644 --- a/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java +++ b/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java @@ -355,7 +355,7 @@ void autoResolve(Consumer autoResolveConcludedEvent) when(botForce.getTeam()).thenReturn(2); when(botForce.getFullEntityList(any())).thenReturn(entities); - resolver = new Resolver(new SetupForces(campaign, units, scenario), SimulationOptions.empty()); + resolver = new Resolver(new AtBSetupForces(campaign, units, scenario), SimulationOptions.empty()); autoResolveConcludedEvent.accept(resolver.resolveSimulation()); } From fa55b6385a26c7880aa98eee66dd55efe97502cd Mon Sep 17 00:00:00 2001 From: Scoppio Date: Tue, 7 Jan 2025 22:54:28 -0300 Subject: [PATCH 5/6] feat: updated to use ACAR directly from Megamek --- MekHQ/src/mekhq/MekHQ.java | 22 +- .../campaign/autoresolve/AtBSetupForces.java | 3 +- .../AutoResolveBehaviorSettingsDialog.java | 1 - .../gui/dialog/AutoResolveChanceDialog.java | 305 ------------------ .../campaign/autoresolve/ResolverTest.java | 2 +- 5 files changed, 14 insertions(+), 319 deletions(-) delete mode 100644 MekHQ/src/mekhq/gui/dialog/AutoResolveChanceDialog.java diff --git a/MekHQ/src/mekhq/MekHQ.java b/MekHQ/src/mekhq/MekHQ.java index 64acead900d..19d9eb3e608 100644 --- a/MekHQ/src/mekhq/MekHQ.java +++ b/MekHQ/src/mekhq/MekHQ.java @@ -34,10 +34,9 @@ import megamek.client.ui.swing.gameConnectionDialogs.ConnectDialog; import megamek.client.ui.swing.gameConnectionDialogs.HostDialog; import megamek.client.ui.swing.util.UIUtil; +import megamek.common.Board; 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.*; import megamek.common.net.marshalling.SanityInputFilter; @@ -57,10 +56,8 @@ import mekhq.campaign.stratcon.StratconRulesManager; import mekhq.campaign.unit.Unit; import mekhq.gui.CampaignGUI; -import mekhq.gui.dialog.AutoResolveChanceDialog; import mekhq.gui.dialog.ChooseMulFilesDialog; import mekhq.gui.dialog.ResolveScenarioWizardDialog; - import mekhq.gui.panels.StartupScreenPanel; import mekhq.gui.preferences.StringPreference; import mekhq.gui.utilities.ObservableString; @@ -648,20 +645,23 @@ public void startAutoResolve(AtBScenario scenario, List units) { this.autosaveService.requestBeforeMissionAutosave(getCampaign()); if (getCampaign().getCampaignOptions().isAutoResolveVictoryChanceEnabled()) { - var proceed = AutoResolveChanceDialog - .showSimulationProgressDialog( + var proceed = megamek.client.ui.dialogs.AutoResolveChanceDialog + .showDialog( getCampaigngui().getFrame(), getCampaign().getCampaignOptions().getAutoResolveNumberOfScenarios(), - units, - scenario, - getCampaign()) == JOptionPane.YES_OPTION; + Runtime.getRuntime().availableProcessors(), + getCampaign().getPlayer().getTeam(), + new AtBSetupForces(getCampaign(), units, scenario), + new Board(scenario.getBaseMapX(), scenario.getBaseMapY())) == JOptionPane.YES_OPTION; if (!proceed) { return; } } - var event = new Resolver(new AtBSetupForces(getCampaign(), units, scenario),new SimulationOptions(getCampaign().getGameOptions())) - .resolveSimulation(); + var event = megamek.client.ui.dialogs.AutoResolveProgressDialog.showDialog( + getCampaigngui().getFrame(), + new AtBSetupForces(getCampaign(), units, scenario), + new Board(scenario.getBaseMapX(), scenario.getBaseMapY())); var autoResolveBattleReport = new AutoResolveSimulationLogDialog(getCampaigngui().getFrame(), event.getLogFile()); autoResolveBattleReport.setModal(true); diff --git a/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java b/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java index cb6e556d70e..e0b5bc49384 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java @@ -21,6 +21,7 @@ import megamek.common.autoresolve.acar.SimulationContext; import megamek.common.autoresolve.converter.ConsolidateForces; import megamek.common.autoresolve.converter.ForceToFormationConverter; +import megamek.common.autoresolve.converter.SetupForces; import megamek.common.autoresolve.converter.SingleElementConsolidateForces; import megamek.common.copy.CrewRefBreak; import megamek.common.force.Forces; @@ -43,7 +44,7 @@ /** * @author Luana Coppio */ -public class AtBSetupForces extends megamek.common.autoresolve.converter.SetupForces { +public class AtBSetupForces extends SetupForces { private static final MMLogger logger = MMLogger.create(AtBSetupForces.class); private final Campaign campaign; diff --git a/MekHQ/src/mekhq/gui/dialog/AutoResolveBehaviorSettingsDialog.java b/MekHQ/src/mekhq/gui/dialog/AutoResolveBehaviorSettingsDialog.java index 1b5c27bf19d..23a5650a5a6 100644 --- a/MekHQ/src/mekhq/gui/dialog/AutoResolveBehaviorSettingsDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/AutoResolveBehaviorSettingsDialog.java @@ -9,7 +9,6 @@ import mekhq.campaign.Campaign; import mekhq.gui.dialog.helpDialogs.AutoResolveBehaviorSettingsHelpDialog; - import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; diff --git a/MekHQ/src/mekhq/gui/dialog/AutoResolveChanceDialog.java b/MekHQ/src/mekhq/gui/dialog/AutoResolveChanceDialog.java deleted file mode 100644 index da9833b3bb4..00000000000 --- a/MekHQ/src/mekhq/gui/dialog/AutoResolveChanceDialog.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MekHQ. - * - * MekHQ is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * MekHQ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MekHQ. If not, see . - */ - -package mekhq.gui.dialog; - -import megamek.client.ui.swing.util.UIUtil; -import megamek.common.autoresolve.Resolver; -import megamek.common.autoresolve.acar.SimulationOptions; -import megamek.common.autoresolve.event.AutoResolveConcludedEvent; -import megamek.logging.MMLogger; -import megamek.server.victory.VictoryResult; -import mekhq.campaign.Campaign; -import mekhq.campaign.autoresolve.AtBSetupForces; -import mekhq.campaign.mission.AtBScenario; -import mekhq.campaign.unit.Unit; -import mekhq.gui.baseComponents.AbstractMHQDialog; -import mekhq.utilities.MHQInternationalization; -import org.apache.commons.lang3.time.StopWatch; - -import javax.swing.*; -import java.awt.*; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicInteger; - -public class AutoResolveChanceDialog extends AbstractMHQDialog implements PropertyChangeListener { - private static final MMLogger logger = MMLogger.create(AutoResolveChanceDialog.class); - - private JProgressBar progressBar; - private final int numberOfSimulations; - private int returnCode = JOptionPane.CLOSED_OPTION; - private final List units; - private final AtBScenario scenario; - private final Campaign campaign; - private JLabel splash; - private final Task task; - - private final List progressText; - - private static class SimulationScore { - private final AtomicInteger victories; - private final AtomicInteger losses; - private final AtomicInteger draws; - private final AtomicInteger gamesRun; - - public SimulationScore() { - this.victories = new AtomicInteger(0); - this.losses = new AtomicInteger(0); - this.draws = new AtomicInteger(0); - this.gamesRun = new AtomicInteger(0); - } - - public void addResult(AutoResolveConcludedEvent event) { - this.addResult(event.getVictoryResult()); - } - - public void addResult(VictoryResult victoryResult) { - if (victoryResult.getWinningTeam() == 1) { - victories.incrementAndGet(); - } else if (victoryResult.getWinningTeam() > 1) { - losses.incrementAndGet(); - } else { - draws.incrementAndGet(); - } - gamesRun.incrementAndGet(); - } - - public int getVictories() { - return victories.get(); - } - - public int getRuns() { - return gamesRun.get(); - } - - public int getLosses() { - return losses.get(); - } - - public int getDraws() { - return draws.get(); - } - } - - public static int showSimulationProgressDialog(JFrame frame, int numberOfSimulations, List units, AtBScenario scenario, Campaign campaign) { - var dialog = new AutoResolveChanceDialog(frame, numberOfSimulations, units, scenario, campaign); - dialog.initialize(); - dialog.setModal(true); - dialog.getTask().execute(); - dialog.setVisible(true); - - return dialog.getReturnCode(); - } - - private AutoResolveChanceDialog(JFrame frame, int numberOfSimulations, List units, AtBScenario scenario, Campaign campaign) { - super(frame, true, "AutoResolveMethod.dialog.name","AutoResolveMethod.dialog.title"); - this.numberOfSimulations = numberOfSimulations; - this.units = units; - this.scenario = scenario; - this.campaign = campaign; - this.task = new Task(this); - getTask().addPropertyChangeListener(this); - progressText = new ArrayList<>(); - for (int i = 1; i < 100; i++) { - progressText.add("AutoResolveMethod.progress." + i); - } - Collections.shuffle(progressText); - initialize(); - } - - @Override - protected void initialize() { - setUndecorated(true); - setLayout(new BorderLayout()); - add(createCenterPane(), BorderLayout.CENTER); - add(createProgressBar(), BorderLayout.PAGE_END); - finalizeInitialization(); - } - - @Override - protected Container createCenterPane() { - setSplash(UIUtil.createSplashComponent( - campaign.getApp().getIconPackage().getAutoResolveScreenImages(), getFrame())); - return getSplash(); - } - - private JProgressBar createProgressBar() { - setProgressBar(new JProgressBar(0, 100)); - - getProgressBar().setString(MHQInternationalization.getText("AutoResolveMethod.progress.0")); - getProgressBar().setValue(0); - getProgressBar().setStringPainted(true); - getProgressBar().setVisible(true); - return getProgressBar(); - } - - @Override - protected void finalizeInitialization() { - setPreferredSize(getSplash().getPreferredSize()); - setSize(getSplash().getPreferredSize()); -// pack(); - fitAndCenter(); -// getFrame().setVisible(false); - } - - public Task getTask() { - return task; - } - - public JLabel getSplash() { - return splash; - } - - public void setSplash(final JLabel splash) { - this.splash = splash; - } - - public JProgressBar getProgressBar() { - return progressBar; - } - - public void setProgressBar(final JProgressBar progressBar) { - this.progressBar = progressBar; - } - - private int getReturnCode() { - return returnCode; - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - int progress = Math.min(progressBar.getMaximum(), task.getProgress()); - progressBar.setValue(progress); - var factor = clamp(numberOfSimulations / 25, 3, 99); - int i = (int) (factor * ((float) progress / progressBar.getMaximum())); - getProgressBar().setString(MHQInternationalization.getText(progressText.get(i))); - } - - public static int clamp(long value, int min, int max) { - if (min > max) { - throw new IllegalArgumentException(min + " > " + max); - } - return (int) Math.min(max, Math.max(value, min)); - } - - - /** - * Main task. This is executed in a background thread. - */ - private class Task extends SwingWorker { - AutoResolveChanceDialog dialog; - - public Task(AutoResolveChanceDialog dialog) { - this.dialog = dialog; - } - - @Override - public Integer doInBackground() { - String message; - String title = MHQInternationalization.getText("AutoResolveDialog.title"); - StopWatch stopWatch = new StopWatch(); - stopWatch.start(); - var simulatedVictories = calculateNumberOfVictories(); - stopWatch.stop(); - - if (simulatedVictories.getRuns() == 0 && simulatedVictories.getRuns() < numberOfSimulations) { - message = MHQInternationalization.getText("AutoResolveDialog.messageFailedCalc"); - logger.debug("No combat scenarios were simulated, possible error!"); - } else { - var timePerRun = stopWatch.getTime() / (numberOfSimulations / Runtime.getRuntime().availableProcessors()); - logger.debug("Simulated victories: {} runs, {} victories, {} losses, {} draws - processed in {} ms per CPU core - total of {}", - simulatedVictories.getRuns(), - simulatedVictories.getVictories(), - simulatedVictories.getLosses(), - simulatedVictories.getDraws(), - timePerRun, - stopWatch.toString()); - - message = MHQInternationalization.getFormattedText("AutoResolveDialog.messageSimulated", - simulatedVictories.getRuns(), - simulatedVictories.getVictories(), - simulatedVictories.getLosses(), - simulatedVictories.getDraws(), - simulatedVictories.getVictories() * 100 / simulatedVictories.getRuns()); - } - - returnCode = JOptionPane.showConfirmDialog( - getFrame(), - message, title, - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE); - return returnCode; - } - - /** - * Calculates the victory chance for a given scenario and list of units by running multiple auto resolve scenarios in parallel. - * - * @return the calculated victory chance score - */ - private SimulationScore calculateNumberOfVictories() { - var simulationScore = new SimulationScore(); - if (dialog.numberOfSimulations <= 0) { - return simulationScore; - } - AtomicInteger runCounter = new AtomicInteger(0); - - ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - java.util.List> futures = new ArrayList<>(); - for (int i = 0; i < numberOfSimulations; i++) { - futures.add(executor.submit(() -> { - var autoResolveConcludedEvent = new Resolver( - new AtBSetupForces(campaign, units, scenario), new SimulationOptions(campaign.getGameOptions())) - .resolveSimulation(); - setProgress(Math.min(100 * runCounter.incrementAndGet() / numberOfSimulations, 100)); - return autoResolveConcludedEvent; - })); - } - - // Wait for all tasks to complete - for (Future future : futures) { - try { - var event = future.get(); - simulationScore.addResult(event); - } catch (InterruptedException | ExecutionException e) { - logger.error("While processing simulation", e); - } - } - - executor.shutdown(); - - return simulationScore; - } - - /** - * Executed in event dispatching thread - */ - @Override - public void done() { - setVisible(false); - } - } -} diff --git a/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java b/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java index 25eb894b825..3fb0868876a 100644 --- a/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java +++ b/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java @@ -355,7 +355,7 @@ void autoResolve(Consumer autoResolveConcludedEvent) when(botForce.getTeam()).thenReturn(2); when(botForce.getFullEntityList(any())).thenReturn(entities); - resolver = new Resolver(new AtBSetupForces(campaign, units, scenario), SimulationOptions.empty()); + resolver = Resolver.simulationRun(new AtBSetupForces(campaign, units, scenario), SimulationOptions.empty(), new Board(30, 30)); autoResolveConcludedEvent.accept(resolver.resolveSimulation()); } From 8acc28b20eb337dccf32bcb3a3f228b9f803a624 Mon Sep 17 00:00:00 2001 From: Daniel L- <103902653+IllianiCBT@users.noreply.github.com> Date: Thu, 9 Jan 2025 11:50:45 -0600 Subject: [PATCH 6/6] Update history.txt --- MekHQ/docs/history.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/MekHQ/docs/history.txt b/MekHQ/docs/history.txt index 0a5de41e7a9..b8c4e57df5f 100644 --- a/MekHQ/docs/history.txt +++ b/MekHQ/docs/history.txt @@ -56,6 +56,7 @@ MEKHQ VERSION HISTORY: + PR #5707: Corrected Available Force Check + PR #5708: Added a Soft Cap to Support Point Generation + Fix #5570: Fixed Maternity Leave Date Handling ++ PR #5699: Moved ACAR Module to MegaMek 0.50.02 (2024-12-30 2130 UTC) + Fix #846, #5185: Fix BattleArmor customization/refit overweight check.