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

AI Toy - Piracy, new behaviors, and a new test platform to build upon #6486

Merged
merged 9 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions megamek/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ task assembleDist(overwrite: true) {
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
jvmArgs = mmJvmOptions
}

jacocoTestReport {
Expand Down Expand Up @@ -410,6 +411,16 @@ task unitFileMigrationTool(type: JavaExec, dependsOn: jar) {
mainClass = 'megamek.utilities.UnitFileMigrationTool'
}

task testAi(type: JavaExec, dependsOn: jar) {
description = 'Test AI'
group = 'utility'
classpath = sourceSets.main.runtimeClasspath
mainClass = 'megamek.utilities.QuickGameRunner'
args(project.hasProperty("testAiArgs") ? project.property("testAiArgs").split(' ') : "")

jvmArgs = mmJvmOptions
}

tasks.withType(Checkstyle) {
minHeapSize = "200m"
maxHeapSize = "1g"
Expand Down
22 changes: 21 additions & 1 deletion megamek/i18n/megamek/client/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1138,7 +1138,7 @@ CommonMenuBar.viewToggleHexCoords=Hex Coords
CommonMenuBar.viewLabels=Change Unit Label Display
CommonMenuBar.viewToggleFovHighlight=Highlight within field of view
CommonMenuBar.viewToggleFovDarken=Darken outside field of view
CommonMenuBar.viewToggleFovDarkenTooltip=<html>Darkens hexes with no LOS to the currently selected hex. <br> The height in the source hex is based upon the selected unit or LOS settings.<br>The height in the destination hex is based on the tallest unit in that hex or LoS Settings when none are present.</html>
CommonMenuBar.viewToggleFovDarkenTooltip=Darkens hexes with no LOS to the currently selected hex. The height in the source hex is based upon the selected unit or LOS settings. The height in the destination hex is based on the tallest unit in that hex or LoS Settings when none are present.
CommonMenuBar.viewToggleIsometric=Isometric View
CommonMenuBar.viewToggleFieldOfFire=Field of Fire
CommonMenuBar.viewToggleFieldOfFireToolTip=Outline firing arcs for selected weapons
Expand Down Expand Up @@ -1264,6 +1264,8 @@ CommonSettingsDialog.colors.myUnitColor=My Unit Color
CommonSettingsDialog.colors.allyUnitColor=Ally Unit Color
CommonSettingsDialog.colors.enemyUnitColor=Enemy Unit Color
CommonSettingsDialog.darkenMapAtNight=Darken Map At Night
CommonSettingsDialog.enableExperimentalBotFeatures=Enable experimental bot features.
CommonSettingsDialog.enableExperimentalBotFeatures.tooltip=If enabled, Princess will have access to experimental systems. These systems may be unbalanced, broken or otherwise unfit for normal gameplay. Use at your own risk.
CommonSettingsDialog.defaultAutoejectDisabled=Disable automatic ejection by default for units added in the lobby.
CommonSettingsDialog.defaultWeaponSortOrder.tooltip=<html>Sets the default weapon sort order for chassis/models without a specified default. <br>Changing this setting won't affect games already in progress.</html>
CommonSettingsDialog.defaultWeaponSortOrder=Default Weapon Sort Order:
Expand Down Expand Up @@ -4532,6 +4534,11 @@ BotConfigDialog.autoFleeCheck=Auto Flee Board
BotConfigDialog.autoFleeTooltip=If true, Princess will flee her units off her home board edge even if they are not required to by Forced Withdrawal.
BotConfigDialog.retreatEdgeLabel=to:
BotConfigDialog.retreatEdgeTooltip=The edge to which the bot's crippled units will attempt to retreat if under Forced Withdrawal. NEAREST will simply run to the closest edge.
BotConfigDialog.iAmAPirateCheck=I am a Pirate
BotConfigDialog.iAmAPirateCheckToolTip=If enabled, Princess will ignore civilian status and Forced Withdrawal rules, it will kill anything that moves
BotConfigDialog.experimentalCheck=Experimental features
BotConfigDialog.experimentalCheckToolTip=If enabled, Princess will have access to experimental systems.<br/>These systems may be unbalanced, broken or otherwise unfit for normal gameplay.<br/>Use at your own risk.

BotConfigDialog.homeEdgeLabel=to:
BotConfigDialog.homeEdgeTooltip=The edge to which the bot will attempt to move her units, unless under Forced Withdrawal.
BotConfigDialog.northEdge=NORTH
Expand All @@ -4556,6 +4563,19 @@ BotConfigDialog.fallShameSliderMin=Highway Menace
BotConfigDialog.fallShameSliderMax=Never takes a risk.
BotConfigDialog.fallShameSliderTitle=Piloting Caution
BotConfigDialog.fallShameToolTip=<EM>Piloting Caution:</EM> How much do I want to avoid failed Piloting Rolls?

BotConfigDialog.antiCrowdingSliderMin=Disabled
BotConfigDialog.antiCrowdingSliderMax=Disperse
BotConfigDialog.antiCrowdingTitle=Anti-Crowding
BotConfigDialog.antiCrowdingToolTip=<EM>Anti-Crowding</EM> Keeps a distance between your units. Try to keep enemies at around 60% of the unit max distance.

BotConfigDialog.favorHigherTMMSliderMin=Disabled
BotConfigDialog.favorHigherTMMSliderMax=Sprinter
BotConfigDialog.favorHigherTMMTitle=Favor Higher TMM
BotConfigDialog.favorHigherTMMToolTip=<EM>Favor Higher TMM:</EM> Prefers to move longer distances or in modes which grants it a higher TMM.



BotConfigDialog.braverySliderMax=I fear nothing!
BotConfigDialog.braverySliderMin=Run Away!
BotConfigDialog.braverySliderTitle=Bravery
Expand Down
1 change: 1 addition & 0 deletions megamek/src/megamek/client/bot/BotClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ public void changePhase(GamePhase phase) {
initialize();
break;
case PREMOVEMENT:

Scoppio marked this conversation as resolved.
Show resolved Hide resolved
break;
case MOVEMENT:
/*
Expand Down
98 changes: 75 additions & 23 deletions megamek/src/megamek/client/bot/princess/BasicPathRanker.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import megamek.client.bot.princess.BotGeometry.CoordFacingCombo;
import megamek.client.bot.princess.BotGeometry.HexLine;
import megamek.client.bot.princess.UnitBehavior.BehaviorType;
import megamek.codeUtilities.MathUtility;
import megamek.common.*;
import megamek.common.options.OptionsConstants;
import megamek.common.planetaryconditions.PlanetaryConditions;
Expand All @@ -41,7 +42,6 @@
*/
public class BasicPathRanker extends PathRanker {
private final static MMLogger logger = MMLogger.create(BasicPathRanker.class);
private static final Logger log = LogManager.getLogger(BasicPathRanker.class);

// this is a value used to indicate how much we value the unit being at its
// destination
Expand Down Expand Up @@ -384,7 +384,7 @@ protected double calculateHerdingMod(Coords friendsCoords, MovePath path) {
return herdingMod;
}

private double calculateFacingMod(Entity movingUnit, Game game, final MovePath path) {
protected double calculateFacingMod(Entity movingUnit, Game game, final MovePath path) {
int facingDiff = getFacingDiff(movingUnit, game, path);
double facingMod = Math.max(0.0, 50 * (facingDiff - 1));
logger.trace("facing mod [-{} = max(0, 50 * ({}) - 1)]", facingMod, facingDiff);
Expand Down Expand Up @@ -566,18 +566,19 @@ protected RankedPath rankPath(MovePath path, Game game, int maxRange, double fal
// The further I am from my teammates, the lower this path
// ranks (weighted by Herd Mentality).
double herdingMod = isNotAirborne ? calculateHerdingMod(friendsCoords, pathCopy) : 0;


var formula = new StringBuilder(512);
var movementModFormula = new StringBuilder(64);
// Movement is good, it gives defense and extends a player power in the game.
double movementMod = calculateMovementMod(pathCopy, game, enemies);
double movementMod = calculateMovementMod(pathCopy, game, enemies, movementModFormula);

// Try to face the enemy.
double facingMod = calculateFacingMod(movingUnit, game, pathCopy);
var formula = new StringBuilder(256);

if (facingMod <= -10000) {
return new RankedPath(facingMod, pathCopy, "Calculation {facing mod[<= -10000]}");
}

var crowdingToleranceFormula = new StringBuilder(64);
double crowdingTolerance = calculateCrowdingTolerance(pathCopy, enemies, crowdingToleranceFormula);
// If I need to flee the board, I want to get closer to my home edge.
double selfPreservationMod= calculateSelfPreservationMod(movingUnit, pathCopy, game);
double offBoardMod = calculateOffBoardMod(pathCopy);
Expand All @@ -589,6 +590,7 @@ protected RankedPath rankPath(MovePath path, Game game, int maxRange, double fal
utility -= aggressionMod;
utility -= herdingMod;
utility += movementMod;
utility -= crowdingTolerance;
utility -= facingMod;
utility -= selfPreservationMod;
utility -= utility * offBoardMod;
Expand Down Expand Up @@ -623,24 +625,28 @@ protected RankedPath rankPath(MovePath path, Game game, int maxRange, double fal
} else {
formula.append("0 no friends");
}
formula
.append("] + movementMod [")
.append(movementMod)
.append("] - facingMod [")
formula.append("]");
if (movementMod != 0.0) {
formula.append(" + ").append(movementModFormula);
}
if (crowdingTolerance != 0.0) {
formula.append(" - ").append(crowdingToleranceFormula);
}

formula.append(" - facingMod [")
.append(LOG_DECIMAL.format(facingMod))
.append(" = max(0, 50 * {")
.append(getFacingDiff(movingUnit, game, pathCopy))
.append(" - 1})]");

logger.trace("utility [{} = - fallMod({}) - offBoard*utility({}) - selfPreservation({}) - facingMod({}) + bravery({}) + movement({}) - aggression({}) - herding({})]",
utility, fallMod, utility * offBoardMod, selfPreservationMod, facingMod, braveryMod, movementMod, aggressionMod, herdingMod);
logger.trace("{}", formula);

RankedPath rankedPath = new RankedPath(utility, pathCopy, formula.toString());
rankedPath.setExpectedDamage(damageEstimate.getMaximumDamageEstimate());
return rankedPath;
}

private double getBraveryMod(double successProbability, FiringPhysicalDamage damageEstimate, double expectedDamageTaken) {
protected double getBraveryMod(double successProbability, FiringPhysicalDamage damageEstimate, double expectedDamageTaken) {
double maximumDamageDone = damageEstimate.getMaximumDamageEstimate();
// My bravery modifier is based on my chance of getting to the
// firing position (successProbability), how much damage I can do
Expand All @@ -653,17 +659,63 @@ private double getBraveryMod(double successProbability, FiringPhysicalDamage dam
}

// Only forces unit to move if there are no units around
private double calculateMovementMod(MovePath pathCopy, Game game, List<Entity> enemies) {
if (!enemies.isEmpty() || !getOwner().getEnemyHotSpots().isEmpty()) {
protected double calculateMovementMod(MovePath pathCopy, Game game, List<Entity> enemies, StringBuilder formula) {
var favorHigherTMM = getOwner().getBehaviorSettings().getFavorHigherTMM();
boolean noEnemiesInSight = enemies.isEmpty() && getOwner().getEnemyHotSpots().isEmpty();
boolean disabledFavorHigherTMM = favorHigherTMM == 0;
if (noEnemiesInSight || !disabledFavorHigherTMM) {
var tmm = Compute.getTargetMovementModifier(pathCopy.getHexesMoved(), pathCopy.isJumping(), pathCopy.isAirborne(), game);
double selfPreservation = getOwner().getBehaviorSettings().getSelfPreservationValue();
var tmmValue = tmm.getValue();
var movementFactor = tmmValue * (selfPreservation + favorHigherTMM);
formula.append("movementMod [").append(movementFactor).append(" = ").append(tmmValue).append(" * (")
.append(selfPreservation).append(" + ").append(favorHigherTMM).append(")]");
logger.trace("movement mod [{} = {} * ({} + {})]", movementFactor, tmmValue, selfPreservation, favorHigherTMM);
return movementFactor;
}
return 0.0;
}

protected double calculateCrowdingTolerance(MovePath movePath, List<Entity> enemies, StringBuilder formula) {
var self = movePath.getEntity();
formula.append(" crowdingTolerance ");
if (!(self instanceof Mek) && !(self instanceof Tank)) {
formula.append("[0 not a Mek or Tank]}");
return 0.0;
}
var distanceMoved = pathCopy.getDistanceTravelled();
var tmm = Compute.getTargetMovementModifier(distanceMoved, pathCopy.isJumping(), pathCopy.isAirborne(), game);
double selfPreservation = getOwner().getBehaviorSettings().getSelfPreservationValue();
var tmmValue = tmm.getValue();
var movementFactor = tmmValue * selfPreservation;
logger.trace("movement mod [{} = {} * {})]", movementFactor, tmmValue, selfPreservation);
return movementFactor;

var antiCrowding = getOwner().getBehaviorSettings().getAntiCrowding();
if (antiCrowding == 0) {
formula.append("[0 antiCrowding is disabled]}");
return 0;
}

var antiCrowdingFactor = (10.0 / (11 - antiCrowding));
final double herdingDistance = Math.ceil(antiCrowding * 1.3);
final double closingDistance = Math.ceil(Math.max(3.0, self.getMaxWeaponRange() * 0.6));

var crowdingFriends = getOwner().getFriendEntities().stream()
.filter(e -> e instanceof Mek || e instanceof Tank)
.filter(Entity::isDeployed)
.map(Entity::getPosition)
.filter(Objects::nonNull)
.filter(c -> c.distance(movePath.getFinalCoords()) <= herdingDistance)
.count();

var crowdingEnemies = enemies.stream()
.filter(e -> e instanceof Mek || e instanceof Tank)
.filter(Entity::isDeployed)
.map(Entity::getPosition)
.filter(Objects::nonNull)
.filter(c -> c.distance(movePath.getFinalCoords()) <= closingDistance)
.count();

double friendsCrowdingTolerance = antiCrowdingFactor * crowdingFriends;
double enemiesCrowdingTolerance = antiCrowdingFactor * crowdingEnemies;
formula.append("[").append(friendsCrowdingTolerance + enemiesCrowdingTolerance).append(" = (")
.append(antiCrowdingFactor).append(" * ").append(crowdingFriends).append(" friends) + (")
.append(antiCrowdingFactor).append(" * ").append(crowdingEnemies).append(" enemies)]");
return friendsCrowdingTolerance + enemiesCrowdingTolerance;
}

/**
Expand Down
Loading
Loading