Skip to content

Commit

Permalink
feat: added Auto-Resolve as a feature for resolving combats using Pri…
Browse files Browse the repository at this point in the history
…ncess
  • Loading branch information
Scoppio committed Oct 31, 2024
1 parent f0099f1 commit 584d614
Show file tree
Hide file tree
Showing 10 changed files with 746 additions and 51 deletions.
3 changes: 3 additions & 0 deletions MekHQ/resources/mekhq/resources/CampaignGUI.properties
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ miMassPersonnelTraining.text=Mass Personnel Training...
miMassPersonnelTraining.toolTipText=This launches the Mass Personnel Training Dialog, which allows you to train large numbers of personnel at the same time.
miScenarioEditor.text=Scenario Template Editor...
miCompanyGenerator.text=Company Generator...
miAutoResolveBehaviorSettings.text=Auto Resolve Behavior Settings

# Help Menu
menuHelp.text=Help
Expand Down Expand Up @@ -177,6 +178,8 @@ btnClearAssignedUnits.toolTipText=Clear all assigned units for this scenario
btnClearAssignedUnits.text=Clear Units
btnResolveScenario.toolTipText=Bring up a wizard that will guide you through the process of resolving this scenario either by MUL files from a MegaMek game or by manually editing for tabletop games.
btnResolveScenario.text=Resolve Manually
btnAutoResolveScenario.toolTipText=<html>Start a game of MegaMek with all the assigned units played by bots.<br>At the game's conclusion, you will be presented with a series of dialogs for resolving the scenario.</html>
btnAutoResolveScenario.text=Auto Resolve
lblMission.text=Current Mission
lblPartsChoice.text=Part Type:
lblPartsChoiceView.text=Part Status:
Expand Down
4 changes: 4 additions & 0 deletions MekHQ/resources/mekhq/resources/GUI.properties
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ CompleteMissionDialog.title=Complete Mission
lblOutcomeStatus.text=Outcome
lblOutcomeStatus.toolTipText=This is the mission's outcome, with Active meaning the mission has not been completed.

### AutoResolveBehaviorSettingsDialog Class
AutoResolveBehaviorSettingsDialog.title=Auto Resolve Behavior Settings


### ContractMarketDialog Class
ContractMarketDialog.title=Contract Market

Expand Down
151 changes: 103 additions & 48 deletions MekHQ/src/mekhq/AtBGameThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,32 @@
*/
package mekhq;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;

import javax.swing.JOptionPane;

import io.sentry.Sentry;
import megamek.client.AbstractClient;
import megamek.client.Client;
import megamek.client.bot.BotClient;
import megamek.client.bot.princess.BehaviorSettings;
import megamek.client.bot.princess.Princess;
import megamek.client.bot.princess.PrincessException;
import megamek.client.generator.RandomCallsignGenerator;
import megamek.client.ui.swing.ClientGUI;
import megamek.common.Entity;
import megamek.common.IAero;
import megamek.common.Infantry;
import megamek.common.MapSettings;
import megamek.common.Minefield;
import megamek.common.UnitType;
import megamek.common.*;
import megamek.common.planetaryconditions.PlanetaryConditions;
import megamek.logging.MMLogger;
import mekhq.campaign.force.Force;
import mekhq.campaign.force.Lance;
import mekhq.campaign.mission.AtBContract;
import mekhq.campaign.mission.AtBDynamicScenario;
import mekhq.campaign.mission.AtBScenario;
import mekhq.campaign.mission.BotForce;
import mekhq.campaign.mission.Scenario;
import mekhq.campaign.mission.*;
import mekhq.campaign.personnel.Person;
import mekhq.campaign.unit.Unit;

