From 36fdd7942a328e56f54378f421c08736c1dae00e Mon Sep 17 00:00:00 2001 From: SuperStucco Date: Fri, 21 Jun 2024 10:55:54 -0600 Subject: [PATCH 1/5] Basic outline --- .../megamek/client/bot/princess/Princess.java | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/megamek/src/megamek/client/bot/princess/Princess.java b/megamek/src/megamek/client/bot/princess/Princess.java index 4a1b1151a5f..d53b84c1cd4 100644 --- a/megamek/src/megamek/client/bot/princess/Princess.java +++ b/megamek/src/megamek/client/bot/princess/Princess.java @@ -41,6 +41,7 @@ import megamek.common.util.StringUtil; import megamek.common.weapons.AmmoWeapon; import megamek.common.weapons.StopSwarmAttack; +import megamek.common.weapons.Weapon; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -50,6 +51,7 @@ import java.text.NumberFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; public class Princess extends BotClient { private static final char PLUS = '+'; @@ -1908,6 +1910,7 @@ public void endOfTurnProcessing() { // refreshCrippledUnits should happen after checkForDishonoredEnemies, since checkForDishoneredEnemies // wants to examine the units that were considered crippled at the *beginning* of the turn and were attacked. refreshCrippledUnits(); + setAMSModes(); } @Override @@ -2157,6 +2160,139 @@ private void launchFighters(MovePath path) { } } + /** + * Sets the mode for AMS on each unit this bot controls. This may be on or off, and possibly + * manual fire if the game options allow it, with any change taking effect next round. + * Normal setting is to have the AMS active/automatic. It may be turned off to conserve + * ammo on relatively undamaged units, and laser AMS may be turned off to help reduce + * overheating. Manual use is reserved as an emergency anti-infantry measure. + * + * FIXME: (remote) filter AMS selection on weapons to manual mode only + * FIXME: add tracking list for entity IDs for manual AMS fire + * FIXME: (remote) log entity as using manual AMS fire, same place where targeting is being set + */ + private void setAMSModes() { + + // Get conventional infantry if manual mode is available + List enemyInfantry = new ArrayList<>(); + if (game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_MANUAL_AMS)) { + for (Entity curEnemy : this.getEnemyEntities().stream().filter(Entity::isDeployed).collect(Collectors.toSet())) { + if (curEnemy.getPosition() != null && curEnemy.isVisibleToEnemy()) { + + if (curEnemy instanceof Infantry) { + enemyInfantry.add(curEnemy); + } + + } + } + } + + for (Entity curEntity : this.getEntitiesOwned()) { + if (!curEntity.isDeployed() || curEntity.getPosition() == null) { + continue; + } + + List activeAMS = curEntity. + getWeaponList(). + stream(). + filter(w -> w.getType().hasFlag(AmmoWeapon.F_AMS) && w.hasModes()). + collect(Collectors.toList()); + + if (!activeAMS.isEmpty()) { + + // Set default to on/automatic and test to see if it should be off or manual instead + EquipmentMode newAMSMode = EquipmentMode.getMode(Weapon.MODE_AMS_ON); + + boolean isOverheating = (curEntity instanceof Mech) && (curEntity.getHeat() >= 14); + + // If there are enough nearby enemy infantry (only counted if the game option is + // set), choose manual fire + // FIXME: also set manual if this entity fired AMS manually this round + if (!enemyInfantry.isEmpty() && !curEntity.isAirborne()) { + int infantryRange = enemyInfantry.stream().mapToInt(e -> Compute.effectiveDistance(game, curEntity, e)).min().getAsInt(); + if (infantryRange <= 3) { + newAMSMode = EquipmentMode.getMode(Weapon.MODE_AMS_MANUAL); + } + } + + for (WeaponMounted curAMS : activeAMS) { + + EquipmentMode curMode = curAMS.curMode(); + + + // Turn off laser AMS to help with overheating problems + if (curAMS.getType().hasFlag(WeaponType.F_ENERGY)) { + if (isOverheating) { + newAMSMode = EquipmentMode.getMode(Weapon.MODE_AMS_OFF); + } + } else { + + // Determine if ammo needs to be conserved + boolean conserveAmmo = curAMS.getLinkedAmmo().getUsableShotsLeft() <= (int) Math.floor(curAMS.getOriginalShots() * + behaviorSettings.getSelfPreservationValue() / 100.0); + + // Consider turning off AMS to conserve ammo unless it's needed for infantry + if (conserveAmmo && !newAMSMode.equals(Weapon.MODE_AMS_MANUAL)) { + + int ammoTN = 12 - behaviorSettings.getBraveryIndex(); + + // Fighting a missile boat is more likely to require an active AMS + int lastTargetID = curEntity.getLastTarget(); + if (lastTargetID >= 1) { + Entity lastTarget = game.getEntity(lastTargetID); + if (lastTarget != null && lastTarget.getRole() == UnitRole.MISSILE_BOAT) { + ammoTN += 4; + } + } + + // Heavily damaged units are more likely to require an active AMS than + // lightly damaged ones + switch (curEntity.getDamageLevel()) { + case Entity.DMG_NONE: + ammoTN -= 4; + break; + case Entity.DMG_LIGHT: + ammoTN -= 2; + break; + case Entity.DMG_MODERATE: + ammoTN += 1; + break; + case Entity.DMG_HEAVY: + ammoTN += 4; + case Entity.DMG_CRIPPLED: + ammoTN += 8; + break; + default: + break; + } + + if (ammoTN < 10) { + if (Compute.d6(2) >= ammoTN) { + newAMSMode = EquipmentMode.getMode(Weapon.MODE_AMS_OFF); + } + } + + } + + } + + // Set the mode for the AMS to get the new mode number, and register the change + // with the server + if (!curMode.equals(newAMSMode)) { + int modeNumber = curAMS.setMode(newAMSMode.getName()); + if (modeNumber != -1) { + sendModeChange(curEntity.getId(), curEntity.getEquipmentNum(curAMS), modeNumber); + } + } + + } + + } + + } + + } + public void sendChat(final String message, final Level logLevel) { if (LogManager.getLogger().getLevel().isLessSpecificThan(logLevel)) { super.sendChat(message); From 11e1948de65734fd106ef0bbe1c947e723fd0916 Mon Sep 17 00:00:00 2001 From: SuperStucco Date: Fri, 21 Jun 2024 11:16:25 -0600 Subject: [PATCH 2/5] Add per-round tracking of manual AMS use --- .../megamek/client/bot/princess/Princess.java | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/megamek/src/megamek/client/bot/princess/Princess.java b/megamek/src/megamek/client/bot/princess/Princess.java index d53b84c1cd4..0274ed92a82 100644 --- a/megamek/src/megamek/client/bot/princess/Princess.java +++ b/megamek/src/megamek/client/bot/princess/Princess.java @@ -90,6 +90,9 @@ public class Princess extends BotClient { private final Set attackedWhileFleeing = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set crippledUnits = new HashSet<>(); + // Track entities that fired an AMS manually this round + private List manualAMSIds; + /** * Returns a new Princess Bot with the given behavior and name, configured for the given * host and port. The new Princess Bot outputs its settings to its own logger. @@ -629,6 +632,13 @@ protected void calculateFiringTurn() { double newDamage = existingTargetDamage + shot.getExpectedDamage(); damageMap.put(targetId, newDamage); + // Track manual AMS use each round + if (shot.getWeapon().getType().hasFlag(Weapon.F_AMS)) { + if (shot.getWeapon().curMode().equals(Weapon.MODE_AMS_MANUAL)) { + flagManualAMSUse(shooter.getId()); + } + } + if (shot.getUpdatedFiringMode() != null) { super.sendModeChange(shooter.getId(), shooter.getEquipmentNum(shot.getWeapon()), shot.getUpdatedFiringMode()); } @@ -2207,7 +2217,6 @@ private void setAMSModes() { // If there are enough nearby enemy infantry (only counted if the game option is // set), choose manual fire - // FIXME: also set manual if this entity fired AMS manually this round if (!enemyInfantry.isEmpty() && !curEntity.isAirborne()) { int infantryRange = enemyInfantry.stream().mapToInt(e -> Compute.effectiveDistance(game, curEntity, e)).min().getAsInt(); if (infantryRange <= 3) { @@ -2215,11 +2224,15 @@ private void setAMSModes() { } } + // If AMS was used manually this round, chances are it will be needed next round too + if (usedManualAMS(curEntity.getId())) { + newAMSMode = EquipmentMode.getMode(Weapon.MODE_AMS_MANUAL); + } + for (WeaponMounted curAMS : activeAMS) { EquipmentMode curMode = curAMS.curMode(); - // Turn off laser AMS to help with overheating problems if (curAMS.getType().hasFlag(WeaponType.F_ENERGY)) { if (isOverheating) { @@ -2291,6 +2304,39 @@ private void setAMSModes() { } + // Clear the manual AMS tracking list for next round + clearManualAMSIds(); + } + + /** + * Flag an entity as having used manual AMS this round + * @param id + */ + public void flagManualAMSUse (int id) { + if (manualAMSIds == null) { + manualAMSIds = new ArrayList<>(); + } + if (!manualAMSIds.contains(id)) { + manualAMSIds.add(id); + } + } + + public boolean usedManualAMS (int id) { + if (manualAMSIds == null) { + manualAMSIds = new ArrayList<>(); + return false; + } + return manualAMSIds.contains(id); + } + + /** + * Clear the manual AMS tracking list + */ + public void clearManualAMSIds () { + if (manualAMSIds == null) { + manualAMSIds = new ArrayList<>(); + } + manualAMSIds.clear(); } public void sendChat(final String message, final Level logLevel) { From ba9933878b9200b52ac98fe7b46fb77e35217a98 Mon Sep 17 00:00:00 2001 From: SuperStucco Date: Fri, 21 Jun 2024 11:34:44 -0600 Subject: [PATCH 3/5] Properly filter out manual AMS use when calculating fire --- .../src/megamek/client/bot/princess/FireControl.java | 10 ++++++---- megamek/src/megamek/client/bot/princess/Princess.java | 3 --- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/megamek/src/megamek/client/bot/princess/FireControl.java b/megamek/src/megamek/client/bot/princess/FireControl.java index 7431b08655d..1c2c9d690c0 100644 --- a/megamek/src/megamek/client/bot/princess/FireControl.java +++ b/megamek/src/megamek/client/bot/princess/FireControl.java @@ -1646,8 +1646,9 @@ FiringPlan guessFullFiringPlan(final Entity shooter, // cycle through my weapons for (final WeaponMounted weapon : shooter.getWeaponList()) { // respect restriction on manual AMS firing. - if (!game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_MANUAL_AMS) && - weapon.getType().hasFlag(WeaponType.F_AMS)) { + if (weapon.getType().hasFlag(WeaponType.F_AMS) && + (!game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_MANUAL_AMS) || + !weapon.curMode().equals(Weapon.MODE_AMS_MANUAL))) { continue; } @@ -1969,8 +1970,9 @@ FiringPlan getFullFiringPlan(final Entity shooter, } // respect restriction on manual AMS firing. - if (!game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_MANUAL_AMS) && - weapon.getType().hasFlag(WeaponType.F_AMS)) { + if (weapon.getType().hasFlag(WeaponType.F_AMS) && + (!game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_MANUAL_AMS) || + !weapon.curMode().equals(Weapon.MODE_AMS_MANUAL))) { continue; } diff --git a/megamek/src/megamek/client/bot/princess/Princess.java b/megamek/src/megamek/client/bot/princess/Princess.java index 0274ed92a82..6e0b738bce7 100644 --- a/megamek/src/megamek/client/bot/princess/Princess.java +++ b/megamek/src/megamek/client/bot/princess/Princess.java @@ -2177,9 +2177,6 @@ private void launchFighters(MovePath path) { * ammo on relatively undamaged units, and laser AMS may be turned off to help reduce * overheating. Manual use is reserved as an emergency anti-infantry measure. * - * FIXME: (remote) filter AMS selection on weapons to manual mode only - * FIXME: add tracking list for entity IDs for manual AMS fire - * FIXME: (remote) log entity as using manual AMS fire, same place where targeting is being set */ private void setAMSModes() { From 724dda8ec0ddbee11b1c8bf1235e4f04717e710b Mon Sep 17 00:00:00 2001 From: SuperStucco Date: Fri, 21 Jun 2024 12:32:33 -0600 Subject: [PATCH 4/5] Exclude ejected vehicle crews and MechWarriors as valid targets --- megamek/src/megamek/client/bot/princess/Princess.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/megamek/src/megamek/client/bot/princess/Princess.java b/megamek/src/megamek/client/bot/princess/Princess.java index 6e0b738bce7..f787439fada 100644 --- a/megamek/src/megamek/client/bot/princess/Princess.java +++ b/megamek/src/megamek/client/bot/princess/Princess.java @@ -2186,7 +2186,7 @@ private void setAMSModes() { for (Entity curEnemy : this.getEnemyEntities().stream().filter(Entity::isDeployed).collect(Collectors.toSet())) { if (curEnemy.getPosition() != null && curEnemy.isVisibleToEnemy()) { - if (curEnemy instanceof Infantry) { + if (curEnemy instanceof Infantry && !(curEnemy instanceof EjectedCrew)) { enemyInfantry.add(curEnemy); } @@ -2248,7 +2248,7 @@ private void setAMSModes() { // Fighting a missile boat is more likely to require an active AMS int lastTargetID = curEntity.getLastTarget(); - if (lastTargetID >= 1) { + if (lastTargetID >= 0) { Entity lastTarget = game.getEntity(lastTargetID); if (lastTarget != null && lastTarget.getRole() == UnitRole.MISSILE_BOAT) { ammoTN += 4; From 20c0216ef46e0514f4dc8a599e001193ff89beda Mon Sep 17 00:00:00 2001 From: SuperStucco Date: Fri, 21 Jun 2024 16:01:16 -0600 Subject: [PATCH 5/5] Replace hard-coded overheat level with a constant --- megamek/src/megamek/client/bot/princess/Princess.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/megamek/src/megamek/client/bot/princess/Princess.java b/megamek/src/megamek/client/bot/princess/Princess.java index f787439fada..d2199343c5d 100644 --- a/megamek/src/megamek/client/bot/princess/Princess.java +++ b/megamek/src/megamek/client/bot/princess/Princess.java @@ -57,6 +57,8 @@ public class Princess extends BotClient { private static final char PLUS = '+'; private static final char MINUS = '-'; + private static final int MAX_OVERHEAT_AMS = 14; + private final IHonorUtil honorUtil = new HonorUtil(); private boolean initialized = false; @@ -2210,7 +2212,7 @@ private void setAMSModes() { // Set default to on/automatic and test to see if it should be off or manual instead EquipmentMode newAMSMode = EquipmentMode.getMode(Weapon.MODE_AMS_ON); - boolean isOverheating = (curEntity instanceof Mech) && (curEntity.getHeat() >= 14); + boolean isOverheating = (curEntity instanceof Mech) && (curEntity.getHeat() >= MAX_OVERHEAT_AMS); // If there are enough nearby enemy infantry (only counted if the game option is // set), choose manual fire