Skip to content

Commit

Permalink
fix: fixing bug with Units matter force construction
Browse files Browse the repository at this point in the history
  • Loading branch information
Scoppio committed Dec 3, 2024
1 parent ac6deb4 commit e61f7dc
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 52 deletions.
6 changes: 6 additions & 0 deletions MekHQ/resources/mekhq/resources/AutoResolveMethod.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
AutoResolveMethod.PRINCESS.text=Princess
AutoResolveMethod.PRINCESS.toolTipText=Princess
AutoResolveMethod.UNITS_MATTER.text=Units Matter
AutoResolveMethod.UNITS_MATTER.toolTipText=Units Matter
AutoResolveMethod.ABSTRACT_COMBAT.text=Abstract Combat
AutoResolveMethod.ABSTRACT_COMBAT.toolTipText=Abstract Combat
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,8 @@ chkUseStratCon.text=Use StratCon campaign rules
chkUseStratCon.toolTipText=An update of the AtB ruleset.
lblSkillLevel.text=Skill Level
lblSkillLevel.toolTipText=<html>This is the difficulty level for generated scenarios. <br>Values above Elite are not recommended.</html>
lblAutoResolveType.text=Auto Resolve method:
lblAutoResolveType.toolTipText=<html>Auto resolve to use in campaign play.<br>Princess: The bot plays the MM game in your place.<br>Units Matter: A very fast simulation focused on speed.<br>Abstract Combat: Complex simulation well suited for very large confrontations.</html>
lblScenarioMod.text=Random Scenario Modifiers
lblScenarioModMax.text=Maximum:
lblScenarioModMax.toolTipText=This is the maximum number of random scenario mods that can spawn on for a single Scenario. Excludes StratCon facility modifiers.
Expand Down Expand Up @@ -966,8 +968,11 @@ chkRegionalMekVariations.text=Varied weight distributions by faction
chkRegionalMekVariations.toolTipText=Use alternate weight class distributions for some factions.
chkAttachedPlayerCamouflage.text=Player-controlled allied units use player's camouflage
chkPlayerControlsAttachedUnits.text=All attached allied units are player controlled
chkUseBotControlledAutoResolve.text=Bots play auto resolve
chkUseBotControlledAutoResolve.toolTipText=If checked, auto resolve will open a game and add all your units to a bot on your team, otherwise, auto resolve will use Abstract Combat System to resolve the scenarios.
chkUseBotControlledAutoResolveAARC.text=Auto Resolve using Abstract Auto Resolve Combat
chkUseBotControlledAutoResolve.text=Auto Resolve using Bots
chkUseBotControlledAutoResolve.toolTipText=If checked, auto resolve will open a game and add all your units to a bot on your team, otherwise, auto resolve will use Abstract Auto Resolve Combat to resolve the scenarios.
chkAutoResolveVictoryChanceEnabled.text=Show auto resolve victory chance
chkAutoResolveVictoryChanceEnabled.toolTipText=If checked, the pre-auto resolve dialog will show the chance of victory for the player. Only works when using Abstract Auto Resolve Combat.
chkUseWeatherConditions.text=Use weather conditions
chkUseWeatherConditions.toolTipText=Generate weather conditions when generating a scenario.
chkUseLightConditions.text=Use light conditions
Expand Down
72 changes: 65 additions & 7 deletions MekHQ/src/mekhq/MekHQ.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
import mekhq.campaign.ResolveScenarioTracker;
import mekhq.campaign.ResolveScenarioTracker.PersonStatus;
import mekhq.campaign.autoResolve.AutoResolveEngine;
import mekhq.campaign.autoResolve.AutoResolveGame;
import mekhq.campaign.autoResolve.AutoResolveMethod;
import mekhq.campaign.autoResolve.helper.AutoResolveClient;
import mekhq.campaign.autoResolve.scenarioResolver.components.AutoResolveConcludedEvent;
Expand Down Expand Up @@ -80,9 +79,10 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.ObjectInputFilter.Config;
import java.util.HashMap;
import java.util.*;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

