Skip to content

Commit

Permalink
Merge pull request #6276 from psikomonkie/issue-4259-princess-doesnt-…
Browse files Browse the repository at this point in the history
…fire-all-weapons

Issue 4259: Fixing Dropship Heat-by-Arc for Players and Princess
  • Loading branch information
Sleet01 authored Dec 18, 2024
2 parents a8aea8b + 6b9f1ee commit 0f3db43
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 67 deletions.
36 changes: 25 additions & 11 deletions megamek/src/megamek/client/bot/princess/MultiTargetFireControl.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import megamek.common.Entity;
import megamek.common.Game;
import megamek.common.Mounted;
import megamek.common.Targetable;
import megamek.common.equipment.AmmoMounted;
import megamek.common.equipment.WeaponMounted;
Expand Down Expand Up @@ -259,6 +260,19 @@ FiringPlan calculatePerArcFiringPlan(Entity shooter, List<WeaponFireInfo> shotLi
// damage values by arc: arc #, arc damage
Map<Integer, Double> arcDamage = new HashMap<>();

int heatCapacity = shooter.getHeatCapacity();
// account for artillery fired during the Targeting (offboard) phase; we reduce the heat capacity and treat
// the arc's heat as 0 because this arc has already fired - so it can be included in the solution for "free"
for ( WeaponMounted weapon : shooter.getWeaponList().stream().filter(Mounted::isUsedThisRound).toList()) {
int arc = weapon.isRearMounted() ? -shooter.getWeaponArc(weapon.getLocation()) : shooter.getWeaponArc(weapon.getLocation());
if (!arcShots.containsKey(arc)) {
heatCapacity -= shooter.getHeatInArc(weapon.getLocation(), weapon.isRearMounted());
arcShots.put(arc, new ArrayList<>());
arcHeat.put(arc, 0);
arcDamage.put(arc, 1.0);
}
}

// assemble the data we'll need to solve the backpack problem
for (WeaponFireInfo shot : shotList) {
int arc = shooter.getWeaponArc(shooter.getEquipmentNum(shot.getWeapon()));
Expand All @@ -280,31 +294,31 @@ FiringPlan calculatePerArcFiringPlan(Entity shooter, List<WeaponFireInfo> shotLi

// initialize the backpack
Map<Integer, Map<Integer, List<Integer>>> arcBackpack = new HashMap<>();
for (int x = 0; x < arcShots.keySet().size(); x++) {
for (int x = 0; x <= arcShots.keySet().size(); x++) {
arcBackpack.put(x, new HashMap<>());

for (int y = 0; y < shooter.getHeatCapacity(); y++) {
for (int y = 0; y <= heatCapacity; y++) {
arcBackpack.get(x).put(y, new ArrayList<>());
}
}

double[][] damageBackpack = new double[arcShots.keySet().size()][shooter.getHeatCapacity()];
double[][] damageBackpack = new double[arcShots.keySet().size() + 1][heatCapacity + 1];
Integer[] arcHeatKeyArray = new Integer[arcHeat.keySet().size()];
System.arraycopy(arcHeat.keySet().toArray(), 0, arcHeatKeyArray, 0, arcHeat.keySet().size());

// now, we essentially solve the backpack problem, where the arcs are the items:
// arc expected damage is the "value", and arc heat is the "weight", while the
// backpack capacity is the unit's heat capacity.
// while we're at it, we assemble the list of arcs fired for each cell
for (int arcIndex = 0; arcIndex < arcHeatKeyArray.length; arcIndex++) {
for (int heatIndex = 0; heatIndex < shooter.getHeatCapacity(); heatIndex++) {
int previousArc = arcIndex > 0 ? arcHeatKeyArray[arcIndex - 1] : 0;
for (int arcIndex = 0; arcIndex <= arcHeatKeyArray.length; arcIndex++) {
for (int heatIndex = 0; heatIndex <= heatCapacity; heatIndex++) {
int currentArc = arcIndex > 0 ? arcHeatKeyArray[arcIndex - 1] : 0;

if (arcIndex == 0 || heatIndex == 0) {
damageBackpack[arcIndex][heatIndex] = 0;
} else if (arcHeat.get(previousArc) <= heatIndex) {
int previousHeatIndex = heatIndex - arcHeat.get(previousArc);
double currentArcDamage = arcDamage.get(previousArc)
} else if (arcHeat.get(currentArc) <= heatIndex) {
int previousHeatIndex = heatIndex - arcHeat.get(currentArc);
double currentArcDamage = arcDamage.get(currentArc)
+ damageBackpack[arcIndex - 1][previousHeatIndex];
double accumulatedPreviousArcDamage = damageBackpack[arcIndex - 1][heatIndex];

Expand All @@ -315,7 +329,7 @@ FiringPlan calculatePerArcFiringPlan(Entity shooter, List<WeaponFireInfo> shotLi
// make sure we don't accidentally update the cell we're examining
List<Integer> appendedArcList = new ArrayList<>(
arcBackpack.get(arcIndex - 1).get(previousHeatIndex));
appendedArcList.add(previousArc);
appendedArcList.add(currentArc);
arcBackpack.get(arcIndex).put(heatIndex, appendedArcList);
} else {
// we *can* add this arc to the list, but it won't take us past the damage
Expand All @@ -336,7 +350,7 @@ FiringPlan calculatePerArcFiringPlan(Entity shooter, List<WeaponFireInfo> shotLi
// solution
// unless there is no firing solution at all, in which case we skip this part
if (!arcBackpack.isEmpty()) {
for (int arc : arcBackpack.get(arcBackpack.size() - 1).get(shooter.getHeatCapacity() - 1)) {
for (int arc : arcBackpack.get(arcBackpack.size() - 1).get(heatCapacity - 1)) {
retVal.addAll(arcShots.get(arc));
}
}
Expand Down
22 changes: 13 additions & 9 deletions megamek/src/megamek/client/ui/swing/unitDisplay/WeaponPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -1195,12 +1195,10 @@ public void displayMek(Entity en) {
&& game.getPhase().isFiring()) {
hasFiredWeapons = true;
// add heat from weapons fire to heat tracker
if (entity.usesWeaponBays()) {
if (entity.isLargeCraft()) {
// if using bay heat option then don't add total arc
if (game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_HEAT_BY_BAY)) {
for (WeaponMounted weapon : mounted.getBayWeapons()) {
currentHeatBuildup += weapon.getCurrentHeat();
}
currentHeatBuildup += mounted.getHeatByBay();
} else {
// check whether arc has fired
int loc = mounted.getLocation();
Expand All @@ -1219,7 +1217,7 @@ public void displayMek(Entity en) {
}
} else {
if (!mounted.isBombMounted()) {
currentHeatBuildup += mounted.getCurrentHeat();
currentHeatBuildup += mounted.getHeatByBay();
}
}
}
Expand Down Expand Up @@ -1257,16 +1255,22 @@ public void displayMek(Entity en) {

// change what is visible based on type
if (entity.usesWeaponBays()) {
wArcHeatL.setVisible(true);
wArcHeatR.setVisible(true);
m_chBayWeapon.setVisible(true);
wBayWeapon.setVisible(true);
} else {
wArcHeatL.setVisible(false);
wArcHeatR.setVisible(false);
m_chBayWeapon.setVisible(false);
wBayWeapon.setVisible(false);
}
if ((!entity.isLargeCraft())
|| ((game != null) && (game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_HEAT_BY_BAY)))){
wArcHeatL.setVisible(false);
wArcHeatR.setVisible(false);
}
else {
wArcHeatL.setVisible(true);
wArcHeatR.setVisible(true);
}


wDamageTrooperL.setVisible(false);
wDamageTrooperR.setVisible(false);
Expand Down
42 changes: 20 additions & 22 deletions megamek/src/megamek/common/actions/WeaponAttackAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,7 @@ private static String toHitIsImpossible(Game game, Entity ae, int attackerId, Ta
}

// are we bracing a location that's not where the weapon is located?
if (ae.isBracing() && (ae.braceLocation() != weapon.getLocation())) {
if (ae.isBracing() && weapon != null && (ae.braceLocation() != weapon.getLocation())) {
return String.format(Messages.getString("WeaponAttackAction.BracingOtherLocation"),
ae.getLocationName(ae.braceLocation()), ae.getLocationName(weapon.getLocation()));
}
Expand Down Expand Up @@ -1486,28 +1486,30 @@ private static String toHitIsImpossible(Game game, Entity ae, int attackerId, Ta

// limit large craft to zero net heat and to heat by arc
final int heatCapacity = ae.getHeatCapacity();
if (ae.usesWeaponBays() && (weapon != null) && !weapon.getBayWeapons().isEmpty()) {
if (ae.isLargeCraft() && (weapon != null)) {
int totalHeat = 0;

// first check to see if there are any usable weapons
boolean usable = false;
for (WeaponMounted m : weapon.getBayWeapons()) {
WeaponType bayWType = m.getType();
boolean bayWUsesAmmo = (bayWType.getAmmoType() != AmmoType.T_NA);
if (m.canFire()) {
if (bayWUsesAmmo) {
if ((m.getLinked() != null) && (m.getLinked().getUsableShotsLeft() > 0)) {
// first check to see if there are any usable bay weapons
if (!weapon.getBayWeapons().isEmpty()) {
boolean usable = false;
for (WeaponMounted m : weapon.getBayWeapons()) {
WeaponType bayWType = m.getType();
boolean bayWUsesAmmo = (bayWType.getAmmoType() != AmmoType.T_NA);
if (m.canFire()) {
if (bayWUsesAmmo) {
if ((m.getLinked() != null) && (m.getLinked().getUsableShotsLeft() > 0)) {
usable = true;
break;
}
} else {
usable = true;
break;
}
} else {
usable = true;
break;
}
}
}
if (!usable) {
return Messages.getString("WeaponAttackAction.BayNotReady");
if (!usable) {
return Messages.getString("WeaponAttackAction.BayNotReady");
}
}

// create an array of booleans of locations
Expand All @@ -1534,9 +1536,7 @@ private static String toHitIsImpossible(Game game, Entity ae, int attackerId, Ta
int loc = prevWeapon.getLocation();
boolean rearMount = prevWeapon.isRearMounted();
if (game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_HEAT_BY_BAY)) {
for (WeaponMounted bWeapon : prevWeapon.getBayWeapons()) {
totalHeat += bWeapon.getCurrentHeat();
}
totalHeat += prevWeapon.getHeatByBay();
} else {
if (!rearMount) {
if (!usedFrontArc[loc]) {
Expand All @@ -1560,9 +1560,7 @@ private static String toHitIsImpossible(Game game, Entity ae, int attackerId, Ta
int currentHeat = ae.getHeatInArc(loc, rearMount);
if (game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_HEAT_BY_BAY)) {
currentHeat = 0;
for (WeaponMounted bWeapon : weapon.getBayWeapons()) {
currentHeat += bWeapon.getCurrentHeat();
}
currentHeat += weapon.getHeatByBay();
}
// check to see if this is currently the only arc being fired
boolean onlyArc = true;
Expand Down
14 changes: 14 additions & 0 deletions megamek/src/megamek/common/equipment/WeaponMounted.java
Original file line number Diff line number Diff line change
Expand Up @@ -372,4 +372,18 @@ && getType().getInternalName().equals("ISBAAPDS")) {
return getType().getAmmoType() == AmmoType.T_APDS;
}
}

/**
* @return The heat generated by the bay, or the heat of the individual weapon if it's not a bay
*/
public int getHeatByBay() {
int heat = 0;
if (!getBayWeapons().isEmpty()) {
heat = getBayWeapons().stream().mapToInt(WeaponMounted::getCurrentHeat).sum();
}
else {
heat += getCurrentHeat();
}
return heat;
}
}
63 changes: 38 additions & 25 deletions megamek/src/megamek/common/weapons/WeaponHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,38 +147,51 @@ public int getSalvoBonus() {
* during the round
* Used to determine whether point defenses can engage.
*
* @param e the entity you wish to get heat data from
* @param entity the entity you wish to get heat data from
* @see TeleMissileAttackAction which contains a modified version of this to
* work against a
* TeleMissile entity in the physical phase
*/
protected int getLargeCraftHeat(Entity e) {
int totalheat = 0;
if (e.hasETypeFlag(Entity.ETYPE_DROPSHIP)
|| e.hasETypeFlag(Entity.ETYPE_JUMPSHIP)) {
if (e.usesWeaponBays()) {
for (Enumeration<AttackHandler> i = game.getAttacks(); i.hasMoreElements();) {
AttackHandler ah = i.nextElement();
WeaponAttackAction prevAttack = ah.getWaa();
if (prevAttack.getEntityId() == e.getId()) {
WeaponMounted prevWeapon = (WeaponMounted) e.getEquipment(prevAttack.getWeaponId());
for (WeaponMounted bayW : prevWeapon.getBayWeapons()) {
totalheat += bayW.getCurrentHeat();
}
protected int getLargeCraftHeat(Entity entity) {
int totalHeat = 0;

if (!entity.isLargeCraft()) {
return totalHeat;
}

for (Enumeration<AttackHandler> attack = game.getAttacks(); attack.hasMoreElements(); ) {
AttackHandler attackHandler = attack.nextElement();
WeaponAttackAction prevAttack = attackHandler.getWaa();
if (prevAttack.getEntityId() == entity.getId()) {
WeaponMounted prevWeapon = (WeaponMounted) entity.getEquipment(prevAttack.getWeaponId());
if (!game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_HEAT_BY_BAY)) {
totalHeat += prevWeapon.getHeatByBay();
} else {
boolean rearMount = prevWeapon.isRearMounted();
int loc = prevWeapon.getLocation();

// create an array of booleans of locations
boolean[] usedFrontArc = new boolean[ae.locations()];
boolean[] usedRearArc = new boolean[ae.locations()];
for (int i = 0; i < ae.locations(); i++) {
usedFrontArc[i] = false;
usedRearArc[i] = false;
}
}
} else {
for (Enumeration<AttackHandler> i = game.getAttacks(); i.hasMoreElements();) {
AttackHandler ah = i.nextElement();
WeaponAttackAction prevAttack = ah.getWaa();
if (prevAttack.getEntityId() == e.getId()) {
Mounted<?> prevWeapon = e.getEquipment(prevAttack.getWeaponId());
totalheat += prevWeapon.getCurrentHeat();
if (!rearMount) {
if (!usedFrontArc[loc]) {
totalHeat += ae.getHeatInArc(loc, rearMount);
usedFrontArc[loc] = true;
}
} else {
if (!usedRearArc[loc]) {
totalHeat += ae.getHeatInArc(loc, rearMount);
usedRearArc[loc] = true;
}
}
}
}
}
return totalheat;
return totalHeat;
}

/**
Expand Down Expand Up @@ -1903,15 +1916,15 @@ protected void addHeat() {
return;
}
if (!(toHit.getValue() == TargetRoll.IMPOSSIBLE)) {
if (ae.usesWeaponBays() && !game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_HEAT_BY_BAY)) {
if (ae.isLargeCraft() && !game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_HEAT_BY_BAY)) {
int loc = weapon.getLocation();
boolean rearMount = weapon.isRearMounted();
if (!ae.hasArcFired(loc, rearMount)) {
ae.heatBuildup += ae.getHeatInArc(loc, rearMount);
ae.setArcFired(loc, rearMount);
}
} else {
ae.heatBuildup += (weapon.getCurrentHeat());
ae.heatBuildup += (weapon.getHeatByBay());
}
}
}
Expand Down

0 comments on commit 0f3db43

Please sign in to comment.