From 3967620180252219317cab9b26949647beba9702 Mon Sep 17 00:00:00 2001 From: Luana Coppio Date: Fri, 9 May 2025 12:23:29 -0300 Subject: [PATCH 1/5] feat: add actual board for acar --- MekHQ/src/mekhq/MekHQ.java | 7 +- MekHQ/src/mekhq/utilities/ScenarioUtils.java | 81 ++++++++++++++++++++ 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 MekHQ/src/mekhq/utilities/ScenarioUtils.java diff --git a/MekHQ/src/mekhq/MekHQ.java b/MekHQ/src/mekhq/MekHQ.java index 794d67c0aed..6c67ebee94d 100644 --- a/MekHQ/src/mekhq/MekHQ.java +++ b/MekHQ/src/mekhq/MekHQ.java @@ -108,6 +108,7 @@ import mekhq.service.AutosaveService; import mekhq.service.IAutosaveService; import mekhq.utilities.MHQInternationalization; +import mekhq.utilities.ScenarioUtils; /** * The main class of the application. @@ -758,13 +759,15 @@ public void startAutoResolve(AtBScenario scenario, List units) { this.autosaveService.requestBeforeScenarioAutosave(getCampaign()); + Board board = ScenarioUtils.getBoardFor(scenario); if (getCampaign().getCampaignOptions().isAutoResolveVictoryChanceEnabled()) { + var proceed = AutoResolveChanceDialog.showDialog(campaignGUI.getFrame(), getCampaign().getCampaignOptions().getAutoResolveNumberOfScenarios(), Runtime.getRuntime().availableProcessors(), 1, new AtBSetupForces(getCampaign(), units, scenario, new SingletonForces()), - new Board(scenario.getBaseMapX(), scenario.getBaseMapY())) == JOptionPane.YES_OPTION; + board) == JOptionPane.YES_OPTION; if (!proceed) { return; } @@ -772,7 +775,7 @@ public void startAutoResolve(AtBScenario scenario, List units) { var event = AutoResolveProgressDialog.showDialog(campaignGUI.getFrame(), new AtBSetupForces(getCampaign(), units, scenario, new SingletonForces()), - new Board(scenario.getBaseMapX(), scenario.getBaseMapY())); + board); var autoResolveBattleReport = new AutoResolveSimulationLogDialog(campaignGUI.getFrame(), event.getLogFile()); autoResolveBattleReport.setModal(true); diff --git a/MekHQ/src/mekhq/utilities/ScenarioUtils.java b/MekHQ/src/mekhq/utilities/ScenarioUtils.java new file mode 100644 index 00000000000..c84f4f5b834 --- /dev/null +++ b/MekHQ/src/mekhq/utilities/ScenarioUtils.java @@ -0,0 +1,81 @@ +/* + * 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. + * + */ + +package mekhq.utilities; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import io.sentry.Sentry; +import megamek.common.Board; +import megamek.common.MapSettings; +import megamek.logging.MMLogger; +import megamek.server.ServerBoardHelper; +import mekhq.campaign.mission.AtBScenario; +import mekhq.campaign.mission.Scenario; + +/** + * @author Luana Coppio + */ +public class ScenarioUtils { + + private static final MMLogger LOGGER = MMLogger.create(ScenarioUtils.class); + + private ScenarioUtils() {} + + public static Board getBoardFor(AtBScenario scenario) { + MapSettings mapSettings = MapSettings.getInstance(); + mapSettings.setBoardSize(scenario.getMapX(), scenario.getMapY()); + mapSettings.setMapSize(1, 1); + mapSettings.getBoardsSelectedVector().clear(); + + // if the scenario is taking place in space, do space settings instead + if (scenario.getBoardType() == Scenario.T_SPACE || scenario.getTerrainType().equals("Space")) { + mapSettings.setMedium(MapSettings.MEDIUM_SPACE); + mapSettings.getBoardsSelectedVector().add(MapSettings.BOARD_GENERATED); + } else if (scenario.isUsingFixedMap()) { + // TODO : remove inline file type + String board = scenario.getMap().replace(".board", ""); + board = board.replace("\\", "/"); + mapSettings.getBoardsSelectedVector().add(board); + + if (scenario.getBoardType() == Scenario.T_ATMOSPHERE) { + mapSettings.setMedium(MapSettings.MEDIUM_ATMOSPHERE); + } + } else { + File mapgenFile = new File("data/mapgen/" + scenario.getMap() + ".xml"); + try (InputStream is = new FileInputStream(mapgenFile)) { + mapSettings = MapSettings.getInstance(is); + } catch (IOException ex) { + Sentry.captureException(ex); + // TODO: Remove inline file path + LOGGER.error(ex, "Could not load map file data/mapgen/{}.xml", scenario.getMap()); + } + + if (scenario.getBoardType() == Scenario.T_ATMOSPHERE) { + mapSettings.setMedium(MapSettings.MEDIUM_ATMOSPHERE); + } + + // duplicate code, but getting a new instance of map settings resets the size + // parameters + mapSettings.setBoardSize(scenario.getMapX(), scenario.getMapY()); + mapSettings.setMapSize(1, 1); + mapSettings.getBoardsSelectedVector().add(MapSettings.BOARD_GENERATED); + } + return ServerBoardHelper.getPossibleGameBoard(mapSettings, false); + } +} From 1fdd2ad5cf6db79e2eb9527a6d095086ff8428fe Mon Sep 17 00:00:00 2001 From: Luana Coppio Date: Fri, 9 May 2025 16:02:24 -0300 Subject: [PATCH 2/5] feat: basis to add non atb ACAR --- MekHQ/src/mekhq/MekHQ.java | 2 +- MekHQ/src/mekhq/gui/BriefingTab.java | 6 +- MekHQ/src/mekhq/utilities/ScenarioUtils.java | 126 +++++++++++++++---- 3 files changed, 103 insertions(+), 31 deletions(-) diff --git a/MekHQ/src/mekhq/MekHQ.java b/MekHQ/src/mekhq/MekHQ.java index 6c67ebee94d..aee3dc8e053 100644 --- a/MekHQ/src/mekhq/MekHQ.java +++ b/MekHQ/src/mekhq/MekHQ.java @@ -755,7 +755,7 @@ public IconPackage getIconPackage() { * * @param units The list of player units involved in the scenario */ - public void startAutoResolve(AtBScenario scenario, List units) { + public void startAutoResolve(Scenario scenario, List units) { this.autosaveService.requestBeforeScenarioAutosave(getCampaign()); diff --git a/MekHQ/src/mekhq/gui/BriefingTab.java b/MekHQ/src/mekhq/gui/BriefingTab.java index 3f1fede2d0a..640ed71b1bb 100644 --- a/MekHQ/src/mekhq/gui/BriefingTab.java +++ b/MekHQ/src/mekhq/gui/BriefingTab.java @@ -888,7 +888,7 @@ private void runAbstractCombatAutoResolve(Scenario scenario) { if (chosen.isEmpty()) { return; } - getCampaign().getApp().startAutoResolve((AtBScenario) scenario, chosen); + getCampaign().getApp().startAutoResolve(scenario, chosen); } private void runPrincessAutoResolve() { @@ -1546,9 +1546,7 @@ public void refreshScenarioView() { } btnResolveScenario.setEnabled(canStartGame); - if (scenario instanceof AtBScenario) { - btnAutoResolveScenario.setEnabled(canStartGame); - } + btnAutoResolveScenario.setEnabled(canStartGame); btnPrintRS.setEnabled(canStartGame); } diff --git a/MekHQ/src/mekhq/utilities/ScenarioUtils.java b/MekHQ/src/mekhq/utilities/ScenarioUtils.java index c84f4f5b834..1e2c5b3a57d 100644 --- a/MekHQ/src/mekhq/utilities/ScenarioUtils.java +++ b/MekHQ/src/mekhq/utilities/ScenarioUtils.java @@ -1,18 +1,35 @@ /* - * Copyright (c) 2025 - The MegaMek Team. All Rights Reserved. + * 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 file is part of MegaMek. * - * 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. + * MegaMek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. * + * MegaMek 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + * + * NOTICE: The MegaMek organization is a non-profit group of volunteers + * creating free software for the BattleTech community. + * + * MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks + * of The Topps Company, Inc. All Rights Reserved. + * + * Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of + * InMediaRes Productions, LLC. + * + * MechWarrior Copyright Microsoft Corporation. MegaMek was created under + * Microsoft's "Game Content Usage Rules" + * and it is not endorsed by or + * affiliated with Microsoft. */ - package mekhq.utilities; import java.io.File; @@ -37,45 +54,102 @@ public class ScenarioUtils { private ScenarioUtils() {} - public static Board getBoardFor(AtBScenario scenario) { + /** + * Creates a game board based on the settings in the provided Scenario. + * This method extracts map configuration from the scenario and delegates to the board creation logic. + * + * @param scenario The Scenario containing board configuration parameters + * @return A Board object configured according to the scenario settings, or a default board if invalid parameters + */ + public static Board getBoardFor(Scenario scenario) { + // Check for valid dimensions and map + if (scenario instanceof AtBScenario atBScenario) { + return getStratconBoardFor(atBScenario); + } + return getNonStratconBoardFor(scenario); + } + + private static Board getNonStratconBoardFor(Scenario scenario) { + if (scenario == null || scenario.getMap() == null || + scenario.getMapSizeX() <= 1 || scenario.getMapSizeY() <= 1) { + LOGGER.error("Invalid map settings provided for scenario {}", + scenario != null ? scenario.getName() : "null"); + return ServerBoardHelper.getPossibleGameBoard(MapSettings.getInstance(), false); + } + + boolean isSpace = scenario.getBoardType() == Scenario.T_SPACE; + boolean isAtmosphere = scenario.getBoardType() == Scenario.T_ATMOSPHERE; + + return createBoard( + scenario.getMapSizeX(), + scenario.getMapSizeY(), + scenario.getMap(), + scenario.isUsingFixedMap(), + isSpace, + isAtmosphere + ); + } + + private static Board getStratconBoardFor(AtBScenario scenario) { + // Check for valid dimensions and map + if (scenario == null || scenario.getMap() == null) { + LOGGER.error("Invalid AtBScenario provided"); + return ServerBoardHelper.getPossibleGameBoard(MapSettings.getInstance(), false); + } + + boolean isSpace = scenario.getBoardType() == Scenario.T_SPACE || + "Space".equals(scenario.getTerrainType()); + boolean isAtmosphere = scenario.getBoardType() == Scenario.T_ATMOSPHERE; + + return createBoard( + scenario.getMapX(), + scenario.getMapY(), + scenario.getMap(), + scenario.isUsingFixedMap(), + isSpace, + isAtmosphere + ); + } + + /** + * Creates a board based on the provided parameters + */ + private static Board createBoard(int mapSizeX, int mapSizeY, String mapName, + boolean isUsingFixedMap, boolean isSpace, boolean isAtmosphere) { MapSettings mapSettings = MapSettings.getInstance(); - mapSettings.setBoardSize(scenario.getMapX(), scenario.getMapY()); + mapSettings.setBoardSize(mapSizeX, mapSizeY); mapSettings.setMapSize(1, 1); mapSettings.getBoardsSelectedVector().clear(); - // if the scenario is taking place in space, do space settings instead - if (scenario.getBoardType() == Scenario.T_SPACE || scenario.getTerrainType().equals("Space")) { + if (isSpace) { mapSettings.setMedium(MapSettings.MEDIUM_SPACE); mapSettings.getBoardsSelectedVector().add(MapSettings.BOARD_GENERATED); - } else if (scenario.isUsingFixedMap()) { - // TODO : remove inline file type - String board = scenario.getMap().replace(".board", ""); - board = board.replace("\\", "/"); + } else if (isUsingFixedMap) { + String board = mapName.replace(".board", "").replace("\\", "/"); mapSettings.getBoardsSelectedVector().add(board); - if (scenario.getBoardType() == Scenario.T_ATMOSPHERE) { + if (isAtmosphere) { mapSettings.setMedium(MapSettings.MEDIUM_ATMOSPHERE); } } else { - File mapgenFile = new File("data/mapgen/" + scenario.getMap() + ".xml"); + File mapgenFile = new File("data/mapgen/" + mapName + ".xml"); try (InputStream is = new FileInputStream(mapgenFile)) { mapSettings = MapSettings.getInstance(is); } catch (IOException ex) { Sentry.captureException(ex); - // TODO: Remove inline file path - LOGGER.error(ex, "Could not load map file data/mapgen/{}.xml", scenario.getMap()); + LOGGER.error(ex, "Could not load map file data/mapgen/{}.xml", mapName); } - if (scenario.getBoardType() == Scenario.T_ATMOSPHERE) { + if (isAtmosphere) { mapSettings.setMedium(MapSettings.MEDIUM_ATMOSPHERE); } - // duplicate code, but getting a new instance of map settings resets the size - // parameters - mapSettings.setBoardSize(scenario.getMapX(), scenario.getMapY()); + // Reset size parameters after getting new instance + mapSettings.setBoardSize(mapSizeX, mapSizeY); mapSettings.setMapSize(1, 1); mapSettings.getBoardsSelectedVector().add(MapSettings.BOARD_GENERATED); } + return ServerBoardHelper.getPossibleGameBoard(mapSettings, false); } } From d47c1e639aec9c9d0e2f8f97dc6f7b33da989f76 Mon Sep 17 00:00:00 2001 From: Luana Coppio Date: Sat, 24 May 2025 18:56:28 -0300 Subject: [PATCH 3/5] feat: added scenario setup for planetary forces --- MekHQ/src/mekhq/AtBGameThread.java | 20 ------------- MekHQ/src/mekhq/GameThread.java | 7 ++++- MekHQ/src/mekhq/MekHQ.java | 18 ++++++++---- MekHQ/src/mekhq/campaign/Campaign.java | 28 +++++++++++++++++++ .../campaign/autoresolve/AtBSetupForces.java | 26 ----------------- .../campaign/autoresolve/ResolverTest.java | 5 +++- 6 files changed, 50 insertions(+), 54 deletions(-) diff --git a/MekHQ/src/mekhq/AtBGameThread.java b/MekHQ/src/mekhq/AtBGameThread.java index 1d5e9f18841..dd8606a49bb 100644 --- a/MekHQ/src/mekhq/AtBGameThread.java +++ b/MekHQ/src/mekhq/AtBGameThread.java @@ -707,26 +707,6 @@ private BotClient setupPlayerBotForAutoResolve(Player player) throws Interrupted return botClient; } - private PlanetaryConditions getPlanetaryConditions() { - PlanetaryConditions planetaryConditions = new PlanetaryConditions(); - if (campaign.getCampaignOptions().isUseLightConditions()) { - planetaryConditions.setLight(scenario.getLight()); - } - if (campaign.getCampaignOptions().isUseWeatherConditions()) { - planetaryConditions.setWeather(scenario.getWeather()); - planetaryConditions.setWind(scenario.getWind()); - planetaryConditions.setFog(scenario.getFog()); - planetaryConditions.setEMI(scenario.getEMI()); - planetaryConditions.setBlowingSand(scenario.getBlowingSand()); - planetaryConditions.setTemperature(scenario.getModifiedTemperature()); - } - if (campaign.getCampaignOptions().isUsePlanetaryConditions()) { - planetaryConditions.setAtmosphere(scenario.getAtmosphere()); - planetaryConditions.setGravity(scenario.getGravity()); - } - return planetaryConditions; - } - /** * wait for the server to add the bot client, then send starting position, camo, and entities * diff --git a/MekHQ/src/mekhq/GameThread.java b/MekHQ/src/mekhq/GameThread.java index 96562d14419..e697071fadf 100644 --- a/MekHQ/src/mekhq/GameThread.java +++ b/MekHQ/src/mekhq/GameThread.java @@ -53,6 +53,7 @@ import megamek.common.Entity; import megamek.common.MapSettings; import megamek.common.WeaponOrderHandler; +import megamek.common.planetaryconditions.PlanetaryConditions; import megamek.common.preference.PreferenceManager; import megamek.logging.MMLogger; import mekhq.campaign.Campaign; @@ -256,7 +257,7 @@ public void run() { client.sendMapSettings(mapSettings); Thread.sleep(MekHQ.getMHQOptions().getStartGameDelay()); - client.sendPlanetaryConditions(scenario.createPlanetaryConditions()); + client.sendPlanetaryConditions(getPlanetaryConditions()); Thread.sleep(MekHQ.getMHQOptions().getStartGameDelay()); // set player deployment @@ -403,6 +404,10 @@ protected List setupBotEntities(BotClient botClient, BotForce botForce, return entities; } + protected PlanetaryConditions getPlanetaryConditions() { + return campaign.getCurrentPlanetaryConditions(scenario); + } + /* * from megamek.client.CloseClientListener clientClosed() Thanks to MM for * adding the listener. And to MMNet for the poorly documented code change. diff --git a/MekHQ/src/mekhq/MekHQ.java b/MekHQ/src/mekhq/MekHQ.java index aee3dc8e053..a8bea256cc4 100644 --- a/MekHQ/src/mekhq/MekHQ.java +++ b/MekHQ/src/mekhq/MekHQ.java @@ -81,6 +81,7 @@ import megamek.common.event.*; import megamek.common.internationalization.I18n; import megamek.common.net.marshalling.SanityInputFilter; +import megamek.common.planetaryconditions.PlanetaryConditions; import megamek.logging.MMLogger; import megamek.server.Server; import megamek.server.totalwarfare.TWGameManager; @@ -756,32 +757,37 @@ public IconPackage getIconPackage() { * @param units The list of player units involved in the scenario */ public void startAutoResolve(Scenario scenario, List units) { - + if (!(scenario instanceof AtBScenario atBScenario)) { + return; + } this.autosaveService.requestBeforeScenarioAutosave(getCampaign()); Board board = ScenarioUtils.getBoardFor(scenario); + + PlanetaryConditions planetaryConditions = getCampaign().getCurrentPlanetaryConditions(scenario); if (getCampaign().getCampaignOptions().isAutoResolveVictoryChanceEnabled()) { var proceed = AutoResolveChanceDialog.showDialog(campaignGUI.getFrame(), getCampaign().getCampaignOptions().getAutoResolveNumberOfScenarios(), Runtime.getRuntime().availableProcessors(), 1, - new AtBSetupForces(getCampaign(), units, scenario, new SingletonForces()), - board) == JOptionPane.YES_OPTION; + new AtBSetupForces(getCampaign(), units, atBScenario, new SingletonForces()), + board, + planetaryConditions) == JOptionPane.YES_OPTION; if (!proceed) { return; } } var event = AutoResolveProgressDialog.showDialog(campaignGUI.getFrame(), - new AtBSetupForces(getCampaign(), units, scenario, new SingletonForces()), - board); + new AtBSetupForces(getCampaign(), units, atBScenario, new SingletonForces()), + board, planetaryConditions); var autoResolveBattleReport = new AutoResolveSimulationLogDialog(campaignGUI.getFrame(), event.getLogFile()); autoResolveBattleReport.setModal(true); autoResolveBattleReport.setVisible(true); - autoResolveConcluded(event, scenario); + autoResolveConcluded(event, atBScenario); } /** diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 9e69d75101c..6a332fafbd5 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -124,6 +124,7 @@ import megamek.common.options.IOption; import megamek.common.options.IOptionGroup; import megamek.common.options.OptionsConstants; +import megamek.common.planetaryconditions.PlanetaryConditions; import megamek.common.util.BuildingBlock; import megamek.common.ITechnology.AvailabilityValue; import megamek.common.ITechnology.TechRating; @@ -7740,6 +7741,33 @@ public TargetRoll getTargetForAcquisition(final IAcquisitionWork acquisition, fi return getTargetForAcquisition(acquisition, person, false); } + public PlanetaryConditions getCurrentPlanetaryConditions(Scenario scenario) { + PlanetaryConditions planetaryConditions = new PlanetaryConditions(); + if (scenario instanceof AtBScenario atBScenario) { + if (getCampaignOptions().isUseLightConditions()) { + planetaryConditions.setLight(atBScenario.getLight()); + } + if (getCampaignOptions().isUseWeatherConditions()) { + planetaryConditions.setWeather(atBScenario.getWeather()); + planetaryConditions.setWind(atBScenario.getWind()); + planetaryConditions.setFog(atBScenario.getFog()); + planetaryConditions.setEMI(atBScenario.getEMI()); + planetaryConditions.setBlowingSand(atBScenario.getBlowingSand()); + planetaryConditions.setTemperature(atBScenario.getModifiedTemperature()); + + } + if (getCampaignOptions().isUsePlanetaryConditions()) { + planetaryConditions.setAtmosphere(atBScenario.getAtmosphere()); + planetaryConditions.setGravity(atBScenario.getGravity()); + } + } else { + planetaryConditions = scenario.createPlanetaryConditions(); + } + + return planetaryConditions; + + } + /** * Determines the target roll required for successfully acquiring a specific part or unit based on various campaign * settings, the acquisition details, and the person attempting the acquisition. diff --git a/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java b/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java index 7b3aa8f8995..30897f67af7 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java @@ -546,32 +546,6 @@ private List setupBotEntities(Player bot, List originalEntities, return entities; } - /** - * Get the planetary conditions for the game, not used at the moment in the auto - * resolve, but planed for the future - * - * @return The planetary conditions object - */ - private PlanetaryConditions getPlanetaryConditions() { - PlanetaryConditions planetaryConditions = new PlanetaryConditions(); - if (campaign.getCampaignOptions().isUseLightConditions()) { - planetaryConditions.setLight(scenario.getLight()); - } - if (campaign.getCampaignOptions().isUseWeatherConditions()) { - planetaryConditions.setWeather(scenario.getWeather()); - planetaryConditions.setWind(scenario.getWind()); - planetaryConditions.setFog(scenario.getFog()); - planetaryConditions.setEMI(scenario.getEMI()); - planetaryConditions.setBlowingSand(scenario.getBlowingSand()); - planetaryConditions.setTemperature(scenario.getModifiedTemperature()); - } - if (campaign.getCampaignOptions().isUsePlanetaryConditions()) { - planetaryConditions.setAtmosphere(scenario.getAtmosphere()); - planetaryConditions.setGravity(scenario.getGravity()); - } - return planetaryConditions; - } - /** * Send the entities to the game object * diff --git a/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java b/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java index 666a0729a05..b543c92f084 100644 --- a/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java +++ b/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java @@ -60,6 +60,7 @@ import megamek.common.planetaryconditions.EMI; import megamek.common.planetaryconditions.Fog; import megamek.common.planetaryconditions.Light; +import megamek.common.planetaryconditions.PlanetaryConditions; import megamek.common.planetaryconditions.Weather; import megamek.common.planetaryconditions.Wind; import mekhq.campaign.Campaign; @@ -372,6 +373,7 @@ void autoResolve(Consumer autoResolveConcludedEvent) var units = getUnits(campaign, teamArrangement); var scenario = createScenario(campaign); var entities = getEntities(teamArrangement); + var planetaryConditions = new PlanetaryConditions(); when(botForce.getCamouflage()).thenReturn(Camouflage.of(PlayerColour.MAROON)); when(botForce.getColour()).thenReturn(PlayerColour.MAROON); @@ -379,7 +381,8 @@ void autoResolve(Consumer autoResolveConcludedEvent) when(botForce.getTeam()).thenReturn(2); when(botForce.getFullEntityList(any())).thenReturn(entities); - var resolver = Resolver.simulationRun(new AtBSetupForces(campaign, units, scenario, new FlattenForces()), SimulationOptions.empty(), new Board(30, 30)); + var resolver = Resolver.simulationRun(new AtBSetupForces(campaign, units, scenario, new FlattenForces()), + SimulationOptions.empty(), new Board(30, 30), planetaryConditions); autoResolveConcludedEvent.accept(resolver.resolveSimulation()); } From dd43db15b5b8137d5761bd184161c3c2bd21624a Mon Sep 17 00:00:00 2001 From: Luana Coppio Date: Sat, 24 May 2025 22:00:11 -0300 Subject: [PATCH 4/5] feat: acar blurb changes upd --- MekHQ/src/mekhq/AtBGameThread.java | 44 +--- MekHQ/src/mekhq/GameThread.java | 48 +--- MekHQ/src/mekhq/MHQConstants.java | 1 + MekHQ/src/mekhq/MekHQ.java | 29 ++- ...upForces.java => ScenarioSetupForces.java} | 238 +++++++----------- .../autoresolve/StratconSetupForces.java | 228 +++++++++++++++++ .../src/mekhq/campaign/mission/Scenario.java | 2 +- MekHQ/src/mekhq/gui/BriefingTab.java | 4 +- MekHQ/src/mekhq/utilities/ScenarioUtils.java | 74 +++--- .../campaign/autoresolve/ResolverTest.java | 2 +- 10 files changed, 376 insertions(+), 294 deletions(-) rename MekHQ/src/mekhq/campaign/autoresolve/{AtBSetupForces.java => ScenarioSetupForces.java} (74%) create mode 100644 MekHQ/src/mekhq/campaign/autoresolve/StratconSetupForces.java diff --git a/MekHQ/src/mekhq/AtBGameThread.java b/MekHQ/src/mekhq/AtBGameThread.java index dd8606a49bb..08ac45ee2b9 100644 --- a/MekHQ/src/mekhq/AtBGameThread.java +++ b/MekHQ/src/mekhq/AtBGameThread.java @@ -67,6 +67,7 @@ import megamek.common.UnitType; import megamek.common.annotations.Nullable; import megamek.common.planetaryconditions.PlanetaryConditions; +import megamek.common.util.BoardUtilities; import megamek.logging.MMLogger; import mekhq.campaign.enums.CampaignTransportType; import mekhq.campaign.force.Force; @@ -80,6 +81,7 @@ import mekhq.campaign.unit.Unit; import mekhq.utilities.MHQInternationalization; import mekhq.utilities.PotentialTransportsMap; +import mekhq.utilities.ScenarioUtils; /** * Enhanced version of GameThread which imports settings and non-player units into the MM game @@ -174,47 +176,7 @@ public void run() { Thread.sleep(MekHQ.getMHQOptions().getStartGameDelay()); } - MapSettings mapSettings = MapSettings.getInstance(); - mapSettings.setBoardSize(scenario.getMapX(), scenario.getMapY()); - mapSettings.setMapSize(1, 1); - mapSettings.getBoardsSelectedVector().clear(); - - // if the scenario is taking place in space, do space settings instead - if (scenario.getBoardType() == Scenario.T_SPACE || scenario.getTerrainType().equals("Space")) { - mapSettings.setMedium(MapSettings.MEDIUM_SPACE); - mapSettings.getBoardsSelectedVector().add(MapSettings.BOARD_GENERATED); - } else if (scenario.isUsingFixedMap()) { - // TODO : remove inline file type - String board = scenario.getMap().replace(".board", ""); - board = board.replace("\\", "/"); - mapSettings.getBoardsSelectedVector().add(board); - - if (scenario.getBoardType() == Scenario.T_ATMOSPHERE) { - mapSettings.setMedium(MapSettings.MEDIUM_ATMOSPHERE); - } - } else { - // TODO : Remove inline file path - File mapgenFile = new File("data/mapgen/" + scenario.getMap() + ".xml"); - try (InputStream is = new FileInputStream(mapgenFile)) { - mapSettings = MapSettings.getInstance(is); - } catch (FileNotFoundException ex) { - Sentry.captureException(ex); - // TODO: Remove inline file path - logger.error(String.format("Could not load map file data/mapgen/%s.xml", scenario.getMap()), - ex); - } - - if (scenario.getBoardType() == Scenario.T_ATMOSPHERE) { - mapSettings.setMedium(MapSettings.MEDIUM_ATMOSPHERE); - } - - // duplicate code, but getting a new instance of map settings resets the size - // parameters - mapSettings.setBoardSize(scenario.getMapX(), scenario.getMapY()); - mapSettings.setMapSize(1, 1); - mapSettings.getBoardsSelectedVector().add(MapSettings.BOARD_GENERATED); - } - + MapSettings mapSettings = ScenarioUtils.getMapSettings(scenario); client.sendMapSettings(mapSettings); Thread.sleep(MekHQ.getMHQOptions().getStartGameDelay()); diff --git a/MekHQ/src/mekhq/GameThread.java b/MekHQ/src/mekhq/GameThread.java index e697071fadf..31bb09e5d81 100644 --- a/MekHQ/src/mekhq/GameThread.java +++ b/MekHQ/src/mekhq/GameThread.java @@ -61,6 +61,7 @@ import mekhq.campaign.mission.BotForce; import mekhq.campaign.mission.Scenario; import mekhq.campaign.unit.Unit; +import mekhq.utilities.ScenarioUtils; class GameThread extends Thread implements CloseClientListener { private static final MMLogger logger = MMLogger.create(GameThread.class); @@ -208,52 +209,7 @@ public void run() { Thread.sleep(MekHQ.getMHQOptions().getStartGameDelay()); } - MapSettings mapSettings = MapSettings.getInstance(); - - // check that we have valid conditions for setting the mapSettings - if ((scenario.getMapSizeX() > 1) && (scenario.getMapSizeY() > 1) && (null != scenario.getMap())) { - - mapSettings.setBoardSize(scenario.getMapSizeX(), scenario.getMapSizeY()); - mapSettings.setMapSize(1, 1); - mapSettings.getBoardsSelectedVector().clear(); - - // if the scenario is taking place in space, do space settings instead - if (scenario.getBoardType() == Scenario.T_SPACE) { - mapSettings.setMedium(MapSettings.MEDIUM_SPACE); - mapSettings.getBoardsSelectedVector().add(MapSettings.BOARD_GENERATED); - } else if (scenario.isUsingFixedMap()) { - String board = scenario.getMap().replace(".board", ""); // TODO : remove inline file type - board = board.replace("\\", "/"); - mapSettings.getBoardsSelectedVector().add(board); - - if (scenario.getBoardType() == Scenario.T_ATMOSPHERE) { - mapSettings.setMedium(MapSettings.MEDIUM_ATMOSPHERE); - } - } else { - File mapgenFile = new File("data/mapgen/" + scenario.getMap() + ".xml"); // TODO : remove inline - // file path - try (InputStream is = new FileInputStream(mapgenFile)) { - mapSettings = MapSettings.getInstance(is); - } catch (FileNotFoundException ex) { - logger.error(String.format("Could not load map file data/mapgen/%s.xml", scenario.getMap()), - ex); - // TODO: remove inline file path - } - - if (scenario.getBoardType() == Scenario.T_ATMOSPHERE) { - mapSettings.setMedium(MapSettings.MEDIUM_ATMOSPHERE); - } - - // duplicate code, but getting a new instance of map settings resets the size - // parameters - mapSettings.setBoardSize(scenario.getMapSizeX(), scenario.getMapSizeY()); - mapSettings.setMapSize(1, 1); - mapSettings.getBoardsSelectedVector().add(MapSettings.BOARD_GENERATED); - } - } else { - logger.error(String.format("invalid map settings provided for scenario %s", scenario.getName())); - } - + MapSettings mapSettings = ScenarioUtils.getMapSettings(scenario); client.sendMapSettings(mapSettings); Thread.sleep(MekHQ.getMHQOptions().getStartGameDelay()); diff --git a/MekHQ/src/mekhq/MHQConstants.java b/MekHQ/src/mekhq/MHQConstants.java index 67237340769..70b93ae2ce2 100644 --- a/MekHQ/src/mekhq/MHQConstants.java +++ b/MekHQ/src/mekhq/MHQConstants.java @@ -277,6 +277,7 @@ public final class MHQConstants extends SuiteConstants { public static final String PLANETARY_SYSTEM_DIRECTORY_PATH = "data/universe/planetary_systems"; public static final String FORCE_ICON_PATH = "data/images/force"; public static final String PERSONNEL_MARKET_DIRECTORY_PATH = "data/universe/markets/personnelMarket/"; + public static final String MAPGEN_PATH = "data/mapgen"; // region StratCon public static final String STRATCON_REQUIRED_HOSTILE_FACILITY_MODS = "./data/scenariomodifiers/requiredHostileFacilityModifiers.xml"; diff --git a/MekHQ/src/mekhq/MekHQ.java b/MekHQ/src/mekhq/MekHQ.java index a8bea256cc4..8f10ca61633 100644 --- a/MekHQ/src/mekhq/MekHQ.java +++ b/MekHQ/src/mekhq/MekHQ.java @@ -76,6 +76,7 @@ import megamek.common.Board; import megamek.common.annotations.Nullable; import megamek.common.autoresolve.acar.SimulatedClient; +import megamek.common.autoresolve.converter.SetupForces; import megamek.common.autoresolve.converter.SingletonForces; import megamek.common.autoresolve.event.AutoResolveConcludedEvent; import megamek.common.event.*; @@ -89,7 +90,8 @@ import mekhq.campaign.Campaign; import mekhq.campaign.CampaignController; import mekhq.campaign.ResolveScenarioTracker; -import mekhq.campaign.autoresolve.AtBSetupForces; +import mekhq.campaign.autoresolve.MekHQSetupForces; +import mekhq.campaign.autoresolve.StratconSetupForces; import mekhq.campaign.handler.PostScenarioDialogHandler; import mekhq.campaign.handler.XPHandler; import mekhq.campaign.mission.AtBDynamicScenario; @@ -751,18 +753,23 @@ public IconPackage getIconPackage() { return iconPackage; } + private SetupForces getSetupForces(Scenario scenario, List units) { + if (scenario instanceof AtBScenario atBScenario) { + return new StratconSetupForces(getCampaign(), units, atBScenario, new SingletonForces()); + } + return new MekHQSetupForces(getCampaign(), units, scenario, new SingletonForces()); + } + /** * This method is called when the player wants to auto resolve the scenario using ACAR method * * @param units The list of player units involved in the scenario */ public void startAutoResolve(Scenario scenario, List units) { - if (!(scenario instanceof AtBScenario atBScenario)) { - return; - } this.autosaveService.requestBeforeScenarioAutosave(getCampaign()); Board board = ScenarioUtils.getBoardFor(scenario); + SetupForces setupForces = getSetupForces(scenario, units); PlanetaryConditions planetaryConditions = getCampaign().getCurrentPlanetaryConditions(scenario); if (getCampaign().getCampaignOptions().isAutoResolveVictoryChanceEnabled()) { @@ -771,7 +778,7 @@ public void startAutoResolve(Scenario scenario, List units) { getCampaign().getCampaignOptions().getAutoResolveNumberOfScenarios(), Runtime.getRuntime().availableProcessors(), 1, - new AtBSetupForces(getCampaign(), units, atBScenario, new SingletonForces()), + setupForces, board, planetaryConditions) == JOptionPane.YES_OPTION; if (!proceed) { @@ -780,14 +787,14 @@ public void startAutoResolve(Scenario scenario, List units) { } var event = AutoResolveProgressDialog.showDialog(campaignGUI.getFrame(), - new AtBSetupForces(getCampaign(), units, atBScenario, new SingletonForces()), + setupForces, board, planetaryConditions); var autoResolveBattleReport = new AutoResolveSimulationLogDialog(campaignGUI.getFrame(), event.getLogFile()); autoResolveBattleReport.setModal(true); autoResolveBattleReport.setVisible(true); - autoResolveConcluded(event, atBScenario); + autoResolveConcluded(event, scenario); } /** @@ -795,7 +802,7 @@ public void startAutoResolve(Scenario scenario, List units) { * * @param autoResolveConcludedEvent The event that contains the results of the auto resolve game. */ - public void autoResolveConcluded(AutoResolveConcludedEvent autoResolveConcludedEvent, AtBScenario scenario) { + public void autoResolveConcluded(AutoResolveConcludedEvent autoResolveConcludedEvent, Scenario scenario) { try { String victoryMessage = autoResolveConcludedEvent.controlledScenario() ? MHQInternationalization.getText("AutoResolveDialog.message.victory") : @@ -803,8 +810,8 @@ public void autoResolveConcluded(AutoResolveConcludedEvent autoResolveConcludedE String decisionMessage = MHQInternationalization.getText("ResolveDialog.control.message"); - if (scenario instanceof AtBDynamicScenario) { - ScenarioTemplate template = ((AtBDynamicScenario) scenario).getTemplate(); + if (scenario instanceof AtBDynamicScenario atBDynamicScenario) { + ScenarioTemplate template = atBDynamicScenario.getTemplate(); if (template != null) { BattlefieldControlType battlefieldControl = template.getBattlefieldControl(); @@ -844,7 +851,7 @@ public void autoResolveConcluded(AutoResolveConcludedEvent autoResolveConcludedE } } - private void postAbortedAutoResolve(AutoResolveConcludedEvent autoResolveConcludedEvent, AtBScenario scenario, + private void postAbortedAutoResolve(AutoResolveConcludedEvent autoResolveConcludedEvent, Scenario scenario, ResolveScenarioTracker tracker) { try { resetPersonsHits(tracker); diff --git a/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java b/MekHQ/src/mekhq/campaign/autoresolve/ScenarioSetupForces.java similarity index 74% rename from MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java rename to MekHQ/src/mekhq/campaign/autoresolve/ScenarioSetupForces.java index 30897f67af7..2e8d57ddc29 100644 --- a/MekHQ/src/mekhq/campaign/autoresolve/AtBSetupForces.java +++ b/MekHQ/src/mekhq/campaign/autoresolve/ScenarioSetupForces.java @@ -1,14 +1,14 @@ /* - * Copyright (C) 2024-2025 The MegaMek Team. All Rights Reserved. + * Copyright (C) 2025 The MegaMek Team. All Rights Reserved. * - * This file is part of MekHQ. + * This file is part of MegaMek. * - * MekHQ is free software: you can redistribute it and/or modify + * MegaMek is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License (GPL), * version 3 or (at your option) any later version, * as published by the Free Software Foundation. * - * MekHQ is distributed in the hope that it will be useful, + * MegaMek 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. @@ -24,18 +24,18 @@ * * Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of * InMediaRes Productions, LLC. + * + * MechWarrior Copyright Microsoft Corporation. MegaMek was created under + * Microsoft's "Game Content Usage Rules" + * and it is not endorsed by or + * affiliated with Microsoft. */ - package mekhq.campaign.autoresolve; import static megamek.common.force.Force.NO_FORCE; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; @@ -52,8 +52,6 @@ import megamek.common.EntitySelector; import megamek.common.Game; import megamek.common.Infantry; -import megamek.common.MapSettings; -import megamek.common.Minefield; import megamek.common.Player; import megamek.common.ProtoMek; import megamek.common.UnitType; @@ -61,38 +59,38 @@ import megamek.common.autoresolve.converter.EntityAsUnit; import megamek.common.autoresolve.converter.ForceConsolidation; import megamek.common.autoresolve.converter.SetupForces; +import megamek.common.enums.SkillLevel; 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.mission.AtBDynamicScenario; -import mekhq.campaign.mission.AtBScenario; import mekhq.campaign.mission.BotForce; import mekhq.campaign.mission.Scenario; import mekhq.campaign.unit.Unit; /** + * This class is responsible for setting up the forces for a scenario * @author Luana Coppio */ -public class AtBSetupForces extends SetupForces { - private static final MMLogger logger = MMLogger.create(AtBSetupForces.class); +public class ScenarioSetupForces extends SetupForces { + private static final MMLogger LOGGER = MMLogger.create(ScenarioSetupForces.class); - private final Campaign campaign; - private final List units; - private final AtBScenario scenario; + protected final Campaign campaign; + protected final List units; + private final SCENARIO scenario; private final ForceConsolidation forceConsolidationMethod; private final Set teamIds = new HashSet<>(); private final OrderFactory orderFactory; private final Game dummyGame; - public AtBSetupForces(Campaign campaign, List units, AtBScenario scenario, - ForceConsolidation forceConsolidationMethod) { + public ScenarioSetupForces(Campaign campaign, List units, SCENARIO scenario, + ForceConsolidation forceConsolidationMethod) { this(campaign, units, scenario, forceConsolidationMethod, new OrderFactory(campaign, scenario)); } - public AtBSetupForces(Campaign campaign, List units, AtBScenario scenario, - ForceConsolidation forceConsolidationMethod, OrderFactory orderFactory) { + public ScenarioSetupForces(Campaign campaign, List units, SCENARIO scenario, + ForceConsolidation forceConsolidationMethod, OrderFactory orderFactory) { this.campaign = campaign; this.dummyGame = campaign.getGame(); this.units = units; @@ -102,6 +100,10 @@ public AtBSetupForces(Campaign campaign, List units, AtBScenario scenario, setupTeamIds(); } + public SCENARIO getScenario() { + return scenario; + } + private void setupTeamIds() { if (!units.isEmpty()) { teamIds.add(1); @@ -164,7 +166,7 @@ private void convertForcesIntoFormations(SimulationContext game) { Sentry.captureException(e); var entities = game.getForces().getFullEntities(force).stream().filter(Entity.class::isInstance) .map(Entity.class::cast).toList(); - logger.error("Error converting force to formation {} - {}", force, entities, e); + LOGGER.error("Error converting force to formation {} - {}", force, entities, e); throw new FailedToConvertForceToFormationException(e); } } @@ -185,32 +187,12 @@ private void setupPlayer(SimulationContext game) { sendEntities(entities, game); } - /** - * Move the entity by copying it, this is used to break references to the original instance - * @param entity The entity to copy - * @return The copied entity - */ - private Entity moveByCopy(Entity entity) { - try { - try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { - try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) { - // Serialize the entities - objectOutputStream.writeObject(entity); - objectOutputStream.flush(); - byte[] serializedData = byteArrayOutputStream.toByteArray(); + protected SkillLevel getEnemySkillLevel() { + return SkillLevel.REGULAR; + } - // Deserialize to create new instances - try(ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serializedData)) { - try (ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) { - return (Entity) objectInputStream.readObject(); - } - } - } - } - } catch (Exception e) { - logger.error(e, "Failed to break references for entity {}", entity); - return null; - } + protected SkillLevel getAlliedSkillLevel() { + return SkillLevel.REGULAR; } /** @@ -221,8 +203,9 @@ private Entity moveByCopy(Entity entity) { */ private void setupBots(SimulationContext game) { var forbiddenColor = game.getPlayer(0).getColour(); - var enemySkill = (scenario.getContract(campaign)).getEnemySkill(); - var allySkill = (scenario.getContract(campaign)).getAllySkill(); + SkillLevel enemySkill = getEnemySkillLevel(); + SkillLevel allySkill = getAlliedSkillLevel(); + var localBots = new HashMap(); for (int i = 0; i < scenario.getNumBots(); i++) { BotForce botForce = scenario.getBotForce(i); @@ -258,7 +241,7 @@ private void setupBots(SimulationContext game) { * * @return The clean player object */ - private Player getCleanPlayer() { + protected Player getCleanPlayer() { var campaignPlayer = campaign.getPlayer(); var player = new Player(campaignPlayer.getId(), campaign.getName()); player.setCamouflage(campaign.getCamouflage().clone()); @@ -271,10 +254,12 @@ private Player getCleanPlayer() { player.setStartingAnySEx(scenario.getStartingAnySEx()); player.setStartingAnySEy(scenario.getStartingAnySEy()); player.setTeam(1); - player.setNbrMFActive(scenario.getNumPlayerMinefields(Minefield.TYPE_ACTIVE)); - player.setNbrMFConventional(scenario.getNumPlayerMinefields(Minefield.TYPE_CONVENTIONAL)); - player.setNbrMFInferno(scenario.getNumPlayerMinefields(Minefield.TYPE_INFERNO)); - player.setNbrMFVibra(scenario.getNumPlayerMinefields(Minefield.TYPE_VIBRABOMB)); + + player.setNbrMFActive(0); + player.setNbrMFConventional(0); + player.setNbrMFInferno(0); + player.setNbrMFVibra(0); + player.getTurnInitBonus(); return player; } @@ -288,11 +273,19 @@ private Player getCleanPlayer() { private List setupPlayerForces(Player player) { boolean useDropship = isUsingDropship(); List entities = new ArrayList<>(); - entities.addAll(getCopyOfEntities(player, useDropship, new UnitEntitySource())); - entities.addAll(getCopyOfEntities(player, useDropship, new AllyEntitySource())); + entities.addAll(getCopyOfEntities(player, useDropship, getUnitEntitySource())); + entities.addAll(getCopyOfEntities(player, useDropship, getAllyEntitySource())); return entities; } + protected EntitySource getUnitEntitySource() { + return new UnitEntitySource(); + } + + protected EntitySource getAllyEntitySource() { + return new AllyEntitySource(); + } + private List getCopyOfEntities(Player player, boolean useDropship, EntitySource entitySource) { List entities = new ArrayList<>(); for (Object source : entitySource.getSources()) { @@ -305,12 +298,12 @@ private List getCopyOfEntities(Player player, boolean useDropship, Entit return entities; } - private interface EntitySource { + protected interface EntitySource { Iterable getSources(); Entity setupEntity(Player player, Object source, boolean useDropship); } - private class UnitEntitySource implements EntitySource { + protected class UnitEntitySource implements EntitySource { @Override public Iterable getSources() { return units; @@ -325,7 +318,8 @@ public Entity setupEntity(Player player, Object source, boolean useDropship) { private class AllyEntitySource implements EntitySource { @Override public Iterable getSources() { - return scenario.getAlliesPlayer(); + return scenario.getBotForces().stream().filter(botForce -> botForce.getTeam() == 1) + .map(botForce -> botForce.getFullEntityList(campaign)).flatMap(List::stream).toList(); } @Override @@ -334,10 +328,38 @@ public Entity setupEntity(Player player, Object source, boolean useDropship) { } } + /** + * Move the entity by copying it, this is used to break references to the original instance + * @param entity The entity to copy + * @return The copied entity + */ + protected Entity moveByCopy(Entity entity) { + try { + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) { + // Serialize the entities + objectOutputStream.writeObject(entity); + objectOutputStream.flush(); + byte[] serializedData = byteArrayOutputStream.toByteArray(); + + // Deserialize to create new instances + try(ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serializedData)) { + try (ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) { + return (Entity) objectInputStream.readObject(); + } + } + } + } + } catch (Exception e) { + LOGGER.error(e, "Failed to break references for entity {}", entity); + return null; + } + } + private Entity setupPlayerAllyEntity(Player player, Entity originalAllyEntity, boolean useDropship) { var entity = moveByCopy(originalAllyEntity); if (Objects.isNull(entity)) { - logger.error("Could not setup ally entity {}", originalAllyEntity); + LOGGER.error("Could not setup ally entity {}", originalAllyEntity); return null; } @@ -353,11 +375,8 @@ private Entity setupPlayerAllyEntity(Player player, Entity originalAllyEntity, b speed++; } } - deploymentRound = Math.max(entity.getDeployRound(), scenario.getDeploymentDelay() - speed); - if (!useDropship - && scenario.getCombatRole().isPatrol() - && (scenario.getCombatTeamById(campaign) != null) - && (scenario.getCombatTeamById(campaign).getForceId() == scenario.getCombatTeamId())) { + deploymentRound = entity.getDeployRound(); + if (!useDropship) { deploymentRound = Math.max(deploymentRound, 6 - speed); } } @@ -369,7 +388,7 @@ private Entity setupPlayerAllyEntity(Player player, Entity originalAllyEntity, b private Entity setupPlayerEntityFromUnit(Player player, Unit unit, boolean useDropship) { var entity = moveByCopy(unit.getEntity()); if (Objects.isNull(entity)) { - logger.error("Could not setup unit {} for player {}", unit, player); + LOGGER.error("Could not setup unit {} for player {}", unit, player); return null; } entity.setOwner(player); @@ -383,27 +402,6 @@ private Entity setupPlayerEntityFromUnit(Player player, Unit unit, boolean useDr entity.setNMarines(unit.getMarineCount()); } // Calculate deployment round - int deploymentRound = entity.getDeployRound(); - if (!(scenario instanceof AtBDynamicScenario)) { - int speed = entity.getWalkMP(); - if (entity.getAnyTypeMaxJumpMP() > 0) { - if (entity instanceof Infantry) { - speed = entity.getJumpMP(); - } else { - speed++; - } - } - // Set scenario type-specific delay - deploymentRound = Math.max(entity.getDeployRound(), scenario.getDeploymentDelay() - speed); - // Lances deployed in scout roles always deploy units in 6-walking speed turns - if (scenario.getCombatRole().isPatrol() - && (scenario.getCombatTeamById(campaign) != null) - && (scenario.getCombatTeamById(campaign).getForceId() == scenario.getCombatTeamId()) - && !useDropship) { - deploymentRound = Math.max(deploymentRound, 6 - speed); - } - } - entity.setDeployRound(deploymentRound); var force = campaign.getForceFor(unit); if (force != null) { entity.setForceString(force.getFullMMName()); @@ -419,70 +417,14 @@ private Entity setupPlayerEntityFromUnit(Player player, Unit unit, boolean useDr * @return True if using dropships under specific conditions, false otherwise */ private boolean isUsingDropship() { - if (scenario.getCombatRole().isPatrol()) { - for (Entity en : scenario.getAlliesPlayer()) { - if (en.getUnitType() == UnitType.DROPSHIP) { - return true; - } - } - for (Unit unit : units) { - if (unit.getEntity().getUnitType() == UnitType.DROPSHIP) { - return true; - } + for (Unit unit : units) { + if (unit.getEntity().getUnitType() == UnitType.DROPSHIP) { + return true; } } return false; } - /** - * Setup the map settings for the game, not relevant at the moment, as the map - * settings are not used in the autoresolve currently - * - * @return The map settings object - */ - private MapSettings setupMapSettings() { - MapSettings mapSettings = MapSettings.getInstance(); - mapSettings.setBoardSize(scenario.getMapX(), scenario.getMapY()); - mapSettings.setMapSize(1, 1); - mapSettings.getBoardsSelectedVector().clear(); - - // if the scenario is taking place in space, do space settings instead - if (scenario.getBoardType() == Scenario.T_SPACE - || scenario.getTerrainType().equals("Space")) { - mapSettings.setMedium(MapSettings.MEDIUM_SPACE); - mapSettings.getBoardsSelectedVector().add(MapSettings.BOARD_GENERATED); - } else if (scenario.isUsingFixedMap()) { - String board = scenario.getMap().replace(".board", ""); - board = board.replace("\\", "/"); - mapSettings.getBoardsSelectedVector().add(board); - - if (scenario.getBoardType() == Scenario.T_ATMOSPHERE) { - mapSettings.setMedium(MapSettings.MEDIUM_ATMOSPHERE); - } - } else { - File mapgenFile = new File("data/mapgen/" + scenario.getMap() + ".xml"); - try (InputStream is = new FileInputStream(mapgenFile)) { - mapSettings = MapSettings.getInstance(is); - } catch (IOException ex) { - Sentry.captureException(ex); - logger.error( - String.format("Could not load map file data/mapgen/%s.xml", scenario.getMap()), - ex); - } - - if (scenario.getBoardType() == Scenario.T_ATMOSPHERE) { - mapSettings.setMedium(MapSettings.MEDIUM_ATMOSPHERE); - } - - // duplicate code, but getting a new instance of map settings resets the size - // parameters - mapSettings.setBoardSize(scenario.getMapX(), scenario.getMapY()); - mapSettings.setMapSize(1, 1); - mapSettings.getBoardsSelectedVector().add(MapSettings.BOARD_GENERATED); - } - return mapSettings; - } - public PlayerColour getNextColor(PlayerColour playerColour) { PlayerColour[] playerColours = PlayerColour.values(); int index = (playerColour.ordinal() + 1) % playerColours.length; @@ -530,7 +472,7 @@ private List setupBotEntities(Player bot, List originalEntities, var entity = moveByCopy(originalBotEntity); if (entity == null) { - logger.warn("Could not convert entity for bot {} - {}", bot.getName(), originalBotEntity); + LOGGER.warn("Could not convert entity for bot {} - {}", bot.getName(), originalBotEntity); continue; } diff --git a/MekHQ/src/mekhq/campaign/autoresolve/StratconSetupForces.java b/MekHQ/src/mekhq/campaign/autoresolve/StratconSetupForces.java new file mode 100644 index 00000000000..2aa83ece853 --- /dev/null +++ b/MekHQ/src/mekhq/campaign/autoresolve/StratconSetupForces.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2024-2025 The MegaMek Team. All Rights Reserved. + * + * This file is part of MegaMek. + * + * MegaMek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * MegaMek 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + * + * NOTICE: The MegaMek organization is a non-profit group of volunteers + * creating free software for the BattleTech community. + * + * MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks + * of The Topps Company, Inc. All Rights Reserved. + * + * Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of + * InMediaRes Productions, LLC. + * + * MechWarrior Copyright Microsoft Corporation. MegaMek was created under + * Microsoft's "Game Content Usage Rules" + * and it is not endorsed by or + * affiliated with Microsoft. + */ +package mekhq.campaign.autoresolve; + +import java.util.List; +import java.util.Objects; + +import megamek.common.Entity; +import megamek.common.Infantry; +import megamek.common.Minefield; +import megamek.common.Player; +import megamek.common.UnitType; +import megamek.common.autoresolve.converter.ForceConsolidation; +import megamek.common.enums.SkillLevel; +import megamek.logging.MMLogger; +import mekhq.campaign.Campaign; +import mekhq.campaign.mission.AtBDynamicScenario; +import mekhq.campaign.mission.AtBScenario; +import mekhq.campaign.unit.Unit; + +/** + * This class is responsible for setting up the forces for a Stratcon scenario + * @author Luana Coppio + */ +public class StratconSetupForces extends ScenarioSetupForces { + private static final MMLogger LOGGER = MMLogger.create(StratconSetupForces.class); + + public StratconSetupForces(Campaign campaign, List units, AtBScenario scenario, + ForceConsolidation forceConsolidationMethod) { + this(campaign, units, scenario, forceConsolidationMethod, new OrderFactory(campaign, scenario)); + } + + public StratconSetupForces(Campaign campaign, List units, AtBScenario scenario, + ForceConsolidation forceConsolidationMethod, OrderFactory orderFactory) { + super(campaign, units, scenario, forceConsolidationMethod, orderFactory); + } + + @Override + protected SkillLevel getEnemySkillLevel() { + return getScenario().getContract(campaign).getEnemySkill(); + } + + @Override + protected SkillLevel getAlliedSkillLevel() { + return getScenario().getContract(campaign).getAllySkill(); + } + + /** + * Create a player object from the campaign and scenario wichi doesnt have a + * reference to the original player + * + * @return The clean player object + */ + @Override + protected Player getCleanPlayer() { + var player = super.getCleanPlayer(); + player.setNbrMFActive(getScenario().getNumPlayerMinefields(Minefield.TYPE_ACTIVE)); + player.setNbrMFConventional(getScenario().getNumPlayerMinefields(Minefield.TYPE_CONVENTIONAL)); + player.setNbrMFInferno(getScenario().getNumPlayerMinefields(Minefield.TYPE_INFERNO)); + player.setNbrMFVibra(getScenario().getNumPlayerMinefields(Minefield.TYPE_VIBRABOMB)); + return player; + } + + @Override + protected EntitySource getUnitEntitySource() { + return new UnitEntitySource(); + } + + @Override + protected EntitySource getAllyEntitySource() { + return new AllyEntitySource(); + } + + private class UnitEntitySource implements EntitySource { + @Override + public Iterable getSources() { + return units; + } + + @Override + public Entity setupEntity(Player player, Object source, boolean useDropship) { + return setupPlayerEntityFromUnit(player, (Unit) source, useDropship); + } + } + + private class AllyEntitySource implements EntitySource { + @Override + public Iterable getSources() { + return getScenario().getAlliesPlayer(); + } + + @Override + public Entity setupEntity(Player player, Object source, boolean useDropship) { + return setupPlayerAllyEntity(player, (Entity) source, useDropship); + } + } + + private Entity setupPlayerAllyEntity(Player player, Entity originalAllyEntity, boolean useDropship) { + var entity = moveByCopy(originalAllyEntity); + if (Objects.isNull(entity)) { + LOGGER.error("Could not setup ally entity {}", originalAllyEntity); + return null; + } + + entity.setOwner(player); + AtBScenario scenario = getScenario(); + int deploymentRound = entity.getDeployRound(); + if (!(getScenario() instanceof AtBDynamicScenario)) { + int speed = entity.getWalkMP(); + if (entity.getAnyTypeMaxJumpMP() > 0) { + if (entity instanceof Infantry) { + speed = entity.getJumpMP(); + } else { + speed++; + } + } + deploymentRound = Math.max(entity.getDeployRound(), scenario.getDeploymentDelay() - speed); + if (!useDropship + && scenario.getCombatRole().isPatrol() + && (scenario.getCombatTeamById(campaign) != null) + && (scenario.getCombatTeamById(campaign).getForceId() == scenario.getCombatTeamId())) { + deploymentRound = Math.max(deploymentRound, 6 - speed); + } + } + + entity.setDeployRound(deploymentRound); + return entity; + } + + private Entity setupPlayerEntityFromUnit(Player player, Unit unit, boolean useDropship) { + var entity = moveByCopy(unit.getEntity()); + AtBScenario scenario = getScenario(); + if (Objects.isNull(entity)) { + LOGGER.error("Could not setup unit {} for player {}", unit, player); + return null; + } + entity.setOwner(player); + + // Set the TempID for auto reporting + entity.setExternalIdAsString(unit.getId().toString()); + + // If this unit is a spacecraft, set the crew size and marine size values + if (entity.isLargeCraft() || (entity.getUnitType() == UnitType.SMALL_CRAFT)) { + entity.setNCrew(unit.getActiveCrew().size()); + entity.setNMarines(unit.getMarineCount()); + } + // Calculate deployment round + int deploymentRound = entity.getDeployRound(); + if (!(scenario instanceof AtBDynamicScenario)) { + int speed = entity.getWalkMP(); + if (entity.getAnyTypeMaxJumpMP() > 0) { + if (entity instanceof Infantry) { + speed = entity.getJumpMP(); + } else { + speed++; + } + } + // Set scenario type-specific delay + deploymentRound = Math.max(entity.getDeployRound(), scenario.getDeploymentDelay() - speed); + // Lances deployed in scout roles always deploy units in 6-walking speed turns + if (scenario.getCombatRole().isPatrol() + && (scenario.getCombatTeamById(campaign) != null) + && (scenario.getCombatTeamById(campaign).getForceId() == scenario.getCombatTeamId()) + && !useDropship) { + deploymentRound = Math.max(deploymentRound, 6 - speed); + } + } + entity.setDeployRound(deploymentRound); + var force = campaign.getForceFor(unit); + if (force != null) { + entity.setForceString(force.getFullMMName()); + } else if (!unit.getEntity().getForceString().isBlank()) { + // this was added mostly to make it easier to run tests + entity.setForceString(unit.getEntity().getForceString()); + } + return entity; + } + + /** + * Check if using dropships for patrol scenario + * @return True if using dropships under specific conditions, false otherwise + */ + private boolean isUsingDropship() { + if (getScenario().getCombatRole().isPatrol()) { + for (Entity en : getScenario().getAlliesPlayer()) { + if (en.getUnitType() == UnitType.DROPSHIP) { + return true; + } + } + for (Unit unit : units) { + if (unit.getEntity().getUnitType() == UnitType.DROPSHIP) { + return true; + } + } + } + return false; + } +} diff --git a/MekHQ/src/mekhq/campaign/mission/Scenario.java b/MekHQ/src/mekhq/campaign/mission/Scenario.java index 3865d18f7b2..d7e442e8b2e 100644 --- a/MekHQ/src/mekhq/campaign/mission/Scenario.java +++ b/MekHQ/src/mekhq/campaign/mission/Scenario.java @@ -84,7 +84,7 @@ public class Scenario implements IPlayerSettings { public static final int T_ATMOSPHERE = 1; public static final int T_SPACE = 2; private static final String[] typeNames = { "Ground", "Low Atmosphere", "Space" }; - private static final Logger log = LogManager.getLogger(Scenario.class); + private int boardType = T_GROUND; private String name; diff --git a/MekHQ/src/mekhq/gui/BriefingTab.java b/MekHQ/src/mekhq/gui/BriefingTab.java index ccb3cf76e54..8d518c1590d 100644 --- a/MekHQ/src/mekhq/gui/BriefingTab.java +++ b/MekHQ/src/mekhq/gui/BriefingTab.java @@ -794,9 +794,9 @@ private void printRecordSheets() { } } - if (scenario instanceof AtBScenario) { + if (scenario instanceof AtBScenario atBScenario) { // Also print off allied sheets - chosen.addAll(((AtBScenario) scenario).getAlliesPlayer()); + chosen.addAll(atBScenario.getAlliesPlayer()); } // add bot forces diff --git a/MekHQ/src/mekhq/utilities/ScenarioUtils.java b/MekHQ/src/mekhq/utilities/ScenarioUtils.java index 1e2c5b3a57d..abb3d633f81 100644 --- a/MekHQ/src/mekhq/utilities/ScenarioUtils.java +++ b/MekHQ/src/mekhq/utilities/ScenarioUtils.java @@ -32,6 +32,8 @@ */ package mekhq.utilities; +import static mekhq.MHQConstants.MAPGEN_PATH; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -40,6 +42,7 @@ import io.sentry.Sentry; import megamek.common.Board; import megamek.common.MapSettings; +import megamek.common.util.fileUtils.MegaMekFile; import megamek.logging.MMLogger; import megamek.server.ServerBoardHelper; import mekhq.campaign.mission.AtBScenario; @@ -63,60 +66,42 @@ private ScenarioUtils() {} */ public static Board getBoardFor(Scenario scenario) { // Check for valid dimensions and map - if (scenario instanceof AtBScenario atBScenario) { - return getStratconBoardFor(atBScenario); - } - return getNonStratconBoardFor(scenario); + MapSettings mapSettings = getMapSettings(scenario); + return ServerBoardHelper.getPossibleGameBoard(mapSettings, false); } - private static Board getNonStratconBoardFor(Scenario scenario) { - if (scenario == null || scenario.getMap() == null || - scenario.getMapSizeX() <= 1 || scenario.getMapSizeY() <= 1) { - LOGGER.error("Invalid map settings provided for scenario {}", - scenario != null ? scenario.getName() : "null"); - return ServerBoardHelper.getPossibleGameBoard(MapSettings.getInstance(), false); + public static MapSettings getMapSettings(Scenario scenario) { + if (scenario instanceof AtBScenario atBScenario) { + return getStratconMapSettings(atBScenario); + } else { + return getNonStratconMapSettings(scenario); } - - boolean isSpace = scenario.getBoardType() == Scenario.T_SPACE; - boolean isAtmosphere = scenario.getBoardType() == Scenario.T_ATMOSPHERE; - - return createBoard( - scenario.getMapSizeX(), - scenario.getMapSizeY(), - scenario.getMap(), - scenario.isUsingFixedMap(), - isSpace, - isAtmosphere - ); } - private static Board getStratconBoardFor(AtBScenario scenario) { - // Check for valid dimensions and map - if (scenario == null || scenario.getMap() == null) { - LOGGER.error("Invalid AtBScenario provided"); - return ServerBoardHelper.getPossibleGameBoard(MapSettings.getInstance(), false); - } - - boolean isSpace = scenario.getBoardType() == Scenario.T_SPACE || - "Space".equals(scenario.getTerrainType()); - boolean isAtmosphere = scenario.getBoardType() == Scenario.T_ATMOSPHERE; + private static MapSettings getNonStratconMapSettings(Scenario scenario) { + return getMapSettings(scenario.getMapSizeX(), scenario.getMapSizeY(), scenario.getMap(), + scenario.isUsingFixedMap(), scenario.getBoardType() == Scenario.T_SPACE, + scenario.getBoardType() == Scenario.T_ATMOSPHERE); + } - return createBoard( - scenario.getMapX(), + private static MapSettings getStratconMapSettings(AtBScenario scenario) { + return getMapSettings(scenario.getMapX(), scenario.getMapY(), scenario.getMap(), scenario.isUsingFixedMap(), - isSpace, - isAtmosphere - ); + scenario.getBoardType() == Scenario.T_SPACE || "Space".equals(scenario.getTerrainType()), + scenario.getBoardType() == Scenario.T_ATMOSPHERE); } - /** - * Creates a board based on the provided parameters - */ - private static Board createBoard(int mapSizeX, int mapSizeY, String mapName, - boolean isUsingFixedMap, boolean isSpace, boolean isAtmosphere) { + public static MapSettings getMapSettings(int mapSizeX, int mapSizeY, String mapName, boolean isUsingFixedMap, + boolean isSpace, boolean isAtmosphere) { MapSettings mapSettings = MapSettings.getInstance(); + + if ((mapName == null) || (mapSizeX <= 1) || (mapSizeY <= 1)) { + LOGGER.error("Invalid map settings provided for scenario {}", mapName); + return mapSettings; + } + mapSettings.setBoardSize(mapSizeX, mapSizeY); mapSettings.setMapSize(1, 1); mapSettings.getBoardsSelectedVector().clear(); @@ -132,7 +117,7 @@ private static Board createBoard(int mapSizeX, int mapSizeY, String mapName, mapSettings.setMedium(MapSettings.MEDIUM_ATMOSPHERE); } } else { - File mapgenFile = new File("data/mapgen/" + mapName + ".xml"); + File mapgenFile = new MegaMekFile(new File(MAPGEN_PATH), mapName + ".xml").getFile(); try (InputStream is = new FileInputStream(mapgenFile)) { mapSettings = MapSettings.getInstance(is); } catch (IOException ex) { @@ -149,7 +134,8 @@ private static Board createBoard(int mapSizeX, int mapSizeY, String mapName, mapSettings.setMapSize(1, 1); mapSettings.getBoardsSelectedVector().add(MapSettings.BOARD_GENERATED); } + return mapSettings; + - return ServerBoardHelper.getPossibleGameBoard(mapSettings, false); } } diff --git a/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java b/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java index b543c92f084..25438976d26 100644 --- a/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java +++ b/MekHQ/unittests/mekhq/campaign/autoresolve/ResolverTest.java @@ -381,7 +381,7 @@ void autoResolve(Consumer autoResolveConcludedEvent) when(botForce.getTeam()).thenReturn(2); when(botForce.getFullEntityList(any())).thenReturn(entities); - var resolver = Resolver.simulationRun(new AtBSetupForces(campaign, units, scenario, new FlattenForces()), + var resolver = Resolver.simulationRun(new StratconSetupForces(campaign, units, scenario, new FlattenForces()), SimulationOptions.empty(), new Board(30, 30), planetaryConditions); autoResolveConcludedEvent.accept(resolver.resolveSimulation()); } From 9ee3686d03bf1bdbdb7b9b5979c6e465e172d0cc Mon Sep 17 00:00:00 2001 From: Luana Coppio Date: Sat, 24 May 2025 22:05:43 -0300 Subject: [PATCH 5/5] feat: missing MekHQ setup force added --- .../autoresolve/MekHQSetupForces.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 MekHQ/src/mekhq/campaign/autoresolve/MekHQSetupForces.java diff --git a/MekHQ/src/mekhq/campaign/autoresolve/MekHQSetupForces.java b/MekHQ/src/mekhq/campaign/autoresolve/MekHQSetupForces.java new file mode 100644 index 00000000000..f1333a5d31c --- /dev/null +++ b/MekHQ/src/mekhq/campaign/autoresolve/MekHQSetupForces.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2025 The MegaMek Team. All Rights Reserved. + * + * This file is part of MegaMek. + * + * MegaMek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * MegaMek 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + * + * NOTICE: The MegaMek organization is a non-profit group of volunteers + * creating free software for the BattleTech community. + * + * MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks + * of The Topps Company, Inc. All Rights Reserved. + * + * Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of + * InMediaRes Productions, LLC. + * + * MechWarrior Copyright Microsoft Corporation. MegaMek was created under + * Microsoft's "Game Content Usage Rules" + * and it is not endorsed by or + * affiliated with Microsoft. + */ +package mekhq.campaign.autoresolve; + +import java.util.List; + +import megamek.common.autoresolve.converter.ForceConsolidation; +import megamek.logging.MMLogger; +import mekhq.campaign.Campaign; +import mekhq.campaign.mission.Scenario; +import mekhq.campaign.unit.Unit; + +/** + * This class is responsible for setting up the forces for a scenario + * @author Luana Coppio + */ +public class MekHQSetupForces extends ScenarioSetupForces { + private static final MMLogger LOGGER = MMLogger.create(MekHQSetupForces.class); + + + public MekHQSetupForces(Campaign campaign, List units, Scenario scenario, + ForceConsolidation forceConsolidationMethod) { + super(campaign, units, scenario, forceConsolidationMethod, new OrderFactory(campaign, scenario)); + } + + public MekHQSetupForces(Campaign campaign, List units, Scenario scenario, + ForceConsolidation forceConsolidationMethod, OrderFactory orderFactory) { + super(campaign, units, scenario, forceConsolidationMethod, orderFactory); + } +}