/**
Expand Down Expand Up @@ -527,6 +527,7 @@ public void gamePhaseChange(GamePhaseChangeEvent e) {
// Why Empty?
}

// TODO: LUANA
public void autoResolveConcluded(AutoResolveConcludedEvent autoResolveConcludedEvent){
try {
String message = autoResolveConcludedEvent.controlledScenario() ?
Expand Down Expand Up @@ -693,11 +694,15 @@ public void gameVictory(PostGameResolution gve) {
final File tempImageDirectory = new File("data/images/temp");
if (tempImageDirectory.isDirectory()) {
// This can't be null because of the above
Stream.of(tempImageDirectory.listFiles()).filter(file -> file.getName().endsWith(".png"))
.forEach(File::delete);
var totalDeletedFiles = Stream.of(Objects.requireNonNull(tempImageDirectory.listFiles()))
.filter(file -> file.getName().endsWith(".png"))
.map(File::delete)
.mapToInt(result -> result ? 1 : 0)
.sum();
logger.info("Deleted {} temporary image files", totalDeletedFiles);
}
} catch (Exception ex) {
logger.error(ex, "gameVictory()");
logger.error("Exception during gameVictory() run", ex);
}
}

Expand Down Expand Up @@ -802,7 +807,60 @@ public static void updateGuiScaling() {

public void startAutoResolve(AtBScenario scenario, List<Unit> units) {
currentScenario = scenario;
new AutoResolveEngine(AutoResolveMethod.ABSTRACT_COMBAT_SYSTEM).resolveBattle(this, units, scenario);
String message = "This will auto resolve the battle. Do you want to proceed?";
var autoResolveMethod = getCampaign().getCampaignOptions().getAutoResolveMethod();
if (getCampaign().getCampaignOptions().isAutoResolveVictoryChanceEnabled()
// unfortunately UNITS MATTER force creating has a bug that makes it impossible to calculate victory chance without causing
// irreversible changes to the campaign
&& autoResolveMethod.equals(AutoResolveMethod.ABSTRACT_COMBAT)) {

var percent = calculateVictoryChance(scenario, units);
message = "Your chance of victory in this combat is " + percent + "%, do you want to proceed?";
}
String title = "Auto Resolve Battle";
boolean proceed = JOptionPane.showConfirmDialog(campaignGUI.getFrame(), message, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;

if (proceed) {
new AutoResolveEngine(autoResolveMethod)
.resolveBattle(this, units, scenario, this::autoResolveConcluded);
}
}

/**
* Calculates the victory chance for a given scenario and list of units by running multiple auto resolve scenarios in parallel.
*
* @param scenario the scenario to resolve
* @param units the list of units involved in the scenario
* @return the calculated victory chance as an integer percentage (0 to 100)
*/
private int calculateVictoryChance(AtBScenario scenario, List<Unit> units) {
var atomicInt = new AtomicInteger(0);
int numTasks = 50;
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
List<Future<?>> futures = new ArrayList<>();

for (int i = 0; i < numTasks; i++) {
futures.add(executor.submit(() -> {
new AutoResolveEngine(AutoResolveMethod.ABSTRACT_COMBAT)
.resolveBattle(this, units, scenario, r -> {
if (r.controlledScenario()) {
atomicInt.incrementAndGet();
}
});
}));
}

// Wait for all tasks to complete
for (Future<?> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
logger.error("Error in parallel execution", e);
}
}

executor.shutdown();
return atomicInt.get() * 2;
}

