Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-Resolve: Make princess take the reins so you can play a game of spreadsheets! #5155

Merged
merged 12 commits into from
Nov 29, 2024
Merged
59 changes: 59 additions & 0 deletions MekHQ/docs/help/en/AutoResolve.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Auto Resolve</title>
</head>
<body>
<div>
<h1 id="auto-resolve-behavior-settings">Auto Resolve Behavior Settings</h1>
<h2 id="introduction">Introduction</h2>
<p>The Auto Resolve feature is a way to let the PrincessBot control your units so she can quickly play out the battles
for you, its pretty straight forward to setup and use if you want to focus on your company management or just dont
want to play every single lengthy battle.</p>
<h2 id="how-to-use">How to Use</h2>
<p>Auto Resolve is available as a new option in the Scenario panel, the button &quot;Auto Resolve&quot; will setup the game scenario
on MegaMek as normal, and then it adds a Princess bot with the name following the format YourCompany:AI, it is setup
as the player is setup, same starting position, color, camouflage and team, and then all the players units change owner
to it.</p>
<p>To make the game faster, you can change configuration to not show the reports between phases, and select the skip
action if no available unit, with those options selected the game will not request you to press Done between those
phases.</p>
<h2 id="configuring-the-auto-resolve-behavior">Configuring the Auto Resolve Behavior</h2>
<p>The configuration is accessible in the <strong>Manage Campaign</strong> menu, in the MekHQ toolbar, there is the entry
<strong>Auto Resolve Behavior Settings</strong>, it will open the <strong>Configure Princess Bot</strong> window, where you can define the
behavior for the auto resolve preset.</p>
<p>By default, every campaign now have an auto-resolve default preset, that is named YourCompany:AI, you can change the
configuration as you like and select whatever preset you want.</p>
<p>Whenever you hit the &quot;OK&quot; button the current configuration being shown overwrites the auto resolve preset
for your company, so if you want to experiment with different behaviors, I suggest that you create new presets and then
just select the one you want before hitting &quot;OK&quot; to close the configuration.</p>
<h2 id="qa">Q&amp;A</h2>
<p><strong>Q: What are its current limitations?</strong></p>
<p>Unfortunatelly Princess, being part of an honorable bloodline, she will abide by the Ares Convention and won&#39;s ever shoot at
retreating units, this means that any scenario to stop enemies from reaching the other side of the map will fail, and every mission
where she has to reach the other side of the map will succeed, both without firing a single shot. She is also resource heavy, so
any scenario with too many VTOLs or Hovertanks or too many units overall can be slugsh.</p>
<p><strong>Q: Can I change the configuration during the game?</strong></p>
<p>Sure, its a Princess Bot, so all chat commands to change behavior apply</p>
<p><strong>Q: Can I change the configuration for a specific scenario?</strong></p>
<p>Also yes, you can create many presets, like for example one for a scape scenario, another for a defend scenario, etc.
Just remember to change the selected preset behavior before entering the game with auto resolve. And remember to change
it back later to your &quot;default&quot; preset.</p>
<p><strong>Q: Can I change the configuration for a specific unit?</strong></p>
<p>No, you can&#39;t, you can emulate that by manually creating many bots with different configurations and then assigning
individual units or lances to them, but that is outside the current scope of the Auto Resolve.</p>
<p><strong>Q: I deleted my company preset! Is everything lost?</strong></p>
<p>You lost the configuration, sure, but MekHQ won&#39;t make any fuss about it, if you delete it by mistake inside MegaMek,
once you open the <strong>Auto Resolve Behavior Settings</strong> again, the default preset will be recreated for you. And if it is
missing before you enter an auto resolve game, it will use the default behavior for the Princess Bot instead.</p>
<p><strong>Q: I want to share my preset with my friends, how can I do that?</strong></p>
<p>It&#39;s just a preset, so you can use the same way you used to share it with your friends before.</p>
<p><strong>Q: The preset is written to the campaign save?</strong></p>
<p>No, the preset is saved in the MekHQ configuration folder, so it&#39;s available for all your campaigns, not just the one
that created it. So... if you create two different campaigns with the same name they would use the same preset.
Also means that if you change the preset in one save file, the preset is the same in your other campaign. Remember, it
is a preset that you are telling MekHQ to use, not a campaign specific configuration.</p>
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -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
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
168 changes: 116 additions & 52 deletions MekHQ/src/mekhq/AtBGameThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,33 @@
*/
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;
Scoppio marked this conversation as resolved.
Show resolved Hide resolved

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.annotations.Nullable;
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 +55,34 @@
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);
/**
* Constructor for AtBGameThread
*
* <p>
* This constructor creates a new AtBGameThread with the given name, password, client, MekHQ application, list of
* units, scenario, and auto resolve behavior settings. The game thread is started by default.
* </p>
*
* @param name The name of the player
* @param password The password for the game
* @param client The client
* @param app The MekHQ application
* @param units The list of units to import into the game
* @param scenario The scenario to use for this game
* @param autoResolveBehaviorSettings The behavior settings for the auto resolve bot
*/
public AtBGameThread(String name, String password, Client client, MekHQ app, List<Unit> units,
AtBScenario scenario, @Nullable BehaviorSettings autoResolveBehaviorSettings) {
this(name, password, client, app, units, scenario, autoResolveBehaviorSettings, true);
}

public AtBGameThread(String name, String password, Client c, MekHQ app, List<Unit> units,
AtBScenario scenario, boolean started) {
super(name, password, c, app, units, scenario, started);
public AtBGameThread(String name, String password, Client client, MekHQ app, List<Unit> units,
AtBScenario scenario, @Nullable BehaviorSettings autoResolveBehaviorSettings, boolean started) {
super(name, password, client, 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 +122,7 @@
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 +189,7 @@
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 +380,7 @@
}

// 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 +442,14 @@
}
}
}


// 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 +467,74 @@
}
}

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

Thread.sleep(MekHQ.getMHQOptions().getStartGameBotClientDelay());
var botClient = new Princess(botName, client.getHost(), client.getPort());
Scoppio marked this conversation as resolved.
Show resolved Hide resolved
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();
Scoppio marked this conversation as resolved.
Show resolved Hide resolved
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 playerEntities = client.getEntitiesVector().stream()
.filter(entity -> entity.getOwnerId() == player.getId())
.collect(Collectors.toList());
Scoppio marked this conversation as resolved.
Show resolved Hide resolved
botClient.sendChangeOwner(playerEntities, 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
Loading