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

Fix 4520 princess only fires aero tag #5020

Merged
merged 10 commits into from
Jan 13, 2024
2 changes: 1 addition & 1 deletion megamek/src/megamek/client/bot/princess/FireControl.java
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ ToHitData guessToHitModifierHelperForAnyAttack(final Entity shooter,

final int smokeLevel = targetHex.terrainLevel(Terrains.SMOKE);
if (1 <= smokeLevel) {
// Smoke level doesn't necessary correspond to the to-hit modifier
// Smoke level doesn't necessarily correspond to the to-hit modifier
// even levels are light smoke, odd are heavy smoke
toHitData.addModifier((smokeLevel % 2) + 1, TH_SMOKE);
}
Expand Down
373 changes: 224 additions & 149 deletions megamek/src/megamek/client/bot/princess/Princess.java

Large diffs are not rendered by default.

39 changes: 36 additions & 3 deletions megamek/src/megamek/client/bot/princess/WeaponFireInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.*;

/**
* WeaponFireInfo is a wrapper around a WeaponAttackAction that includes
Expand Down Expand Up @@ -412,6 +410,12 @@ private WeaponAttackAction buildBombAttackAction(final HashMap<String, int[]> bo
return 1;
}

// Aero TAG usage needs to take into account incoming Indirect Fire shots from friendlies, homing
// weapons, and the disutility of foregoing its own other weapons.
if (weapon.getEntity().isAero() && weaponType.hasFlag(WeaponType.F_TAG) && !target.isAero()){
return computeAeroExpectedTAGDamage();
}

if (getTarget() instanceof Entity) {
double dmg = Compute.getExpectedDamage(getGame(), getAction(),
true, owner.getPrecognition().getECMInfo());
Expand All @@ -424,6 +428,21 @@ private WeaponAttackAction buildBombAttackAction(final HashMap<String, int[]> bo
return weaponType.getDamage();
}

/**
* Aerospace units need to think carefully before firing TAGs at ground targets, because this
* precludes firing _any_ other weapons this turn.
* @return expected damage of firing a TAG weapon, in light of other options.
*/
double computeAeroExpectedTAGDamage(){
int myWeaponsDamage = Compute.computeTotalDamage(shooter.getTotalWeaponList());

// Get a list of incoming or potentially incoming guidable weapons from the relevant Princess and compute damage.
int incomingAttacksDamage = Compute.computeTotalDamage(owner.computeGuidedWeapons(shooter, target.getPosition()));

// If TAG damage exceeds the attacking unit's own max damage capacity, go for it!
return Math.max(incomingAttacksDamage - myWeaponsDamage, 0);
}

/**
* Compute the heat output by firing a given weapon.
* Contains special logic for bay weapons when using individual bay heat.
Expand Down Expand Up @@ -572,6 +591,20 @@ void initDamage(@Nullable final MovePath shooterPath,
msg.append("\n\tMax Damage: ").append(LOG_DEC.format(maxDamage));
}

// If expected damage from Aero tagging is zero, return out - save attacks for later.
if (weapon.getType().hasFlag(WeaponType.F_TAG) && shooter.isAero() && getExpectedDamageOnHit() <= 0) {
if (debugging) {
LogManager.getLogger().debug(msg.append("\n\tAerospace TAG attack not advised at this juncture"));
}
setProbabilityToHit(0);
setMaxDamage(0);
setHeat(0);
setExpectedCriticals(0);
setKillProbability(0);
setExpectedDamageOnHit(0);
return;
}

final double expectedCriticalHitCount = ProbabilityCalculator.getExpectedCriticalHitCount();

// there's always the chance of rolling a '2'
Expand Down
46 changes: 33 additions & 13 deletions megamek/src/megamek/common/Compute.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import megamek.common.weapons.InfantryAttack;
import megamek.common.weapons.Weapon;
import megamek.common.weapons.artillery.ArtilleryCannonWeapon;
import megamek.common.weapons.battlearmor.ISBAPopUpMineLauncher;
import megamek.common.weapons.bayweapons.BayWeapon;
import megamek.common.weapons.gaussrifles.HAGWeapon;
import megamek.common.weapons.infantry.InfantryWeapon;
Expand Down Expand Up @@ -1105,19 +1106,7 @@ public static ToHitData getRangeMods(Game game, Entity ae, int weaponId,
boolean isAttackerBA = (ae instanceof BattleArmor);
boolean isWeaponInfantry = (wtype instanceof InfantryWeapon) && !wtype.hasFlag(WeaponType.F_TAG);
boolean isSwarmOrLegAttack = (wtype instanceof InfantryAttack);
boolean isIndirect = ((wtype.getAmmoType() == AmmoType.T_LRM)
|| (wtype.getAmmoType() == AmmoType.T_LRM_IMP)
|| (wtype.getAmmoType() == AmmoType.T_MML)
|| (wtype.getAmmoType() == AmmoType.T_EXLRM)
|| (wtype.getAmmoType() == AmmoType.T_TBOLT_5)
|| (wtype.getAmmoType() == AmmoType.T_TBOLT_10)
|| (wtype.getAmmoType() == AmmoType.T_TBOLT_15)
|| (wtype.getAmmoType() == AmmoType.T_TBOLT_20)
|| (wtype.getAmmoType() == AmmoType.T_IATM)
|| (wtype.getAmmoType() == AmmoType.T_LRM_TORPEDO)
|| (wtype.getAmmoType() == AmmoType.T_MEK_MORTAR)
|| (wtype instanceof ArtilleryCannonWeapon))
&& weapon.curMode().equals("Indirect");
boolean isIndirect = wtype.hasIndirectFire() && weapon.curMode().equals("Indirect");
boolean useExtremeRange = game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_RANGE);
boolean useLOSRange = game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_LOS_RANGE);
//Naval C3 only provides full C3 range benefits to energy weapons and guided missiles
Expand Down Expand Up @@ -6487,6 +6476,37 @@ public static int directBlowInfantryDamage(double damage, int mos,
isAttackThruBuilding, attackerId, vReport, 1);
}

/**
* @return the maximum damage that a set of weapons can generate.
*/
public static int computeTotalDamage(List<Mounted> weaponList){
int totalDmg = 0;
for (Mounted weapon : weaponList) {
if (!weapon.isBombMounted() && weapon.isCrippled()) {
continue;
}
WeaponType type = (WeaponType) weapon.getType();
if (type.getDamage() == WeaponType.DAMAGE_VARIABLE) {
// Estimate rather than compute exact bay / trooper damage sum.
totalDmg += type.getRackSize();
} else if (type.getDamage() == WeaponType.DAMAGE_ARTILLERY
|| type.getDamage() == WeaponType.DAMAGE_BY_CLUSTERTABLE) {
totalDmg += type.getRackSize();
} else if (type.getDamage() == WeaponType.DAMAGE_SPECIAL) {// Handle dive bomb attacks here!
if (type instanceof DiveBombAttack) {
totalDmg += weapon.getEntity().bombList.stream().mapToInt(Mounted::getExplosionDamage).sum();
}
if (type instanceof ISBAPopUpMineLauncher) {
totalDmg += 4;
}
} else {
totalDmg += type.getDamage();
}
}

return totalDmg;
}

/**
* Method replicates the Non-Conventional Damage against Infantry damage
* table as well as shifting for direct blows. also adjust for non-infantry
Expand Down
47 changes: 32 additions & 15 deletions megamek/src/megamek/common/Entity.java
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,12 @@ public abstract class Entity extends TurnOrdered implements Transporter, Targeta

private UnitRole role = UnitRole.UNDETERMINED;

/**
* Vector storing references to friendly weapon attack actions this entity may need to support;
* Primarily used by Princess to speed up TAG utility calculations.
*/
protected ArrayList<WeaponAttackAction> incomingGuidedAttacks;

/**
* Generates a new, blank, entity.
*/
Expand Down Expand Up @@ -852,6 +858,7 @@ public Entity() {
externalId = UUID.randomUUID().toString();
initTechAdvancement();
offBoardShotObservers = new HashSet<>();
incomingGuidedAttacks = new ArrayList<WeaponAttackAction>();
}

/**
Expand All @@ -868,30 +875,19 @@ protected void initMilitary() {
}

protected boolean hasViableWeapons() {
int totalDmg = 0;
int totalDmg = Compute.computeTotalDamage(getTotalWeaponList());

// Find any weapons with range of 6+
boolean hasRangeSixPlus = false;
List<Mounted> weaponList = getTotalWeaponList();
for (Mounted weapon : weaponList) {
if (weapon.isCrippled()) {
continue;
}
WeaponType type = (WeaponType) weapon.getType();
if (type.getDamage() == WeaponType.DAMAGE_VARIABLE) {

} else if (type.getDamage() == WeaponType.DAMAGE_ARTILLERY) {
return true;
} else if (type.getDamage() == WeaponType.DAMAGE_BY_CLUSTERTABLE) {
totalDmg += type.getRackSize();
} else if (type.getDamage() == WeaponType.DAMAGE_SPECIAL) {
if (type instanceof ISBAPopUpMineLauncher) {
totalDmg += 4;
}
} else {
totalDmg += type.getDamage();
}

if (type.getLongRange() >= 6) {
hasRangeSixPlus = true;
break;
}
}
return (totalDmg >= 5) || hasRangeSixPlus;
Expand Down Expand Up @@ -6324,6 +6320,13 @@ public void newRound(int roundNumber) {
resetBombAttacks();
}

// Reset deployed or incoming weapons which need TAG guidance (mainly for bot computations)
if (incomingGuidedAttacks == null) {
incomingGuidedAttacks = new ArrayList<WeaponAttackAction>();
} else {
incomingGuidedAttacks.clear();
}

// reset evasion
setEvading(false);

Expand Down Expand Up @@ -12041,6 +12044,20 @@ public int getSensorCheck() {
return sensorCheck;
}

/**
* @param attacksList of know or possible WAAs that might need TAG assistance this turn.
*/
public void setIncomingGuidedAttacks(ArrayList<WeaponAttackAction> attacksList){
incomingGuidedAttacks = (ArrayList<WeaponAttackAction>) attacksList.clone();
}

/**
* @return the vector of possible WAAs that may need this entity's TAG support.
*/
public ArrayList<WeaponAttackAction> getIncomingGuidedAttacks(){
return incomingGuidedAttacks;
}

/**
* A method to determine if an aero has suffered 3 sensor hits.
* When double-blind is on, this affects both standard visibility and sensor rolls
Expand Down
7 changes: 7 additions & 0 deletions megamek/src/megamek/common/WeaponType.java
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,13 @@ public class WeaponType extends EquipmentType {
protected boolean subCapital = false;
protected int atClass = CLASS_NONE;

/**
* @return true if the wtype is able to be fired indirectly.
*/
public boolean canIndirect() {
return false;
}

public void setDamage(int inD) {
damage = inD;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,19 @@ public ArtilleryCannonWeapon() {
atClass = CLASS_AC;
}

@Override
public boolean isAlphaStrikeIndirectFire() {
return false;
}

@Override
public boolean hasIndirectFire() {
return true;
}

/*
* (non-Javadoc)
*
*
* @see
* megamek.common.weapons.Weapon#getCorrectHandler(megamek.common.ToHitData,
* megamek.common.actions.WeaponAttackAction, megamek.common.Game,
Expand All @@ -52,7 +62,7 @@ protected AttackHandler getCorrectHandler(ToHitData toHit,
// game.getEntity(waa.getEntityId()).getEquipment(waa.getWeaponId()).getLinked().getType();
return new ArtilleryCannonWeaponHandler(toHit, waa, game, manager);
}

@Override
public void adaptToGameOptions(GameOptions gOp) {
super.adaptToGameOptions(gOp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,19 @@ public ArtilleryWeapon() {
atClass = CLASS_ARTILLERY;
}

@Override
public boolean isAlphaStrikeIndirectFire() {
return false;
}

@Override
public boolean hasIndirectFire() {
return true;
}

/*
* (non-Javadoc)
*
*
* @see
* megamek.common.weapons.Weapon#getCorrectHandler(megamek.common.ToHitData,
* megamek.common.actions.WeaponAttackAction, megamek.common.Game,
Expand Down
11 changes: 8 additions & 3 deletions megamek/src/megamek/common/weapons/lrms/LRTWeapon.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ public LRTWeapon() {
flags = flags.or(F_ARTEMIS_COMPATIBLE);

}


@Override
public boolean hasIndirectFire() {
return true;
}

@Override
public double getTonnage(Entity entity, int location, double size) {
if ((entity != null) && entity.hasETypeFlag(Entity.ETYPE_PROTOMECH)) {
Expand All @@ -65,12 +70,12 @@ protected AttackHandler getCorrectHandler(ToHitData toHit,
WeaponAttackAction waa, Game game, GameManager manager) {
return new MissileWeaponHandler(toHit, waa, game, manager);
}

@Override
public int getBattleForceClass() {
return BFCLASS_TORP;
}

@Override
public boolean isAlphaStrikeIndirectFire() {
return false;
Expand Down
Loading