Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
13 changes: 11 additions & 2 deletions megamek/mmconf/log4j2.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Configuration status="debug">
<Configuration status="error">
<Properties>
<Property name="layout">%d{ABSOLUTE} %-5p [%logger{36}] {%t}%n%l - %m%n%ex%n</Property>
<Property name="tsvLayout">%d{ABSOLUTE}\t%p\t[%logger{36}]\t{%t}\t%l\t%m\t%ex%n</Property>
Expand Down Expand Up @@ -58,14 +58,23 @@
</Appenders>

<Loggers>
<!-- Suppress noisy Precognition lock logging -->
<Logger name="megamek.client.bot.princess.Precognition" level="info" additivity="false">
<AppenderRef ref="UnifiedLog" />
</Logger>
<!-- Princess bot debug output - shows hidden unit reveal decisions, firing plans, etc. -->
<Logger name="megamek.client.bot.princess" level="debug" additivity="false">
<AppenderRef ref="dev" />
<AppenderRef ref="UnifiedLog" />
</Logger>
<Logger name="megamek.client.bot" level="error" additivity="false">
<AppenderRef ref="${env:mm.profile:-null}" />
<AppenderRef ref="UnifiedLog" />
</Logger>
<Logger name="BotLogger" level="info" additivity="false">
<AppenderRef ref="BotLog" />
</Logger>
<Logger name="megamek" level="debug" additivity="false">
<Logger name="megamek" level="error" additivity="false">
<AppenderRef ref="${env:mm.profile:-null}" />
<AppenderRef ref="UnifiedLog" />
<AppenderRef ref="MegaMekLog" />
Expand Down
3 changes: 3 additions & 0 deletions megamek/mmconf/princessBehaviors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
<selfPreservationIndex>10</selfPreservationIndex>
<herdMentalityIndex>4</herdMentalityIndex>
<braveryIndex>8</braveryIndex>
<hiddenUnitRevealIndex>2</hiddenUnitRevealIndex>
<verbosity>WARNING</verbosity>
<strategicBuildingTargets />
</behavior>
Expand All @@ -162,6 +163,7 @@
<selfPreservationIndex>5</selfPreservationIndex>
<herdMentalityIndex>5</herdMentalityIndex>
<braveryIndex>5</braveryIndex>
<hiddenUnitRevealIndex>5</hiddenUnitRevealIndex>
<verbosity>WARNING</verbosity>
<strategicBuildingTargets />
</behavior>
Expand Down Expand Up @@ -192,6 +194,7 @@
<selfPreservationIndex>10</selfPreservationIndex>
<herdMentalityIndex>8</herdMentalityIndex>
<braveryIndex>2</braveryIndex>
<hiddenUnitRevealIndex>1</hiddenUnitRevealIndex>
<verbosity>WARNING</verbosity>
<strategicBuildingTargets />
</behavior>
Expand Down
1 change: 1 addition & 0 deletions megamek/resources/megamek/client/bot/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,4 @@ Princess.command.artillery.order=Artillery order issued. Halt: orders the artill
Princess.command.artillery.ammo=Special type of ammo to use. (Optional, default is None, it will use the best available ammo)
Princess.command.artillery.noTargets=No targets were provided.
Princess.command.artillery.description=Artillery - Orders off-board artillery to fire at the specified hexes.
Princess.command.reveal.description=Reveal - Forces all hidden units to reveal themselves. Useful for testing.
4 changes: 4 additions & 0 deletions megamek/resources/megamek/client/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4878,6 +4878,10 @@ BotConfigDialog.braverySliderMax=I fear nothing!
BotConfigDialog.braverySliderMin=Run Away!
BotConfigDialog.braverySliderTitle=Bravery {0}
BotConfigDialog.braveryTooltip=<EM>Bravery:</EM> How worried about enemy damage am I?
BotConfigDialog.hiddenUnitRevealSliderMax=Ambush Freely
BotConfigDialog.hiddenUnitRevealSliderMin=Stay Hidden
BotConfigDialog.hiddenUnitRevealSliderTitle=Hidden Unit Reveal {0}
BotConfigDialog.hiddenUnitRevealTooltip=<EM>Hidden Unit Reveal:</EM> How willing am I to reveal hidden units to attack?<BR>Lower values keep units hidden longer. Higher values reveal for smaller opportunities.
BotConfigDialog.targetsLabel=Strategic Targets
BotConfigDialog.addUnitTarget=+ Add Unit
BotConfigDialog.addHexTarget=+ Add Hex
Expand Down
12 changes: 12 additions & 0 deletions megamek/src/megamek/client/bot/BotClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,10 @@ public void changePhase(GamePhase phase) {
case DEPLOYMENT:
initialize();
break;
case PREMOVEMENT:
evaluateHiddenUnitRepositioning();
sendDone(true);
break;
case MOVEMENT:
/*
* Do not uncomment this. It is so that bots stick around till end of game
Expand Down Expand Up @@ -521,6 +525,14 @@ public void changePhase(GamePhase phase) {

protected abstract void postMovementProcessing();

/**
* Evaluates whether hidden units should reveal to reposition for better firing opportunities. Override in
* subclasses to implement intelligent hidden unit repositioning. Default implementation does nothing.
*/
protected void evaluateHiddenUnitRepositioning() {
// Default implementation - subclasses can override
}

private void runEndGame() {
// Make a list of the player's living units.
ArrayList<Entity> living = game.getPlayerEntities(getLocalPlayer(), false);
Expand Down
80 changes: 80 additions & 0 deletions megamek/src/megamek/client/bot/princess/BehaviorSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,22 @@ public class BehaviorSettings implements Serializable {
1.8,
2.0 };

// Hidden unit reveal thresholds - higher index = more willing to reveal
// Values represent minimum (expectedDamage / selfPreservation) ratio to justify revealing
static final double[] HIDDEN_REVEAL_VALUES = {
0.0, // 0: Never reveal
0.5, // 1: Very conservative
0.75, // 2: Conservative
1.0, // 3: Cautious
1.25, // 4: Moderate-conservative
1.5, // 5: Moderate (default)
2.0, // 6: Moderate-aggressive
2.5, // 7: Aggressive
3.0, // 8: Very aggressive
4.0, // 9: Reckless
Double.MAX_VALUE // 10: Always reveal if any targets exist
};

public static final int MAX_NUMBER_OF_ENEMIES_TO_CONSIDER_FACING = 12;
public static final int MIN_NUMBER_OF_ENEMIES_TO_CONSIDER_FACING = 1;
public static final int DEFAULT_NUMBER_OF_ENEMIES_TO_CONSIDER_FACING = 4;
Expand All @@ -146,6 +162,7 @@ public class BehaviorSettings implements Serializable {
private final Set<Integer> priorityUnitTargets = new HashSet<>(); // What units do I especially want to blow up?
private int herdMentalityIndex = 5; // How close do I want to stick to my teammates?
private int braveryIndex = 5; // How quickly will I try to escape once damaged?
private int hiddenUnitRevealIndex = 5; // How willing to reveal hidden units to fire?
private int antiCrowding = 0; // How much do I want to avoid crowding my teammates?
private int favorHigherTMM = 0; // How much do I want to favor moving in my turn?
private int numberOfEnemiesToConsiderFacing = DEFAULT_NUMBER_OF_ENEMIES_TO_CONSIDER_FACING; // How many enemies do I want to consider when calculating facing?
Expand Down Expand Up @@ -174,6 +191,7 @@ public BehaviorSettings getCopy() throws PrincessException {
copy.setDescription(getDescription());
copy.setFallShameIndex(getFallShameIndex());
copy.setBraveryIndex(getBraveryIndex());
copy.setHiddenUnitRevealIndex(getHiddenUnitRevealIndex());
copy.setHerdMentalityIndex(getHerdMentalityIndex());
copy.setHyperAggressionIndex(getHyperAggressionIndex());
copy.setSelfPreservationIndex(getSelfPreservationIndex());
Expand Down Expand Up @@ -508,6 +526,57 @@ public void setBraveryIndex(final String index) throws PrincessException {
}
}

/**
* How willing am I to reveal hidden units to fire?
*
* @return Index of the current hidden unit reveal value.
*/
public int getHiddenUnitRevealIndex() {
return hiddenUnitRevealIndex;
}

/**
* How willing am I to reveal hidden units to fire?
*
* @return Current hidden unit reveal threshold value.
*/
public double getHiddenUnitRevealValue() {
return getHiddenUnitRevealValue(hiddenUnitRevealIndex);
}

/**
* How willing am I to reveal hidden units to fire?
*
* @param index The index [0-10] of the hidden unit reveal value desired.
*
* @return The hidden unit reveal threshold at the specified index.
*/
public double getHiddenUnitRevealValue(int index) {
return HIDDEN_REVEAL_VALUES[validateIndex(index)];
}

/**
* How willing am I to reveal hidden units to fire?
*
* @param index The index [0-10] of the hidden unit reveal value to be used.
*/
public void setHiddenUnitRevealIndex(final int index) {
this.hiddenUnitRevealIndex = validateIndex(index);
}

/**
* How willing am I to reveal hidden units to fire?
*
* @param index The index ["0"-"10"] of the hidden unit reveal value to be used.
*/
public void setHiddenUnitRevealIndex(final String index) throws PrincessException {
try {
setHiddenUnitRevealIndex(Integer.parseInt(index));
} catch (final NumberFormatException ex) {
throw new PrincessException(ex);
}
}

/**
* @return The index of my current {@link #FALL_SHAME_VALUES}.
*/
Expand Down Expand Up @@ -898,6 +967,8 @@ boolean fromXml(final Element behavior) throws PrincessException {
setHerdMentalityIndex(child.getTextContent());
} else if ("braveryIndex".equalsIgnoreCase(child.getNodeName())) {
setBraveryIndex(child.getTextContent());
} else if ("hiddenUnitRevealIndex".equalsIgnoreCase(child.getNodeName())) {
setHiddenUnitRevealIndex(child.getTextContent());
} else if ("antiCrowding".equalsIgnoreCase(child.getNodeName())) {
setAntiCrowding(child.getTextContent());
} else if ("favorHigherTMM".equalsIgnoreCase(child.getNodeName())) {
Expand Down Expand Up @@ -993,6 +1064,10 @@ Element toXml(final Document doc,
braveryNode.setTextContent("" + getBraveryIndex());
behavior.appendChild(braveryNode);

final Element hiddenUnitRevealNode = doc.createElement("hiddenUnitRevealIndex");
hiddenUnitRevealNode.setTextContent("" + getHiddenUnitRevealIndex());
behavior.appendChild(hiddenUnitRevealNode);

final Element antiCrowdingNode = doc.createElement("antiCrowding");
antiCrowdingNode.setTextContent("" + getAntiCrowding());
behavior.appendChild(antiCrowdingNode);
Expand Down Expand Up @@ -1069,6 +1144,8 @@ public String toLog() {
out.append("\n\t Fall Shame: ").append(getFallShameIndex()).append(":")
.append(getFallShameValue(getFallShameIndex()));
out.append("\n\t Bravery: ").append(getBraveryIndex()).append(":").append(getBraveryValue(getBraveryIndex()));
out.append("\n\t Hidden Unit Reveal: ").append(getHiddenUnitRevealIndex()).append(":")
.append(getHiddenUnitRevealValue(getHiddenUnitRevealIndex()));
out.append("\n\t AntiCrowding: ").append(getAntiCrowding());
out.append("\n\t FavorHigherTMM: ").append(getFavorHigherTMM());
out.append("\n\t NumberOfEnemiesToConsiderFacing: ").append(getNumberOfEnemiesToConsiderFacing());
Expand Down Expand Up @@ -1108,6 +1185,8 @@ public boolean equals(final Object o) {
return false;
} else if (braveryIndex != that.braveryIndex) {
return false;
} else if (hiddenUnitRevealIndex != that.hiddenUnitRevealIndex) {
return false;
} else if (fallShameIndex != that.fallShameIndex) {
return false;
} else if (forcedWithdrawal != that.forcedWithdrawal) {
Expand Down Expand Up @@ -1167,6 +1246,7 @@ public int hashCode() {
result = 31 * result + allowFacingTolerance;
result = 31 * result + herdMentalityIndex;
result = 31 * result + braveryIndex;
result = 31 * result + hiddenUnitRevealIndex;
result = 31 * result + (exclusiveHerding ? 1 : 0);
result = 31 * result + (iAmAPirate ? 1 : 0);
result = 31 * result + (experimental ? 1 : 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ private BehaviorSettings buildBerserkBehavior() {
berserkBehavior.setSelfPreservationIndex(2);
berserkBehavior.setHerdMentalityIndex(5);
berserkBehavior.setBraveryIndex(9);
berserkBehavior.setHiddenUnitRevealIndex(9);
berserkBehavior.setAntiCrowding(0);
berserkBehavior.setFavorHigherTMM(0);
berserkBehavior.setNumberOfEnemiesToConsiderFacing(BehaviorSettings.DEFAULT_NUMBER_OF_ENEMIES_TO_CONSIDER_FACING);
Expand Down Expand Up @@ -355,6 +356,7 @@ private BehaviorSettings buildCowardlyBehavior() {
cowardlyBehavior.setSelfPreservationIndex(10);
cowardlyBehavior.setHerdMentalityIndex(8);
cowardlyBehavior.setBraveryIndex(2);
cowardlyBehavior.setHiddenUnitRevealIndex(1);
cowardlyBehavior.setAntiCrowding(0);
cowardlyBehavior.setFavorHigherTMM(0);
cowardlyBehavior.setNumberOfEnemiesToConsiderFacing(BehaviorSettings.DEFAULT_NUMBER_OF_ENEMIES_TO_CONSIDER_FACING);
Expand Down Expand Up @@ -390,6 +392,7 @@ private BehaviorSettings buildEscapeBehavior() {
escapeBehavior.setSelfPreservationIndex(10);
escapeBehavior.setHerdMentalityIndex(5);
escapeBehavior.setBraveryIndex(2);
escapeBehavior.setHiddenUnitRevealIndex(2);
escapeBehavior.setAntiCrowding(0);
escapeBehavior.setFavorHigherTMM(0);
escapeBehavior.setNumberOfEnemiesToConsiderFacing(BehaviorSettings.DEFAULT_NUMBER_OF_ENEMIES_TO_CONSIDER_FACING);
Expand Down Expand Up @@ -425,6 +428,7 @@ private BehaviorSettings buildRuthlessBehavior() {
ruthlessBehavior.setSelfPreservationIndex(10);
ruthlessBehavior.setHerdMentalityIndex(1);
ruthlessBehavior.setBraveryIndex(7);
ruthlessBehavior.setHiddenUnitRevealIndex(7);
ruthlessBehavior.setAntiCrowding(10);
ruthlessBehavior.setFavorHigherTMM(8);
ruthlessBehavior.setNumberOfEnemiesToConsiderFacing(2);
Expand Down Expand Up @@ -460,6 +464,7 @@ private BehaviorSettings buildPirateBehavior() {
pirateBehavior.setSelfPreservationIndex(6);
pirateBehavior.setHerdMentalityIndex(9);
pirateBehavior.setBraveryIndex(10);
pirateBehavior.setHiddenUnitRevealIndex(8);
pirateBehavior.setAntiCrowding(5);
pirateBehavior.setFavorHigherTMM(5);
pirateBehavior.setIAmAPirate(true);
Expand Down Expand Up @@ -498,6 +503,7 @@ private BehaviorSettings buildConvoyBehavior() {
convoyBehavior.setFallShameIndex(6);
convoyBehavior.setHyperAggressionIndex(3);
convoyBehavior.setBraveryIndex(2);
convoyBehavior.setHiddenUnitRevealIndex(0);
convoyBehavior.setSelfPreservationIndex(10);
convoyBehavior.setHerdMentalityIndex(5);
convoyBehavior.setAntiCrowding(5);
Expand Down Expand Up @@ -538,6 +544,7 @@ private BehaviorSettings buildDefaultBehavior() {
defaultBehavior.setSelfPreservationIndex(5);
defaultBehavior.setHerdMentalityIndex(5);
defaultBehavior.setBraveryIndex(5);
defaultBehavior.setHiddenUnitRevealIndex(5);
defaultBehavior.setAntiCrowding(0);
defaultBehavior.setFavorHigherTMM(0);
defaultBehavior.setNumberOfEnemiesToConsiderFacing(BehaviorSettings.DEFAULT_NUMBER_OF_ENEMIES_TO_CONSIDER_FACING);
Expand Down
6 changes: 5 additions & 1 deletion megamek/src/megamek/client/bot/princess/ChatCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ public enum ChatCommands {
SET_WAYPOINT("sw",
"set-waypoints",
Messages.getString("Princess.command.setWaypoints.description"),
new SetWaypointsCommand());
new SetWaypointsCommand()),
REVEAL("rv",
"reveal",
Messages.getString("Princess.command.reveal.description"),
new RevealCommand());

private final String abbreviation;
private final String command;
Expand Down
46 changes: 38 additions & 8 deletions megamek/src/megamek/client/bot/princess/EnemyTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,23 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import megamek.common.compute.Compute;
import megamek.common.ToHitData;
import megamek.common.actions.WeaponAttackAction;
import megamek.common.board.Coords;
import megamek.common.units.Entity;
import megamek.common.compute.Compute;
import megamek.common.game.Game;
import megamek.common.units.Entity;
import megamek.common.units.Targetable;
import megamek.common.ToHitData;
import megamek.common.actions.WeaponAttackAction;
import megamek.logging.MMLogger;

/**
* Tracks enemy units and calculates threat scores based on their positions and capabilities.
*
* @author Luana Coppio
*/
public class EnemyTracker {
private static final MMLogger LOGGER = MMLogger.create(EnemyTracker.class);

private final Map<Integer, EnemyProfile> enemyProfiles = new HashMap<>();

private final Princess owner;
Expand Down Expand Up @@ -165,9 +168,16 @@ public void updateThreatAssessment(Coords currentSwarmCenter) {
&& enemyProfiles.size() == visibleEnemies.size()) {
return;
}

LOGGER.debug("Round {}: Updating threat assessment - {} visible enemies",
currentRound, visibleEnemies.size());

lastRoundUpdate = getOwner().getGame().getCurrentRound();
int previousProfileCount = enemyProfiles.size();

// 1. Update known enemy positions/states
visibleEnemies.forEach(enemy -> {
boolean isNewEnemy = !enemyProfiles.containsKey(enemy.getId());
EnemyProfile profile = enemyProfiles.computeIfAbsent(enemy.getId(),
id -> new EnemyProfile(enemy));

Expand All @@ -183,18 +193,38 @@ public void updateThreatAssessment(Coords currentSwarmCenter) {
averageDamagePotential,
currentRound
);

if (isNewEnemy) {
LOGGER.debug(" [NEW] Detected enemy: {} at {} (avg damage potential: {})",
enemy.getDisplayName(), enemy.getPosition(), String.format("%.1f", averageDamagePotential));
} else {
LOGGER.debug(" [UPDATE] Tracking enemy: {} at {} (avg damage potential: {})",
enemy.getDisplayName(), enemy.getPosition(), String.format("%.1f", averageDamagePotential));
}
});

// remove all the dead entities and the entities that have not been seen for 3 turns
enemyProfiles.entrySet().removeIf(entry ->
(owner.getGame().getEntity(entry.getValue().id()) == null)
|| (entry.getValue().getLastSeenTurn() < currentRound - 3)
);
enemyProfiles.entrySet().removeIf(entry -> {
Entity entity = owner.getGame().getEntity(entry.getValue().id());
boolean shouldRemove = (entity == null)
|| (entry.getValue().getLastSeenTurn() < currentRound - 3);
if (shouldRemove && entity != null) {
LOGGER.debug(" [LOST] Lost track of enemy: {} (last seen round {})",
entity.getDisplayName(), entry.getValue().getLastSeenTurn());
}
return shouldRemove;
});

// 3. Calculate threat scores
enemyProfiles.values().forEach(profile ->
profile.calculateThreatScore(currentSwarmCenter)
);

int newEnemies = enemyProfiles.size() - previousProfileCount;
if (newEnemies > 0) {
LOGGER.debug("Round {}: Now tracking {} enemies ({} new this round)",
currentRound, enemyProfiles.size(), newEnemies);
}
}

private static class EnemyProfile implements Comparable<Double> {
Expand Down
Loading