import javax.swing.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
* Enhanced version of GameThread which imports settings and non-player units
* into the MM game
Expand All @@ -69,16 +54,23 @@ public class AtBGameThread extends GameThread {
private static final MMLogger logger = MMLogger.create(AtBGameThread.class);

private final AtBScenario scenario;
private final BehaviorSettings autoResolveBehaviorSettings;

public AtBGameThread(String name, String password, Client c, MekHQ app, List<Unit> units,
AtBScenario scenario) {
this(name, password, c, app, units, scenario, true);
this(name, password, c, app, units, scenario, null, true);
}

public AtBGameThread(String name, String password, Client c, MekHQ app, List<Unit> units,
AtBScenario scenario, BehaviorSettings autoResolveBehaviorSettings) {
this(name, password, c, app, units, scenario, autoResolveBehaviorSettings, true);
}

public AtBGameThread(String name, String password, Client c, MekHQ app, List<Unit> units,
AtBScenario scenario, boolean started) {
AtBScenario scenario, BehaviorSettings autoResolveBehaviorSettings, boolean started) {
super(name, password, c, app, units, scenario, started);
this.scenario = Objects.requireNonNull(scenario);
this.autoResolveBehaviorSettings = autoResolveBehaviorSettings;
}

// String tokens for dialog boxes used for transport loading
Expand Down Expand Up @@ -118,7 +110,7 @@ public void run() {
while (client.getLocalPlayer() == null) {
Thread.sleep(MekHQ.getMHQOptions().getStartGameClientDelay());
}

var player = client.getLocalPlayer();
// if game is running, shouldn't do the following, so detect the phase
for (int i = 0; (i < MekHQ.getMHQOptions().getStartGameClientRetryCount())
&& client.getGame().getPhase().isUnknown(); i++) {
Expand Down Expand Up @@ -185,23 +177,7 @@ public void run() {
client.sendMapSettings(mapSettings);
Thread.sleep(MekHQ.getMHQOptions().getStartGameDelay());

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());
}
client.sendPlanetaryConditions(planetaryConditions);
client.sendPlanetaryConditions(getPlanetaryConditions());
Thread.sleep(MekHQ.getMHQOptions().getStartGameDelay());

// set player deployment
Expand Down Expand Up @@ -392,7 +368,7 @@ public void run() {
}

// All player and bot units have been added to the lobby
// Prompt the player to auto load units into transports
// Prompt the player to autoload units into transports
if (!scenario.getPlayerTransportLinkages().isEmpty()) {
for (UUID id : scenario.getPlayerTransportLinkages().keySet()) {
boolean loadDropShips = false;
Expand Down Expand Up @@ -454,6 +430,14 @@ public void run() {
}
}
}


// if AtB was loaded with the auto resolve bot behavior settings then it loads a new bot,
// set to the players team
// and then moves all the player forces under this new bot
if (Objects.nonNull(autoResolveBehaviorSettings)) {
setupPlayerBotForAutoResolve(player);
}
}

while (!stop) {
Expand All @@ -471,6 +455,77 @@ public void run() {
}
}

private void setupPlayerBotForAutoResolve(Player player) throws InterruptedException, PrincessException {
var botName = player.getName() + ":AI";
var autoResolveBot = new BotForce();
autoResolveBot.setName(botName);

Thread.sleep(MekHQ.getMHQOptions().getStartGameBotClientDelay());
var botClient = new Princess(botName, client.getHost(), client.getPort());
botClient.setBehaviorSettings(autoResolveBehaviorSettings.getCopy());
try {
botClient.connect();
Thread.sleep(MekHQ.getMHQOptions().getStartGameBotClientDelay());
} catch (Exception e) {
Sentry.captureException(e);
logger.error(String.format("Could not connect with Bot name %s", botName),
e);
}
swingGui.getLocalBots().put(botName, botClient);

var retryCount = MekHQ.getMHQOptions().getStartGameBotClientRetryCount();
while (botClient.getLocalPlayer() == null) {
Thread.sleep(MekHQ.getMHQOptions().getStartGameBotClientDelay());
retryCount--;
if (retryCount <= 0) {
break;
}
}
if (retryCount <= 0) {
logger.error(String.format("Could not connect with Bot name %s", botName));
}
botClient.getLocalPlayer().setName(botName);
botClient.getLocalPlayer().setStartingPos(player.getStartingPos());
botClient.getLocalPlayer().setStartOffset(player.getStartOffset());
botClient.getLocalPlayer().setStartWidth(player.getStartWidth());
botClient.getLocalPlayer().setStartingAnyNWx(player.getStartingAnyNWx());
botClient.getLocalPlayer().setStartingAnyNWy(player.getStartingAnyNWy());
botClient.getLocalPlayer().setStartingAnySEx(player.getStartingAnySEx());
botClient.getLocalPlayer().setStartingAnySEy(player.getStartingAnySEy());
botClient.getLocalPlayer().setCamouflage(player.getCamouflage().clone());
botClient.getLocalPlayer().setColour(player.getColour());
botClient.getLocalPlayer().setTeam(player.getTeam());
botClient.sendPlayerInfo();
Thread.sleep(MekHQ.getMHQOptions().getStartGameBotClientDelay());

var ent = client.getEntitiesVector().stream()
.filter(entity -> entity.getOwnerId() == player.getId())
.collect(Collectors.toList());
botClient.sendChangeOwner(ent, botClient.getLocalPlayer().getId());
Thread.sleep(MekHQ.getMHQOptions().getStartGameBotClientDelay());

}

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
Expand Down
8 changes: 7 additions & 1 deletion MekHQ/src/mekhq/MekHQ.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import megamek.MegaMek;
import megamek.SuiteConstants;
import megamek.client.Client;
import megamek.client.bot.princess.BehaviorSettings;
import megamek.client.generator.RandomNameGenerator;
import megamek.client.generator.RandomUnitGenerator;
import megamek.client.ui.preferences.PreferencesNode;
Expand Down Expand Up @@ -374,6 +375,11 @@ public void joinGame(Scenario scenario, List<Unit> meks) {
}

public void startHost(Scenario scenario, boolean loadSavegame, List<Unit> meks) {
startHost(scenario, loadSavegame, meks, null);
}

public void startHost(Scenario scenario, boolean loadSavegame, List<Unit> meks, BehaviorSettings autoResolveBehaviorSettings)
{
HostDialog hostDialog = new HostDialog(campaignGUI.getFrame(), getCampaign().getName());
hostDialog.setVisible(true);

Expand Down Expand Up @@ -426,7 +432,7 @@ public void startHost(Scenario scenario, boolean loadSavegame, List<Unit> meks)

// Start the game thread
if (getCampaign().getCampaignOptions().isUseAtB() && (scenario instanceof AtBScenario)) {
gameThread = new AtBGameThread(playerName, password, client, this, meks, (AtBScenario) scenario);
gameThread = new AtBGameThread(playerName, password, client, this, meks, (AtBScenario) scenario, autoResolveBehaviorSettings);
} else {
gameThread = new GameThread(playerName, password, client, this, meks, scenario);
}
Expand Down
14 changes: 14 additions & 0 deletions MekHQ/src/mekhq/campaign/Campaign.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
*/
package mekhq.campaign;

import megamek.client.bot.princess.BehaviorSettings;
import megamek.client.bot.princess.BehaviorSettingsFactory;
import megamek.client.generator.RandomGenderGenerator;
import megamek.client.generator.RandomNameGenerator;
import megamek.client.generator.RandomUnitGenerator;
Expand Down Expand Up @@ -268,6 +270,7 @@ public class Campaign implements ITechManager {
private final Quartermaster quartermaster;
private StoryArc storyArc;
private FameAndInfamyController fameAndInfamy;
private BehaviorSettings autoResolveBehaviorSettings;

private final transient ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Campaign",
MekHQ.getMHQOptions().getLocale());
Expand Down Expand Up @@ -335,6 +338,7 @@ public Campaign() {
quartermaster = new Quartermaster(this);
fieldKitchenWithinCapacity = false;
fameAndInfamy = new FameAndInfamyController();
autoResolveBehaviorSettings = BehaviorSettingsFactory.getInstance().DEFAULT_BEHAVIOR;
}

/**
Expand Down Expand Up @@ -5488,6 +5492,7 @@ public void writeToXML(final PrintWriter pw) {
MHQXMLUtility.writeSimpleXMLTag(pw, indent, "shipSearchType", shipSearchType);
MHQXMLUtility.writeSimpleXMLTag(pw, indent, "shipSearchResult", shipSearchResult);
MHQXMLUtility.writeSimpleXMLTag(pw, indent, "shipSearchExpiration", getShipSearchExpiration());
MHQXMLUtility.writeSimpleXMLTag(pw, indent, "autoResolveBehaviorSettings", autoResolveBehaviorSettings.getDescription());
}

retirementDefectionTracker.writeToXML(pw, indent);
Expand Down Expand Up @@ -8374,4 +8379,13 @@ public boolean useVariableTechLevel() {
public boolean showExtinct() {
return !campaignOptions.isDisallowExtinctStuff();
}

public BehaviorSettings getAutoResolveBehaviorSettings() {
return autoResolveBehaviorSettings;
}

public void setAutoResolveBehaviorSettings(BehaviorSettings settings) {
autoResolveBehaviorSettings = settings;
}

}
8 changes: 8 additions & 0 deletions MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package mekhq.campaign.io;

import megamek.Version;
import megamek.client.bot.princess.BehaviorSettingsFactory;
import megamek.client.generator.RandomGenderGenerator;
import megamek.client.generator.RandomNameGenerator;
import megamek.client.ui.swing.util.PlayerColour;
Expand Down Expand Up @@ -77,6 +78,8 @@
import java.util.*;
import java.util.Map.Entry;

import static mekhq.utilities.MoreObjects.firstNonNull;

public class CampaignXmlParser {
private final InputStream is;
private final MekHQ app;
Expand Down Expand Up @@ -292,6 +295,11 @@ public Campaign parse() throws CampaignXmlParseException, NullEntityException {
retVal.setShipSearchResult(wn.getTextContent());
} else if (xn.equalsIgnoreCase("shipSearchExpiration")) {
retVal.setShipSearchExpiration(MHQXMLUtility.parseDate(wn.getTextContent().trim()));
} else if (xn.equalsIgnoreCase("autoResolveBehaviorSettings")) {
retVal.setAutoResolveBehaviorSettings(
firstNonNull(BehaviorSettingsFactory.getInstance().getBehavior(wn.getTextContent()),
BehaviorSettingsFactory.getInstance().DEFAULT_BEHAVIOR)
);
} else if (xn.equalsIgnoreCase("customPlanetaryEvents")) {
updatePlanetaryEventsFromXML(wn);
}
Expand Down
Loading

0 comments on commit 584d614

Please sign in to comment.