diff --git a/megamek/src/megamek/client/ui/swing/UnitEditorDialog.java b/megamek/src/megamek/client/ui/swing/UnitEditorDialog.java index 6a499ca2d22..751c57e864c 100644 --- a/megamek/src/megamek/client/ui/swing/UnitEditorDialog.java +++ b/megamek/src/megamek/client/ui/swing/UnitEditorDialog.java @@ -1153,6 +1153,31 @@ private void setupAeroSystemPanel() { } } + /** Applies the given number of total crits to a Super-Cooled Myomer (which is spread over 6 locations). */ + public void damageSCM(Entity entity, int eqNum, int hits) { + int nhits = 0; + Mounted m = entity.getEquipment(eqNum); + for (int loc = 0; loc < entity.locations(); loc++) { + for (int i = 0; i < entity.getNumberOfCriticals(loc); i++) { + CriticalSlot cs = entity.getCritical(loc, i); + if ((cs == null) || (cs.getType() != CriticalSlot.TYPE_EQUIPMENT) + || ((m != cs.getMount()) && (m != cs.getMount2()))) { + continue; + } + + if (nhits < hits) { + cs.setHit(true); + cs.setDestroyed(true); + nhits++; + } else { + cs.setHit(false); + cs.setDestroyed(false); + cs.setRepairable(true); + } + } + } + } + private void btnOkayActionPerformed(java.awt.event.ActionEvent evt) { for (int i = 0; i < entity.locations(); i++) { if (null != spnInternal[i]) { @@ -1186,9 +1211,15 @@ private void btnOkayActionPerformed(java.awt.event.ActionEvent evt) { CheckCritPanel crit = equipCrits.get(eqNum); if (null != crit) { int hits = crit.getHits(); - m.setDestroyed(hits > 0); - m.setHit(hits > 0); - entity.damageSystem(CriticalSlot.TYPE_EQUIPMENT, eqNum, hits); + if (m.is(EquipmentTypeLookup.SCM)) { + m.setDestroyed(hits >= 6); + m.setHit(hits >= 6); + damageSCM(entity, eqNum, hits); + } else { + m.setDestroyed(hits > 0); + m.setHit(hits > 0); + entity.damageSystem(CriticalSlot.TYPE_EQUIPMENT, eqNum, hits); + } } } if (entity instanceof Infantry) { diff --git a/megamek/src/megamek/common/Engine.java b/megamek/src/megamek/common/Engine.java index c9592d8c7a6..fdec8bcc1b4 100644 --- a/megamek/src/megamek/common/Engine.java +++ b/megamek/src/megamek/common/Engine.java @@ -590,24 +590,22 @@ public int[] getSideTorsoCriticalSlots() { * @return the heat generated while the mech is standing still. */ public int getStandingHeat() { - if (engineType == XXL_ENGINE) { - return 2; - } - return 0; + return (engineType == XXL_ENGINE) ? 2 : 0; } /** * @return the heat generated while the mech is walking. */ public int getWalkHeat(Entity e) { + boolean hasSCM = (e instanceof Mech) && e.hasWorkingSCM(); switch (engineType) { case COMBUSTION_ENGINE: case FUEL_CELL: return 0; case XXL_ENGINE: - return 4; + return hasSCM ? 3 : 4; default: - return 1; + return hasSCM ? 0 : 1; } } @@ -615,29 +613,31 @@ public int getWalkHeat(Entity e) { * @return the heat generated while the mech is running. */ public int getRunHeat(Entity e) { + boolean hasSCM = (e instanceof Mech) && e.hasWorkingSCM(); switch (engineType) { case COMBUSTION_ENGINE: case FUEL_CELL: return 0; case XXL_ENGINE: - return 6; + return hasSCM ? 4 : 6; default: - return 2; + return hasSCM ? 0 : 2; } } /** * @return the heat generated while the mech is sprinting. */ - public int getSprintHeat() { + public int getSprintHeat(Entity e) { + boolean hasSCM = (e instanceof Mech) && e.hasWorkingSCM(); switch (engineType) { case COMBUSTION_ENGINE: case FUEL_CELL: return 0; case XXL_ENGINE: - return 9; + return hasSCM ? 6 : 9; default: - return 3; + return hasSCM ? 0 : 3; } } diff --git a/megamek/src/megamek/common/Entity.java b/megamek/src/megamek/common/Entity.java index 65cd72d1932..f057ae35f3b 100644 --- a/megamek/src/megamek/common/Entity.java +++ b/megamek/src/megamek/common/Entity.java @@ -42,6 +42,7 @@ import java.math.BigInteger; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -4566,6 +4567,34 @@ public boolean hasWorkingWeapon(BigInteger flag, int secondary, int location) { return false; } + /** @return True when this unit has a RISC Super-Cooled Myomer System (even if the SCM is destroyed). */ + public boolean hasSCM() { + return miscList.stream().anyMatch(m -> m.is(EquipmentTypeLookup.SCM)); + } + + /** @return True when this unit has an operable RISC Super-Cooled Myomer System. */ + public boolean hasWorkingSCM() { + return miscList.stream().filter(m -> m.is(EquipmentTypeLookup.SCM)).anyMatch(Mounted::isOperable); + } + + public int damagedSCMCritCount() { + return scmCritStateCount(CriticalSlot::isDamaged); + } + + protected int scmCritStateCount(Predicate slotState) { + int stateAppliesCount = 0; + for (int location = 0; location < locations(); location++) { + for (int index = 0; index < crits[location].length; index++) { + final CriticalSlot slot = crits[location][index]; + if ((slot != null) && (slot.getType() == CriticalSlot.TYPE_EQUIPMENT) + && slot.getMount().is(EquipmentTypeLookup.SCM) && slotState.test(slot)) { + stateAppliesCount++; + } + } + } + return stateAppliesCount; + } + /** * Returns the amount of heat that the entity can sink each turn. */ @@ -4742,32 +4771,7 @@ public int getGoodCriticals(CriticalSlot cs, int loc) { * the location */ public int getGoodCriticals(int type, int index, int loc) { - int operational = 0; - Mounted m = null; - if (type == CriticalSlot.TYPE_EQUIPMENT) { - m = getEquipment(index); - } - - int numberOfCriticals = getNumberOfCriticals(loc); - for (int i = 0; i < numberOfCriticals; i++) { - CriticalSlot ccs = getCritical(loc, i); - - // Check to see if this crit mounts the supplied item - // For systems, we can compare the index, but for equipment we - // need to get the Mounted that is mounted in that index and - // compare types. Superheavies may have two Mounted in each crit - if ((ccs != null) && (ccs.getType() == type)) { - if (!ccs.isDestroyed() && !ccs.isBreached()) { - if ((type == CriticalSlot.TYPE_SYSTEM) && (ccs.getIndex() == index)) { - operational++; - } else if ((type == CriticalSlot.TYPE_EQUIPMENT) && (m.equals(ccs.getMount()) || m.equals(ccs - .getMount2()))) { - operational++; - } - } - } - } - return operational; + return critStateCount(type, index, loc, cs -> !cs.isDestroyed() && !cs.isBreached()); } /** @@ -4775,74 +4779,41 @@ public int getGoodCriticals(int type, int index, int loc) { * location or missing along with it (if it was blown off). */ public int getBadCriticals(int type, int index, int loc) { - int hits = 0; - Mounted m = null; - if (type == CriticalSlot.TYPE_EQUIPMENT) { - m = getEquipment(index); - } - - int numberOfCriticals = getNumberOfCriticals(loc); - for (int i = 0; i < numberOfCriticals; i++) { - CriticalSlot ccs = getCritical(loc, i); - - // Check to see if this crit mounts the supplied item - // For systems, we can compare the index, but for equipment we - // need to get the Mounted that is mounted in that index and - // compare types. Superheavies may have two Mounted in each crit - if ((ccs != null) && (ccs.getType() == type)) { - if (ccs.isDestroyed() || ccs.isBreached() || ccs.isMissing()) { - if ((type == CriticalSlot.TYPE_SYSTEM) && (ccs.getIndex() == index)) { - hits++; - } else if ((type == CriticalSlot.TYPE_EQUIPMENT) && - ((m != null) && (m.equals(ccs.getMount()) || m.equals(ccs.getMount2())))) { - hits++; - } - } - } - } - return hits; + return critStateCount(type, index, loc, cs -> cs.isDestroyed() || cs.isBreached() || cs.isMissing()); } /** * Number of slots damaged (but not breached) in a location */ public int getDamagedCriticals(int type, int index, int loc) { - int hits = 0; - Mounted m = null; - if (type == CriticalSlot.TYPE_EQUIPMENT) { - m = getEquipment(index); - } - - int numberOfCriticals = getNumberOfCriticals(loc); - for (int i = 0; i < numberOfCriticals; i++) { - CriticalSlot ccs = getCritical(loc, i); - - // Check to see if this crit mounts the supplied item - // For systems, we can compare the index, but for equipment we - // need to get the Mounted that is mounted in that index and - // compare types. Superheavies may have two Mounted in each crit - if ((ccs != null) && (ccs.getType() == type)) { - if (ccs.isDamaged()) { - if ((type == CriticalSlot.TYPE_SYSTEM) && (ccs.getIndex() == index)) { - hits++; - } else if ((type == CriticalSlot.TYPE_EQUIPMENT) && (m.equals(ccs.getMount()) || m.equals(ccs - .getMount2()))) { - hits++; - } - } - } - } - return hits; + return critStateCount(type, index, loc, CriticalSlot::isDamaged); } /** * Number of slots doomed, missing or destroyed in a location */ public int getHitCriticals(int type, int index, int loc) { - int hits = 0; + return critStateCount(type, index, loc, cs -> cs.isDamaged() || cs.isBreached() || cs.isMissing()); + } + + /** + * @returns the number of critical slots of the equipment given as index for {@link #getEquipment(int)} in + * location loc wherein the type is the critical slot type that fit the slot state given as slotState Predicate + * such as {@link CriticalSlot#isDestroyed()}. The critslots tested are only those in location loc except + * for Super-Cooled Myomer where all locations are considered. + */ + protected int critStateCount(int type, int index, int loc, Predicate slotState) { + int stateAppliesCount = 0; Mounted m = null; if (type == CriticalSlot.TYPE_EQUIPMENT) { m = getEquipment(index); + if (m == null) { + LogManager.getLogger().error("Null Equipment found in equipment list of entity " + this); + return 0; + } + if ((this instanceof Mech) && m.is(EquipmentTypeLookup.SCM)) { + return ((Mech) this).scmCritStateCount(slotState); + } } int numberOfCriticals = getNumberOfCriticals(loc); @@ -4853,18 +4824,16 @@ public int getHitCriticals(int type, int index, int loc) { // For systems, we can compare the index, but for equipment we // need to get the Mounted that is mounted in that index and // compare types. Superheavies may have two Mounted in each crit - if ((ccs != null) && (ccs.getType() == type)) { - if (ccs.isDamaged() || ccs.isBreached() || ccs.isMissing()) { - if ((type == CriticalSlot.TYPE_SYSTEM) && (ccs.getIndex() == index)) { - hits++; - } else if ((type == CriticalSlot.TYPE_EQUIPMENT) && (m.equals(ccs.getMount()) || m.equals(ccs - .getMount2()))) { - hits++; - } + if ((ccs != null) && (ccs.getType() == type) && slotState.test(ccs)) { + if ((type == CriticalSlot.TYPE_SYSTEM) && (ccs.getIndex() == index)) { + stateAppliesCount++; + } else if ((type == CriticalSlot.TYPE_EQUIPMENT) + && (m.equals(ccs.getMount()) || m.equals(ccs.getMount2()))) { + stateAppliesCount++; } } } - return hits; + return stateAppliesCount; } protected abstract int[] getNoOfSlots(); diff --git a/megamek/src/megamek/common/EquipmentType.java b/megamek/src/megamek/common/EquipmentType.java index 73d307a2742..7619f875f79 100644 --- a/megamek/src/megamek/common/EquipmentType.java +++ b/megamek/src/megamek/common/EquipmentType.java @@ -1488,14 +1488,28 @@ public String getSortingName() { } /** - * Returns true if this equipment is any of those identified by the given type Strings. + * Returns true if this equipment is any of those identified by the given type Strings. The + * given typeInternalNames are compared to the internal name of this EquipmentType, not the (display) name! * Best use the constants defined in EquipmentTypeLookup. * - * @param eType An equipment type to check - * @param eTypes More equipment types to check + * @param typeInternalName An Equipment internal name to check + * @param typeInternalNames More Equipment internal names to check * @return true if the internalName of this equipment matches any of the given types */ - public boolean isAnyOf(String eType, String... eTypes) { - return internalName.equals(eType) || Arrays.asList(eTypes).contains(internalName); + public boolean isAnyOf(String typeInternalName, String... typeInternalNames) { + return internalName.equals(typeInternalName) || Arrays.asList(typeInternalNames).contains(internalName); + } + + /** + * Returns true if this equipment is that identified by the given typeInternalName String. The + * given typeInternalName is compared to the internal name of this EquipmentType, not the (display) name! + * Best use the constants defined in EquipmentTypeLookup. Calling this is equivalent to + * {@link #isAnyOf(String, String...)} with only the one parameter. + * + * @param typeInternalName An Equipment internal name to check + * @return true if the internalName of this equipment matches the given type + */ + public boolean is(String typeInternalName) { + return isAnyOf(typeInternalName); } } diff --git a/megamek/src/megamek/common/Mech.java b/megamek/src/megamek/common/Mech.java index 90138d39bc5..df9ede3f7fd 100644 --- a/megamek/src/megamek/common/Mech.java +++ b/megamek/src/megamek/common/Mech.java @@ -33,6 +33,7 @@ import java.io.PrintWriter; import java.math.BigInteger; import java.util.*; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -824,19 +825,6 @@ public boolean hasActiveTSM(boolean includeIndustrial) { return false; } - /** - * does this mech have SCM? - */ - public boolean hasSCM() { - for (Mounted m : getEquipment()) { - if ((m.getType() instanceof MiscType) - && m.getType().hasFlag(MiscType.F_SCM)) { - return true; - } - } - return false; - } - /** * does this mech have industrial TSM= * @@ -1169,8 +1157,8 @@ public int getRunningGravityLimit() { */ @Override public int getSprintHeat() { - int extra = bDamagedCoolantSystem?1:0; - return extra + (hasEngine() ? getEngine().getSprintHeat() : 0); + int extra = bDamagedCoolantSystem ? 1 : 0; + return extra + (hasEngine() ? getEngine().getSprintHeat(this) : 0); } /** @@ -1667,10 +1655,10 @@ public String getHeatSinkTypeName() { return "Heat Sink"; } - public int getHeatCapacity(boolean includePartialWing, - boolean includeRadicalHeatSink) { + public int getHeatCapacity(boolean includePartialWing, boolean includeRadicalHeatSink) { int capacity = 0; int activeCount = getActiveSinks(); + boolean isDoubleHeatSink = false; for (Mounted mounted : getMisc()) { if (mounted.isDestroyed() || mounted.isBreached()) { @@ -1684,9 +1672,11 @@ public int getHeatCapacity(boolean includePartialWing, && mounted.getType().hasFlag(MiscType.F_DOUBLE_HEAT_SINK)) { activeCount--; capacity += 2; + isDoubleHeatSink = true; } else if (mounted.getType().hasFlag( MiscType.F_IS_DOUBLE_HEAT_SINK_PROTOTYPE)) { capacity += 2; + isDoubleHeatSink = true; } else if (includePartialWing && mounted.getType().hasFlag(MiscType.F_PARTIAL_WING) && // unless all crits are destroyed, we get the bonus @@ -1699,6 +1689,7 @@ public int getHeatCapacity(boolean includePartialWing, // once. } } + capacity -= damagedSCMCritCount() * (isDoubleHeatSink ? 2 : 1); // AirMech mode for LAMs confers the same heat benefits as a partial wing. if (includePartialWing && movementMode == EntityMovementMode.WIGE) { capacity += getPartialWingHeatBonus(); @@ -1708,7 +1699,7 @@ && hasWorkingMisc(MiscType.F_RADICAL_HEATSINK)) { capacity += (int) Math.ceil(getActiveSinks() * 0.4); } - return capacity; + return Math.max(capacity, 0); } /** diff --git a/megamek/src/megamek/common/MiscType.java b/megamek/src/megamek/common/MiscType.java index 6c822dc48ae..8ab1c70bec6 100644 --- a/megamek/src/megamek/common/MiscType.java +++ b/megamek/src/megamek/common/MiscType.java @@ -8789,7 +8789,7 @@ public static MiscType createRISCSuperCooledMyomer() { misc.omniFixedOnly = true; misc.bv = 0; // TODO: add game rules, BV rules are implemented - misc.rulesRefs = "92, IO"; + misc.rulesRefs = "94, IO"; misc.techAdvancement.setTechBase(TECH_BASE_IS).setIntroLevel(false).setUnofficial(false).setTechRating(RATING_F) .setAvailability(RATING_X, RATING_X, RATING_X, RATING_F) .setISAdvancement(3133, DATE_NONE, DATE_NONE, 3138, DATE_NONE) diff --git a/megamek/src/megamek/common/Mounted.java b/megamek/src/megamek/common/Mounted.java index e74fe1485d9..b55fa447c68 100644 --- a/megamek/src/megamek/common/Mounted.java +++ b/megamek/src/megamek/common/Mounted.java @@ -630,10 +630,43 @@ && getType().hasFlag(MiscType.F_RADICAL_HEATSINK)) { } } + /** + * Returns true when this Mounted is not destroyed nor located in a blown-off + * or breached location. Does not check any other conditions such as DWP mounting or prior use. + * Also does not check if this mounted has been hit in this phase. + * This is equivalent to !{@link #isInoperable()}. + * + * @return True when this Mounted is operable + */ + public boolean isOperable() { + return !isInoperable(); + } + + /** + * Returns true when this Mounted destroyed or located in a blown-off + * or breached location. Does not check any other conditions such as DWP mounting or prior use. + * Also does not check if this mounted has been hit in this phase. + * Equivalent to !{@link #isOperable()}. + * + * @return True when this Mounted is operable + */ public boolean isInoperable() { return destroyed || missing || useless; } + /** + * Returns true if this Mounted's EquipmentType is that identified by the given typeInternalName String. The + * given typeInternalName is compared to the internal name of the EquipmentType of this Mounted, + * not the (display) name! + * Best use the constants defined in EquipmentTypeLookup. + * + * @param typeInternalName An Equipment internal name to check + * @return true if the internalName of this equipment matches the given type + */ + public boolean is(String typeInternalName) { + return getType().is(typeInternalName); + } + public boolean isHit() { return hit; } diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index ca9bedea26d..f7e3c06e464 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -23907,9 +23907,13 @@ private Vector applyEquipmentCritical(Entity en, int loc, CriticalSlot c reports.addElement(r); // Shield objects are not useless when they take one crit. - // Shields can be critted and still be usable. if ((eqType instanceof MiscType) && ((MiscType) eqType).isShield()) { mounted.setHit(false); + } else if (mounted.is(EquipmentTypeLookup.SCM)) { + // Super-Cooled Myomer remains functional until all its slots have been hit + if (en.damagedSCMCritCount() >= 6) { + mounted.setHit(true); + } } else { mounted.setHit(true); }