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

Issue 4259: Fixing Dropship Heat-by-Arc for Players and Princess #6276

Merged
Merged
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
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()) {
psikomonkie marked this conversation as resolved.
Show resolved Hide resolved
// 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
Loading