diff --git a/megamek/src/megamek/client/bot/princess/MultiTargetFireControl.java b/megamek/src/megamek/client/bot/princess/MultiTargetFireControl.java index 4b7e9736b58..b7dbfd3dd34 100644 --- a/megamek/src/megamek/client/bot/princess/MultiTargetFireControl.java +++ b/megamek/src/megamek/client/bot/princess/MultiTargetFireControl.java @@ -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; @@ -259,6 +260,19 @@ FiringPlan calculatePerArcFiringPlan(Entity shooter, List shotLi // damage values by arc: arc #, arc damage Map 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())); @@ -280,15 +294,15 @@ FiringPlan calculatePerArcFiringPlan(Entity shooter, List shotLi // initialize the backpack Map>> 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()); @@ -296,15 +310,15 @@ FiringPlan calculatePerArcFiringPlan(Entity shooter, List shotLi // 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]; @@ -315,7 +329,7 @@ FiringPlan calculatePerArcFiringPlan(Entity shooter, List shotLi // make sure we don't accidentally update the cell we're examining List 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 @@ -336,7 +350,7 @@ FiringPlan calculatePerArcFiringPlan(Entity shooter, List 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)); } } diff --git a/megamek/src/megamek/client/ui/swing/unitDisplay/WeaponPanel.java b/megamek/src/megamek/client/ui/swing/unitDisplay/WeaponPanel.java index db9153b3e45..b743562a3fc 100644 --- a/megamek/src/megamek/client/ui/swing/unitDisplay/WeaponPanel.java +++ b/megamek/src/megamek/client/ui/swing/unitDisplay/WeaponPanel.java @@ -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(); @@ -1219,7 +1217,7 @@ public void displayMek(Entity en) { } } else { if (!mounted.isBombMounted()) { - currentHeatBuildup += mounted.getCurrentHeat(); + currentHeatBuildup += mounted.getHeatByBay(); } } } @@ -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); diff --git a/megamek/src/megamek/common/actions/WeaponAttackAction.java b/megamek/src/megamek/common/actions/WeaponAttackAction.java index 1876be5a9cc..f8e40d3d808 100644 --- a/megamek/src/megamek/common/actions/WeaponAttackAction.java +++ b/megamek/src/megamek/common/actions/WeaponAttackAction.java @@ -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())); } @@ -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 @@ -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]) { @@ -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; diff --git a/megamek/src/megamek/common/equipment/WeaponMounted.java b/megamek/src/megamek/common/equipment/WeaponMounted.java index 5b5102d9bca..e705cae3fb2 100644 --- a/megamek/src/megamek/common/equipment/WeaponMounted.java +++ b/megamek/src/megamek/common/equipment/WeaponMounted.java @@ -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; + } } diff --git a/megamek/src/megamek/common/weapons/WeaponHandler.java b/megamek/src/megamek/common/weapons/WeaponHandler.java index dedba430824..9ce40841629 100644 --- a/megamek/src/megamek/common/weapons/WeaponHandler.java +++ b/megamek/src/megamek/common/weapons/WeaponHandler.java @@ -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 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 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 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; } /** @@ -1903,7 +1916,7 @@ 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)) { @@ -1911,7 +1924,7 @@ protected void addHeat() { ae.setArcFired(loc, rearMount); } } else { - ae.heatBuildup += (weapon.getCurrentHeat()); + ae.heatBuildup += (weapon.getHeatByBay()); } } }