private static class MekHqPropertyChangedListener implements PropertyChangeListener {
Expand Down
32 changes: 23 additions & 9 deletions MekHQ/src/mekhq/campaign/CampaignOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import megamek.logging.MMLogger;
import mekhq.MekHQ;
import mekhq.Utilities;
import mekhq.campaign.autoResolve.AutoResolveMethod;
import mekhq.campaign.enums.PlanetaryAcquisitionFactionLimit;
import mekhq.campaign.finances.Money;
import mekhq.campaign.finances.enums.FinancialYearDuration;
Expand Down Expand Up @@ -600,7 +601,8 @@ public static String getTransitUnitName(final int unit) {
private int scenarioModChance;
private int scenarioModBV;
private boolean autoConfigMunitions;
private boolean princessBotAutoResolve;
private AutoResolveMethod autoResolveMethod;
private boolean autoResolveVictoryChanceEnabled;
// endregion Against the Bot Tab
// endregion Variable Declarations

Expand Down Expand Up @@ -1191,7 +1193,8 @@ public CampaignOptions() {
useAtB = false;
useStratCon = false;
setSkillLevel(SkillLevel.REGULAR);
princessBotAutoResolve = true;
autoResolveMethod = AutoResolveMethod.PRINCESS;
autoResolveVictoryChanceEnabled = false;

// Unit Administration
useAero = false;
Expand Down Expand Up @@ -5163,7 +5166,8 @@ public void writeToXml(final PrintWriter pw, int indent) {

// region AtB Tab
MHQXMLUtility.writeSimpleXMLTag(pw, indent, "skillLevel", getSkillLevel().name());
MHQXMLUtility.writeSimpleXMLTag(pw, indent, "princessBotAutoResolve", isPrincessBotAutoResolve());
MHQXMLUtility.writeSimpleXMLTag(pw, indent, "autoResolveMethod", getAutoResolveMethod().name());
MHQXMLUtility.writeSimpleXMLTag(pw, indent, "autoResolveVictoryChanceEnabled", isAutoResolveVictoryChanceEnabled());
// endregion AtB Tab

MHQXMLUtility.writeSimpleXMLTag(pw, indent, "phenotypeProbabilities", phenotypeProbabilities);
Expand Down Expand Up @@ -6148,8 +6152,10 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve
// region AtB Tab
} else if (wn2.getNodeName().equalsIgnoreCase("skillLevel")) {
retVal.setSkillLevel(SkillLevel.valueOf(wn2.getTextContent().trim()));
} else if (wn2.getNodeName().equalsIgnoreCase("princessBotAutoResolve")) {
retVal.setPrincessBotAutoResolve(Boolean.parseBoolean(wn2.getTextContent().trim()));
} else if (wn2.getNodeName().equalsIgnoreCase("autoResolveMethod")) {
retVal.setAutoResolveMethod(AutoResolveMethod.valueOf(wn2.getTextContent().trim()));
} else if (wn2.getNodeName().equalsIgnoreCase("autoResolveVictoryChanceEnabled")) {
retVal.setAutoResolveVictoryChanceEnabled(Boolean.parseBoolean(wn2.getTextContent().trim()));
// endregion AtB Tab

} else if (wn2.getNodeName().equalsIgnoreCase("phenotypeProbabilities")) {
Expand Down Expand Up @@ -6424,12 +6430,20 @@ public void migrateMarriageSurnameWeights47(final String... values) {
}
}

public boolean isPrincessBotAutoResolve() {
return princessBotAutoResolve;
public AutoResolveMethod getAutoResolveMethod() {
return autoResolveMethod;
}

public void setPrincessBotAutoResolve(final boolean princessBotAutoResolve) {
this.princessBotAutoResolve = princessBotAutoResolve;
public void setAutoResolveMethod(final AutoResolveMethod autoResolveMethod) {
this.autoResolveMethod = autoResolveMethod;
}

public boolean isAutoResolveVictoryChanceEnabled() {
return autoResolveVictoryChanceEnabled;
}

public void setAutoResolveVictoryChanceEnabled(final boolean autoResolveVictoryChanceEnabled) {
this.autoResolveVictoryChanceEnabled = autoResolveVictoryChanceEnabled;
}

// endregion File IO
Expand Down
9 changes: 7 additions & 2 deletions MekHQ/src/mekhq/campaign/autoResolve/AutoResolveEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@
package mekhq.campaign.autoResolve;

import mekhq.MekHQ;
import mekhq.campaign.autoResolve.scenarioResolver.components.AutoResolveConcludedEvent;
import mekhq.campaign.mission.AtBScenario;
import mekhq.campaign.unit.Unit;

import java.util.List;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* @author Luana Coppio
Expand All @@ -30,11 +35,11 @@ public AutoResolveEngine(AutoResolveMethod method) {
this.autoResolveMethod = method;
}

public void resolveBattle(MekHQ app, List<Unit> units, AtBScenario scenario) {
public void resolveBattle(MekHQ app, List<Unit> units, AtBScenario scenario, Consumer<AutoResolveConcludedEvent> autoResolveConcludedEvent) {
var scenarioSpecificResolutionResolver = autoResolveMethod.of(scenario);
var game = new AutoResolveGame(app, units, scenario);
var result = scenarioSpecificResolutionResolver.resolveScenario(game);
app.autoResolveConcluded(result);
autoResolveConcludedEvent.accept(result);
}

}
55 changes: 49 additions & 6 deletions MekHQ/src/mekhq/campaign/autoResolve/AutoResolveMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,78 @@
*/
package mekhq.campaign.autoResolve;

import megamek.MegaMek;
import mekhq.MekHQ;
import mekhq.campaign.autoResolve.scenarioResolver.ScenarioResolver;
import mekhq.campaign.autoResolve.scenarioResolver.abstractCombatSystem.AcsSimpleScenarioResolver;
import mekhq.campaign.autoResolve.scenarioResolver.unitsMatter.UnitsMatterSimpleScenarioResolver;
import mekhq.campaign.mission.AtBScenario;

import java.util.Optional;
import java.util.ResourceBundle;

/**
* @author Luana Coppio
*/
public enum AutoResolveMethod {
UNITS_MATTER(){
PRINCESS("AutoResolveMethod.PRINCESS.text", "AutoResolveMethod.PRINCESS.toolTipText") {
@Override
public ScenarioResolver of(AtBScenario scenario) {
throw new UnsupportedOperationException("Princess method not implemented");
}
},
UNITS_MATTER("AutoResolveMethod.UNITS_MATTER.text", "AutoResolveMethod.UNITS_MATTER.toolTipText") {
@Override
public ScenarioResolver of(AtBScenario scenario) {
return new UnitsMatterSimpleScenarioResolver(scenario);
}
},
ABSTRACT_COMBAT_SYSTEM() {
ABSTRACT_COMBAT("AutoResolveMethod.ABSTRACT_COMBAT.text", "AutoResolveMethod.ABSTRACT_COMBAT.toolTipText") {
@Override
public ScenarioResolver of(AtBScenario scenario) {
return new AcsSimpleScenarioResolver(scenario);
}
};

public static AutoResolveMethod fromString(String method) {
private final String name;
private final String toolTipText;

AutoResolveMethod(final String name, final String toolTipText) {
final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.AutoResolveMethod",
MekHQ.getMHQOptions().getLocale());
this.name = resources.getString(name);
this.toolTipText = resources.getString(toolTipText);
}

public String getToolTipText() {
return toolTipText;
}

public String getName() {
return name;
}

public static Optional<AutoResolveMethod> fromInteger(int index) {
if (index < 0 || index >= values().length) {
return Optional.empty();
}
return Optional.of(values()[index]);
}

public static Optional<AutoResolveMethod> fromString(String method) {
return switch (method) {
case "UNITS_MATTER" -> UNITS_MATTER;
case "ABSTRACT_COMBAT_SYSTEM" -> ABSTRACT_COMBAT_SYSTEM;
default -> throw new IllegalArgumentException("Invalid method: " + method);
case "PRINCESS" -> Optional.of(PRINCESS);
case "UNITS_MATTER" -> Optional.of(UNITS_MATTER);
case "ABSTRACT_COMBAT_SYSTEM" -> Optional.of(ABSTRACT_COMBAT);
default -> Optional.empty();
};
}

public abstract ScenarioResolver of(AtBScenario scenario);

@Override
public String toString() {
return name;
}
}

19 changes: 14 additions & 5 deletions MekHQ/src/mekhq/campaign/autoResolve/helper/SetupForces.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,7 @@ private void setupPlayerForces(Campaign campaign, List<Unit> units, AtBScenario
if (force != null) {
entity.setForceString(force.getFullMMName());
}

var newCrewRef = getNewCrewRef(unit);
var newCrewRef = getNewCrewRef(unit.getEntity().getCrew());
entity.setCrew(newCrewRef);
entities.add(entity);
}
Expand Down Expand Up @@ -202,8 +201,8 @@ private void setupPlayerForces(Campaign campaign, List<Unit> units, AtBScenario
sendEntities(entities);
}

private static Crew getNewCrewRef(Unit unit) {
var originalCrew = unit.getEntity().getCrew();
public static Crew getNewCrewRef(Crew originalCrew) {

var newCrewRef = new Crew(originalCrew.getCrewType(), originalCrew.getName(), originalCrew.getSize(),
originalCrew.getGunnery(), originalCrew.getPiloting(), originalCrew.getGender(), originalCrew.isClanPilot(),
originalCrew.getExtraData());
Expand Down Expand Up @@ -282,9 +281,19 @@ private void setupBotEntities(Player bot, BotForce botForce) {
String forceName = bot.getName() + "|1";
var entities = new ArrayList<Entity>();
botForce.generateRandomForces(units, campaign);
for (Entity entity : botForce.getFullEntityList(campaign)) {
for (Entity originalBotEntity : botForce.getFullEntityList(campaign)) {
var entity = ASConverter.getUndamagedEntity(originalBotEntity);
if (entity == null) {
logger.warn("Could not convert entity for bot {} - {}", bot.getName(), originalBotEntity);
continue;
}

entity.setOwner(bot);
entity.setForceString(forceName);
entity.setCrew(getNewCrewRef(entity.getCrew()));
entity.setId(originalBotEntity.getId());
entity.setExternalIdAsString(originalBotEntity.getExternalIdAsString());
entity.setCommander(originalBotEntity.isCommander());

if (entity.getDeployRound() == 0) {
entity.setDeployRound(botForce.getDeployRound());
Expand Down
Loading

0 comments on commit e61f7dc

Please sign in to comment.