From 869b90a8cc1ec60341868f889f76b89b6728495e Mon Sep 17 00:00:00 2001 From: sleet01 Date: Mon, 4 Dec 2023 18:56:51 -0800 Subject: [PATCH 01/22] Start on implementing #4397: Internal Bomb Bay quirk --- megamek/src/megamek/common/Aero.java | 8 +- megamek/src/megamek/common/SmallCraft.java | 102 ++++++++++-------- .../common/loaders/BLKSmallCraftFile.java | 8 +- 3 files changed, 68 insertions(+), 50 deletions(-) diff --git a/megamek/src/megamek/common/Aero.java b/megamek/src/megamek/common/Aero.java index 6828e5c60eb..5d3477de53b 100644 --- a/megamek/src/megamek/common/Aero.java +++ b/megamek/src/megamek/common/Aero.java @@ -2998,9 +2998,15 @@ public boolean isAero() { return true; } + /** + * Fighters may carry external ordnance; + * Other Aerospace units with cargo bays and the Internal Bomb Bay quirk may carry bombs internally. + * @return boolean + */ @Override public boolean isBomber() { - return isFighter(); + return (isFighter() || + (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB))); } @Override diff --git a/megamek/src/megamek/common/SmallCraft.java b/megamek/src/megamek/common/SmallCraft.java index fb6be7bd94a..73db04b0e24 100644 --- a/megamek/src/megamek/common/SmallCraft.java +++ b/megamek/src/megamek/common/SmallCraft.java @@ -25,35 +25,35 @@ public class SmallCraft extends Aero { private static final long serialVersionUID = 6708788176436555036L; - + public static final int LOC_HULL = 4; - - private static String[] LOCATION_ABBRS = { "NOS", "LS", "RS", "AFT", "HULL" }; - private static String[] LOCATION_NAMES = { "Nose", "Left Side", "Right Side", "Aft", "Hull" }; + + private static String[] LOCATION_ABBRS = {"NOS", "LS", "RS", "AFT", "HULL"}; + private static String[] LOCATION_NAMES = {"Nose", "Left Side", "Right Side", "Aft", "Hull"}; // crew and passengers private int nOfficers = 0; private int nGunners = 0; private int nBattleArmor = 0; private int nOtherPassenger = 0; - + // Maps transported crew, passengers, marines to a host ship so we can match them up again post-game - private Map nOtherCrew = new HashMap<>(); - private Map passengers = new HashMap<>(); - + private Map nOtherCrew = new HashMap<>(); + private Map passengers = new HashMap<>(); + // escape pods and lifeboats private int escapePods = 0; private int lifeBoats = 0; private int escapePodsLaunched = 0; private int lifeBoatsLaunched = 0; - + private static final TechAdvancement TA_SM_CRAFT = new TechAdvancement(TECH_BASE_ALL) .setAdvancement(DATE_NONE, 2350, 2400).setISApproximate(false, true, false) .setProductionFactions(F_TH).setTechRating(RATING_D) .setAvailability(RATING_D, RATING_E, RATING_D, RATING_D) .setStaticTechLevel(SimpleTechLevel.STANDARD); private static final TechAdvancement TA_SM_CRAFT_PRIMITIVE = new TechAdvancement(TECH_BASE_IS) - //Per MUL team and per availability codes should exist to around 2781 + //Per MUL team and per availability codes should exist to around 2781 .setISAdvancement(DATE_ES, 2200, DATE_NONE, 2781, DATE_NONE) .setISApproximate(false, true, false, true, false) .setProductionFactions(F_TA).setTechRating(RATING_D) @@ -73,7 +73,7 @@ public TechAdvancement getConstructionTechAdvancement() { return TA_SM_CRAFT; } } - + /** * @return Returns the autoEject setting (always off for large craft) */ @@ -81,7 +81,7 @@ public TechAdvancement getConstructionTechAdvancement() { public boolean isAutoEject() { return false; } - + @Override public boolean isPrimitive() { return getArmorType(LOC_NOSE) == EquipmentType.T_ARMOR_PRIMITIVE_AERO; @@ -96,15 +96,15 @@ public boolean isSmallCraft() { public void setNCrew(int crew) { nCrew = crew; } - + public void setNOfficers(int officer) { nOfficers = officer; } - + public void setNGunners(int gunners) { nGunners = gunners; } - + @Override public void setNPassenger(int pass) { nPassenger = pass; @@ -132,17 +132,17 @@ public int getNCrew() { public int getNPassenger() { return nPassenger; } - + @Override public int getNOfficers() { return nOfficers; } - + @Override public int getNGunners() { return nGunners; } - + @Override public int getNBattleArmor() { return nBattleArmor; @@ -156,15 +156,15 @@ public int getNMarines() { public int getNOtherPassenger() { return nOtherPassenger; } - + /** * Returns a mapping of how many crewmembers from other units this unit is carrying - * and what ship they're from by external ID + * and what ship they're from by external ID */ - public Map getNOtherCrew() { + public Map getNOtherCrew() { return nOtherCrew; } - + /** * Convenience method to return all crew from other craft aboard from the above Map * @return @@ -176,28 +176,28 @@ public int getTotalOtherCrew() { } return toReturn; } - + /** * Adds a number of crewmembers from another ship keyed by that ship's external ID * @param id The external ID of the ship these crew came from * @param n The number to add */ public void addNOtherCrew(String id, int n) { - if (nOtherCrew.containsKey(id)) { - nOtherCrew.replace(id, nOtherCrew.get(id) + n); - } else { - nOtherCrew.put(id, n); - } + if (nOtherCrew.containsKey(id)) { + nOtherCrew.replace(id, nOtherCrew.get(id) + n); + } else { + nOtherCrew.put(id, n); + } } - + /** * Returns a mapping of how many passengers from other units this unit is carrying - * and what ship they're from by external ID + * and what ship they're from by external ID */ - public Map getPassengers() { + public Map getPassengers() { return passengers; } - + /** * Convenience method to return all passengers aboard from the above Map * @return @@ -209,20 +209,20 @@ public int getTotalPassengers() { } return toReturn; } - + /** * Adds a number of passengers from another ship keyed by that ship's external ID * @param id The external ID of the ship these passengers came from * @param n The number to add */ public void addPassengers(String id, int n) { - if (passengers.containsKey(id)) { - passengers.replace(id, passengers.get(id) + n); - } else { - passengers.put(id, n); - } + if (passengers.containsKey(id)) { + passengers.replace(id, passengers.get(id) + n); + } else { + passengers.put(id, n); + } } - + public void setEscapePods(int n) { escapePods = n; } @@ -231,7 +231,7 @@ public void setEscapePods(int n) { public int getEscapePods() { return escapePods; } - + /** * Returns the total number of escape pods launched so far */ @@ -239,7 +239,7 @@ public int getEscapePods() { public int getLaunchedEscapePods() { return escapePodsLaunched; } - + /** * Updates the total number of escape pods launched so far * @param n The number to change @@ -257,7 +257,7 @@ public void setLifeBoats(int n) { public int getLifeBoats() { return lifeBoats; } - + /** * Returns the total number of lifeboats launched so far */ @@ -265,7 +265,7 @@ public int getLifeBoats() { public int getLaunchedLifeBoats() { return lifeBoatsLaunched; } - + /** * Updates the total number of lifeboats launched so far * @param n The number to change @@ -274,7 +274,7 @@ public int getLaunchedLifeBoats() { public void setLaunchedLifeBoats(int n) { lifeBoatsLaunched = n; } - + @Override public double getStrategicFuelUse() { if (isPrimitive()) { @@ -336,7 +336,7 @@ public HitData rollHitLocation(int table, int side) { // special rules for spheroids in atmosphere // http://www.classicbattletech.com/forums/index.php/topic,54077.0.html - if (isSpheroid() && table != ToHitData.HIT_SPHEROID_CRASH && + if (isSpheroid() && table != ToHitData.HIT_SPHEROID_CRASH && !game.getBoard().inSpace()) { int preroll = Compute.d6(1); if ((table == ToHitData.HIT_ABOVE) && (preroll < 4)) { @@ -729,7 +729,7 @@ public double getArmorWeight() { return RoundWeight.nextHalfTon(armorPoints / armorPerTon); } - + public static double armorPointsPerTon(double craftWeight, boolean spheroid, int at, boolean isClan) { double base = 16.0; if (spheroid) { @@ -891,7 +891,7 @@ public int height() { public long getEntityType() { return Entity.ETYPE_AERO | Entity.ETYPE_SMALL_CRAFT; } - + @Override public boolean isFighter() { return false; @@ -919,4 +919,12 @@ public boolean isLargeAerospace() { public int getLandingLength() { return 8; } + + @Override + public void autoSetMaxBombPoints() { + // Only count free whole tons per bay + maxBombPoints = getTransportBays().stream().mapToInt( + tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused()) : 0 + ).sum(); + } } \ No newline at end of file diff --git a/megamek/src/megamek/common/loaders/BLKSmallCraftFile.java b/megamek/src/megamek/common/loaders/BLKSmallCraftFile.java index b854a9257c7..f81b77e7d4b 100644 --- a/megamek/src/megamek/common/loaders/BLKSmallCraftFile.java +++ b/megamek/src/megamek/common/loaders/BLKSmallCraftFile.java @@ -165,6 +165,10 @@ public Entity getEntity() throws EntityLoadingException { } addTransports(a); + + // how many bombs can it carry; depends on transport bays + a.autoSetMaxBombPoints(); + a.setArmorTonnage(a.getArmorWeight()); loadQuirks(a); return a; @@ -222,7 +226,7 @@ protected void loadEquipment(Entity t, String sName, int nLoc) throws EntityLoad facing = 2; equipName = equipName.substring(0, equipName.length() - 4) .trim(); - } + } EquipmentType etype = EquipmentType.get(equipName); @@ -239,7 +243,7 @@ protected void loadEquipment(Entity t, String sName, int nLoc) throws EntityLoad int useLoc = TestEntity.eqRequiresLocation(t, etype) ? nLoc : SmallCraft.LOC_HULL; Mounted mount = t.addEquipment(etype, useLoc, rearMount); // Need to set facing for VGLs - if ((etype instanceof WeaponType) + if ((etype instanceof WeaponType) && etype.hasFlag(WeaponType.F_VGL)) { if (facing == -1) { mount.setFacing(defaultAeroVGLFacing(useLoc, rearMount)); From cec225c46b64c40c182fe00d93d0a7e6325177e6 Mon Sep 17 00:00:00 2001 From: sleet01 Date: Mon, 4 Dec 2023 23:01:23 -0800 Subject: [PATCH 02/22] Start of Aero class hierarchy refactor to simplify shared attributes --- megamek/src/megamek/common/Aero.java | 7 + .../src/megamek/common/AeroSpaceFighter.java | 3082 +++++++++++++++++ megamek/src/megamek/common/Dropship.java | 78 +- megamek/src/megamek/common/SmallCraft.java | 2 +- 4 files changed, 3133 insertions(+), 36 deletions(-) create mode 100644 megamek/src/megamek/common/AeroSpaceFighter.java diff --git a/megamek/src/megamek/common/Aero.java b/megamek/src/megamek/common/Aero.java index 5d3477de53b..8ea66ad84c8 100644 --- a/megamek/src/megamek/common/Aero.java +++ b/megamek/src/megamek/common/Aero.java @@ -481,7 +481,14 @@ public int getMaxBombPoints() { } public void autoSetMaxBombPoints() { + // Aerospace fighters can carry both external and internal ordnances, if configured and quirked + // appropriately maxBombPoints = (int) Math.round(getWeight() / 5); + if (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) { + maxBombPoints += getTransportBays().stream().mapToInt( + tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused() / 5) : 0 + ).sum(); + } } @Override diff --git a/megamek/src/megamek/common/AeroSpaceFighter.java b/megamek/src/megamek/common/AeroSpaceFighter.java new file mode 100644 index 00000000000..3278d0ef3d2 --- /dev/null +++ b/megamek/src/megamek/common/AeroSpaceFighter.java @@ -0,0 +1,3082 @@ +/* + * MegaMek - Copyright (C) 2000-2003 Ben Mazur (bmazur@sev.org) + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ +package megamek.common; + +import megamek.client.ui.swing.calculationReport.CalculationReport; +import megamek.common.cost.AeroCostCalculator; +import megamek.common.enums.AimingMode; +import megamek.common.options.OptionsConstants; +import org.apache.logging.log4j.LogManager; + +import java.text.NumberFormat; +import java.util.*; + +/** + * Taharqa's attempt at creating an Aerospace entity + */ +public class AeroSpaceFighter extends Aero { + public AeroSpaceFighter() { + super(); + } + + @Override + public int getUnitType() { + return UnitType.AERO; + } + + protected static final TechAdvancement TA_ASF = new TechAdvancement(TECH_BASE_ALL) + .setAdvancement(DATE_NONE, 2470, 2490).setProductionFactions(F_TH) + .setTechRating(RATING_D).setAvailability(RATING_C, RATING_E, RATING_D, RATING_C) + .setStaticTechLevel(SimpleTechLevel.STANDARD); + protected static final TechAdvancement TA_ASF_PRIMITIVE = new TechAdvancement(TECH_BASE_IS) + //Per MUL team and per availability codes should exist to around 2781 + .setISAdvancement(DATE_ES, 2200, DATE_NONE, 2781, DATE_NONE) + .setISApproximate(false, true, false, true, false).setProductionFactions(F_TA) + .setTechRating(RATING_D).setAvailability(RATING_D, RATING_X, RATING_F, RATING_F) + .setStaticTechLevel(SimpleTechLevel.ADVANCED); + + @Override + public TechAdvancement getConstructionTechAdvancement() { + if (isPrimitive()) { + return TA_ASF_PRIMITIVE; + } else { + return TA_ASF; + } + } + + protected static final TechAdvancement[] COCKPIT_TA = { + new TechAdvancement(TECH_BASE_ALL).setAdvancement(2460, 2470, 2491) + .setApproximate(true, false, false).setPrototypeFactions(F_TH) + .setPrototypeFactions(F_TH).setTechRating(RATING_C) + .setAvailability(RATING_C, RATING_C, RATING_C, RATING_C) + .setStaticTechLevel(SimpleTechLevel.STANDARD), //Standard + new TechAdvancement(TECH_BASE_IS).setISAdvancement(3065, 3070, 3080) + .setClanAdvancement(DATE_NONE, DATE_NONE, 3080) + .setISApproximate(true, false, false).setPrototypeFactions(F_WB) + .setPrototypeFactions(F_WB, F_CSR).setTechRating(RATING_E) + .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) + .setStaticTechLevel(SimpleTechLevel.STANDARD), //Small + new TechAdvancement(TECH_BASE_ALL).setISAdvancement(2625, 2631, DATE_NONE, 2850, 3030) + .setISApproximate(true, false, false, true, true) + .setClanAdvancement(2625, 2631).setClanApproximate(true, false) + .setClanApproximate(true, false).setPrototypeFactions(F_TH) + .setPrototypeFactions(F_TH).setReintroductionFactions(F_FS).setTechRating(RATING_D) + .setAvailability(RATING_C, RATING_F, RATING_E, RATING_D) + .setStaticTechLevel(SimpleTechLevel.ADVANCED), //Cockpit command console + new TechAdvancement(TECH_BASE_ALL).setAdvancement(DATE_ES, 2300, DATE_NONE, 2520) + .setISApproximate(false, true, false, false) + .setPrototypeFactions(F_TA).setTechRating(RATING_C) + .setAvailability(RATING_D, RATING_X, RATING_X, RATING_F) + .setStaticTechLevel(SimpleTechLevel.STANDARD), //Primitive + }; + + public static TechAdvancement getCockpitTechAdvancement(int cockpitType) { + if (cockpitType >= 0 && cockpitType < COCKPIT_TA.length) { + return new TechAdvancement(COCKPIT_TA[cockpitType]); + } + return null; + } + + public TechAdvancement getCockpitTechAdvancement() { + return getCockpitTechAdvancement(getCockpitType()); + } + + @Override + protected void addSystemTechAdvancement(CompositeTechLevel ctl) { + super.addSystemTechAdvancement(ctl); + if (isFighter() && (getCockpitTechAdvancement() != null)) { + ctl.addComponent(getCockpitTechAdvancement()); + } + } + + // Is it Civilian or Military + public static final int CIVILIAN = 0; + public static final int MILITARY = 1; + protected int designType = MILITARY; + + /** + * Sets the unit as either a civilian or military design + */ + public void setDesignType(int design) { + designType = design; + } + + @Override + public void setDestroyed(boolean destroyed) { + this.destroyed = destroyed; + land(); + } + /** + * Returns the unit's design type + */ + public int getDesignType() { + return designType; + } + + /** + * 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 + */ + @Override + public boolean isAeroSensorDestroyed() { + return getSensorHits() >= 3; + } + + @Override + public int getWalkMP(MPCalculationSetting mpCalculationSetting) { + int mp = getOriginalWalkMP(); + if (engineHits >= getMaxEngineHits()) { + return 0; + } + + int engineLoss = 2; + if ((this instanceof SmallCraft) || (this instanceof Jumpship)) { + engineLoss = 1; + } + mp = Math.max(0, mp - (engineHits * engineLoss)); + + if (!mpCalculationSetting.ignoreCargo) { + mp = Math.max(0, mp - getCargoMpReduction(this)); + } + + if ((null != game) && !mpCalculationSetting.ignoreWeather) { + int weatherMod = game.getPlanetaryConditions().getMovementMods(this); + mp = Math.max(mp + weatherMod, 0); + if (getCrew().getOptions().stringOption(OptionsConstants.MISC_ENV_SPECIALIST).equals(Crew.ENVSPC_WIND) + && (game.getPlanetaryConditions().getWindStrength() == PlanetaryConditions.WI_TORNADO_F13) + && (game.getPlanetaryConditions().getWeather() == PlanetaryConditions.WE_NONE)) { + mp += 1; + } + } + + if (!mpCalculationSetting.ignoreCargo) { + mp = reduceMPByBombLoad(mp); + } + + if (!mpCalculationSetting.ignoreModularArmor && hasModularArmor()) { + mp--; + } + + if (getPartialRepairs().booleanOption("aero_engine_crit")) { + mp--; + } + + if (!mpCalculationSetting.ignoreGrounded && !isAirborne()) { + mp = isSpheroid() ? 0 : mp / 2; + } + + return mp; + } + + /** + * This is the same as getWalkMP, but does not divide by 2 when grounded + * + * @return + */ + @Override + public int getCurrentThrust() { + return getWalkMP(MPCalculationSetting.NO_GROUNDED); + } + + /** + * Returns the number of locations in the entity + */ + @Override + public int locations() { + return 6; + } + + @Override + public int getBodyLocation() { + return LOC_FUSELAGE; + } + + @Override + public boolean canChangeSecondaryFacing() { + return false; + } + + @Override + public boolean isValidSecondaryFacing(int n) { + return false; + } + + /** + * Aeros really can't torso twist? + */ + @Override + public int clipSecondaryFacing(int n) { + return getFacing(); + } + + @Override + public boolean isOutControlTotal() { + // due to control roll, heat, shut down, or crew unconscious + return (outControl || shutDown || getCrew().isUnconscious()); + } + + @Override + public boolean isOutControl() { + return outControl; + } + + @Override + public boolean isOutCtrlHeat() { + return outCtrlHeat; + } + + @Override + public boolean isRandomMove() { + return randomMove; + } + + @Override + public boolean didAccLast() { + return accLast; + } + + @Override + public boolean hasLifeSupport() { + return lifeSupport; + } + + public void setLifeSupport(boolean b) { + lifeSupport = b; + } + + @Override + public boolean isRolled() { + return rolled; + } + + @Override + public void setOutControl(boolean ocontrol) { + outControl = ocontrol; + } + + @Override + public void setOutCtrlHeat(boolean octrlheat) { + outCtrlHeat = octrlheat; + } + + @Override + public void setRandomMove(boolean randmove) { + randomMove = randmove; + } + + @Override + public void setRolled(boolean roll) { + rolled = roll; + } + + @Override + public void setAccLast(boolean b) { + accLast = b; + } + + @Override + public int getMaxBombPoints() { + return maxBombPoints; + } + + public void autoSetMaxBombPoints() { + // Aerospace fighters can carry both external and internal ordnances, if configured and quirked + // appropriately + maxBombPoints = (int) Math.round(getWeight() / 5); + if (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) { + maxBombPoints += getTransportBays().stream().mapToInt( + tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused() / 5) : 0 + ).sum(); + } + } + + @Override + public int[] getBombChoices() { + return bombChoices.clone(); + } + + @Override + public void setBombChoices(int[] bc) { + if (bc.length == bombChoices.length) { + bombChoices = bc; + } + } + + @Override + public void clearBombChoices() { + Arrays.fill(bombChoices, 0); + } + + @Override + public int reduceMPByBombLoad(int t) { + return Math.max(0, t - (int) Math.ceil(getBombPoints() / 5.0)); + } + + public void setWhoFirst() { + whoFirst = Compute.randomInt(500); + } + + public int getWhoFirst() { + return whoFirst; + } + + @Override + public int getCurrentVelocity() { + // if using advanced movement then I just want to sum up + // the different vectors + if ((game != null) && game.useVectorMove()) { + return getVelocity(); + } + return currentVelocity; + } + + @Override + public void setCurrentVelocity(int velocity) { + currentVelocity = velocity; + } + + @Override + public int getNextVelocity() { + return nextVelocity; + } + + @Override + public void setNextVelocity(int velocity) { + nextVelocity = velocity; + } + + // need some way of retrieving true current velocity + // even when using advanced movement + @Override + public int getCurrentVelocityActual() { + return currentVelocity; + } + + public int getPotCrit() { + return potCrit; + } + + public void setPotCrit(int crit) { + potCrit = crit; + } + + @Override + public int getSI() { + return structIntegrity; + } + + @Override + public int get0SI() { + return orig_structIntegrity; + } + + /** + * Used to determine modifier for landing; different for Aero and LAM. + */ + @Override + public int getNoseArmor() { + return getArmor(LOC_NOSE); + } + + @Override + public int getCapArmor() { + return capitalArmor; + } + + @Override + public void setCapArmor(int i) { + capitalArmor = i; + } + + @Override + public int getCap0Armor() { + return capitalArmor_orig; + } + + @Override + public int getFatalThresh() { + return fatalThresh; + } + + @Override + public int getCurrentDamage() { + return currentDamage; + } + + @Override + public void setCurrentDamage(int i) { + currentDamage = i; + } + + public void set0SI(int si) { + orig_structIntegrity = si; + structIntegrity = si; + } + + public void autoSetSI() { + int siweight = (int) Math.floor(weight / 10.0); + int sithrust = getOriginalWalkMP(); + initializeSI(Math.max(siweight, sithrust)); + } + + @Override + public void autoSetCapArmor() { + double divisor = 10.0; + if ((null != game) && game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AERO_SANITY)) { + divisor = 1.0; + } + capitalArmor_orig = (int) Math.round(getTotalOArmor() / divisor); + capitalArmor = (int) Math.round(getTotalArmor() / divisor); + } + + @Override + public void autoSetFatalThresh() { + int baseThresh = 2; + if ((null != game) && game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AERO_SANITY)) { + baseThresh = 20; + } + fatalThresh = Math.max(baseThresh, (int) Math.ceil(capitalArmor / 4.0)); + } + + public void initializeSI(int val) { + orig_structIntegrity = val; + setSI(val); + } + + @Override + public void setSI(int si) { + structIntegrity = si; + } + + @Override + public int getSensorHits() { + return sensorHits; + } + + public void setSensorHits(int hits) { + if (hits > 3) { + hits = 3; + } + sensorHits = hits; + } + + @Override + public int getFCSHits() { + return fcsHits; + } + + public void setFCSHits(int hits) { + if (hits > 3) { + hits = 3; + } + fcsHits = hits; + } + + public boolean fuelTankHit() { + return fuelTankHit; + } + + public void setFuelTankHit(boolean value) { + fuelTankHit = value; + } + + public void setCICHits(int hits) { + if (hits > 3) { + hits = 3; + } + cicHits = hits; + } + + public int getCICHits() { + return cicHits; + } + + public void setIgnoredCrewHits(int hits) { + ignoredCrewHits = hits; + } + + public int getIgnoredCrewHits() { + return ignoredCrewHits; + } + + @Override + public int getEngineHits() { + return engineHits; + } + + public void setEngineHits(int hits) { + engineHits = hits; + } + + @Override + public int getAvionicsHits() { + return avionicsHits; + } + + public void setAvionicsHits(int hits) { + avionicsHits = hits; + } + + public boolean isGearHit() { + return gearHit; + } + + @Override + public void setGearHit(boolean hit) { + gearHit = hit; + } + + /** + * Modifier to landing or vertical takeoff roll for landing gear damage. + * + * @param vTakeoff + * true if this is for a vertical takeoff, false if for a landing + * @return the control roll modifier + */ + @Override + public int getLandingGearMod(boolean vTakeoff) { + if (gearHit) { + return vTakeoff ? 1 : 5; + } else { + return 0; + } + } + + //Landing mods for partial repairs + @Override + public int getLandingGearPartialRepairs() { + if (getPartialRepairs().booleanOption("aero_gear_crit")) { + return 2; + } else if (getPartialRepairs().booleanOption("aero_gear_replace")) { + return 1; + } else { + return 0; + } + } + + // Avionics mods for partial repairs + @Override + public int getAvionicsMisreplaced() { + if (getPartialRepairs().booleanOption("aero_avionics_replace")) { + return 1; + } else { + return 0; + } + } + + @Override + public int getAvionicsMisrepaired() { + if (getPartialRepairs().booleanOption("aero_avionics_crit")) { + return 1; + } else { + return 0; + } + } + + public void setOHeatSinks(int hs) { + heatSinksOriginal = hs; + } + + public int getOHeatSinks() { + return heatSinksOriginal; + } + + public void setHeatSinks(int hs) { + heatSinks = hs; + } + + @Override + public int getHeatSinks() { + return heatSinks; + } + + public int getHeatSinkHits() { + return heatSinksOriginal - heatSinks; + } + + public void setHeatType(int hstype) { + heatType = hstype; + } + + public int getPodHeatSinks() { + return podHeatSinks; + } + + public void setPodHeatSinks(int hs) { + podHeatSinks = hs; + } + + @Override + public boolean tracksHeat() { + return true; + } + + public void setLeftThrustHits(int hits) { + leftThrustHits = hits; + } + + @Override + public int getLeftThrustHits() { + return leftThrustHits; + } + + public void setRightThrustHits(int hits) { + rightThrustHits = hits; + } + + @Override + public int getRightThrustHits() { + return rightThrustHits; + } + + public int getOriginalFuel() { + return fuel; + } + + @Override + public int getFuel() { + if ((getPartialRepairs().booleanOption("aero_asf_fueltank_crit")) + || (getPartialRepairs().booleanOption("aero_fueltank_crit"))) { + return (int) (fuel * 0.9); + } else { + return fuel; + } + } + + @Override + public int getCurrentFuel() { + if ((getPartialRepairs().booleanOption("aero_asf_fueltank_crit")) + || (getPartialRepairs().booleanOption("aero_fueltank_crit"))) { + return (int) (currentfuel * 0.9); + } else { + return currentfuel; + } + } + + /** + * Sets the number of fuel points. + * + * @param gas + * Number of fuel points. + */ + @Override + public void setFuel(int gas) { + fuel = gas; + currentfuel = gas; + } + + @Override + public void setCurrentFuel(int gas) { + currentfuel = gas; + } + + @Override + public double getFuelPointsPerTon() { + if (isPrimitive()) { + return 80 / primitiveFuelFactor(); + } + return 80; + } + + /** + * Set number of fuel points based on fuel tonnage. + * + * @param fuelTons + * The number of tons of fuel + */ + @Override + public void setFuelTonnage(double fuelTons) { + double pointsPerTon = getFuelPointsPerTon(); + fuel = (int) Math.floor(pointsPerTon * fuelTons + 0.001); + } + + /** + * Gets the fuel for this Aero in terms of tonnage. + * + * @return The number of tons of fuel on this Aero. + */ + @Override + public double getFuelTonnage() { + // Rounding required for primitive small craft/dropship fuel multipliers. + // The reason this is rounded normally instead of up is that the fuel points are actually calculated + // from the tonnage and rounded down. + return Math.round(2.0 * fuel / getFuelPointsPerTon()) / 2.0; + } + + /** + * Used by SmallCraft and Jumpship and their child classes. + * + * @return The tons of fuel burned in a day at 1G using strategic movement. + */ + public double getStrategicFuelUse() { + return 0.0; + } + + /** + * Some primitve aerospace units have their fuel efficiency reduced by a factor based + * on construction year. + * + * @return The primitive fuel factor for the build year. + */ + public double primitiveFuelFactor() { + return 1.0; + } + + public int getHeatType() { + return heatType; + } + + @Override + public boolean wasCritThresh() { + return critThresh; + } + + @Override + public void setCritThresh(boolean b) { + critThresh = b; + } + + @Override + public boolean isImmobile() { + // aeros are never immobile when in the air or space + if (isAirborne() || isSpaceborne()) { + return false; + } + return super.isImmobile(); + } + + @Override + public void newRound(int roundNumber) { + super.newRound(roundNumber); + + // reset threshold critted + setCritThresh(false); + + // reset maneuver status + setFailedManeuver(false); + // reset acc/dec this turn + setAccDecNow(false); + + updateBays(); + + // update recovery turn if in recovery + if (getRecoveryTurn() > 0) { + setRecoveryTurn(getRecoveryTurn() - 1); + } + + // if in atmosphere, then halve next turn's velocity + if (!game.getBoard().inSpace() && isDeployed() && (roundNumber > 0)) { + setNextVelocity((int) Math.floor(getNextVelocity() / 2.0)); + } + + // update velocity + setCurrentVelocity(getNextVelocity()); + + // if using variable damage thresholds then autoset them + if (game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_VARIABLE_DAMAGE_THRESH)) { + autoSetThresh(); + autoSetFatalThresh(); + } + + // if they are out of control due to heat, then apply this and reset + if (isOutCtrlHeat()) { + setOutControl(true); + setOutCtrlHeat(false); + } + + // reset eccm bonus + setECCMRoll(Compute.d6(2)); + + // get new random whofirst + setWhoFirst(); + + resetAltLossThisRound(); + } + + /** + * Returns the name of the type of movement used. This is tank-specific. + */ + @Override + public String getMovementString(EntityMovementType mtype) { + switch (mtype) { + case MOVE_SKID: + return "Skidded"; + case MOVE_NONE: + return "None"; + case MOVE_WALK: + return "Cruised"; + case MOVE_RUN: + return "Flanked"; + case MOVE_SAFE_THRUST: + return "Safe Thrust"; + case MOVE_OVER_THRUST: + return "Over Thrust"; + default: + return "Unknown!"; + } + } + + /** + * Returns the name of the type of movement used. This is tank-specific. + */ + @Override + public String getMovementAbbr(EntityMovementType mtype) { + switch (mtype) { + case MOVE_NONE: + return "N"; + case MOVE_SAFE_THRUST: + return "S"; + case MOVE_OVER_THRUST: + return "O"; + default: + return "?"; + } + } + + @Override + public boolean hasRearArmor(int loc) { + return false; + } + + /** + * Returns the Compute.ARC that the weapon fires into. + */ + // need to figure out aft-pointed wing weapons + // need to figure out new arcs + @Override + public int getWeaponArc(int wn) { + final Mounted mounted = getEquipment(wn); + if (mounted.getType().hasFlag(WeaponType.F_SPACE_BOMB) || mounted.getType().hasFlag(WeaponType.F_DIVE_BOMB) + || mounted.getType().hasFlag(WeaponType.F_ALT_BOMB)) { + return Compute.ARC_360; + } + int arc; + switch (mounted.getLocation()) { + case LOC_NOSE: + case LOC_WINGS: + arc = Compute.ARC_NOSE; + break; + case LOC_RWING: + if (mounted.isRearMounted()) { + arc = Compute.ARC_RWINGA; + } else { + arc = Compute.ARC_RWING; + } + break; + case LOC_LWING: + if (mounted.isRearMounted()) { + arc = Compute.ARC_LWINGA; + } else { + arc = Compute.ARC_LWING; + } + break; + case LOC_AFT: + arc = Compute.ARC_AFT; + break; + default: + arc = Compute.ARC_360; + break; + } + + return rollArcs(arc); + } + + /** + * Returns true if this weapon fires into the secondary facing arc. If + * false, assume it fires into the primary. + */ + @Override + public boolean isSecondaryArcWeapon(int weaponId) { + // just leave true for now in case we implement rolls or + // newtonian movement this way + return true; + } + + /** + * Rolls up a hit location + */ + @Override + public HitData rollHitLocation(int table, int side, int aimedLocation, AimingMode aimingMode, + int cover) { + return rollHitLocation(table, side); + } + + @Override + public HitData rollHitLocation(int table, int side) { + + /* + * Unlike other units, ASFs determine potential crits based on the + * to-hit roll so I need to set this potential value as well as return + * the to hit data + */ + + int roll = Compute.d6(2); + + // first check for above/below + if ((table == ToHitData.HIT_ABOVE) || (table == ToHitData.HIT_BELOW)) { + + // have to decide which wing + int wingloc = LOC_RWING; + int wingroll = Compute.d6(1); + if (wingroll > 3) { + wingloc = LOC_LWING; + } + switch (roll) { + case 2: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 3: + setPotCrit(CRIT_GEAR); + return new HitData(wingloc, false, HitData.EFFECT_NONE); + case 4: + setPotCrit(CRIT_SENSOR); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 5: + setPotCrit(CRIT_CREW); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 6: + setPotCrit(CRIT_WEAPON); + return new HitData(wingloc, false, HitData.EFFECT_NONE); + case 7: + setPotCrit(CRIT_AVIONICS); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 8: + setPotCrit(CRIT_WEAPON); + return new HitData(wingloc, false, HitData.EFFECT_NONE); + case 9: + setPotCrit(CRIT_CONTROL); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + case 10: + setPotCrit(CRIT_ENGINE); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + case 11: + setPotCrit(CRIT_GEAR); + return new HitData(wingloc, false, HitData.EFFECT_NONE); + case 12: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + } + } + + if (side == ToHitData.SIDE_FRONT) { + // normal front hits + switch (roll) { + case 2: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 3: + setPotCrit(CRIT_SENSOR); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 4: + setPotCrit(CRIT_HEATSINK); + return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); + case 5: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); + case 6: + setPotCrit(CRIT_AVIONICS); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 7: + setPotCrit(CRIT_CONTROL); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 8: + setPotCrit(CRIT_FCS); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 9: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); + case 10: + setPotCrit(CRIT_HEATSINK); + return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); + case 11: + setPotCrit(CRIT_GEAR); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 12: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + } + } else if (side == ToHitData.SIDE_LEFT) { + // normal left-side hits + switch (roll) { + case 2: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 3: + setPotCrit(CRIT_GEAR); + return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); + case 4: + setPotCrit(CRIT_SENSOR); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 5: + setPotCrit(CRIT_CREW); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 6: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); + case 7: + setPotCrit(CRIT_AVIONICS); + return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); + case 8: + setPotCrit(CRIT_BOMB); + return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); + case 9: + setPotCrit(CRIT_CONTROL); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + case 10: + setPotCrit(CRIT_ENGINE); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + case 11: + setPotCrit(CRIT_GEAR); + return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); + case 12: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + } + } else if (side == ToHitData.SIDE_RIGHT) { + // normal right-side hits + switch (roll) { + case 2: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 3: + setPotCrit(CRIT_GEAR); + return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); + case 4: + setPotCrit(CRIT_SENSOR); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 5: + setPotCrit(CRIT_CREW); + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + case 6: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); + case 7: + setPotCrit(CRIT_AVIONICS); + return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); + case 8: + setPotCrit(CRIT_BOMB); + return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); + case 9: + setPotCrit(CRIT_CONTROL); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + case 10: + setPotCrit(CRIT_ENGINE); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + case 11: + setPotCrit(CRIT_GEAR); + return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); + case 12: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + } + } else if (side == ToHitData.SIDE_REAR) { + // normal aft hits + switch (roll) { + case 2: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + case 3: + setPotCrit(CRIT_HEATSINK); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + case 4: + setPotCrit(CRIT_FUEL_TANK); + return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); + case 5: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); + case 6: + setPotCrit(CRIT_ENGINE); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + case 7: + setPotCrit(CRIT_CONTROL); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + case 8: + setPotCrit(CRIT_ENGINE); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + case 9: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); + case 10: + setPotCrit(CRIT_FUEL_TANK); + return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); + case 11: + setPotCrit(CRIT_HEATSINK); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + case 12: + setPotCrit(CRIT_WEAPON); + return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); + } + } + return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); + } + + /** + * Gets the location that excess damage transfers to + */ + @Override + public HitData getTransferLocation(HitData hit) { + return new HitData(LOC_DESTROYED); + } + + /** + * Gets the location that is destroyed recursively + */ + @Override + public int getDependentLocation(int loc) { + return LOC_NONE; + } + + public double getBVTypeModifier() { + return 1.2; + } + + @Override + public PilotingRollData addEntityBonuses(PilotingRollData prd) { + // this is a control roll. Affected by: + // avionics damage + // partial repairs + // pilot damage + // current velocity + int avihits = getAvionicsHits(); + int pilothits = getCrew().getHits(); + + if ((avihits > 0) && (avihits < 3)) { + prd.addModifier(avihits, "Avionics Damage"); + } + + // this should probably be replaced with some kind of AVI_DESTROYED + // boolean + if (avihits >= 3) { + prd.addModifier(5, "Avionics Destroyed"); + } + + // partial repairs to avionics system, but only if the avionics aren't already destroyed + if ((getPartialRepairs() != null) && (avihits < 3)) { + if (getPartialRepairs().booleanOption("aero_avionics_crit")) { + prd.addModifier(1, "Partial repair of Avionics"); + } + if (getPartialRepairs().booleanOption("aero_avionics_replace")) { + prd.addModifier(1, "Misreplaced Avionics"); + } + } + + if (pilothits > 0) { + prd.addModifier(pilothits, "Pilot Hits"); + } + + // movement effects + // some question as to whether "above safe thrust" applies to thrust or + // velocity + // I will treat it as thrust until it is resolved + if (moved == EntityMovementType.MOVE_OVER_THRUST) { + prd.addModifier(+1, "Used more than safe thrust"); + } + int vel = getCurrentVelocity(); + int vmod = vel - (2 * getWalkMP()); + if (!getGame().getBoard().inSpace() && (vmod > 0)) { + prd.addModifier(vmod, "Velocity greater than 2x safe thrust"); + } + + int atmoCond = game.getPlanetaryConditions().getAtmosphere(); + // add in atmospheric effects later + if (!(game.getBoard().inSpace() || (atmoCond == PlanetaryConditions.ATMO_VACUUM)) && isAirborne()) { + prd.addModifier(+2, "Atmospheric operations"); + + // check type + if (this instanceof Dropship) { + if (isSpheroid()) { + prd.addModifier(+1, "spheroid dropship"); + } else { + prd.addModifier(0, "aerodyne dropship"); + } + } else { + prd.addModifier(-1, "fighter/small craft"); + } + } + + // life support (only applicable to non-ASFs + if (!hasLifeSupport()) { + prd.addModifier(+2, "No life support"); + } + + if (hasModularArmor()) { + prd.addModifier(1, "Modular Armor"); + } + // VDNI bonus? + if (hasAbility(OptionsConstants.MD_VDNI) + && !hasAbility(OptionsConstants.MD_BVDNI)) { + prd.addModifier(-1, "VDNI"); + } + + // Small/torso-mounted cockpit penalty? + if ((getCockpitType() == AeroSpaceFighter.COCKPIT_SMALL) + && !hasAbility(OptionsConstants.MD_BVDNI) + && !hasAbility(OptionsConstants.UNOFF_SMALL_PILOT)) { + prd.addModifier(1, "Small Cockpit"); + } + + // quirks? + if (hasQuirk(OptionsConstants.QUIRK_POS_ATMO_FLYER) && !game.getBoard().inSpace()) { + prd.addModifier(-1, "atmospheric flyer"); + } + if (hasQuirk(OptionsConstants.QUIRK_NEG_ATMO_INSTABILITY) && !game.getBoard().inSpace()) { + prd.addModifier(+1, "atmospheric flight instability"); + } + if (hasQuirk(OptionsConstants.QUIRK_NEG_CRAMPED_COCKPIT) && !hasAbility(OptionsConstants.UNOFF_SMALL_PILOT)) { + prd.addModifier(1, "cramped cockpit"); + } + + return prd; + } + + @Override + public Vector victoryReport() { + Vector vDesc = new Vector<>(); + + Report r = new Report(7025); + r.type = Report.PUBLIC; + r.addDesc(this); + vDesc.addElement(r); + + if (((getEntityType() & Entity.ETYPE_DROPSHIP) == 0) || ((getEntityType() & Entity.ETYPE_SMALL_CRAFT) == 0) + || ((getEntityType() & Entity.ETYPE_FIGHTER_SQUADRON) == 0) + || ((getEntityType() & Entity.ETYPE_JUMPSHIP) == 0) + || ((getEntityType() & Entity.ETYPE_SPACE_STATION) == 0)) { + r = new Report(7036); + } else { + r = new Report(7030); + } + r.type = Report.PUBLIC; + r.newlines = 0; + vDesc.addElement(r); + vDesc.addAll(getCrew().getDescVector(false)); + r = new Report(7070, Report.PUBLIC); + r.add(getKillNumber()); + vDesc.addElement(r); + + if (isDestroyed()) { + Entity killer = game.getEntity(killerId); + if (killer == null) { + killer = game.getOutOfGameEntity(killerId); + } + if (killer != null) { + r = new Report(7072, Report.PUBLIC); + r.addDesc(killer); + } else { + if (this instanceof FighterSquadron) { + r = new Report(7076, Report.PUBLIC); + } else { + r = new Report(7073, Report.PUBLIC); + } + } + vDesc.addElement(r); + } else if (getCrew().isEjected()) { + r = new Report(7074, Report.PUBLIC); + vDesc.addElement(r); + } + r.newlines = 2; + + return vDesc; + } + + @Override + public int[] getNoOfSlots() { + return NUM_OF_SLOTS; + } + + @Override + public int getRunMP(MPCalculationSetting mpCalculationSetting) { + if (isAirborne()) { + return super.getRunMP(mpCalculationSetting); + } else { + return getWalkMP(mpCalculationSetting); + } + } + + @Override + public int getHeatCapacity(boolean includeRadicalHeatSink) { + int capacity = (getHeatSinks() * (getHeatType() + 1)); + if (includeRadicalHeatSink && hasWorkingMisc(MiscType.F_RADICAL_HEATSINK)) { + capacity += Math.ceil(getHeatSinks() * 0.4); + } + return capacity; + } + + // If the aero is in the water, it is dead so no worries + @Override + public int getHeatCapacityWithWater() { + return getHeatCapacity(false); + } + + @Override + public int getEngineCritHeat() { + // Engine hits cause excess heat for fighters, TW pg 240 + if (!((this instanceof SmallCraft) || (this instanceof Jumpship))) { + return 2 * getEngineHits(); + } else { + return 0; + } + } + + @Override + public void autoSetInternal() { + // should be no internals because only one SI + // It doesn't seem to be screwing anything up yet. + // Need to figure out how destruction of entity is determined + int nInternal = (int) Math.ceil(weight / 10.0); + nInternal = 0; + // I need to look at safe thrust as well at some point + + for (int x = 0; x < locations(); x++) { + initializeInternal(nInternal, x); + } + } + + // initialize the Damage threshold + public void autoSetThresh() { + for (int x = 0; x < locations(); x++) { + initializeThresh(x); + } + } + + public void setThresh(int val, int loc) { + if (loc < damThresh.length) { + damThresh[loc] = val; + } + } + + public void initializeThresh(int loc) { + int nThresh = (int) Math.ceil(getArmor(loc) / 10.0); + setThresh(nThresh, loc); + } + + @Override + public int getThresh(int loc) { + if (isCapitalFighter()) { + if ((null != game) && game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AERO_SANITY)) { + if (game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_VARIABLE_DAMAGE_THRESH)) { + return (int) Math.round(getCapArmor() / 40.0) + 1; + } else { + return (int) Math.round(getCap0Armor() / 40.0) + 1; + } + } else { + return 2; + } + } else if (loc < damThresh.length) { + return damThresh[loc]; + } + return 0; + } + + /** + * Determine if the unit can be repaired, or only harvested for spares. + * + * @return A boolean that is true if the unit can + * be repaired (given enough time and parts); if this value is + * false, the unit is only a source of spares. + * @see Entity#isSalvage() + */ + @Override + public boolean isRepairable() { + return true; // deal with this later + } + + @Override + public boolean canCharge() { + // ramming is resolved differently than charging + return false; + } + + @Override + public boolean canDFA() { + // Aero can't DFA + return false; + } + + @Override + public boolean canRam() { + return !isImmobile() && (getWalkMP() > 0); + } + + /** + * @return suspension factor of vehicle + */ + // Doesn't really do anything so just return 0 + public int getSuspensionFactor() { + return 0; + } + + @Override + public double getCost(CalculationReport calcReport, boolean ignoreAmmo) { + return AeroCostCalculator.calculateCost(this, calcReport, ignoreAmmo); + } + + @Override + public double getPriceMultiplier() { + double priceModifier = 1.0; + if (isOmni()) { + priceModifier *= 1.25f; + } + priceModifier *= 1 + (weight / 200f); + return priceModifier; + } + + @Override + public int implicitClanCASE() { + if (!isClan() || !isFighter()) { + return 0; + } + // Ammo is actually supposed to be assigned to a fuselage location rather than one of the four + // weapon arcs. We will use LOC_NONE to record the existence of non-weapon explosive equipment. + Set caseLocations = new HashSet<>(); + int explicit = 0; + for (Mounted m : getEquipment()) { + if ((m.getType() instanceof MiscType) && (m.getType().hasFlag(MiscType.F_CASE))) { + explicit++; + } else if (m.getType().isExplosive(m)) { + if (m.getType() instanceof WeaponType) { + caseLocations.add(m.getLocation()); + } else { + caseLocations.add(LOC_NONE); + } + } + } + return Math.max(0, caseLocations.size() - explicit); + } + + @Override + public boolean doomedInExtremeTemp() { + return false; + } + + @Override + public boolean doomedInVacuum() { + return false; + } + + @Override + public boolean doomedOnGround() { + return game != null ? !game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AERO_GROUND_MOVE) : false; + } + + @Override + public boolean doomedInAtmosphere() { + return false; + } + + @Override + public boolean doomedInSpace() { + return false; + } + + @Override + public boolean canGoHullDown() { + return false; + } + + /* + * public void addMovementDamage(int level) { movementDamage += level; } + */ + + @Override + public void setEngine(Engine e) { + super.setEngine(e); + if (hasEngine() && getEngine().engineValid) { + setOriginalWalkMP(calculateWalk()); + } + } + + /** + * Returns the percent of the SI remaining + */ + @Override + public double getInternalRemainingPercent() { + return ((double) getSI() / (double) get0SI()); + } + + protected int calculateWalk() { + if (!hasEngine()) { + return 0; + } + if (isPrimitive()) { + double rating = getEngine().getRating(); + rating /= 1.2; + if ((rating % 5) != 0) { + return (int) (((rating - (rating % 5)) + 5) / (int) weight) + 2; + } + return (int) (rating / (int) weight) + 2; + } + return (getEngine().getRating() / (int) weight) + 2; + } + + @Override + public boolean isNuclearHardened() { + return true; + } + + @Override + public void addEquipment(Mounted mounted, int loc, boolean rearMounted) throws LocationFullException { + if (getEquipmentNum(mounted) == -1) { + super.addEquipment(mounted, loc, rearMounted); + } + // Add the piece equipment to our slots. + addCritical(loc, new CriticalSlot(mounted)); + } + + /** + * get the type of critical caused by a critical roll, taking account of + * existing damage + * + * @param roll + * the final dice roll + * @param target + * the hit location + * @return a critical type + */ + public int getCriticalEffect(int roll, int target) { + // just grab the latest potential crit + if (roll < target) { + return CRIT_NONE; + } + + int critical = getPotCrit(); + return critical; + } + + /** + + */ + @Override + public void setOmni(boolean omni) { + + // Perform the superclass' action. + super.setOmni(omni); + + } + + @Override + public void addClanCase() { + if (!(isClan() && isFighter())) { + return; + } + boolean explosiveFound = false; + EquipmentType clCase = EquipmentType.get(EquipmentTypeLookup.CLAN_CASE); + for (int i = 0; i < locations(); i++) { + // Ignore wings location: it's not a valid loc to put equipment in + if (i == LOC_WINGS) { + continue; + } + explosiveFound = false; + for (Mounted m : getEquipment()) { + if (m.getType().isExplosive(m, true) && (m.getLocation() == i)) { + explosiveFound = true; + } + } + if (explosiveFound) { + try { + addEquipment(new Mounted(this, clCase), i, false); + } catch (LocationFullException ex) { + // um, that's impossible. + } + } + } + + } + + /** + * check to see if case is available anywhere + * + * @return + */ + @Override + public boolean hasCase() { + + boolean hasCase = false; + + for (int x = 0; x < locations(); x++) { + if (!hasCase) { + hasCase = locationHasCase(x); + } + } + return hasCase; + } + + /** + * Used to determine net velocity of ramming attack + * + */ + @Override + public int sideTableRam(Coords src) { + int side = super.sideTableRam(src); + if (game.useVectorMove() && game.getBoard().inSpace()) { + int newside = chooseSideRam(src); + if (newside != -1) { + side = newside; + } + } + return side; + + } + + public int chooseSideRam(Coords src) { + // loop through directions and if we have a non-zero vector, then + // compute + // the targetsidetable. If we come to a higher vector, then replace. If + // we come to an equal vector then take it if it is better + int thrust = 0; + int high = -1; + int side = -1; + for (int dir = 0; dir < 6; dir++) { + thrust = getVector(dir); + if (thrust == 0) { + continue; + } + + if (thrust > high) { + high = thrust; + side = sideTableRam(src, dir); + } + + // what if they tie + if (thrust == high) { + int newside = sideTableRam(src, dir); + // choose the better + if (newside > side) { + newside = side; + } + // that should be the only case, because it can't shift you from + // front + // to aft or vice-versa + } + + } + return side; + } + + public int getMaxEngineHits() { + return 3; + } + + @Override + public int getMaxElevationChange() { + if (isAirborne()) { + return UNLIMITED_JUMP_DOWN; + } + return 1; + } + + /** + * Determine if this unit has an active and working stealth system. (stealth + * can be active and not working when under ECCM) + *

+ * Sub-classes are encouraged to override this method. + * + * @return true if this unit has a stealth system that is + * currently active, false if there is no stealth + * system or if it is inactive. + */ + @Override + public boolean isStealthActive() { + // Try to find a Mek Stealth system. + for (Mounted mEquip : getMisc()) { + MiscType mtype = (MiscType) mEquip.getType(); + if (mtype.hasFlag(MiscType.F_STEALTH)) { + + if (mEquip.curMode().equals("On") && hasActiveECM()) { + // Return true if the mode is "On" and ECM is working + return true; + } + } + } + // No Mek Stealth or system inactive. Return false. + return false; + } + + /** + * Determine if this unit has an active and working stealth system. (stealth + * can be active and not working when under ECCM) + *

+ * Sub-classes are encouraged to override this method. + * + * @return true if this unit has a stealth system that is + * currently active, false if there is no stealth + * system or if it is inactive. + */ + @Override + public boolean isStealthOn() { + // Try to find a Mek Stealth system. + for (Mounted mEquip : getMisc()) { + MiscType mtype = (MiscType) mEquip.getType(); + if (mtype.hasFlag(MiscType.F_STEALTH)) { + if (mEquip.curMode().equals("On")) { + // Return true if the mode is "On" + return true; + } + } + } + // No Mek Stealth or system inactive. Return false. + return false; + } + + /** + * Determine the stealth modifier for firing at this unit from the given + * range. If the value supplied for range is not one of the + * Entity class range constants, an + * IllegalArgumentException will be thrown. + *

+ * Sub-classes are encouraged to override this method. + * + * @param range + * - an int value that must match one of the + * Compute class range constants. + * @param ae + * - entity making the attack + * @return a TargetRoll value that contains the stealth + * modifier for the given range. + */ + @Override + public TargetRoll getStealthModifier(int range, Entity ae) { + TargetRoll result = null; + + // Stealth or null sig must be active. + if (!isStealthActive()) { + result = new TargetRoll(0, "stealth not active"); + } + // Determine the modifier based upon the range. + // Infantry do not ignore Chameleon LPS!!! + else { + switch (range) { + case RangeType.RANGE_MINIMUM: + case RangeType.RANGE_SHORT: + if (!ae.isConventionalInfantry()) { + result = new TargetRoll(0, "stealth"); + } else { + result = new TargetRoll(0, "infantry ignore stealth"); + } + break; + case RangeType.RANGE_MEDIUM: + if (!ae.isConventionalInfantry()) { + result = new TargetRoll(1, "stealth"); + } else { + result = new TargetRoll(0, "infantry ignore stealth"); + } + break; + case RangeType.RANGE_LONG: + case RangeType.RANGE_EXTREME: + case RangeType.RANGE_LOS: + if (!ae.isConventionalInfantry()) { + result = new TargetRoll(2, "stealth"); + } else { + result = new TargetRoll(0, "infantry ignore stealth"); + } + break; + case RangeType.RANGE_OUT: + break; + default: + throw new IllegalArgumentException("Unknown range constant: " + range); + } + } + + // Return the result. + return result; + + } // End public TargetRoll getStealthModifier( char ) + + @Override + public void setArmorType(int armType) { + setArmorType(armType, true); + } + + public void setArmorType(int armType, boolean addMount) { + super.setArmorType(armType); + if ((armType == EquipmentType.T_ARMOR_STEALTH_VEHICLE) && addMount) { + try { + this.addEquipment( + EquipmentType.get(EquipmentType.getArmorTypeName(EquipmentType.T_ARMOR_STEALTH_VEHICLE, false)), + LOC_AFT); + } catch (LocationFullException e) { + // this should never happen + } + } + } + + @Override + public boolean isLocationProhibited(Coords c, int currElevation) { + if (isAirborne()) { + return false; + } + + Hex hex = game.getBoard().getHex(c); + + // Additional restrictions for hidden units + if (isHidden()) { + // Can't deploy in paved hexes + if (hex.containsTerrain(Terrains.PAVEMENT) || hex.containsTerrain(Terrains.ROAD)) { + return true; + } + // Can't deploy on a bridge + if ((hex.terrainLevel(Terrains.BRIDGE_ELEV) == currElevation) && hex.containsTerrain(Terrains.BRIDGE)) { + return true; + } + // Can't deploy on the surface of water + if (hex.containsTerrain(Terrains.WATER) && (currElevation == 0)) { + return true; + } + } + + // grounded aeros have the same prohibitions as wheeled tanks + return hex.containsTerrain(Terrains.WOODS) || hex.containsTerrain(Terrains.ROUGH) + || ((hex.terrainLevel(Terrains.WATER) > 0) && !hex.containsTerrain(Terrains.ICE)) + || hex.containsTerrain(Terrains.RUBBLE) || hex.containsTerrain(Terrains.MAGMA) + || hex.containsTerrain(Terrains.JUNGLE) || (hex.terrainLevel(Terrains.SNOW) > 1) + || (hex.terrainLevel(Terrains.GEYSER) == 2); + } + + @Override + public boolean isNightwalker() { + return false; + } + + @Override + public boolean isSpheroid() { + return spheroid; + } + + public void setSpheroid(boolean b) { + spheroid = b; + } + + @Override + public int height() { + return 0; + } + + @Override + public int getStraightMoves() { + return straightMoves; + } + + @Override + public void setStraightMoves(int i) { + straightMoves = i; + } + + @Override + public boolean isVSTOL() { + return vstol; + } + + @Override + public boolean isSTOL() { + return false; + } + + public void setVSTOL(boolean b) { + vstol = b; + } + + @Override + public boolean didFailManeuver() { + return failedManeuver; + } + + @Override + public void setFailedManeuver(boolean b) { + failedManeuver = b; + } + + @Override + public void setAccDecNow(boolean b) { + accDecNow = b; + } + + @Override + public boolean didAccDecNow() { + return accDecNow; + } + + /* + * (non-Javadoc) + * + * @see megamek.common.Entity#getTotalCommGearTons() + */ + @Override + public int getTotalCommGearTons() { + return 1 + getExtraCommGearTons(); + } + + /** + * The number of critical slots that are destroyed in the component. + */ + @Override + public int getBadCriticals(int type, int index, int loc) { + return 0; + } + + public int getCockpitType() { + return cockpitType; + } + + public void setCockpitType(int type) { + cockpitType = type; + if (type == COCKPIT_COMMAND_CONSOLE) { + setCrew(new Crew(CrewType.COMMAND_CONSOLE)); + } else { + setCrew(new Crew(CrewType.SINGLE)); + } + } + + public String getCockpitTypeString() { + return AeroSpaceFighter.getCockpitTypeString(getCockpitType()); + } + + public static String getCockpitTypeString(int inCockpitType) { + if ((inCockpitType < 0) || (inCockpitType >= COCKPIT_STRING.length)) { + return "Unknown"; + } + return COCKPIT_STRING[inCockpitType]; + } + + @Override + public boolean hasCommandConsoleBonus() { + return getCockpitType() == COCKPIT_COMMAND_CONSOLE && getCrew().hasActiveCommandConsole() + && getWeightClass() >= EntityWeightClass.WEIGHT_HEAVY; + } + + @Override + public double getArmorRemainingPercent() { + int armor0 = getTotalOArmor(); + int armor = getTotalArmor(); + if (isCapitalFighter()) { + armor0 = getCap0Armor(); + armor = getCapArmor(); + } + if (armor0 == 0) { + return IArmorState.ARMOR_NA; + } + return ((double) armor / (double) armor0); + } + + /** + * keep track of whether the wings have suffered a weapon critical hit + */ + public boolean areWingsHit() { + return wingsHit; + } + + public void setWingsHit(boolean b) { + wingsHit = b; + } + + /** + * what location is opposite the given one + */ + public int getOppositeLocation(int loc) { + switch (loc) { + case AeroSpaceFighter.LOC_NOSE: + return AeroSpaceFighter.LOC_AFT; + case AeroSpaceFighter.LOC_LWING: + return AeroSpaceFighter.LOC_RWING; + case AeroSpaceFighter.LOC_RWING: + return AeroSpaceFighter.LOC_LWING; + case AeroSpaceFighter.LOC_AFT: + return AeroSpaceFighter.LOC_NOSE; + default: + return AeroSpaceFighter.LOC_NOSE; + } + } + + /** + * get modifications to the cluster hit table for critical hits + */ + @Override + public int getClusterMods() { + return -1 * (getFCSHits() + getSensorHits()); + } + + /** + * What's the range of the ECM equipment? + * + * @return the int range of this unit's ECM. This value will be + * Entity.NONE if no ECM is active. + */ + @Override + public int getECMRange() { + if (!game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_STRATOPS_ECM) + || !game.getBoard().inSpace()) { + return super.getECMRange(); + } + return Math.min(super.getECMRange(), 0); + } + + /** + * @return the strength of the ECCM field this unit emits + */ + @Override + public double getECCMStrength() { + if (!game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_STRATOPS_ECM) + || !game.getBoard().inSpace()) { + return super.getECCMStrength(); + } + if (hasActiveECCM()) { + return 1; + } + return 0; + } + + public void setECCMRoll(int i) { + eccmRoll = i; + } + + public int getECCMRoll() { + return eccmRoll; + } + + public int getECCMTarget() { + return getCrew().getPiloting() + getSensorHits() + getCICHits() + getFCSHits(); + } + + public int getECCMBonus() { + return Math.max(0, eccmRoll - getECCMTarget()); + } + + /** + * @return is the crew of this vessel protected from gravitational effects, + * see StratOps, pg. 36 + */ + public boolean isCrewProtected() { + return true; + } + + public int getGravSecondaryThreshold() { + int thresh = 6; + if (isCrewProtected()) { + thresh = 12; + } + // TODO: clan phenotypes + return thresh; + } + + public int getGravPrimaryThreshold() { + int thresh = 12; + if (isCrewProtected()) { + thresh = 22; + } + // TODO: clan phenotypes + return thresh; + } + + /** + * Determines if this object can accept the given unit. The unit may not be + * of the appropriate type or there may be no room for the unit. + * + * @param unit + * - the Entity to be loaded. + * @return true if the unit can be loaded, false + * otherwise. + */ + @Override + public boolean canLoad(Entity unit, boolean checkFalse) { + // capital fighters can load other capital fighters (becoming squadrons) + // but not in the deployment phase + if (isCapitalFighter() && !unit.isEnemyOf(this) && unit.isCapitalFighter() && (getId() != unit.getId()) + && !getGame().getPhase().isDeployment()) { + return true; + } + + return super.canLoad(unit, checkFalse); + } + + @Override + public Map getWeaponGroups() { + return weaponGroups; + } + + /** + * Iterate through current weapons and count the number in each capital + * fighter location. + * + * @return A map with keys in the format "weaponName:loc", with the number + * of weapons of that type in that location as the value. + */ + @Override + public Map groupWeaponsByLocation() { + Map groups = new HashMap<>(); + for (Mounted mounted : getTotalWeaponList()) { + int loc = mounted.getLocation(); + if (isFighter() && ((loc == AeroSpaceFighter.LOC_RWING) || (loc == AeroSpaceFighter.LOC_LWING))) { + loc = AeroSpaceFighter.LOC_WINGS; + } + if (mounted.isRearMounted()) { + loc = AeroSpaceFighter.LOC_AFT; + } + String key = mounted.getType().getInternalName() + ":" + loc; + if (null == groups.get(key)) { + groups.put(key, mounted.getNWeapons()); + } else { + groups.put(key, groups.get(key) + mounted.getNWeapons()); + } + } + return groups; + } + + /** + * In cases where another unit occupies the same hex, determine if this Aero + * should be moved back a hex for targeting purposes + * + * @param other + * @return + */ + public boolean shouldMoveBackHex(AeroSpaceFighter other) { + if (null == getPosition()) { + return false; + } + if (null == other.getPosition()) { + return false; + } + if (!getPosition().equals(other.getPosition())) { + return false; + } + int type = this.getUnitType(); + int otherType = other.getUnitType(); + int vel = getCurrentVelocity(); + int otherVel = other.getCurrentVelocity(); + if (type > otherType) { + return false; + } else if (type < otherType) { + return true; + } + // if we are still here then type is the same so compare velocity + if (vel < otherVel) { + return false; + } else if (vel > otherVel) { + return true; + } + // if we are still here then type and velocity same, so roll for it + if (getWhoFirst() < other.getWhoFirst()) { + return false; + } + return true; + } + + @Override + public boolean hasArmoredEngine() { + for (int slot = 0; slot < getNumberOfCriticals(LOC_AFT); slot++) { + CriticalSlot cs = getCritical(LOC_AFT, slot); + if ((cs != null) && (cs.getType() == CriticalSlot.TYPE_SYSTEM) && (cs.getIndex() == Mech.SYSTEM_ENGINE)) { + return cs.isArmored(); + } + } + return false; + } + + /** + * see {@link Entity#getForwardArc()} + */ + @Override + public int getForwardArc() { + return Compute.ARC_NOSE; + } + + /** + * see {@link Entity#getRearArc()} + */ + @Override + public int getRearArc() { + return Compute.ARC_AFT; + } + + @Override + public int getAltLoss() { + return altLoss; + } + + @Override + public void setAltLoss(int i) { + altLoss = i; + } + + @Override + public void resetAltLoss() { + altLoss = 0; + } + + @Override + public int getAltLossThisRound() { + return altLossThisRound; + } + + @Override + public void setAltLossThisRound(int i) { + altLossThisRound = i; + } + + @Override + public void resetAltLossThisRound() { + altLossThisRound = 0; + } + + @Override + public int getElevation() { + if ((game != null) && game.getBoard().inSpace()) { + return 0; + } + // Altitude is not the same as elevation. If an aero is at 0 altitude, then it is grounded + // and uses elevation normally. Otherwise, just set elevation to a very large number so that + // a flying aero won't interact with the ground maps in any way + return isAirborne() ? 999 : super.getElevation(); + } + + @Override + public boolean canGoDown() { + return canGoDown(altitude, getPosition()); + } + + @Override + public boolean isPrimitive() { + return (getCockpitType() == AeroSpaceFighter.COCKPIT_PRIMITIVE); + } + + @Override + public String getLocationDamage(int loc) { + return ""; + } + + public String getCritDamageString() { + StringBuilder toReturn = new StringBuilder(); + boolean first = true; + if (getSensorHits() > 0) { + if (!first) { + toReturn.append(", "); + } + toReturn.append(String.format(Messages.getString("Aero.sensorDamageString"), getSensorHits())); + first = false; + } + if (getAvionicsHits() > 0) { + if (!first) { + toReturn.append(", "); + } + toReturn.append(String.format(Messages.getString("Aero.avionicsDamageString"), getAvionicsHits())); + first = false; + } + if (getFCSHits() > 0) { + if (!first) { + toReturn.append(", "); + } + toReturn.append(String.format(Messages.getString("Aero.fcsDamageString"), getFCSHits())); + first = false; + } + if (getCICHits() > 0) { + if (!first) { + toReturn.append(", "); + } + toReturn.append(String.format(Messages.getString("Aero.cicDamageString"), getCICHits())); + first = false; + } + if (isGearHit()) { + if (!first) { + toReturn.append(", "); + } + toReturn.append(Messages.getString("Aero.landingGearDamageString")); + first = false; + } + if (!hasLifeSupport()) { + if (!first) { + toReturn.append(", "); + } + toReturn.append(Messages.getString("Aero.lifeSupportDamageString")); + first = false; + } + if (getLeftThrustHits() > 0) { + if (!first) { + toReturn.append(", "); + } + toReturn.append(String.format(Messages.getString("Aero.leftThrusterDamageString"), getLeftThrustHits())); + first = false; + } + if (getRightThrustHits() > 0) { + if (!first) { + toReturn.append(", "); + } + toReturn.append(String.format(Messages.getString("Aero.rightThrusterDamageString"), getRightThrustHits())); + first = false; + } + // Cargo bays and bay doors for large craft + for (Bay next : getTransportBays()) { + if (next.getBayDamage() > 0) { + if (!first) { + toReturn.append(", "); + } + toReturn.append(String.format(Messages.getString("Aero.bayDamageString"), next.getType(), next.getBayNumber())); + first = false; + } + if (next.getCurrentDoors() < next.getDoors()) { + if (!first) { + toReturn.append(", "); + } + toReturn.append(String.format(Messages.getString("Aero.bayDoorDamageString"), next.getType(), next.getBayNumber(), (next.getDoors() - next.getCurrentDoors()))); + first = false; + } + } + return toReturn.toString(); + } + + @Override + public boolean isCrippled() { + return isCrippled(true); + } + + @Override + public boolean isCrippled(boolean checkCrew) { + if (isEjecting()) { + LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: The crew is currently ejecting."); + return true; + } else if (getInternalRemainingPercent() < 0.5) { + LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: Only " + + NumberFormat.getPercentInstance().format(getInternalRemainingPercent()) + " internals remaining."); + return true; + } else if (getEngineHits() > 0) { + LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: " + engineHits + " Engine Hits."); + return true; + } else if (fuelTankHit()) { + LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: Fuel Tank Hit"); + return true; + } else if (checkCrew && (getCrew() != null) && (getCrew().getHits() >= 4)) { + LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: " + getCrew().getHits() + " Crew Hits taken."); + return true; + } else if (getFCSHits() >= 3) { + LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: Fire Control Destroyed by taking " + fcsHits); + return true; + } else if (getCICHits() >= 3) { + LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: Combat Information Center Destroyed by taking " + cicHits); + return true; + } + + // If this is not a military unit, we don't care about weapon status. + if (!isMilitary()) { + return false; + } + + if (!hasViableWeapons()) { + LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: No more viable weapons."); + return true; + } else { + return false; + } + } + + @Override + public boolean isDmgHeavy() { + if (getArmorRemainingPercent() <= 0.33) { + LogManager.getLogger().debug(getDisplayName() + + " Heavily Damaged: Armour Remaining percent of " + getArmorRemainingPercent() + + " is less than or equal to 0.33."); + return true; + } else if (getInternalRemainingPercent() < 0.67) { + LogManager.getLogger().debug(getDisplayName() + + " Heavily Damaged: Internal Structure Remaining percent of " + getInternalRemainingPercent() + + " is less than 0.67."); + return true; + } else if ((getCrew() != null) && (getCrew().getHits() == 3)) { + LogManager.getLogger().debug(getDisplayName() + + "Moderately Damaged: The crew has taken a minimum of three hits."); + return true; + } + + // If this is not a military unit, we don't care about weapon status. + if (!isMilitary()) { + return false; + } + + List weaponList = getTotalWeaponList(); + int totalWeapons = weaponList.size(); + int totalInoperable = 0; + for (Mounted weap : weaponList) { + if (weap.isCrippled()) { + totalInoperable++; + } + } + return ((double) totalInoperable / totalWeapons) >= 0.75; + } + + @Override + public boolean isDmgModerate() { + if (getArmorRemainingPercent() <= 0.5) { + LogManager.getLogger().debug(getDisplayName() + + " Moderately Damaged: Armour Remaining percent of " + getArmorRemainingPercent() + + " is less than or equal to 0.50."); + return true; + } else if (getInternalRemainingPercent() < 0.75) { + LogManager.getLogger().debug(getDisplayName() + + " Moderately Damaged: Internal Structure Remaining percent of " + getInternalRemainingPercent() + + " is less than 0.75."); + return true; + } else if ((getCrew() != null) && (getCrew().getHits() == 2)) { + LogManager.getLogger().debug(getDisplayName() + + " Moderately Damaged: The crew has taken a minimum of two hits."); + return true; + } + + // If this is not a military unit, we don't care about weapon status. + if (!isMilitary()) { + return false; + } + + int totalWeapons = getTotalWeaponList().size(); + int totalInoperable = 0; + for (Mounted weap : getTotalWeaponList()) { + if (weap.isCrippled()) { + totalInoperable++; + } + } + return ((double) totalInoperable / totalWeapons) >= 0.5; + } + + @Override + public boolean isDmgLight() { + if (getArmorRemainingPercent() <= 0.75) { + LogManager.getLogger().debug(getDisplayName() + + " Lightly Damaged: Armour Remaining percent of " + getArmorRemainingPercent() + + " is less than or equal to 0.75."); + return true; + } else if (getInternalRemainingPercent() < 0.9) { + LogManager.getLogger().debug(getDisplayName() + + " Lightly Damaged: Internal Structure Remaining percent of " + getInternalRemainingPercent() + + " is less than 0.9."); + return true; + } else if ((getCrew() != null) && (getCrew().getHits() == 1)) { + LogManager.getLogger().debug(getDisplayName() + + " Lightly Damaged: The crew has taken a minimum of one hit."); + return true; + } + + // If this is not a military unit, we don't care about weapon status. + if (!isMilitary()) { + return false; + } + + int totalWeapons = getTotalWeaponList().size(); + int totalInoperable = 0; + for (Mounted weap : getTotalWeaponList()) { + if (weap.isCrippled()) { + totalInoperable++; + } + } + return ((double) totalInoperable / totalWeapons) >= 0.25; + } + + @Override + public boolean canSpot() { + // per a recent ruling on the official forums, aero units can't spot + // for indirect LRM fire, unless they have a recon cam, an infrared or + // hyperspec imager, or a high-res imager and it's not night + if (!isAirborne() || hasWorkingMisc(MiscType.F_RECON_CAMERA) || hasWorkingMisc(MiscType.F_INFRARED_IMAGER) + || hasWorkingMisc(MiscType.F_HYPERSPECTRAL_IMAGER) + || (hasWorkingMisc(MiscType.F_HIRES_IMAGER) + && ((game.getPlanetaryConditions().getLight() == PlanetaryConditions.L_DAY) + || (game.getPlanetaryConditions().getLight() == PlanetaryConditions.L_DUSK)))) { + return true; + } else { + return false; + } + } + + // Damage a fighter that was part of a squadron when splitting it. Per + // StratOps pg. 32 & 34 + @Override + public void doDisbandDamage() { + + int dealt = 0; + + // Check for critical threshold and if so damage all armor on one facing + // of the fighter completely, + // reduce SI by half, and mark three engine hits. + if (isDestroyed() || isDoomed()) { + int loc = Compute.randomInt(4); + dealt = getArmor(loc); + setArmor(0, loc); + int finalSI = Math.min(getSI(), getSI() / 2); + dealt += getSI() - finalSI; + setSI(finalSI); + setEngineHits(Math.max(3, getEngineHits())); + } + + // Move on to actual damage... + int damage = getCap0Armor() - getCapArmor(); + // Fix for #587. Only multiply if Aero Sanity is off + if ((null != game) && !game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AERO_SANITY)) { + damage *= 10; + } + damage -= dealt; // We already dealt a bunch of damage, move on. + if (damage < 1) { + return; + } + int hits = (int) Math.ceil(damage / 5.0); + int damPerHit = 5; + for (int i = 0; i < hits; i++) { + int loc = Compute.randomInt(4); + // Fix for #587. Apply in 5 point groups unless damage remainder is less. + setArmor(getArmor(loc) - Math.min(damPerHit, damage), loc); + // We did too much damage, so we need to damage the SI, but we wont + // reduce the SI below 1 here + // unless the fighter is destroyed. + if (getArmor(loc) < 0) { + if (getSI() > 1) { + int si = getSI() + (getArmor(loc) / 2); + si = Math.max(si, isDestroyed() || isDoomed() ? 0 : 1); + setSI(si); + } + setArmor(0, loc); + } + damage -= damPerHit; + } + } + + /** + * Damage a capital fighter's weapons. WeaponGroups are damaged by critical hits. + * This matches up the individual fighter's weapons and critical slots and damages those + * for MHQ resolution + * @param loc - Int corresponding to the location struck + */ + public void damageCapFighterWeapons(int loc) { + for (Mounted weapon : weaponList) { + if (weapon.getLocation() == loc) { + //Damage the weapon + weapon.setHit(true); + //Damage the critical slot + for (int i = 0; i < getNumberOfCriticals(loc); i++) { + CriticalSlot slot1 = getCritical(loc, i); + if ((slot1 == null) || + (slot1.getType() == CriticalSlot.TYPE_SYSTEM)) { + continue; + } + Mounted mounted = slot1.getMount(); + if (mounted.equals(weapon)) { + hitAllCriticals(loc, i); + break; + } + } + } + } + } + + /** + * @return The total number of crew available to supplement marines on boarding actions. + * Includes officers, enlisted, and bay personnel, but not marines/ba or passengers. + */ + @Override + public int getNCrew() { + return 1; + } + + @Override + public void setNCrew(int crew) { + } + + /** + * @return The total number of officers for vessels. + */ + public int getNOfficers() { + return 0; + } + + /** + * @return The total number of gunners for vessels. + */ + public int getNGunners() { + return 0; + } + + /** + * Returns the number of passengers on this unit + * Intended for spacecraft, where we want to get the crews of transported units + * plus actual passengers assigned to quarters + * @return + */ + @Override + public int getNPassenger() { + return 0; + } + + @Override + public void setNPassenger(int pass) { + } + + /** + * Returns the list of Entity IDs used by this ship as escape craft + * @return + */ + public Set getEscapeCraft() { + return escapeCraftList; + } + + /** + * Adds an Escape Craft. Used by MHQ to track where escaped crew and passengers end up. + * @param id The Entity ID of the ship to add. + */ + public void addEscapeCraft(String id) { + escapeCraftList.add(id); + } + + /** + * Removes an Escape Craft. Used by MHQ to track where escaped crew and passengers end up. + * @param id The Entity ID of the ship to remove. + */ + public void removeEscapeCraft(String id) { + escapeCraftList.remove(id); + } + + /** + * @return The number battlearmored marines available to vessels for boarding actions. + */ + public int getNBattleArmor() { + return 0; + } + + /** + * @return The number conventional marines available to vessels for boarding actions. + */ + @Override + public int getNMarines() { + return 0; + } + + /** + * Updates the number of marines aboard + * @param marines The number of marines to add/subtract + */ + @Override + public void setNMarines(int marines) { + } + + /** + * Returns our list of unique individuals being transported as marines + * @return + */ + public Map getMarines() { + return marines; + } + + /** + * Adds a marine. Used by MHQ to track where a given person ends up. + * Also used by MM to move marines around between ships + * @param personId The unique ID of the person to add. + * @param pointValue The marine point value of the person being added + */ + public void addMarine(UUID personId, int pointValue) { + marines.put(personId, pointValue); + } + + /** + * Removes a marine. Used by MHQ to track where a given person ends up. + * Also used by MM to move marines around between ships + * @param personId The unique ID of the person to remove. + */ + public void removeMarine(UUID personId) { + marines.remove(personId); + } + + /** + * Returns the number of marines assigned to a unit + * Used for abandoning a unit + * @return + */ + public int getMarineCount() { + return 0; + } + + /** + * Convenience method that compiles the total number of people aboard a ship - Crew, Marines, Passengers... + * @return An integer representing everyone aboard + */ + public int getTotalAboard() { + return (getNCrew() + getNPassenger() + getMarineCount()); + } + + /** + * @return The number of escape pods carried by the unit + */ + public int getEscapePods() { + return 0; + } + + /** + * Convenience method to return the number of escape pods remaining + * @return + */ + public int getPodsLeft() { + return getEscapePods() - getLaunchedEscapePods(); + } + + /** + * @return The number of lifeboats carried by the unit + */ + public int getLifeBoats() { + return 0; + } + + /** + * Returns the total number of escape pods launched so far + */ + public int getLaunchedEscapePods() { + return 0; + } + + /** + * Updates the total number of escape pods launched so far + * @param n The number to change + */ + public void setLaunchedEscapePods(int n) { + } + + /** + * Returns the total number of life boats launched so far + */ + public int getLaunchedLifeBoats() { + return 0; + } + + /** + * Convenience method to return the number of life boats remaining + * @return + */ + public int getLifeBoatsLeft() { + return getLifeBoats() - getLaunchedLifeBoats(); + } + + /** + * Updates the total number of life boats launched so far + * @param n The number to change + */ + public void setLaunchedLifeBoats(int n) { + } + + /** + * Calculates whether this ship has any available escape systems remaining + * return + */ + public boolean hasEscapeSystemsLeft() { + return ((getLaunchedLifeBoats() < getLifeBoats()) + || (getLaunchedEscapePods() < getEscapePods()) + || !getLaunchableSmallCraft().isEmpty()); + } + + /** + * Calculates the total number of people that can be carried in this unit's escape systems + * 6 people per lifeboat/escape pod + troop capacity of any small craft + * Most small craft use cargo space instead of infantry bays, so we'll assume 0.1 tons/person + * (Taken from Infantry.getWeight() - foot trooper + .015t for the spacesuit everyone aboard is wearing ;) ) + * @return The total escape count for the unit + */ + public int getEscapeCapacity() { + int people = 0; + // We can cram 6 people in an escape pod + people += getEscapePods() * 6; + // Lifeboats hold 6 comfortably + people += getLifeBoats() * 6; + + // Any small craft aboard and able to launch? + for (Entity sc : getLaunchableSmallCraft()) { + // There could be an ASF in the bay... + if (sc instanceof SmallCraft) { + for (Bay b : sc.getTransportBays()) { + if (b instanceof InfantryBay || b instanceof BattleArmorBay || b instanceof CargoBay) { + // Use the available tonnage + people += (b.getCapacity() / 0.1); + } + } + } + } + return people; + } + + @Override + public long getEntityType() { + return Entity.ETYPE_AERO; + } + + public boolean isInASquadron() { + return game.getEntity(getTransportId()) instanceof FighterSquadron; + } + + @Override + public boolean isAero() { + return true; + } + + /** + * Fighters may carry external ordnance; + * Other Aerospace units with cargo bays and the Internal Bomb Bay quirk may carry bombs internally. + * @return boolean + */ + @Override + public boolean isBomber() { + return (isFighter() || + (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB))); + } + + @Override + /** + * Returns true if this is an aerospace or conventional fighter + * but not a larger craft (i.e. "SmallCraft" or "Dropship" and bigger + */ + public boolean isFighter() { + return true; + } + + @Override + /** + * Returns true if and only if this is an aerospace fighter. + */ + public boolean isAerospaceFighter() { + return true; + } + + @Override + public int availableBombLocation(int cost) { + return LOC_NOSE; + } + + /** + * Used to determine the draw priority of different Entity subclasses. This + * allows different unit types to always be draw above/below other types. + * + * @return + */ + @Override + public int getSpriteDrawPriority() { + return 10; + } + + @Override + public List getActiveAMS() { + //Large craft use AMS and Point Defense bays + if ((this instanceof Dropship) + || (this instanceof Jumpship) + || (this instanceof Warship) + || (this instanceof SpaceStation)) { + + ArrayList ams = new ArrayList<>(); + for (Mounted weapon : getWeaponBayList()) { + // Skip anything that's not an AMS, AMS Bay or Point Defense Bay + if (!weapon.getType().hasFlag(WeaponType.F_AMS) + && !weapon.getType().hasFlag(WeaponType.F_AMSBAY) + && !weapon.getType().hasFlag(WeaponType.F_PDBAY)) { + continue; + } + + // Make sure the AMS is good to go + if (!weapon.isReady() || weapon.isMissing() + || weapon.curMode().equals("Off") + || weapon.curMode().equals("Normal")) { + continue; + } + + // AMS blocked by transported units can not fire + if (isWeaponBlockedAt(weapon.getLocation(), + weapon.isRearMounted())) { + continue; + } + + // Make sure ammo is loaded + for (int wId : weapon.getBayWeapons()) { + Mounted bayW = getEquipment(wId); + Mounted bayWAmmo = bayW.getLinked(); + if (!(weapon.getType().hasFlag(WeaponType.F_ENERGY)) + && ((bayWAmmo == null) || (bayWAmmo.getUsableShotsLeft() == 0) + || bayWAmmo.isDumping())) { + loadWeapon(weapon); + bayWAmmo = weapon.getLinked(); + } + + // try again + if (!(weapon.getType().hasFlag(WeaponType.F_ENERGY)) + && ((bayWAmmo == null) || (bayWAmmo.getUsableShotsLeft() == 0) + || bayWAmmo.isDumping())) { + // No ammo for this AMS. + continue; + } + } + ams.add(weapon); + } + return ams; + } + //ASFs and Small Craft should use regular old AMS... + return super.getActiveAMS(); + } + + /** + * A method to add/remove sensors that only work in space as we transition in and out of an atmosphere + */ + @Override + public void updateSensorOptions() { + //Prevent adding duplicates + boolean hasSpacecraftThermal = false; + boolean hasAeroThermal = false; + boolean hasESM = false; + for (Sensor sensor : getSensors()) { + if (sensor.getType() == Sensor.TYPE_SPACECRAFT_THERMAL) { + hasSpacecraftThermal = true; + } + if (sensor.getType() == Sensor.TYPE_AERO_THERMAL) { + hasAeroThermal = true; + } + if (sensor.getType() == Sensor.TYPE_SPACECRAFT_ESM) { + hasESM = true; + } + } + //Remove everything but Radar if we're not in space + if (!isSpaceborne()) { + Vector sensorsToRemove = new Vector<>(); + if (hasETypeFlag(Entity.ETYPE_DROPSHIP)) { + for (Sensor sensor : getSensors()) { + if (sensor.getType() == Sensor.TYPE_SPACECRAFT_ESM) { + hasESM = false; + sensorsToRemove.add(sensor); + } + if (sensor.getType() == Sensor.TYPE_SPACECRAFT_THERMAL) { + hasSpacecraftThermal = false; + sensorsToRemove.add(sensor); + } + } + } else if (hasETypeFlag(Entity.ETYPE_AERO)) { + for (Sensor sensor : getSensors()) { + if (sensor.getType() == Sensor.TYPE_AERO_THERMAL) { + hasAeroThermal = false; + sensorsToRemove.add(sensor); + } + } + } + getSensors().removeAll(sensorsToRemove); + if (sensorsToRemove.size() >= 1) { + setNextSensor(getSensors().firstElement()); + } + } + //If we are in space, add them back... + if (isSpaceborne()) { + if (hasETypeFlag(Entity.ETYPE_DROPSHIP) + || hasETypeFlag(Entity.ETYPE_SPACE_STATION) + || hasETypeFlag(Entity.ETYPE_JUMPSHIP) + || hasETypeFlag(Entity.ETYPE_WARSHIP)) { + //Large craft get thermal/optical sensors + if (!hasSpacecraftThermal) { + getSensors().add(new Sensor(Sensor.TYPE_SPACECRAFT_THERMAL)); + hasSpacecraftThermal = true; + } + //Only military craft get ESM, which detects active radar + if (getDesignType() == AeroSpaceFighter.MILITARY) { + if (!hasESM) { + getSensors().add(new Sensor(Sensor.TYPE_SPACECRAFT_ESM)); + hasESM = true; + } + } + } else if (hasETypeFlag(Entity.ETYPE_AERO) + || hasETypeFlag(Entity.ETYPE_SMALL_CRAFT)) { + //ASFs and small craft get thermal/optical sensors + if (!hasAeroThermal) { + getSensors().add(new Sensor(Sensor.TYPE_AERO_THERMAL)); + hasAeroThermal = true; + } + } + } + } + + // autoejection methods + + /** + * @return unit has an ejection seat + */ + public boolean hasEjectSeat() { + return !hasQuirk(OptionsConstants.QUIRK_NEG_NO_EJECT); + } + + /** + * @return Returns the autoEject. + */ + public boolean isAutoEject() { + return autoEject && hasEjectSeat(); + } + + /** + * @param autoEject + * Turn the master autoejection system on or off + */ + public void setAutoEject(boolean autoEject) { + this.autoEject = autoEject; + } + + /** + * Is autoejection enabled for ammo explosions? + * @return + */ + public boolean isCondEjectAmmo() { + return condEjectAmmo; + } + + /** + * Used by Conditional Auto Ejection - will we eject when an ammo explosion is triggered? + * @param condEjectAmmo Sets autoejection for ammo explosions + */ + public void setCondEjectAmmo(boolean condEjectAmmo) { + this.condEjectAmmo = condEjectAmmo; + } + + /** + * Is autoejection enabled for fuel explosions? + * @return + */ + public boolean isCondEjectFuel() { + return condEjectFuel; + } + + /** + * Used by Conditional Auto Ejection - will we eject when a fuel explosion is triggered? + * @param condEjectFuel Sets autoejection for fuel tank explosions + */ + public void setCondEjectFuel(boolean condEjectFuel) { + this.condEjectFuel = condEjectFuel; + } + + /** + * Is autoejection enabled for SI destruction (Fighter only)? + * @return + */ + public boolean isCondEjectSIDest() { + return condEjectSIDest; + } + + /** + * Used by Conditional Auto Ejection - will we eject when structural integrity is reduced to 0? + * @param condEjectSIDest Sets autoejection for structural integrity destruction + */ + public void setCondEjectSIDest(boolean condEjectSIDest) { + this.condEjectSIDest = condEjectSIDest; + } + + /** + * Intended for large craft. Indicates that the ship is being abandoned. + * @return + */ + public boolean isEjecting() { + return ejecting; + } + + /** + * Changes the ejecting flag when the order to abandon ship is given + * @param ejecting Change to the ejecting status of this ship + */ + public void setEjecting(boolean ejecting) { + this.ejecting = ejecting; + } +} \ No newline at end of file diff --git a/megamek/src/megamek/common/Dropship.java b/megamek/src/megamek/common/Dropship.java index c04f709f116..073dcc54c8e 100644 --- a/megamek/src/megamek/common/Dropship.java +++ b/megamek/src/megamek/common/Dropship.java @@ -25,11 +25,11 @@ */ public class Dropship extends SmallCraft { private static final long serialVersionUID = 1528728632696989565L; - + // ASEW Missile Effects, per location // Values correspond to Locations: NOS, Left, Right, AFT private int[] asewAffectedTurns = { 0, 0, 0, 0 }; - + /** * Sets the number of rounds a specified firing arc is affected by an ASEW missile * @param arc - integer representing the desired firing arc @@ -41,14 +41,14 @@ public void setASEWAffected(int arc, int turns) { asewAffectedTurns[arc] = turns; } } - + /** * Returns the number of rounds a specified firing arc is affected by an ASEW missile * @param arc - integer representing the desired firing arc */ public int getASEWAffected(int arc) { if (arc < asewAffectedTurns.length) { - return asewAffectedTurns[arc]; + return asewAffectedTurns[arc]; } return 0; } @@ -59,15 +59,15 @@ public int getASEWAffected(int arc) { public static final int COLLAR_STANDARD = 0; public static final int COLLAR_PROTOTYPE = 1; public static final int COLLAR_NO_BOOM = 2; - + private static final String[] COLLAR_NAMES = { "KF-Boom", "Prototype KF-Boom", "No Boom" }; - + // Likewise, you can have a prototype or standard K-F Boom public static final int BOOM_STANDARD = 0; public static final int BOOM_PROTOTYPE = 1; - + // what needs to go here? // loading and unloading of units? private boolean dockCollarDamaged = false; @@ -106,39 +106,39 @@ public CrewType defaultCrewType() { public boolean isDockCollarDamaged() { return dockCollarDamaged; } - + public int getCollarType() { return collarType; } - + public void setCollarType(int collarType) { this.collarType = collarType; } - + public String getCollarName() { return COLLAR_NAMES[collarType]; } - + public static String getCollarName(int type) { return COLLAR_NAMES[type]; } - + public static TechAdvancement getCollarTA() { return new TechAdvancement(TECH_BASE_ALL).setAdvancement(2458, 2470, 2500) .setPrototypeFactions(F_TH).setProductionFactions(F_TH).setTechRating(RATING_C) .setAvailability(RATING_C, RATING_C, RATING_C, RATING_C) .setStaticTechLevel(SimpleTechLevel.STANDARD); } - + //KF Boom Stuff public boolean isKFBoomDamaged() { return kfBoomDamaged; } - + public int getBoomType() { return boomType; } - + public void setBoomType(int boomType) { this.boomType = boomType; } @@ -257,26 +257,26 @@ public boolean isLocationProhibited(Coords c, int currElevation) { return isProhibited; } - + /** * Worker function that checks if a given hex contains terrain onto which a grounded dropship - * cannot deploy. + * cannot deploy. */ private boolean hexContainsProhibitedTerrain(Hex hex) { return hex.containsTerrain(Terrains.WOODS) || hex.containsTerrain(Terrains.ROUGH) || ((hex.terrainLevel(Terrains.WATER) > 0) && !hex.containsTerrain(Terrains.ICE)) || hex.containsTerrain(Terrains.RUBBLE) || hex.containsTerrain(Terrains.MAGMA) || hex.containsTerrain(Terrains.JUNGLE) || (hex.terrainLevel(Terrains.SNOW) > 1) - || (hex.terrainLevel(Terrains.GEYSER) == 2) - || hex.containsTerrain(Terrains.BUILDING) || hex.containsTerrain(Terrains.IMPASSABLE) + || (hex.terrainLevel(Terrains.GEYSER) == 2) + || hex.containsTerrain(Terrains.BUILDING) || hex.containsTerrain(Terrains.IMPASSABLE) || hex.containsTerrain(Terrains.BRIDGE); - + } public void setDamageDockCollar(boolean b) { dockCollarDamaged = b; } - + public void setDamageKFBoom(boolean b) { kfBoomDamaged = b; } @@ -332,7 +332,7 @@ public double getStrategicFuelUse() { } return fuelUse; } - + @Override public double primitiveFuelFactor() { int year = getOriginalBuildYear(); @@ -364,12 +364,12 @@ public double primitiveFuelFactor() { .setProductionFactions(F_TA).setTechRating(RATING_D) .setAvailability(RATING_D, RATING_X, RATING_X, RATING_X) .setStaticTechLevel(SimpleTechLevel.STANDARD); - + @Override public TechAdvancement getConstructionTechAdvancement() { return isPrimitive() ? TA_DROPSHIP_PRIMITIVE : TA_DROPSHIP; } - + @Override protected void addSystemTechAdvancement(CompositeTechLevel ctl) { super.addSystemTechAdvancement(ctl); @@ -377,7 +377,7 @@ protected void addSystemTechAdvancement(CompositeTechLevel ctl) { ctl.addComponent(getCollarTA()); } } - + @Override public double getCost(CalculationReport calcReport, boolean ignoreAmmo) { return DropShipCostCalculator.calculateCost(this, calcReport, ignoreAmmo); @@ -677,11 +677,11 @@ public long getEntityType() { @Override public boolean canChangeSecondaryFacing() { - // flying dropships can execute the "ECHO" maneuver (stratops 113), aka a torso twist, + // flying dropships can execute the "ECHO" maneuver (stratops 113), aka a torso twist, // if they have the MP for it return isAirborne() && !isEvading() && (mpUsed <= getRunMP() - 2); } - + /** * Can this dropship "torso twist" in the given direction? */ @@ -694,7 +694,7 @@ public boolean isValidSecondaryFacing(int dir) { } return rotate == 0; } - + /** * Return the nearest valid direction to "torso twist" in */ @@ -703,29 +703,29 @@ public int clipSecondaryFacing(int dir) { if (isValidSecondaryFacing(dir)) { return dir; } - + // can't twist without enough MP if (!canChangeSecondaryFacing()) { return getFacing(); } - + // otherwise, twist once in the appropriate direction final int rotate = (dir + (6 - getFacing())) % 6; - + return rotate >= 3 ? (getFacing() + 5) % 6 : (getFacing() + 1) % 6; } - + @Override public void newRound(int roundNumber) { super.newRound(roundNumber); - + if (getGame().useVectorMove()) { setFacing(getSecondaryFacing()); } - + setSecondaryFacing(getFacing()); } - + /** * Utility function that handles situations where a facing change * has some kind of permanent effect on the entity. @@ -752,4 +752,12 @@ public boolean canLandVertically() { public boolean canTakeOffVertically() { return (isSpheroid() || game.getPlanetaryConditions().isVacuum()) && (getCurrentThrust() > 2); } + + @Override + public void autoSetMaxBombPoints() { + // Only count free whole tons per bay + maxBombPoints = getTransportBays().stream().mapToInt( + tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused() / 5) : 0 + ).sum(); + } } diff --git a/megamek/src/megamek/common/SmallCraft.java b/megamek/src/megamek/common/SmallCraft.java index 73db04b0e24..a64c21ce3ff 100644 --- a/megamek/src/megamek/common/SmallCraft.java +++ b/megamek/src/megamek/common/SmallCraft.java @@ -924,7 +924,7 @@ public int getLandingLength() { public void autoSetMaxBombPoints() { // Only count free whole tons per bay maxBombPoints = getTransportBays().stream().mapToInt( - tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused()) : 0 + tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused() / 5) : 0 ).sum(); } } \ No newline at end of file From ca077cdf2e4b8b034241c6d8a6f2dae46d3e03d0 Mon Sep 17 00:00:00 2001 From: sleet01 Date: Wed, 6 Dec 2023 08:20:07 -0800 Subject: [PATCH 03/22] First pass at splitting Aero into ASF (for external stores MP calcs) and others --- megamek/src/megamek/common/Aero.java | 22 +- .../src/megamek/common/AeroSpaceFighter.java | 2939 +---------------- megamek/src/megamek/common/AmmoType.java | 318 +- megamek/src/megamek/common/ConvFighter.java | 8 +- megamek/src/megamek/common/Dropship.java | 8 - .../src/megamek/common/FighterSquadron.java | 30 +- .../src/megamek/common/FixedWingSupport.java | 13 +- megamek/src/megamek/common/IBomber.java | 43 +- .../src/megamek/common/MechFileParser.java | 14 +- megamek/src/megamek/common/Mounted.java | 9 + megamek/src/megamek/common/SmallCraft.java | 13 +- ...File.java => BLKAeroSpaceFighterFile.java} | 27 +- .../common/loaders/BLKConvFighterFile.java | 12 +- .../common/loaders/BLKDropshipFile.java | 9 +- .../src/megamek/common/loaders/BLKFile.java | 4 +- .../loaders/BLKFixedWingSupportFile.java | 20 +- .../common/loaders/BLKSmallCraftFile.java | 8 +- 17 files changed, 323 insertions(+), 3174 deletions(-) rename megamek/src/megamek/common/loaders/{BLKAeroFile.java => BLKAeroSpaceFighterFile.java} (94%) diff --git a/megamek/src/megamek/common/Aero.java b/megamek/src/megamek/common/Aero.java index 8ea66ad84c8..d80763052f4 100644 --- a/megamek/src/megamek/common/Aero.java +++ b/megamek/src/megamek/common/Aero.java @@ -481,14 +481,8 @@ public int getMaxBombPoints() { } public void autoSetMaxBombPoints() { - // Aerospace fighters can carry both external and internal ordnances, if configured and quirked - // appropriately - maxBombPoints = (int) Math.round(getWeight() / 5); - if (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) { - maxBombPoints += getTransportBays().stream().mapToInt( - tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused() / 5) : 0 - ).sum(); - } + // Stock Aerospace units cannot carry bombs + maxBombPoints = 0; } @Override @@ -510,7 +504,8 @@ public void clearBombChoices() { @Override public int reduceMPByBombLoad(int t) { - return Math.max(0, t - (int) Math.ceil(getBombPoints() / 5.0)); + // The base Aero cannot carry bombs so no MP reduction + return 0; } public void setWhoFirst() { @@ -2997,7 +2992,7 @@ public long getEntityType() { } public boolean isInASquadron() { - return game.getEntity(getTransportId()) instanceof FighterSquadron; + return false; } @Override @@ -3012,8 +3007,7 @@ public boolean isAero() { */ @Override public boolean isBomber() { - return (isFighter() || - (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB))); + return false; } @Override @@ -3022,7 +3016,7 @@ public boolean isBomber() { * but not a larger craft (i.e. "SmallCraft" or "Dropship" and bigger */ public boolean isFighter() { - return true; + return false; } @Override @@ -3030,7 +3024,7 @@ public boolean isFighter() { * Returns true if and only if this is an aerospace fighter. */ public boolean isAerospaceFighter() { - return true; + return false; } @Override diff --git a/megamek/src/megamek/common/AeroSpaceFighter.java b/megamek/src/megamek/common/AeroSpaceFighter.java index 3278d0ef3d2..334fdc8d36d 100644 --- a/megamek/src/megamek/common/AeroSpaceFighter.java +++ b/megamek/src/megamek/common/AeroSpaceFighter.java @@ -23,2473 +23,54 @@ import java.util.*; /** - * Taharqa's attempt at creating an Aerospace entity + * AeroSpaceFighter subclass of Aero that encapsulates Fighter functionality */ public class AeroSpaceFighter extends Aero { public AeroSpaceFighter() { super(); } - @Override - public int getUnitType() { - return UnitType.AERO; - } - - protected static final TechAdvancement TA_ASF = new TechAdvancement(TECH_BASE_ALL) - .setAdvancement(DATE_NONE, 2470, 2490).setProductionFactions(F_TH) - .setTechRating(RATING_D).setAvailability(RATING_C, RATING_E, RATING_D, RATING_C) - .setStaticTechLevel(SimpleTechLevel.STANDARD); - protected static final TechAdvancement TA_ASF_PRIMITIVE = new TechAdvancement(TECH_BASE_IS) - //Per MUL team and per availability codes should exist to around 2781 - .setISAdvancement(DATE_ES, 2200, DATE_NONE, 2781, DATE_NONE) - .setISApproximate(false, true, false, true, false).setProductionFactions(F_TA) - .setTechRating(RATING_D).setAvailability(RATING_D, RATING_X, RATING_F, RATING_F) - .setStaticTechLevel(SimpleTechLevel.ADVANCED); - - @Override - public TechAdvancement getConstructionTechAdvancement() { - if (isPrimitive()) { - return TA_ASF_PRIMITIVE; - } else { - return TA_ASF; - } - } - - protected static final TechAdvancement[] COCKPIT_TA = { - new TechAdvancement(TECH_BASE_ALL).setAdvancement(2460, 2470, 2491) - .setApproximate(true, false, false).setPrototypeFactions(F_TH) - .setPrototypeFactions(F_TH).setTechRating(RATING_C) - .setAvailability(RATING_C, RATING_C, RATING_C, RATING_C) - .setStaticTechLevel(SimpleTechLevel.STANDARD), //Standard - new TechAdvancement(TECH_BASE_IS).setISAdvancement(3065, 3070, 3080) - .setClanAdvancement(DATE_NONE, DATE_NONE, 3080) - .setISApproximate(true, false, false).setPrototypeFactions(F_WB) - .setPrototypeFactions(F_WB, F_CSR).setTechRating(RATING_E) - .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) - .setStaticTechLevel(SimpleTechLevel.STANDARD), //Small - new TechAdvancement(TECH_BASE_ALL).setISAdvancement(2625, 2631, DATE_NONE, 2850, 3030) - .setISApproximate(true, false, false, true, true) - .setClanAdvancement(2625, 2631).setClanApproximate(true, false) - .setClanApproximate(true, false).setPrototypeFactions(F_TH) - .setPrototypeFactions(F_TH).setReintroductionFactions(F_FS).setTechRating(RATING_D) - .setAvailability(RATING_C, RATING_F, RATING_E, RATING_D) - .setStaticTechLevel(SimpleTechLevel.ADVANCED), //Cockpit command console - new TechAdvancement(TECH_BASE_ALL).setAdvancement(DATE_ES, 2300, DATE_NONE, 2520) - .setISApproximate(false, true, false, false) - .setPrototypeFactions(F_TA).setTechRating(RATING_C) - .setAvailability(RATING_D, RATING_X, RATING_X, RATING_F) - .setStaticTechLevel(SimpleTechLevel.STANDARD), //Primitive - }; - - public static TechAdvancement getCockpitTechAdvancement(int cockpitType) { - if (cockpitType >= 0 && cockpitType < COCKPIT_TA.length) { - return new TechAdvancement(COCKPIT_TA[cockpitType]); - } - return null; - } - - public TechAdvancement getCockpitTechAdvancement() { - return getCockpitTechAdvancement(getCockpitType()); - } - - @Override - protected void addSystemTechAdvancement(CompositeTechLevel ctl) { - super.addSystemTechAdvancement(ctl); - if (isFighter() && (getCockpitTechAdvancement() != null)) { - ctl.addComponent(getCockpitTechAdvancement()); - } - } - - // Is it Civilian or Military - public static final int CIVILIAN = 0; - public static final int MILITARY = 1; - protected int designType = MILITARY; - - /** - * Sets the unit as either a civilian or military design - */ - public void setDesignType(int design) { - designType = design; - } - - @Override - public void setDestroyed(boolean destroyed) { - this.destroyed = destroyed; - land(); - } - /** - * Returns the unit's design type - */ - public int getDesignType() { - return designType; - } - - /** - * 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 - */ - @Override - public boolean isAeroSensorDestroyed() { - return getSensorHits() >= 3; - } - - @Override - public int getWalkMP(MPCalculationSetting mpCalculationSetting) { - int mp = getOriginalWalkMP(); - if (engineHits >= getMaxEngineHits()) { - return 0; - } - - int engineLoss = 2; - if ((this instanceof SmallCraft) || (this instanceof Jumpship)) { - engineLoss = 1; - } - mp = Math.max(0, mp - (engineHits * engineLoss)); - - if (!mpCalculationSetting.ignoreCargo) { - mp = Math.max(0, mp - getCargoMpReduction(this)); - } - - if ((null != game) && !mpCalculationSetting.ignoreWeather) { - int weatherMod = game.getPlanetaryConditions().getMovementMods(this); - mp = Math.max(mp + weatherMod, 0); - if (getCrew().getOptions().stringOption(OptionsConstants.MISC_ENV_SPECIALIST).equals(Crew.ENVSPC_WIND) - && (game.getPlanetaryConditions().getWindStrength() == PlanetaryConditions.WI_TORNADO_F13) - && (game.getPlanetaryConditions().getWeather() == PlanetaryConditions.WE_NONE)) { - mp += 1; - } - } - - if (!mpCalculationSetting.ignoreCargo) { - mp = reduceMPByBombLoad(mp); - } - - if (!mpCalculationSetting.ignoreModularArmor && hasModularArmor()) { - mp--; - } - - if (getPartialRepairs().booleanOption("aero_engine_crit")) { - mp--; - } - - if (!mpCalculationSetting.ignoreGrounded && !isAirborne()) { - mp = isSpheroid() ? 0 : mp / 2; - } - - return mp; - } - - /** - * This is the same as getWalkMP, but does not divide by 2 when grounded - * - * @return - */ - @Override - public int getCurrentThrust() { - return getWalkMP(MPCalculationSetting.NO_GROUNDED); - } - - /** - * Returns the number of locations in the entity - */ - @Override - public int locations() { - return 6; - } - - @Override - public int getBodyLocation() { - return LOC_FUSELAGE; - } - - @Override - public boolean canChangeSecondaryFacing() { - return false; - } - - @Override - public boolean isValidSecondaryFacing(int n) { - return false; - } - - /** - * Aeros really can't torso twist? - */ - @Override - public int clipSecondaryFacing(int n) { - return getFacing(); - } - - @Override - public boolean isOutControlTotal() { - // due to control roll, heat, shut down, or crew unconscious - return (outControl || shutDown || getCrew().isUnconscious()); - } - - @Override - public boolean isOutControl() { - return outControl; - } - - @Override - public boolean isOutCtrlHeat() { - return outCtrlHeat; - } - - @Override - public boolean isRandomMove() { - return randomMove; - } - - @Override - public boolean didAccLast() { - return accLast; - } - - @Override - public boolean hasLifeSupport() { - return lifeSupport; - } - - public void setLifeSupport(boolean b) { - lifeSupport = b; - } - - @Override - public boolean isRolled() { - return rolled; - } - - @Override - public void setOutControl(boolean ocontrol) { - outControl = ocontrol; - } - - @Override - public void setOutCtrlHeat(boolean octrlheat) { - outCtrlHeat = octrlheat; - } - - @Override - public void setRandomMove(boolean randmove) { - randomMove = randmove; - } - - @Override - public void setRolled(boolean roll) { - rolled = roll; - } - - @Override - public void setAccLast(boolean b) { - accLast = b; - } - - @Override - public int getMaxBombPoints() { - return maxBombPoints; - } - public void autoSetMaxBombPoints() { - // Aerospace fighters can carry both external and internal ordnances, if configured and quirked - // appropriately - maxBombPoints = (int) Math.round(getWeight() / 5); - if (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) { - maxBombPoints += getTransportBays().stream().mapToInt( - tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused() / 5) : 0 - ).sum(); - } - } - - @Override - public int[] getBombChoices() { - return bombChoices.clone(); - } - - @Override - public void setBombChoices(int[] bc) { - if (bc.length == bombChoices.length) { - bombChoices = bc; - } - } - - @Override - public void clearBombChoices() { - Arrays.fill(bombChoices, 0); - } - - @Override - public int reduceMPByBombLoad(int t) { - return Math.max(0, t - (int) Math.ceil(getBombPoints() / 5.0)); - } - - public void setWhoFirst() { - whoFirst = Compute.randomInt(500); - } - - public int getWhoFirst() { - return whoFirst; - } - - @Override - public int getCurrentVelocity() { - // if using advanced movement then I just want to sum up - // the different vectors - if ((game != null) && game.useVectorMove()) { - return getVelocity(); - } - return currentVelocity; - } - - @Override - public void setCurrentVelocity(int velocity) { - currentVelocity = velocity; - } - - @Override - public int getNextVelocity() { - return nextVelocity; - } - - @Override - public void setNextVelocity(int velocity) { - nextVelocity = velocity; - } - - // need some way of retrieving true current velocity - // even when using advanced movement - @Override - public int getCurrentVelocityActual() { - return currentVelocity; - } - - public int getPotCrit() { - return potCrit; - } - - public void setPotCrit(int crit) { - potCrit = crit; - } - - @Override - public int getSI() { - return structIntegrity; - } - - @Override - public int get0SI() { - return orig_structIntegrity; - } - - /** - * Used to determine modifier for landing; different for Aero and LAM. - */ - @Override - public int getNoseArmor() { - return getArmor(LOC_NOSE); - } - - @Override - public int getCapArmor() { - return capitalArmor; - } - - @Override - public void setCapArmor(int i) { - capitalArmor = i; - } - - @Override - public int getCap0Armor() { - return capitalArmor_orig; - } - - @Override - public int getFatalThresh() { - return fatalThresh; - } - - @Override - public int getCurrentDamage() { - return currentDamage; - } - - @Override - public void setCurrentDamage(int i) { - currentDamage = i; - } - - public void set0SI(int si) { - orig_structIntegrity = si; - structIntegrity = si; - } - - public void autoSetSI() { - int siweight = (int) Math.floor(weight / 10.0); - int sithrust = getOriginalWalkMP(); - initializeSI(Math.max(siweight, sithrust)); - } - - @Override - public void autoSetCapArmor() { - double divisor = 10.0; - if ((null != game) && game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AERO_SANITY)) { - divisor = 1.0; - } - capitalArmor_orig = (int) Math.round(getTotalOArmor() / divisor); - capitalArmor = (int) Math.round(getTotalArmor() / divisor); - } - - @Override - public void autoSetFatalThresh() { - int baseThresh = 2; - if ((null != game) && game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AERO_SANITY)) { - baseThresh = 20; - } - fatalThresh = Math.max(baseThresh, (int) Math.ceil(capitalArmor / 4.0)); - } - - public void initializeSI(int val) { - orig_structIntegrity = val; - setSI(val); - } - - @Override - public void setSI(int si) { - structIntegrity = si; - } - - @Override - public int getSensorHits() { - return sensorHits; - } - - public void setSensorHits(int hits) { - if (hits > 3) { - hits = 3; - } - sensorHits = hits; - } - - @Override - public int getFCSHits() { - return fcsHits; - } - - public void setFCSHits(int hits) { - if (hits > 3) { - hits = 3; - } - fcsHits = hits; - } - - public boolean fuelTankHit() { - return fuelTankHit; - } - - public void setFuelTankHit(boolean value) { - fuelTankHit = value; - } - - public void setCICHits(int hits) { - if (hits > 3) { - hits = 3; - } - cicHits = hits; - } - - public int getCICHits() { - return cicHits; - } - - public void setIgnoredCrewHits(int hits) { - ignoredCrewHits = hits; - } - - public int getIgnoredCrewHits() { - return ignoredCrewHits; - } - - @Override - public int getEngineHits() { - return engineHits; - } - - public void setEngineHits(int hits) { - engineHits = hits; - } - - @Override - public int getAvionicsHits() { - return avionicsHits; - } - - public void setAvionicsHits(int hits) { - avionicsHits = hits; - } - - public boolean isGearHit() { - return gearHit; - } - - @Override - public void setGearHit(boolean hit) { - gearHit = hit; - } - - /** - * Modifier to landing or vertical takeoff roll for landing gear damage. - * - * @param vTakeoff - * true if this is for a vertical takeoff, false if for a landing - * @return the control roll modifier - */ - @Override - public int getLandingGearMod(boolean vTakeoff) { - if (gearHit) { - return vTakeoff ? 1 : 5; - } else { - return 0; - } - } - - //Landing mods for partial repairs - @Override - public int getLandingGearPartialRepairs() { - if (getPartialRepairs().booleanOption("aero_gear_crit")) { - return 2; - } else if (getPartialRepairs().booleanOption("aero_gear_replace")) { - return 1; - } else { - return 0; - } - } - - // Avionics mods for partial repairs - @Override - public int getAvionicsMisreplaced() { - if (getPartialRepairs().booleanOption("aero_avionics_replace")) { - return 1; - } else { - return 0; - } - } - - @Override - public int getAvionicsMisrepaired() { - if (getPartialRepairs().booleanOption("aero_avionics_crit")) { - return 1; - } else { - return 0; - } - } - - public void setOHeatSinks(int hs) { - heatSinksOriginal = hs; - } - - public int getOHeatSinks() { - return heatSinksOriginal; - } - - public void setHeatSinks(int hs) { - heatSinks = hs; - } - - @Override - public int getHeatSinks() { - return heatSinks; - } - - public int getHeatSinkHits() { - return heatSinksOriginal - heatSinks; - } - - public void setHeatType(int hstype) { - heatType = hstype; - } - - public int getPodHeatSinks() { - return podHeatSinks; - } - - public void setPodHeatSinks(int hs) { - podHeatSinks = hs; - } - - @Override - public boolean tracksHeat() { - return true; - } - - public void setLeftThrustHits(int hits) { - leftThrustHits = hits; - } - - @Override - public int getLeftThrustHits() { - return leftThrustHits; - } - - public void setRightThrustHits(int hits) { - rightThrustHits = hits; - } - - @Override - public int getRightThrustHits() { - return rightThrustHits; - } - - public int getOriginalFuel() { - return fuel; - } - - @Override - public int getFuel() { - if ((getPartialRepairs().booleanOption("aero_asf_fueltank_crit")) - || (getPartialRepairs().booleanOption("aero_fueltank_crit"))) { - return (int) (fuel * 0.9); - } else { - return fuel; - } - } - - @Override - public int getCurrentFuel() { - if ((getPartialRepairs().booleanOption("aero_asf_fueltank_crit")) - || (getPartialRepairs().booleanOption("aero_fueltank_crit"))) { - return (int) (currentfuel * 0.9); - } else { - return currentfuel; - } - } - - /** - * Sets the number of fuel points. - * - * @param gas - * Number of fuel points. - */ - @Override - public void setFuel(int gas) { - fuel = gas; - currentfuel = gas; - } - - @Override - public void setCurrentFuel(int gas) { - currentfuel = gas; - } - - @Override - public double getFuelPointsPerTon() { - if (isPrimitive()) { - return 80 / primitiveFuelFactor(); - } - return 80; - } - - /** - * Set number of fuel points based on fuel tonnage. - * - * @param fuelTons - * The number of tons of fuel - */ - @Override - public void setFuelTonnage(double fuelTons) { - double pointsPerTon = getFuelPointsPerTon(); - fuel = (int) Math.floor(pointsPerTon * fuelTons + 0.001); - } - - /** - * Gets the fuel for this Aero in terms of tonnage. - * - * @return The number of tons of fuel on this Aero. - */ - @Override - public double getFuelTonnage() { - // Rounding required for primitive small craft/dropship fuel multipliers. - // The reason this is rounded normally instead of up is that the fuel points are actually calculated - // from the tonnage and rounded down. - return Math.round(2.0 * fuel / getFuelPointsPerTon()) / 2.0; - } - - /** - * Used by SmallCraft and Jumpship and their child classes. - * - * @return The tons of fuel burned in a day at 1G using strategic movement. - */ - public double getStrategicFuelUse() { - return 0.0; - } - - /** - * Some primitve aerospace units have their fuel efficiency reduced by a factor based - * on construction year. - * - * @return The primitive fuel factor for the build year. - */ - public double primitiveFuelFactor() { - return 1.0; - } - - public int getHeatType() { - return heatType; - } - - @Override - public boolean wasCritThresh() { - return critThresh; - } - - @Override - public void setCritThresh(boolean b) { - critThresh = b; - } - - @Override - public boolean isImmobile() { - // aeros are never immobile when in the air or space - if (isAirborne() || isSpaceborne()) { - return false; - } - return super.isImmobile(); - } - - @Override - public void newRound(int roundNumber) { - super.newRound(roundNumber); - - // reset threshold critted - setCritThresh(false); - - // reset maneuver status - setFailedManeuver(false); - // reset acc/dec this turn - setAccDecNow(false); - - updateBays(); - - // update recovery turn if in recovery - if (getRecoveryTurn() > 0) { - setRecoveryTurn(getRecoveryTurn() - 1); - } - - // if in atmosphere, then halve next turn's velocity - if (!game.getBoard().inSpace() && isDeployed() && (roundNumber > 0)) { - setNextVelocity((int) Math.floor(getNextVelocity() / 2.0)); - } - - // update velocity - setCurrentVelocity(getNextVelocity()); - - // if using variable damage thresholds then autoset them - if (game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_VARIABLE_DAMAGE_THRESH)) { - autoSetThresh(); - autoSetFatalThresh(); - } - - // if they are out of control due to heat, then apply this and reset - if (isOutCtrlHeat()) { - setOutControl(true); - setOutCtrlHeat(false); - } - - // reset eccm bonus - setECCMRoll(Compute.d6(2)); - - // get new random whofirst - setWhoFirst(); - - resetAltLossThisRound(); - } - - /** - * Returns the name of the type of movement used. This is tank-specific. - */ - @Override - public String getMovementString(EntityMovementType mtype) { - switch (mtype) { - case MOVE_SKID: - return "Skidded"; - case MOVE_NONE: - return "None"; - case MOVE_WALK: - return "Cruised"; - case MOVE_RUN: - return "Flanked"; - case MOVE_SAFE_THRUST: - return "Safe Thrust"; - case MOVE_OVER_THRUST: - return "Over Thrust"; - default: - return "Unknown!"; - } - } - - /** - * Returns the name of the type of movement used. This is tank-specific. - */ - @Override - public String getMovementAbbr(EntityMovementType mtype) { - switch (mtype) { - case MOVE_NONE: - return "N"; - case MOVE_SAFE_THRUST: - return "S"; - case MOVE_OVER_THRUST: - return "O"; - default: - return "?"; - } - } - - @Override - public boolean hasRearArmor(int loc) { - return false; - } - - /** - * Returns the Compute.ARC that the weapon fires into. - */ - // need to figure out aft-pointed wing weapons - // need to figure out new arcs - @Override - public int getWeaponArc(int wn) { - final Mounted mounted = getEquipment(wn); - if (mounted.getType().hasFlag(WeaponType.F_SPACE_BOMB) || mounted.getType().hasFlag(WeaponType.F_DIVE_BOMB) - || mounted.getType().hasFlag(WeaponType.F_ALT_BOMB)) { - return Compute.ARC_360; - } - int arc; - switch (mounted.getLocation()) { - case LOC_NOSE: - case LOC_WINGS: - arc = Compute.ARC_NOSE; - break; - case LOC_RWING: - if (mounted.isRearMounted()) { - arc = Compute.ARC_RWINGA; - } else { - arc = Compute.ARC_RWING; - } - break; - case LOC_LWING: - if (mounted.isRearMounted()) { - arc = Compute.ARC_LWINGA; - } else { - arc = Compute.ARC_LWING; - } - break; - case LOC_AFT: - arc = Compute.ARC_AFT; - break; - default: - arc = Compute.ARC_360; - break; - } - - return rollArcs(arc); - } - - /** - * Returns true if this weapon fires into the secondary facing arc. If - * false, assume it fires into the primary. - */ - @Override - public boolean isSecondaryArcWeapon(int weaponId) { - // just leave true for now in case we implement rolls or - // newtonian movement this way - return true; - } - - /** - * Rolls up a hit location - */ - @Override - public HitData rollHitLocation(int table, int side, int aimedLocation, AimingMode aimingMode, - int cover) { - return rollHitLocation(table, side); - } - - @Override - public HitData rollHitLocation(int table, int side) { - - /* - * Unlike other units, ASFs determine potential crits based on the - * to-hit roll so I need to set this potential value as well as return - * the to hit data - */ - - int roll = Compute.d6(2); - - // first check for above/below - if ((table == ToHitData.HIT_ABOVE) || (table == ToHitData.HIT_BELOW)) { - - // have to decide which wing - int wingloc = LOC_RWING; - int wingroll = Compute.d6(1); - if (wingroll > 3) { - wingloc = LOC_LWING; - } - switch (roll) { - case 2: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 3: - setPotCrit(CRIT_GEAR); - return new HitData(wingloc, false, HitData.EFFECT_NONE); - case 4: - setPotCrit(CRIT_SENSOR); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 5: - setPotCrit(CRIT_CREW); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 6: - setPotCrit(CRIT_WEAPON); - return new HitData(wingloc, false, HitData.EFFECT_NONE); - case 7: - setPotCrit(CRIT_AVIONICS); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 8: - setPotCrit(CRIT_WEAPON); - return new HitData(wingloc, false, HitData.EFFECT_NONE); - case 9: - setPotCrit(CRIT_CONTROL); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - case 10: - setPotCrit(CRIT_ENGINE); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - case 11: - setPotCrit(CRIT_GEAR); - return new HitData(wingloc, false, HitData.EFFECT_NONE); - case 12: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - } - } - - if (side == ToHitData.SIDE_FRONT) { - // normal front hits - switch (roll) { - case 2: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 3: - setPotCrit(CRIT_SENSOR); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 4: - setPotCrit(CRIT_HEATSINK); - return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); - case 5: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); - case 6: - setPotCrit(CRIT_AVIONICS); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 7: - setPotCrit(CRIT_CONTROL); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 8: - setPotCrit(CRIT_FCS); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 9: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); - case 10: - setPotCrit(CRIT_HEATSINK); - return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); - case 11: - setPotCrit(CRIT_GEAR); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 12: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - } - } else if (side == ToHitData.SIDE_LEFT) { - // normal left-side hits - switch (roll) { - case 2: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 3: - setPotCrit(CRIT_GEAR); - return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); - case 4: - setPotCrit(CRIT_SENSOR); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 5: - setPotCrit(CRIT_CREW); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 6: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); - case 7: - setPotCrit(CRIT_AVIONICS); - return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); - case 8: - setPotCrit(CRIT_BOMB); - return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); - case 9: - setPotCrit(CRIT_CONTROL); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - case 10: - setPotCrit(CRIT_ENGINE); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - case 11: - setPotCrit(CRIT_GEAR); - return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); - case 12: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - } - } else if (side == ToHitData.SIDE_RIGHT) { - // normal right-side hits - switch (roll) { - case 2: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 3: - setPotCrit(CRIT_GEAR); - return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); - case 4: - setPotCrit(CRIT_SENSOR); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 5: - setPotCrit(CRIT_CREW); - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - case 6: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); - case 7: - setPotCrit(CRIT_AVIONICS); - return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); - case 8: - setPotCrit(CRIT_BOMB); - return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); - case 9: - setPotCrit(CRIT_CONTROL); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - case 10: - setPotCrit(CRIT_ENGINE); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - case 11: - setPotCrit(CRIT_GEAR); - return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); - case 12: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - } - } else if (side == ToHitData.SIDE_REAR) { - // normal aft hits - switch (roll) { - case 2: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - case 3: - setPotCrit(CRIT_HEATSINK); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - case 4: - setPotCrit(CRIT_FUEL_TANK); - return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); - case 5: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_RWING, false, HitData.EFFECT_NONE); - case 6: - setPotCrit(CRIT_ENGINE); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - case 7: - setPotCrit(CRIT_CONTROL); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - case 8: - setPotCrit(CRIT_ENGINE); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - case 9: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); - case 10: - setPotCrit(CRIT_FUEL_TANK); - return new HitData(LOC_LWING, false, HitData.EFFECT_NONE); - case 11: - setPotCrit(CRIT_HEATSINK); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - case 12: - setPotCrit(CRIT_WEAPON); - return new HitData(LOC_AFT, false, HitData.EFFECT_NONE); - } - } - return new HitData(LOC_NOSE, false, HitData.EFFECT_NONE); - } - - /** - * Gets the location that excess damage transfers to - */ - @Override - public HitData getTransferLocation(HitData hit) { - return new HitData(LOC_DESTROYED); - } - - /** - * Gets the location that is destroyed recursively - */ - @Override - public int getDependentLocation(int loc) { - return LOC_NONE; - } - - public double getBVTypeModifier() { - return 1.2; - } - - @Override - public PilotingRollData addEntityBonuses(PilotingRollData prd) { - // this is a control roll. Affected by: - // avionics damage - // partial repairs - // pilot damage - // current velocity - int avihits = getAvionicsHits(); - int pilothits = getCrew().getHits(); - - if ((avihits > 0) && (avihits < 3)) { - prd.addModifier(avihits, "Avionics Damage"); - } - - // this should probably be replaced with some kind of AVI_DESTROYED - // boolean - if (avihits >= 3) { - prd.addModifier(5, "Avionics Destroyed"); - } - - // partial repairs to avionics system, but only if the avionics aren't already destroyed - if ((getPartialRepairs() != null) && (avihits < 3)) { - if (getPartialRepairs().booleanOption("aero_avionics_crit")) { - prd.addModifier(1, "Partial repair of Avionics"); - } - if (getPartialRepairs().booleanOption("aero_avionics_replace")) { - prd.addModifier(1, "Misreplaced Avionics"); - } - } - - if (pilothits > 0) { - prd.addModifier(pilothits, "Pilot Hits"); - } - - // movement effects - // some question as to whether "above safe thrust" applies to thrust or - // velocity - // I will treat it as thrust until it is resolved - if (moved == EntityMovementType.MOVE_OVER_THRUST) { - prd.addModifier(+1, "Used more than safe thrust"); - } - int vel = getCurrentVelocity(); - int vmod = vel - (2 * getWalkMP()); - if (!getGame().getBoard().inSpace() && (vmod > 0)) { - prd.addModifier(vmod, "Velocity greater than 2x safe thrust"); - } - - int atmoCond = game.getPlanetaryConditions().getAtmosphere(); - // add in atmospheric effects later - if (!(game.getBoard().inSpace() || (atmoCond == PlanetaryConditions.ATMO_VACUUM)) && isAirborne()) { - prd.addModifier(+2, "Atmospheric operations"); - - // check type - if (this instanceof Dropship) { - if (isSpheroid()) { - prd.addModifier(+1, "spheroid dropship"); - } else { - prd.addModifier(0, "aerodyne dropship"); - } - } else { - prd.addModifier(-1, "fighter/small craft"); - } - } - - // life support (only applicable to non-ASFs - if (!hasLifeSupport()) { - prd.addModifier(+2, "No life support"); - } - - if (hasModularArmor()) { - prd.addModifier(1, "Modular Armor"); - } - // VDNI bonus? - if (hasAbility(OptionsConstants.MD_VDNI) - && !hasAbility(OptionsConstants.MD_BVDNI)) { - prd.addModifier(-1, "VDNI"); - } - - // Small/torso-mounted cockpit penalty? - if ((getCockpitType() == AeroSpaceFighter.COCKPIT_SMALL) - && !hasAbility(OptionsConstants.MD_BVDNI) - && !hasAbility(OptionsConstants.UNOFF_SMALL_PILOT)) { - prd.addModifier(1, "Small Cockpit"); - } - - // quirks? - if (hasQuirk(OptionsConstants.QUIRK_POS_ATMO_FLYER) && !game.getBoard().inSpace()) { - prd.addModifier(-1, "atmospheric flyer"); - } - if (hasQuirk(OptionsConstants.QUIRK_NEG_ATMO_INSTABILITY) && !game.getBoard().inSpace()) { - prd.addModifier(+1, "atmospheric flight instability"); - } - if (hasQuirk(OptionsConstants.QUIRK_NEG_CRAMPED_COCKPIT) && !hasAbility(OptionsConstants.UNOFF_SMALL_PILOT)) { - prd.addModifier(1, "cramped cockpit"); - } - - return prd; - } - - @Override - public Vector victoryReport() { - Vector vDesc = new Vector<>(); - - Report r = new Report(7025); - r.type = Report.PUBLIC; - r.addDesc(this); - vDesc.addElement(r); - - if (((getEntityType() & Entity.ETYPE_DROPSHIP) == 0) || ((getEntityType() & Entity.ETYPE_SMALL_CRAFT) == 0) - || ((getEntityType() & Entity.ETYPE_FIGHTER_SQUADRON) == 0) - || ((getEntityType() & Entity.ETYPE_JUMPSHIP) == 0) - || ((getEntityType() & Entity.ETYPE_SPACE_STATION) == 0)) { - r = new Report(7036); - } else { - r = new Report(7030); - } - r.type = Report.PUBLIC; - r.newlines = 0; - vDesc.addElement(r); - vDesc.addAll(getCrew().getDescVector(false)); - r = new Report(7070, Report.PUBLIC); - r.add(getKillNumber()); - vDesc.addElement(r); - - if (isDestroyed()) { - Entity killer = game.getEntity(killerId); - if (killer == null) { - killer = game.getOutOfGameEntity(killerId); - } - if (killer != null) { - r = new Report(7072, Report.PUBLIC); - r.addDesc(killer); - } else { - if (this instanceof FighterSquadron) { - r = new Report(7076, Report.PUBLIC); - } else { - r = new Report(7073, Report.PUBLIC); - } - } - vDesc.addElement(r); - } else if (getCrew().isEjected()) { - r = new Report(7074, Report.PUBLIC); - vDesc.addElement(r); - } - r.newlines = 2; - - return vDesc; - } - - @Override - public int[] getNoOfSlots() { - return NUM_OF_SLOTS; - } - - @Override - public int getRunMP(MPCalculationSetting mpCalculationSetting) { - if (isAirborne()) { - return super.getRunMP(mpCalculationSetting); - } else { - return getWalkMP(mpCalculationSetting); - } - } - - @Override - public int getHeatCapacity(boolean includeRadicalHeatSink) { - int capacity = (getHeatSinks() * (getHeatType() + 1)); - if (includeRadicalHeatSink && hasWorkingMisc(MiscType.F_RADICAL_HEATSINK)) { - capacity += Math.ceil(getHeatSinks() * 0.4); - } - return capacity; - } - - // If the aero is in the water, it is dead so no worries - @Override - public int getHeatCapacityWithWater() { - return getHeatCapacity(false); - } - - @Override - public int getEngineCritHeat() { - // Engine hits cause excess heat for fighters, TW pg 240 - if (!((this instanceof SmallCraft) || (this instanceof Jumpship))) { - return 2 * getEngineHits(); - } else { - return 0; - } - } - - @Override - public void autoSetInternal() { - // should be no internals because only one SI - // It doesn't seem to be screwing anything up yet. - // Need to figure out how destruction of entity is determined - int nInternal = (int) Math.ceil(weight / 10.0); - nInternal = 0; - // I need to look at safe thrust as well at some point - - for (int x = 0; x < locations(); x++) { - initializeInternal(nInternal, x); - } - } - - // initialize the Damage threshold - public void autoSetThresh() { - for (int x = 0; x < locations(); x++) { - initializeThresh(x); - } - } - - public void setThresh(int val, int loc) { - if (loc < damThresh.length) { - damThresh[loc] = val; - } - } - - public void initializeThresh(int loc) { - int nThresh = (int) Math.ceil(getArmor(loc) / 10.0); - setThresh(nThresh, loc); - } - - @Override - public int getThresh(int loc) { - if (isCapitalFighter()) { - if ((null != game) && game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AERO_SANITY)) { - if (game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_VARIABLE_DAMAGE_THRESH)) { - return (int) Math.round(getCapArmor() / 40.0) + 1; - } else { - return (int) Math.round(getCap0Armor() / 40.0) + 1; - } - } else { - return 2; - } - } else if (loc < damThresh.length) { - return damThresh[loc]; - } - return 0; - } - - /** - * Determine if the unit can be repaired, or only harvested for spares. - * - * @return A boolean that is true if the unit can - * be repaired (given enough time and parts); if this value is - * false, the unit is only a source of spares. - * @see Entity#isSalvage() - */ - @Override - public boolean isRepairable() { - return true; // deal with this later - } - - @Override - public boolean canCharge() { - // ramming is resolved differently than charging - return false; - } - - @Override - public boolean canDFA() { - // Aero can't DFA - return false; - } - - @Override - public boolean canRam() { - return !isImmobile() && (getWalkMP() > 0); - } - - /** - * @return suspension factor of vehicle - */ - // Doesn't really do anything so just return 0 - public int getSuspensionFactor() { - return 0; - } - - @Override - public double getCost(CalculationReport calcReport, boolean ignoreAmmo) { - return AeroCostCalculator.calculateCost(this, calcReport, ignoreAmmo); - } - - @Override - public double getPriceMultiplier() { - double priceModifier = 1.0; - if (isOmni()) { - priceModifier *= 1.25f; - } - priceModifier *= 1 + (weight / 200f); - return priceModifier; - } - - @Override - public int implicitClanCASE() { - if (!isClan() || !isFighter()) { - return 0; - } - // Ammo is actually supposed to be assigned to a fuselage location rather than one of the four - // weapon arcs. We will use LOC_NONE to record the existence of non-weapon explosive equipment. - Set caseLocations = new HashSet<>(); - int explicit = 0; - for (Mounted m : getEquipment()) { - if ((m.getType() instanceof MiscType) && (m.getType().hasFlag(MiscType.F_CASE))) { - explicit++; - } else if (m.getType().isExplosive(m)) { - if (m.getType() instanceof WeaponType) { - caseLocations.add(m.getLocation()); - } else { - caseLocations.add(LOC_NONE); - } - } - } - return Math.max(0, caseLocations.size() - explicit); - } - - @Override - public boolean doomedInExtremeTemp() { - return false; - } - - @Override - public boolean doomedInVacuum() { - return false; - } - - @Override - public boolean doomedOnGround() { - return game != null ? !game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AERO_GROUND_MOVE) : false; - } - - @Override - public boolean doomedInAtmosphere() { - return false; - } - - @Override - public boolean doomedInSpace() { - return false; - } - - @Override - public boolean canGoHullDown() { - return false; - } - - /* - * public void addMovementDamage(int level) { movementDamage += level; } - */ - - @Override - public void setEngine(Engine e) { - super.setEngine(e); - if (hasEngine() && getEngine().engineValid) { - setOriginalWalkMP(calculateWalk()); - } - } - - /** - * Returns the percent of the SI remaining - */ - @Override - public double getInternalRemainingPercent() { - return ((double) getSI() / (double) get0SI()); - } - - protected int calculateWalk() { - if (!hasEngine()) { - return 0; - } - if (isPrimitive()) { - double rating = getEngine().getRating(); - rating /= 1.2; - if ((rating % 5) != 0) { - return (int) (((rating - (rating % 5)) + 5) / (int) weight) + 2; - } - return (int) (rating / (int) weight) + 2; - } - return (getEngine().getRating() / (int) weight) + 2; - } - - @Override - public boolean isNuclearHardened() { - return true; - } - - @Override - public void addEquipment(Mounted mounted, int loc, boolean rearMounted) throws LocationFullException { - if (getEquipmentNum(mounted) == -1) { - super.addEquipment(mounted, loc, rearMounted); - } - // Add the piece equipment to our slots. - addCritical(loc, new CriticalSlot(mounted)); - } - - /** - * get the type of critical caused by a critical roll, taking account of - * existing damage - * - * @param roll - * the final dice roll - * @param target - * the hit location - * @return a critical type - */ - public int getCriticalEffect(int roll, int target) { - // just grab the latest potential crit - if (roll < target) { - return CRIT_NONE; - } - - int critical = getPotCrit(); - return critical; - } - - /** - - */ - @Override - public void setOmni(boolean omni) { - - // Perform the superclass' action. - super.setOmni(omni); - - } - - @Override - public void addClanCase() { - if (!(isClan() && isFighter())) { - return; - } - boolean explosiveFound = false; - EquipmentType clCase = EquipmentType.get(EquipmentTypeLookup.CLAN_CASE); - for (int i = 0; i < locations(); i++) { - // Ignore wings location: it's not a valid loc to put equipment in - if (i == LOC_WINGS) { - continue; - } - explosiveFound = false; - for (Mounted m : getEquipment()) { - if (m.getType().isExplosive(m, true) && (m.getLocation() == i)) { - explosiveFound = true; - } - } - if (explosiveFound) { - try { - addEquipment(new Mounted(this, clCase), i, false); - } catch (LocationFullException ex) { - // um, that's impossible. - } - } - } - - } - - /** - * check to see if case is available anywhere - * - * @return - */ - @Override - public boolean hasCase() { - - boolean hasCase = false; - - for (int x = 0; x < locations(); x++) { - if (!hasCase) { - hasCase = locationHasCase(x); - } - } - return hasCase; - } - - /** - * Used to determine net velocity of ramming attack - * - */ - @Override - public int sideTableRam(Coords src) { - int side = super.sideTableRam(src); - if (game.useVectorMove() && game.getBoard().inSpace()) { - int newside = chooseSideRam(src); - if (newside != -1) { - side = newside; - } - } - return side; - - } - - public int chooseSideRam(Coords src) { - // loop through directions and if we have a non-zero vector, then - // compute - // the targetsidetable. If we come to a higher vector, then replace. If - // we come to an equal vector then take it if it is better - int thrust = 0; - int high = -1; - int side = -1; - for (int dir = 0; dir < 6; dir++) { - thrust = getVector(dir); - if (thrust == 0) { - continue; - } - - if (thrust > high) { - high = thrust; - side = sideTableRam(src, dir); - } - - // what if they tie - if (thrust == high) { - int newside = sideTableRam(src, dir); - // choose the better - if (newside > side) { - newside = side; - } - // that should be the only case, because it can't shift you from - // front - // to aft or vice-versa - } - - } - return side; - } - - public int getMaxEngineHits() { - return 3; - } - - @Override - public int getMaxElevationChange() { - if (isAirborne()) { - return UNLIMITED_JUMP_DOWN; - } - return 1; - } - - /** - * Determine if this unit has an active and working stealth system. (stealth - * can be active and not working when under ECCM) - *

- * Sub-classes are encouraged to override this method. - * - * @return true if this unit has a stealth system that is - * currently active, false if there is no stealth - * system or if it is inactive. - */ - @Override - public boolean isStealthActive() { - // Try to find a Mek Stealth system. - for (Mounted mEquip : getMisc()) { - MiscType mtype = (MiscType) mEquip.getType(); - if (mtype.hasFlag(MiscType.F_STEALTH)) { - - if (mEquip.curMode().equals("On") && hasActiveECM()) { - // Return true if the mode is "On" and ECM is working - return true; - } - } - } - // No Mek Stealth or system inactive. Return false. - return false; - } - - /** - * Determine if this unit has an active and working stealth system. (stealth - * can be active and not working when under ECCM) - *

- * Sub-classes are encouraged to override this method. - * - * @return true if this unit has a stealth system that is - * currently active, false if there is no stealth - * system or if it is inactive. - */ - @Override - public boolean isStealthOn() { - // Try to find a Mek Stealth system. - for (Mounted mEquip : getMisc()) { - MiscType mtype = (MiscType) mEquip.getType(); - if (mtype.hasFlag(MiscType.F_STEALTH)) { - if (mEquip.curMode().equals("On")) { - // Return true if the mode is "On" - return true; - } - } - } - // No Mek Stealth or system inactive. Return false. - return false; - } - - /** - * Determine the stealth modifier for firing at this unit from the given - * range. If the value supplied for range is not one of the - * Entity class range constants, an - * IllegalArgumentException will be thrown. - *

- * Sub-classes are encouraged to override this method. - * - * @param range - * - an int value that must match one of the - * Compute class range constants. - * @param ae - * - entity making the attack - * @return a TargetRoll value that contains the stealth - * modifier for the given range. - */ - @Override - public TargetRoll getStealthModifier(int range, Entity ae) { - TargetRoll result = null; - - // Stealth or null sig must be active. - if (!isStealthActive()) { - result = new TargetRoll(0, "stealth not active"); - } - // Determine the modifier based upon the range. - // Infantry do not ignore Chameleon LPS!!! - else { - switch (range) { - case RangeType.RANGE_MINIMUM: - case RangeType.RANGE_SHORT: - if (!ae.isConventionalInfantry()) { - result = new TargetRoll(0, "stealth"); - } else { - result = new TargetRoll(0, "infantry ignore stealth"); - } - break; - case RangeType.RANGE_MEDIUM: - if (!ae.isConventionalInfantry()) { - result = new TargetRoll(1, "stealth"); - } else { - result = new TargetRoll(0, "infantry ignore stealth"); - } - break; - case RangeType.RANGE_LONG: - case RangeType.RANGE_EXTREME: - case RangeType.RANGE_LOS: - if (!ae.isConventionalInfantry()) { - result = new TargetRoll(2, "stealth"); - } else { - result = new TargetRoll(0, "infantry ignore stealth"); - } - break; - case RangeType.RANGE_OUT: - break; - default: - throw new IllegalArgumentException("Unknown range constant: " + range); - } - } - - // Return the result. - return result; - - } // End public TargetRoll getStealthModifier( char ) - - @Override - public void setArmorType(int armType) { - setArmorType(armType, true); - } - - public void setArmorType(int armType, boolean addMount) { - super.setArmorType(armType); - if ((armType == EquipmentType.T_ARMOR_STEALTH_VEHICLE) && addMount) { - try { - this.addEquipment( - EquipmentType.get(EquipmentType.getArmorTypeName(EquipmentType.T_ARMOR_STEALTH_VEHICLE, false)), - LOC_AFT); - } catch (LocationFullException e) { - // this should never happen - } - } - } - - @Override - public boolean isLocationProhibited(Coords c, int currElevation) { - if (isAirborne()) { - return false; - } - - Hex hex = game.getBoard().getHex(c); - - // Additional restrictions for hidden units - if (isHidden()) { - // Can't deploy in paved hexes - if (hex.containsTerrain(Terrains.PAVEMENT) || hex.containsTerrain(Terrains.ROAD)) { - return true; - } - // Can't deploy on a bridge - if ((hex.terrainLevel(Terrains.BRIDGE_ELEV) == currElevation) && hex.containsTerrain(Terrains.BRIDGE)) { - return true; - } - // Can't deploy on the surface of water - if (hex.containsTerrain(Terrains.WATER) && (currElevation == 0)) { - return true; - } - } - - // grounded aeros have the same prohibitions as wheeled tanks - return hex.containsTerrain(Terrains.WOODS) || hex.containsTerrain(Terrains.ROUGH) - || ((hex.terrainLevel(Terrains.WATER) > 0) && !hex.containsTerrain(Terrains.ICE)) - || hex.containsTerrain(Terrains.RUBBLE) || hex.containsTerrain(Terrains.MAGMA) - || hex.containsTerrain(Terrains.JUNGLE) || (hex.terrainLevel(Terrains.SNOW) > 1) - || (hex.terrainLevel(Terrains.GEYSER) == 2); - } - - @Override - public boolean isNightwalker() { - return false; - } - - @Override - public boolean isSpheroid() { - return spheroid; - } - - public void setSpheroid(boolean b) { - spheroid = b; - } - - @Override - public int height() { - return 0; - } - - @Override - public int getStraightMoves() { - return straightMoves; - } - - @Override - public void setStraightMoves(int i) { - straightMoves = i; - } - - @Override - public boolean isVSTOL() { - return vstol; - } - - @Override - public boolean isSTOL() { - return false; - } - - public void setVSTOL(boolean b) { - vstol = b; - } - - @Override - public boolean didFailManeuver() { - return failedManeuver; - } - - @Override - public void setFailedManeuver(boolean b) { - failedManeuver = b; - } - - @Override - public void setAccDecNow(boolean b) { - accDecNow = b; - } - - @Override - public boolean didAccDecNow() { - return accDecNow; - } - - /* - * (non-Javadoc) - * - * @see megamek.common.Entity#getTotalCommGearTons() - */ - @Override - public int getTotalCommGearTons() { - return 1 + getExtraCommGearTons(); - } - - /** - * The number of critical slots that are destroyed in the component. - */ - @Override - public int getBadCriticals(int type, int index, int loc) { - return 0; - } - - public int getCockpitType() { - return cockpitType; - } - - public void setCockpitType(int type) { - cockpitType = type; - if (type == COCKPIT_COMMAND_CONSOLE) { - setCrew(new Crew(CrewType.COMMAND_CONSOLE)); - } else { - setCrew(new Crew(CrewType.SINGLE)); - } - } - - public String getCockpitTypeString() { - return AeroSpaceFighter.getCockpitTypeString(getCockpitType()); - } - - public static String getCockpitTypeString(int inCockpitType) { - if ((inCockpitType < 0) || (inCockpitType >= COCKPIT_STRING.length)) { - return "Unknown"; - } - return COCKPIT_STRING[inCockpitType]; - } - - @Override - public boolean hasCommandConsoleBonus() { - return getCockpitType() == COCKPIT_COMMAND_CONSOLE && getCrew().hasActiveCommandConsole() - && getWeightClass() >= EntityWeightClass.WEIGHT_HEAVY; - } - - @Override - public double getArmorRemainingPercent() { - int armor0 = getTotalOArmor(); - int armor = getTotalArmor(); - if (isCapitalFighter()) { - armor0 = getCap0Armor(); - armor = getCapArmor(); - } - if (armor0 == 0) { - return IArmorState.ARMOR_NA; - } - return ((double) armor / (double) armor0); - } - - /** - * keep track of whether the wings have suffered a weapon critical hit - */ - public boolean areWingsHit() { - return wingsHit; - } - - public void setWingsHit(boolean b) { - wingsHit = b; - } - - /** - * what location is opposite the given one - */ - public int getOppositeLocation(int loc) { - switch (loc) { - case AeroSpaceFighter.LOC_NOSE: - return AeroSpaceFighter.LOC_AFT; - case AeroSpaceFighter.LOC_LWING: - return AeroSpaceFighter.LOC_RWING; - case AeroSpaceFighter.LOC_RWING: - return AeroSpaceFighter.LOC_LWING; - case AeroSpaceFighter.LOC_AFT: - return AeroSpaceFighter.LOC_NOSE; - default: - return AeroSpaceFighter.LOC_NOSE; - } - } - - /** - * get modifications to the cluster hit table for critical hits - */ - @Override - public int getClusterMods() { - return -1 * (getFCSHits() + getSensorHits()); - } - - /** - * What's the range of the ECM equipment? - * - * @return the int range of this unit's ECM. This value will be - * Entity.NONE if no ECM is active. - */ - @Override - public int getECMRange() { - if (!game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_STRATOPS_ECM) - || !game.getBoard().inSpace()) { - return super.getECMRange(); - } - return Math.min(super.getECMRange(), 0); - } - - /** - * @return the strength of the ECCM field this unit emits - */ - @Override - public double getECCMStrength() { - if (!game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_STRATOPS_ECM) - || !game.getBoard().inSpace()) { - return super.getECCMStrength(); - } - if (hasActiveECCM()) { - return 1; - } - return 0; - } - - public void setECCMRoll(int i) { - eccmRoll = i; - } - - public int getECCMRoll() { - return eccmRoll; - } - - public int getECCMTarget() { - return getCrew().getPiloting() + getSensorHits() + getCICHits() + getFCSHits(); - } - - public int getECCMBonus() { - return Math.max(0, eccmRoll - getECCMTarget()); - } - - /** - * @return is the crew of this vessel protected from gravitational effects, - * see StratOps, pg. 36 - */ - public boolean isCrewProtected() { - return true; - } - - public int getGravSecondaryThreshold() { - int thresh = 6; - if (isCrewProtected()) { - thresh = 12; - } - // TODO: clan phenotypes - return thresh; - } - - public int getGravPrimaryThreshold() { - int thresh = 12; - if (isCrewProtected()) { - thresh = 22; - } - // TODO: clan phenotypes - return thresh; - } - - /** - * Determines if this object can accept the given unit. The unit may not be - * of the appropriate type or there may be no room for the unit. - * - * @param unit - * - the Entity to be loaded. - * @return true if the unit can be loaded, false - * otherwise. - */ - @Override - public boolean canLoad(Entity unit, boolean checkFalse) { - // capital fighters can load other capital fighters (becoming squadrons) - // but not in the deployment phase - if (isCapitalFighter() && !unit.isEnemyOf(this) && unit.isCapitalFighter() && (getId() != unit.getId()) - && !getGame().getPhase().isDeployment()) { - return true; - } - - return super.canLoad(unit, checkFalse); - } - - @Override - public Map getWeaponGroups() { - return weaponGroups; - } - - /** - * Iterate through current weapons and count the number in each capital - * fighter location. - * - * @return A map with keys in the format "weaponName:loc", with the number - * of weapons of that type in that location as the value. - */ - @Override - public Map groupWeaponsByLocation() { - Map groups = new HashMap<>(); - for (Mounted mounted : getTotalWeaponList()) { - int loc = mounted.getLocation(); - if (isFighter() && ((loc == AeroSpaceFighter.LOC_RWING) || (loc == AeroSpaceFighter.LOC_LWING))) { - loc = AeroSpaceFighter.LOC_WINGS; - } - if (mounted.isRearMounted()) { - loc = AeroSpaceFighter.LOC_AFT; - } - String key = mounted.getType().getInternalName() + ":" + loc; - if (null == groups.get(key)) { - groups.put(key, mounted.getNWeapons()); - } else { - groups.put(key, groups.get(key) + mounted.getNWeapons()); - } - } - return groups; - } - - /** - * In cases where another unit occupies the same hex, determine if this Aero - * should be moved back a hex for targeting purposes - * - * @param other - * @return - */ - public boolean shouldMoveBackHex(AeroSpaceFighter other) { - if (null == getPosition()) { - return false; - } - if (null == other.getPosition()) { - return false; - } - if (!getPosition().equals(other.getPosition())) { - return false; - } - int type = this.getUnitType(); - int otherType = other.getUnitType(); - int vel = getCurrentVelocity(); - int otherVel = other.getCurrentVelocity(); - if (type > otherType) { - return false; - } else if (type < otherType) { - return true; - } - // if we are still here then type is the same so compare velocity - if (vel < otherVel) { - return false; - } else if (vel > otherVel) { - return true; - } - // if we are still here then type and velocity same, so roll for it - if (getWhoFirst() < other.getWhoFirst()) { - return false; - } - return true; - } - - @Override - public boolean hasArmoredEngine() { - for (int slot = 0; slot < getNumberOfCriticals(LOC_AFT); slot++) { - CriticalSlot cs = getCritical(LOC_AFT, slot); - if ((cs != null) && (cs.getType() == CriticalSlot.TYPE_SYSTEM) && (cs.getIndex() == Mech.SYSTEM_ENGINE)) { - return cs.isArmored(); - } - } - return false; - } - - /** - * see {@link Entity#getForwardArc()} - */ - @Override - public int getForwardArc() { - return Compute.ARC_NOSE; - } - - /** - * see {@link Entity#getRearArc()} - */ - @Override - public int getRearArc() { - return Compute.ARC_AFT; - } - - @Override - public int getAltLoss() { - return altLoss; - } - - @Override - public void setAltLoss(int i) { - altLoss = i; - } - - @Override - public void resetAltLoss() { - altLoss = 0; - } - - @Override - public int getAltLossThisRound() { - return altLossThisRound; - } - - @Override - public void setAltLossThisRound(int i) { - altLossThisRound = i; - } - - @Override - public void resetAltLossThisRound() { - altLossThisRound = 0; - } - - @Override - public int getElevation() { - if ((game != null) && game.getBoard().inSpace()) { - return 0; - } - // Altitude is not the same as elevation. If an aero is at 0 altitude, then it is grounded - // and uses elevation normally. Otherwise, just set elevation to a very large number so that - // a flying aero won't interact with the ground maps in any way - return isAirborne() ? 999 : super.getElevation(); - } - - @Override - public boolean canGoDown() { - return canGoDown(altitude, getPosition()); - } - - @Override - public boolean isPrimitive() { - return (getCockpitType() == AeroSpaceFighter.COCKPIT_PRIMITIVE); - } - - @Override - public String getLocationDamage(int loc) { - return ""; - } - - public String getCritDamageString() { - StringBuilder toReturn = new StringBuilder(); - boolean first = true; - if (getSensorHits() > 0) { - if (!first) { - toReturn.append(", "); - } - toReturn.append(String.format(Messages.getString("Aero.sensorDamageString"), getSensorHits())); - first = false; - } - if (getAvionicsHits() > 0) { - if (!first) { - toReturn.append(", "); - } - toReturn.append(String.format(Messages.getString("Aero.avionicsDamageString"), getAvionicsHits())); - first = false; - } - if (getFCSHits() > 0) { - if (!first) { - toReturn.append(", "); - } - toReturn.append(String.format(Messages.getString("Aero.fcsDamageString"), getFCSHits())); - first = false; - } - if (getCICHits() > 0) { - if (!first) { - toReturn.append(", "); - } - toReturn.append(String.format(Messages.getString("Aero.cicDamageString"), getCICHits())); - first = false; - } - if (isGearHit()) { - if (!first) { - toReturn.append(", "); - } - toReturn.append(Messages.getString("Aero.landingGearDamageString")); - first = false; - } - if (!hasLifeSupport()) { - if (!first) { - toReturn.append(", "); - } - toReturn.append(Messages.getString("Aero.lifeSupportDamageString")); - first = false; - } - if (getLeftThrustHits() > 0) { - if (!first) { - toReturn.append(", "); - } - toReturn.append(String.format(Messages.getString("Aero.leftThrusterDamageString"), getLeftThrustHits())); - first = false; - } - if (getRightThrustHits() > 0) { - if (!first) { - toReturn.append(", "); - } - toReturn.append(String.format(Messages.getString("Aero.rightThrusterDamageString"), getRightThrustHits())); - first = false; - } - // Cargo bays and bay doors for large craft - for (Bay next : getTransportBays()) { - if (next.getBayDamage() > 0) { - if (!first) { - toReturn.append(", "); - } - toReturn.append(String.format(Messages.getString("Aero.bayDamageString"), next.getType(), next.getBayNumber())); - first = false; - } - if (next.getCurrentDoors() < next.getDoors()) { - if (!first) { - toReturn.append(", "); - } - toReturn.append(String.format(Messages.getString("Aero.bayDoorDamageString"), next.getType(), next.getBayNumber(), (next.getDoors() - next.getCurrentDoors()))); - first = false; - } + // Aerospace fighters can carry both external and internal ordnances, if configured and quirked + // appropriately + maxBombPoints = (int) Math.round(getWeight() / 5); + if (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) { + maxBombPoints += getTransportBays().stream().mapToInt( + tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused()) : 0 + ).sum(); } - return toReturn.toString(); } @Override - public boolean isCrippled() { - return isCrippled(true); + public boolean isInASquadron() { + return game.getEntity(getTransportId()) instanceof FighterSquadron; } @Override - public boolean isCrippled(boolean checkCrew) { - if (isEjecting()) { - LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: The crew is currently ejecting."); - return true; - } else if (getInternalRemainingPercent() < 0.5) { - LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: Only " - + NumberFormat.getPercentInstance().format(getInternalRemainingPercent()) + " internals remaining."); - return true; - } else if (getEngineHits() > 0) { - LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: " + engineHits + " Engine Hits."); - return true; - } else if (fuelTankHit()) { - LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: Fuel Tank Hit"); - return true; - } else if (checkCrew && (getCrew() != null) && (getCrew().getHits() >= 4)) { - LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: " + getCrew().getHits() + " Crew Hits taken."); - return true; - } else if (getFCSHits() >= 3) { - LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: Fire Control Destroyed by taking " + fcsHits); - return true; - } else if (getCICHits() >= 3) { - LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: Combat Information Center Destroyed by taking " + cicHits); - return true; - } - - // If this is not a military unit, we don't care about weapon status. - if (!isMilitary()) { - return false; - } - - if (!hasViableWeapons()) { - LogManager.getLogger().debug(getDisplayName() + " CRIPPLED: No more viable weapons."); - return true; - } else { - return false; - } + public int[] getBombChoices() { + return bombChoices.clone(); } @Override - public boolean isDmgHeavy() { - if (getArmorRemainingPercent() <= 0.33) { - LogManager.getLogger().debug(getDisplayName() - + " Heavily Damaged: Armour Remaining percent of " + getArmorRemainingPercent() - + " is less than or equal to 0.33."); - return true; - } else if (getInternalRemainingPercent() < 0.67) { - LogManager.getLogger().debug(getDisplayName() - + " Heavily Damaged: Internal Structure Remaining percent of " + getInternalRemainingPercent() - + " is less than 0.67."); - return true; - } else if ((getCrew() != null) && (getCrew().getHits() == 3)) { - LogManager.getLogger().debug(getDisplayName() - + "Moderately Damaged: The crew has taken a minimum of three hits."); - return true; - } - - // If this is not a military unit, we don't care about weapon status. - if (!isMilitary()) { - return false; - } - - List weaponList = getTotalWeaponList(); - int totalWeapons = weaponList.size(); - int totalInoperable = 0; - for (Mounted weap : weaponList) { - if (weap.isCrippled()) { - totalInoperable++; - } + public void setBombChoices(int[] bc) { + if (bc.length == bombChoices.length) { + bombChoices = bc; } - return ((double) totalInoperable / totalWeapons) >= 0.75; } @Override - public boolean isDmgModerate() { - if (getArmorRemainingPercent() <= 0.5) { - LogManager.getLogger().debug(getDisplayName() - + " Moderately Damaged: Armour Remaining percent of " + getArmorRemainingPercent() - + " is less than or equal to 0.50."); - return true; - } else if (getInternalRemainingPercent() < 0.75) { - LogManager.getLogger().debug(getDisplayName() - + " Moderately Damaged: Internal Structure Remaining percent of " + getInternalRemainingPercent() - + " is less than 0.75."); - return true; - } else if ((getCrew() != null) && (getCrew().getHits() == 2)) { - LogManager.getLogger().debug(getDisplayName() - + " Moderately Damaged: The crew has taken a minimum of two hits."); - return true; - } - - // If this is not a military unit, we don't care about weapon status. - if (!isMilitary()) { - return false; - } - - int totalWeapons = getTotalWeaponList().size(); - int totalInoperable = 0; - for (Mounted weap : getTotalWeaponList()) { - if (weap.isCrippled()) { - totalInoperable++; - } - } - return ((double) totalInoperable / totalWeapons) >= 0.5; + public void clearBombChoices() { + Arrays.fill(bombChoices, 0); } @Override - public boolean isDmgLight() { - if (getArmorRemainingPercent() <= 0.75) { - LogManager.getLogger().debug(getDisplayName() - + " Lightly Damaged: Armour Remaining percent of " + getArmorRemainingPercent() - + " is less than or equal to 0.75."); - return true; - } else if (getInternalRemainingPercent() < 0.9) { - LogManager.getLogger().debug(getDisplayName() - + " Lightly Damaged: Internal Structure Remaining percent of " + getInternalRemainingPercent() - + " is less than 0.9."); - return true; - } else if ((getCrew() != null) && (getCrew().getHits() == 1)) { - LogManager.getLogger().debug(getDisplayName() - + " Lightly Damaged: The crew has taken a minimum of one hit."); - return true; - } - - // If this is not a military unit, we don't care about weapon status. - if (!isMilitary()) { - return false; - } - - int totalWeapons = getTotalWeaponList().size(); - int totalInoperable = 0; - for (Mounted weap : getTotalWeaponList()) { - if (weap.isCrippled()) { - totalInoperable++; - } - } - return ((double) totalInoperable / totalWeapons) >= 0.25; + public int reduceMPByBombLoad(int t) { + return Math.max(0, t - (int) Math.ceil(getExternalBombPoints() / 5.0)); } @Override - public boolean canSpot() { - // per a recent ruling on the official forums, aero units can't spot - // for indirect LRM fire, unless they have a recon cam, an infrared or - // hyperspec imager, or a high-res imager and it's not night - if (!isAirborne() || hasWorkingMisc(MiscType.F_RECON_CAMERA) || hasWorkingMisc(MiscType.F_INFRARED_IMAGER) - || hasWorkingMisc(MiscType.F_HYPERSPECTRAL_IMAGER) - || (hasWorkingMisc(MiscType.F_HIRES_IMAGER) - && ((game.getPlanetaryConditions().getLight() == PlanetaryConditions.L_DAY) - || (game.getPlanetaryConditions().getLight() == PlanetaryConditions.L_DUSK)))) { - return true; - } else { - return false; - } + public boolean isSpheroid() { + return false; } // Damage a fighter that was part of a squadron when splitting it. Per @@ -2571,245 +152,6 @@ public void damageCapFighterWeapons(int loc) { } } - /** - * @return The total number of crew available to supplement marines on boarding actions. - * Includes officers, enlisted, and bay personnel, but not marines/ba or passengers. - */ - @Override - public int getNCrew() { - return 1; - } - - @Override - public void setNCrew(int crew) { - } - - /** - * @return The total number of officers for vessels. - */ - public int getNOfficers() { - return 0; - } - - /** - * @return The total number of gunners for vessels. - */ - public int getNGunners() { - return 0; - } - - /** - * Returns the number of passengers on this unit - * Intended for spacecraft, where we want to get the crews of transported units - * plus actual passengers assigned to quarters - * @return - */ - @Override - public int getNPassenger() { - return 0; - } - - @Override - public void setNPassenger(int pass) { - } - - /** - * Returns the list of Entity IDs used by this ship as escape craft - * @return - */ - public Set getEscapeCraft() { - return escapeCraftList; - } - - /** - * Adds an Escape Craft. Used by MHQ to track where escaped crew and passengers end up. - * @param id The Entity ID of the ship to add. - */ - public void addEscapeCraft(String id) { - escapeCraftList.add(id); - } - - /** - * Removes an Escape Craft. Used by MHQ to track where escaped crew and passengers end up. - * @param id The Entity ID of the ship to remove. - */ - public void removeEscapeCraft(String id) { - escapeCraftList.remove(id); - } - - /** - * @return The number battlearmored marines available to vessels for boarding actions. - */ - public int getNBattleArmor() { - return 0; - } - - /** - * @return The number conventional marines available to vessels for boarding actions. - */ - @Override - public int getNMarines() { - return 0; - } - - /** - * Updates the number of marines aboard - * @param marines The number of marines to add/subtract - */ - @Override - public void setNMarines(int marines) { - } - - /** - * Returns our list of unique individuals being transported as marines - * @return - */ - public Map getMarines() { - return marines; - } - - /** - * Adds a marine. Used by MHQ to track where a given person ends up. - * Also used by MM to move marines around between ships - * @param personId The unique ID of the person to add. - * @param pointValue The marine point value of the person being added - */ - public void addMarine(UUID personId, int pointValue) { - marines.put(personId, pointValue); - } - - /** - * Removes a marine. Used by MHQ to track where a given person ends up. - * Also used by MM to move marines around between ships - * @param personId The unique ID of the person to remove. - */ - public void removeMarine(UUID personId) { - marines.remove(personId); - } - - /** - * Returns the number of marines assigned to a unit - * Used for abandoning a unit - * @return - */ - public int getMarineCount() { - return 0; - } - - /** - * Convenience method that compiles the total number of people aboard a ship - Crew, Marines, Passengers... - * @return An integer representing everyone aboard - */ - public int getTotalAboard() { - return (getNCrew() + getNPassenger() + getMarineCount()); - } - - /** - * @return The number of escape pods carried by the unit - */ - public int getEscapePods() { - return 0; - } - - /** - * Convenience method to return the number of escape pods remaining - * @return - */ - public int getPodsLeft() { - return getEscapePods() - getLaunchedEscapePods(); - } - - /** - * @return The number of lifeboats carried by the unit - */ - public int getLifeBoats() { - return 0; - } - - /** - * Returns the total number of escape pods launched so far - */ - public int getLaunchedEscapePods() { - return 0; - } - - /** - * Updates the total number of escape pods launched so far - * @param n The number to change - */ - public void setLaunchedEscapePods(int n) { - } - - /** - * Returns the total number of life boats launched so far - */ - public int getLaunchedLifeBoats() { - return 0; - } - - /** - * Convenience method to return the number of life boats remaining - * @return - */ - public int getLifeBoatsLeft() { - return getLifeBoats() - getLaunchedLifeBoats(); - } - - /** - * Updates the total number of life boats launched so far - * @param n The number to change - */ - public void setLaunchedLifeBoats(int n) { - } - - /** - * Calculates whether this ship has any available escape systems remaining - * return - */ - public boolean hasEscapeSystemsLeft() { - return ((getLaunchedLifeBoats() < getLifeBoats()) - || (getLaunchedEscapePods() < getEscapePods()) - || !getLaunchableSmallCraft().isEmpty()); - } - - /** - * Calculates the total number of people that can be carried in this unit's escape systems - * 6 people per lifeboat/escape pod + troop capacity of any small craft - * Most small craft use cargo space instead of infantry bays, so we'll assume 0.1 tons/person - * (Taken from Infantry.getWeight() - foot trooper + .015t for the spacesuit everyone aboard is wearing ;) ) - * @return The total escape count for the unit - */ - public int getEscapeCapacity() { - int people = 0; - // We can cram 6 people in an escape pod - people += getEscapePods() * 6; - // Lifeboats hold 6 comfortably - people += getLifeBoats() * 6; - - // Any small craft aboard and able to launch? - for (Entity sc : getLaunchableSmallCraft()) { - // There could be an ASF in the bay... - if (sc instanceof SmallCraft) { - for (Bay b : sc.getTransportBays()) { - if (b instanceof InfantryBay || b instanceof BattleArmorBay || b instanceof CargoBay) { - // Use the available tonnage - people += (b.getCapacity() / 0.1); - } - } - } - } - return people; - } - - @Override - public long getEntityType() { - return Entity.ETYPE_AERO; - } - - public boolean isInASquadron() { - return game.getEntity(getTransportId()) instanceof FighterSquadron; - } - @Override public boolean isAero() { return true; @@ -2822,8 +164,7 @@ public boolean isAero() { */ @Override public boolean isBomber() { - return (isFighter() || - (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB))); + return true; } @Override @@ -2844,239 +185,7 @@ public boolean isAerospaceFighter() { } @Override - public int availableBombLocation(int cost) { - return LOC_NOSE; - } - - /** - * Used to determine the draw priority of different Entity subclasses. This - * allows different unit types to always be draw above/below other types. - * - * @return - */ - @Override - public int getSpriteDrawPriority() { - return 10; - } - - @Override - public List getActiveAMS() { - //Large craft use AMS and Point Defense bays - if ((this instanceof Dropship) - || (this instanceof Jumpship) - || (this instanceof Warship) - || (this instanceof SpaceStation)) { - - ArrayList ams = new ArrayList<>(); - for (Mounted weapon : getWeaponBayList()) { - // Skip anything that's not an AMS, AMS Bay or Point Defense Bay - if (!weapon.getType().hasFlag(WeaponType.F_AMS) - && !weapon.getType().hasFlag(WeaponType.F_AMSBAY) - && !weapon.getType().hasFlag(WeaponType.F_PDBAY)) { - continue; - } - - // Make sure the AMS is good to go - if (!weapon.isReady() || weapon.isMissing() - || weapon.curMode().equals("Off") - || weapon.curMode().equals("Normal")) { - continue; - } - - // AMS blocked by transported units can not fire - if (isWeaponBlockedAt(weapon.getLocation(), - weapon.isRearMounted())) { - continue; - } - - // Make sure ammo is loaded - for (int wId : weapon.getBayWeapons()) { - Mounted bayW = getEquipment(wId); - Mounted bayWAmmo = bayW.getLinked(); - if (!(weapon.getType().hasFlag(WeaponType.F_ENERGY)) - && ((bayWAmmo == null) || (bayWAmmo.getUsableShotsLeft() == 0) - || bayWAmmo.isDumping())) { - loadWeapon(weapon); - bayWAmmo = weapon.getLinked(); - } - - // try again - if (!(weapon.getType().hasFlag(WeaponType.F_ENERGY)) - && ((bayWAmmo == null) || (bayWAmmo.getUsableShotsLeft() == 0) - || bayWAmmo.isDumping())) { - // No ammo for this AMS. - continue; - } - } - ams.add(weapon); - } - return ams; - } - //ASFs and Small Craft should use regular old AMS... - return super.getActiveAMS(); - } - - /** - * A method to add/remove sensors that only work in space as we transition in and out of an atmosphere - */ - @Override - public void updateSensorOptions() { - //Prevent adding duplicates - boolean hasSpacecraftThermal = false; - boolean hasAeroThermal = false; - boolean hasESM = false; - for (Sensor sensor : getSensors()) { - if (sensor.getType() == Sensor.TYPE_SPACECRAFT_THERMAL) { - hasSpacecraftThermal = true; - } - if (sensor.getType() == Sensor.TYPE_AERO_THERMAL) { - hasAeroThermal = true; - } - if (sensor.getType() == Sensor.TYPE_SPACECRAFT_ESM) { - hasESM = true; - } - } - //Remove everything but Radar if we're not in space - if (!isSpaceborne()) { - Vector sensorsToRemove = new Vector<>(); - if (hasETypeFlag(Entity.ETYPE_DROPSHIP)) { - for (Sensor sensor : getSensors()) { - if (sensor.getType() == Sensor.TYPE_SPACECRAFT_ESM) { - hasESM = false; - sensorsToRemove.add(sensor); - } - if (sensor.getType() == Sensor.TYPE_SPACECRAFT_THERMAL) { - hasSpacecraftThermal = false; - sensorsToRemove.add(sensor); - } - } - } else if (hasETypeFlag(Entity.ETYPE_AERO)) { - for (Sensor sensor : getSensors()) { - if (sensor.getType() == Sensor.TYPE_AERO_THERMAL) { - hasAeroThermal = false; - sensorsToRemove.add(sensor); - } - } - } - getSensors().removeAll(sensorsToRemove); - if (sensorsToRemove.size() >= 1) { - setNextSensor(getSensors().firstElement()); - } - } - //If we are in space, add them back... - if (isSpaceborne()) { - if (hasETypeFlag(Entity.ETYPE_DROPSHIP) - || hasETypeFlag(Entity.ETYPE_SPACE_STATION) - || hasETypeFlag(Entity.ETYPE_JUMPSHIP) - || hasETypeFlag(Entity.ETYPE_WARSHIP)) { - //Large craft get thermal/optical sensors - if (!hasSpacecraftThermal) { - getSensors().add(new Sensor(Sensor.TYPE_SPACECRAFT_THERMAL)); - hasSpacecraftThermal = true; - } - //Only military craft get ESM, which detects active radar - if (getDesignType() == AeroSpaceFighter.MILITARY) { - if (!hasESM) { - getSensors().add(new Sensor(Sensor.TYPE_SPACECRAFT_ESM)); - hasESM = true; - } - } - } else if (hasETypeFlag(Entity.ETYPE_AERO) - || hasETypeFlag(Entity.ETYPE_SMALL_CRAFT)) { - //ASFs and small craft get thermal/optical sensors - if (!hasAeroThermal) { - getSensors().add(new Sensor(Sensor.TYPE_AERO_THERMAL)); - hasAeroThermal = true; - } - } - } - } - - // autoejection methods - - /** - * @return unit has an ejection seat - */ - public boolean hasEjectSeat() { - return !hasQuirk(OptionsConstants.QUIRK_NEG_NO_EJECT); - } - - /** - * @return Returns the autoEject. - */ - public boolean isAutoEject() { - return autoEject && hasEjectSeat(); - } - - /** - * @param autoEject - * Turn the master autoejection system on or off - */ - public void setAutoEject(boolean autoEject) { - this.autoEject = autoEject; - } - - /** - * Is autoejection enabled for ammo explosions? - * @return - */ - public boolean isCondEjectAmmo() { - return condEjectAmmo; - } - - /** - * Used by Conditional Auto Ejection - will we eject when an ammo explosion is triggered? - * @param condEjectAmmo Sets autoejection for ammo explosions - */ - public void setCondEjectAmmo(boolean condEjectAmmo) { - this.condEjectAmmo = condEjectAmmo; - } - - /** - * Is autoejection enabled for fuel explosions? - * @return - */ - public boolean isCondEjectFuel() { - return condEjectFuel; - } - - /** - * Used by Conditional Auto Ejection - will we eject when a fuel explosion is triggered? - * @param condEjectFuel Sets autoejection for fuel tank explosions - */ - public void setCondEjectFuel(boolean condEjectFuel) { - this.condEjectFuel = condEjectFuel; - } - - /** - * Is autoejection enabled for SI destruction (Fighter only)? - * @return - */ - public boolean isCondEjectSIDest() { - return condEjectSIDest; - } - - /** - * Used by Conditional Auto Ejection - will we eject when structural integrity is reduced to 0? - * @param condEjectSIDest Sets autoejection for structural integrity destruction - */ - public void setCondEjectSIDest(boolean condEjectSIDest) { - this.condEjectSIDest = condEjectSIDest; - } - - /** - * Intended for large craft. Indicates that the ship is being abandoned. - * @return - */ - public boolean isEjecting() { - return ejecting; - } - - /** - * Changes the ejecting flag when the order to abandon ship is given - * @param ejecting Change to the ejecting status of this ship - */ - public void setEjecting(boolean ejecting) { - this.ejecting = ejecting; + public long getEntityType() { + return super.getEntityType() | Entity.ETYPE_AEROSPACEFIGHTER; } } \ No newline at end of file diff --git a/megamek/src/megamek/common/AmmoType.java b/megamek/src/megamek/common/AmmoType.java index e307cc292bd..a8e6d423406 100644 --- a/megamek/src/megamek/common/AmmoType.java +++ b/megamek/src/megamek/common/AmmoType.java @@ -146,11 +146,11 @@ public class AmmoType extends EquipmentType { * Contains the {@code AmmoType}s that could share ammo (e.g. SRM 2 and SRM 6, * both fire SRM rounds). */ - private static final Integer[] ALLOWED_BY_TYPE_ARRAY = { AmmoType.T_LRM, AmmoType.T_LRM_PRIMITIVE, + private static final Integer[] ALLOWED_BY_TYPE_ARRAY = {AmmoType.T_LRM, AmmoType.T_LRM_PRIMITIVE, AmmoType.T_LRM_STREAK, AmmoType.T_LRM_TORPEDO, AmmoType.T_LRM_TORPEDO_COMBO, AmmoType.T_SRM, AmmoType.T_SRM_ADVANCED, AmmoType.T_SRM_PRIMITIVE, AmmoType.T_SRM_STREAK, AmmoType.T_SRM_TORPEDO, AmmoType.T_MRM, AmmoType.T_ROCKET_LAUNCHER, AmmoType.T_EXLRM, AmmoType.T_MML, AmmoType.T_NLRM, AmmoType.T_MG, AmmoType.T_MG_LIGHT, AmmoType.T_MG_HEAVY, - AmmoType.T_NAIL_RIVET_GUN, AmmoType.T_ATM, AmmoType.T_IATM, }; + AmmoType.T_NAIL_RIVET_GUN, AmmoType.T_ATM, AmmoType.T_IATM,}; /** * Contains the set of {@code AmmoType}s which could share ammo (e.g. SRM 2 and @@ -190,10 +190,13 @@ public class AmmoType extends EquipmentType { // Used by MHQ for loading ammo bins public static final BigInteger F_SCREEN = BigInteger.valueOf(1).shiftLeft(18); + // Used for Internal Bomb Bay bombs; to differentiate them from + public static final BigInteger F_INTERNAL_BOMB = BigInteger.valueOf(1).shiftLeft(19); + // ammo munitions, used for custom loadouts // N.B. We use EnumSet allow "incendiary" // to be combined to other munition types. - public enum Munitions{ + public enum Munitions { M_STANDARD, // AC Munition Types @@ -425,8 +428,8 @@ public boolean isCompatibleWith(AmmoType other) { // ATM Launchers if (((is(T_ATM) && other.is(T_IATM)) || (is(T_IATM) && other.is(T_ATM))) && (getMunitionType() == other.getMunitionType())) { - // Ammo exclusive to iATMs couldn't have the same munition type as standard ATMs - return true; + // Ammo exclusive to iATMs couldn't have the same munition type as standard ATMs + return true; } // General Launchers @@ -460,11 +463,11 @@ public boolean is(int ammoType) { public boolean countsAsFlak() { boolean counts = false; - if(ArrayUtils.contains(ARTILLERY_TYPES, this.getAmmoType())){ + if (ArrayUtils.contains(ARTILLERY_TYPES, this.getAmmoType())) { // Air-Defense Arrow IV _is_ Flak, but is _not_ Artillery counts = ARTILLERY_FLAK_MUNITIONS.containsAll(this.getMunitionType()) || this.getMunitionType().contains(Munitions.M_ADA); - } else if(ArrayUtils.contains(ARTILLERY_CANNON_TYPES, this.getAmmoType())){ + } else if (ArrayUtils.contains(ARTILLERY_CANNON_TYPES, this.getAmmoType())) { counts = this.getMunitionType().contains(Munitions.M_STANDARD); } return counts; @@ -2328,13 +2331,13 @@ public static void initializeTypes() { // Create the munition types for IS Arrow IV launchers. munitions.clear(); munitions.add(new MunitionMutator("Air-Defense Arrow (ADA) Missiles", 1, Munitions.M_ADA, - new TechAdvancement(TECH_BASE_IS).setIntroLevel(false).setUnofficial(false) - .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setPrototypeFactions(F_CC) - .setISAdvancement(3068, 3080, DATE_NONE, DATE_NONE, DATE_NONE) - .setApproximate(false, false, false, false, false).setTechRating(RATING_E) - .setProductionFactions(F_CC).setStaticTechLevel(SimpleTechLevel.ADVANCED) - , "165, TO:AU&E")); + new TechAdvancement(TECH_BASE_IS).setIntroLevel(false).setUnofficial(false) + .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) + .setPrototypeFactions(F_CC) + .setISAdvancement(3068, 3080, DATE_NONE, DATE_NONE, DATE_NONE) + .setApproximate(false, false, false, false, false).setTechRating(RATING_E) + .setProductionFactions(F_CC).setStaticTechLevel(SimpleTechLevel.ADVANCED) + , "165, TO:AU&E")); munitions.add(new MunitionMutator("Cluster", 1, Munitions.M_CLUSTER, new TechAdvancement(TECH_BASE_IS).setIntroLevel(false).setUnofficial(false).setTechRating(RATING_E) @@ -2765,7 +2768,7 @@ public static void initializeTypes() { AmmoType.createMunitions(clanHeavyFlamerAmmos, munitions); // cache types that share a launcher for loadout purposes - for (Enumeration e = EquipmentType.getAllTypes(); e.hasMoreElements();) { + for (Enumeration e = EquipmentType.getAllTypes(); e.hasMoreElements(); ) { EquipmentType et = e.nextElement(); if (!(et instanceof AmmoType)) { continue; @@ -2991,13 +2994,13 @@ private static AmmoType createISCruiseMissile50Ammo() { ammo.rulesRefs = "284, TO"; //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_IS) - .setTechRating(RATING_E) - .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setISAdvancement(3065, 3095, DATE_NONE, DATE_NONE, DATE_NONE) - .setISApproximate(false, true, false, false, false) - .setPrototypeFactions(F_FS) - .setProductionFactions(F_FS) - .setStaticTechLevel(SimpleTechLevel.ADVANCED); + .setTechRating(RATING_E) + .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) + .setISAdvancement(3065, 3095, DATE_NONE, DATE_NONE, DATE_NONE) + .setISApproximate(false, true, false, false, false) + .setPrototypeFactions(F_FS) + .setProductionFactions(F_FS) + .setStaticTechLevel(SimpleTechLevel.ADVANCED); return ammo; } @@ -3017,13 +3020,13 @@ private static AmmoType createISCruiseMissile70Ammo() { ammo.rulesRefs = "284, TO"; //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_IS) - .setTechRating(RATING_E) - .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setISAdvancement(3065, 3095, DATE_NONE, DATE_NONE, DATE_NONE) - .setISApproximate(false, true, false, false, false) - .setPrototypeFactions(F_FS) - .setProductionFactions(F_FS) - .setStaticTechLevel(SimpleTechLevel.ADVANCED); + .setTechRating(RATING_E) + .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) + .setISAdvancement(3065, 3095, DATE_NONE, DATE_NONE, DATE_NONE) + .setISApproximate(false, true, false, false, false) + .setPrototypeFactions(F_FS) + .setProductionFactions(F_FS) + .setStaticTechLevel(SimpleTechLevel.ADVANCED); return ammo; } @@ -3043,13 +3046,13 @@ private static AmmoType createISCruiseMissile90Ammo() { ammo.rulesRefs = "284, TO"; //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_IS) - .setTechRating(RATING_E) - .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setISAdvancement(3065, 3095, DATE_NONE, DATE_NONE, DATE_NONE) - .setISApproximate(false, true, false, false, false) - .setPrototypeFactions(F_FS) - .setProductionFactions(F_FS) - .setStaticTechLevel(SimpleTechLevel.ADVANCED); + .setTechRating(RATING_E) + .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) + .setISAdvancement(3065, 3095, DATE_NONE, DATE_NONE, DATE_NONE) + .setISApproximate(false, true, false, false, false) + .setPrototypeFactions(F_FS) + .setProductionFactions(F_FS) + .setStaticTechLevel(SimpleTechLevel.ADVANCED); return ammo; } @@ -3069,13 +3072,13 @@ private static AmmoType createISCruiseMissile120Ammo() { ammo.rulesRefs = "284, TO"; //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_IS) - .setTechRating(RATING_E) - .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setISAdvancement(3065, 3095, DATE_NONE, DATE_NONE, DATE_NONE) - .setISApproximate(false, true, false, false, false) - .setPrototypeFactions(F_FS) - .setProductionFactions(F_FS) - .setStaticTechLevel(SimpleTechLevel.ADVANCED); + .setTechRating(RATING_E) + .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) + .setISAdvancement(3065, 3095, DATE_NONE, DATE_NONE, DATE_NONE) + .setISApproximate(false, true, false, false, false) + .setPrototypeFactions(F_FS) + .setProductionFactions(F_FS) + .setStaticTechLevel(SimpleTechLevel.ADVANCED); return ammo; } @@ -3106,9 +3109,9 @@ private static AmmoType createISLongTomCannonAmmo() { //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_ALL).setTechRating(RATING_B) .setAvailability(RATING_X, RATING_F, RATING_E, RATING_D) - .setISAdvancement(3012, 3079, DATE_NONE, DATE_NONE,DATE_NONE) + .setISAdvancement(3012, 3079, DATE_NONE, DATE_NONE, DATE_NONE) .setISApproximate(false, true, false, false, false) - .setClanAdvancement(3032, 3079, DATE_NONE, DATE_NONE,DATE_NONE) + .setClanAdvancement(3032, 3079, DATE_NONE, DATE_NONE, DATE_NONE) .setClanApproximate(false, true, false, false, false) .setPrototypeFactions(F_LC, F_CWF) .setProductionFactions(F_LC) @@ -3160,9 +3163,9 @@ private static AmmoType createISSniperCannonAmmo() { // Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_ALL).setTechRating(RATING_B) .setAvailability(RATING_X, RATING_F, RATING_E, RATING_D) - .setISAdvancement(3012, 3079, DATE_NONE, DATE_NONE,DATE_NONE) + .setISAdvancement(3012, 3079, DATE_NONE, DATE_NONE, DATE_NONE) .setISApproximate(false, true, false, false, false) - .setClanAdvancement(3032, 3079, DATE_NONE, DATE_NONE,DATE_NONE) + .setClanAdvancement(3032, 3079, DATE_NONE, DATE_NONE, DATE_NONE) .setClanApproximate(false, true, false, false, false) .setPrototypeFactions(F_LC, F_CWF) .setProductionFactions(F_LC) @@ -3215,9 +3218,9 @@ private static AmmoType createISThumperCannonAmmo() { //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_ALL).setTechRating(RATING_B) .setAvailability(RATING_X, RATING_F, RATING_E, RATING_D) - .setISAdvancement(3012, 3079, DATE_NONE, DATE_NONE,DATE_NONE) + .setISAdvancement(3012, 3079, DATE_NONE, DATE_NONE, DATE_NONE) .setISApproximate(false, true, false, false, false) - .setClanAdvancement(3032, 3079, DATE_NONE, DATE_NONE,DATE_NONE) + .setClanAdvancement(3032, 3079, DATE_NONE, DATE_NONE, DATE_NONE) .setClanApproximate(false, true, false, false, false) .setPrototypeFactions(F_LC, F_CWF) .setProductionFactions(F_LC) @@ -3438,7 +3441,7 @@ private static AmmoType createCLPROAC2Ammo() { //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_CLAN) .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setClanAdvancement(DATE_NONE, 3070, 3073, DATE_NONE,DATE_NONE) + .setClanAdvancement(DATE_NONE, 3070, 3073, DATE_NONE, DATE_NONE) .setClanApproximate(false, true, false, false, false) .setPrototypeFactions(F_CBS).setProductionFactions(F_CBS) .setStaticTechLevel(SimpleTechLevel.STANDARD); @@ -3464,7 +3467,7 @@ private static AmmoType createCLPROAC4Ammo() { //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_CLAN) .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setClanAdvancement(DATE_NONE, 3070, 3073, DATE_NONE,DATE_NONE) + .setClanAdvancement(DATE_NONE, 3070, 3073, DATE_NONE, DATE_NONE) .setClanApproximate(false, true, false, false, false) .setPrototypeFactions(F_CBS).setProductionFactions(F_CBS) .setStaticTechLevel(SimpleTechLevel.STANDARD); @@ -3490,7 +3493,7 @@ private static AmmoType createCLPROAC8Ammo() { //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_CLAN) .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setClanAdvancement(DATE_NONE, 3070, 3073, DATE_NONE,DATE_NONE) + .setClanAdvancement(DATE_NONE, 3070, 3073, DATE_NONE, DATE_NONE) .setClanApproximate(false, true, false, false, false) .setPrototypeFactions(F_CBS).setProductionFactions(F_CBS) .setStaticTechLevel(SimpleTechLevel.STANDARD); @@ -4230,11 +4233,11 @@ private static AmmoType createCLRotary2Ammo() { ammo.rulesRefs = "286, TO"; //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_CLAN) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setClanAdvancement(3073, DATE_NONE, 3104, DATE_NONE, DATE_NONE) - .setClanApproximate(false, false, false, false, false) - .setPrototypeFactions(F_CSF).setProductionFactions(F_CSF) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) + .setClanAdvancement(3073, DATE_NONE, 3104, DATE_NONE, DATE_NONE) + .setClanApproximate(false, false, false, false, false) + .setPrototypeFactions(F_CSF).setProductionFactions(F_CSF) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -4255,11 +4258,11 @@ private static AmmoType createCLRotary5Ammo() { ammo.rulesRefs = "286, TO"; //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_CLAN) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setClanAdvancement(3073, DATE_NONE, 3104, DATE_NONE, DATE_NONE) - .setClanApproximate(false, false, false, false, false) - .setPrototypeFactions(F_CSF).setProductionFactions(F_CSF) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) + .setClanAdvancement(3073, DATE_NONE, 3104, DATE_NONE, DATE_NONE) + .setClanApproximate(false, false, false, false, false) + .setPrototypeFactions(F_CSF).setProductionFactions(F_CSF) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -4280,10 +4283,10 @@ private static AmmoType createISLightRifleAmmo() { ammo.rulesRefs = "338, TO"; //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_B) - .setAvailability(RATING_C, RATING_F, RATING_X, RATING_D) - .setISAdvancement(DATE_PS, DATE_NONE, 3084, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + .setAvailability(RATING_C, RATING_F, RATING_X, RATING_D) + .setISAdvancement(DATE_PS, DATE_NONE, 3084, DATE_NONE, DATE_NONE) + .setISApproximate(false, false, true, false, false) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -4304,10 +4307,10 @@ private static AmmoType createISMediumRifleAmmo() { ammo.rulesRefs = "338, TO"; //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_B) - .setAvailability(RATING_C, RATING_F, RATING_X, RATING_D) - .setISAdvancement(DATE_PS, DATE_NONE, 3084, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + .setAvailability(RATING_C, RATING_F, RATING_X, RATING_D) + .setISAdvancement(DATE_PS, DATE_NONE, 3084, DATE_NONE, DATE_NONE) + .setISApproximate(false, false, true, false, false) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -4328,10 +4331,10 @@ private static AmmoType createISHeavyRifleAmmo() { ammo.rulesRefs = "338, TO"; //Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_B) - .setAvailability(RATING_C, RATING_F, RATING_X, RATING_D) - .setISAdvancement(DATE_PS, DATE_NONE, 3084, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + .setAvailability(RATING_C, RATING_F, RATING_X, RATING_D) + .setISAdvancement(DATE_PS, DATE_NONE, 3084, DATE_NONE, DATE_NONE) + .setISApproximate(false, false, true, false, false) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -5526,10 +5529,10 @@ private static AmmoType createCLIATM6Ammo() { ammo.setModes("", "HotLoad"); ammo.rulesRefs = "65, IO"; ammo.techAdvancement.setTechBase(TECH_BASE_CLAN).setIntroLevel(false).setUnofficial(false) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) - .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) - .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) - .setProductionFactions(F_CCY); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) + .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) + .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) + .setProductionFactions(F_CCY); return ammo; } @@ -5551,10 +5554,10 @@ private static AmmoType createCLIATM9Ammo() { ammo.setModes("", "HotLoad"); ammo.rulesRefs = "65, IO"; ammo.techAdvancement.setTechBase(TECH_BASE_CLAN).setIntroLevel(false).setUnofficial(false) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) - .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) - .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) - .setProductionFactions(F_CCY); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) + .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) + .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) + .setProductionFactions(F_CCY); return ammo; } @@ -5576,10 +5579,10 @@ private static AmmoType createCLIATM12Ammo() { ammo.setModes("", "HotLoad"); ammo.rulesRefs = "65, IO"; ammo.techAdvancement.setTechBase(TECH_BASE_CLAN).setIntroLevel(false).setUnofficial(false) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) - .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) - .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) - .setProductionFactions(F_CCY); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) + .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) + .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) + .setProductionFactions(F_CCY); return ammo; } @@ -5603,10 +5606,10 @@ private static AmmoType createCLIATM3ERAmmo() { ammo.setModes("", "HotLoad"); ammo.rulesRefs = "65, IO"; ammo.techAdvancement.setTechBase(TECH_BASE_CLAN).setIntroLevel(false).setUnofficial(false) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) - .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) - .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) - .setProductionFactions(F_CCY); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) + .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) + .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) + .setProductionFactions(F_CCY); return ammo; } @@ -5628,10 +5631,10 @@ private static AmmoType createCLIATM6ERAmmo() { ammo.flags = ammo.flags.or(F_HOTLOAD); ammo.rulesRefs = "65, IO"; ammo.techAdvancement.setTechBase(TECH_BASE_CLAN).setIntroLevel(false).setUnofficial(false) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) - .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) - .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) - .setProductionFactions(F_CCY); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) + .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) + .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) + .setProductionFactions(F_CCY); return ammo; } @@ -5654,10 +5657,10 @@ private static AmmoType createCLIATM9ERAmmo() { ammo.setModes("", "HotLoad"); ammo.rulesRefs = "65, IO"; ammo.techAdvancement.setTechBase(TECH_BASE_CLAN).setIntroLevel(false).setUnofficial(false) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) - .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) - .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) - .setProductionFactions(F_CCY); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) + .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) + .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) + .setProductionFactions(F_CCY); return ammo; } @@ -5680,10 +5683,10 @@ private static AmmoType createCLIATM12ERAmmo() { ammo.setModes("", "HotLoad"); ammo.rulesRefs = "65, IO"; ammo.techAdvancement.setTechBase(TECH_BASE_CLAN).setIntroLevel(false).setUnofficial(false) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) - .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) - .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) - .setProductionFactions(F_CCY); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) + .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) + .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) + .setProductionFactions(F_CCY); return ammo; } @@ -5707,10 +5710,10 @@ private static AmmoType createCLIATM3HEAmmo() { ammo.setModes("", "HotLoad"); ammo.rulesRefs = "65, IO"; ammo.techAdvancement.setTechBase(TECH_BASE_CLAN).setIntroLevel(false).setUnofficial(false) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) - .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) - .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) - .setProductionFactions(F_CCY); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) + .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) + .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) + .setProductionFactions(F_CCY); return ammo; } @@ -5733,10 +5736,10 @@ private static AmmoType createCLIATM6HEAmmo() { ammo.setModes("", "HotLoad"); ammo.rulesRefs = "65, IO"; ammo.techAdvancement.setTechBase(TECH_BASE_CLAN).setIntroLevel(false).setUnofficial(false) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) - .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) - .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) - .setProductionFactions(F_CCY); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) + .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) + .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) + .setProductionFactions(F_CCY); return ammo; } @@ -5759,10 +5762,10 @@ private static AmmoType createCLIATM9HEAmmo() { ammo.setModes("", "HotLoad"); ammo.rulesRefs = "65, IO"; ammo.techAdvancement.setTechBase(TECH_BASE_CLAN).setIntroLevel(false).setUnofficial(false) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) - .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) - .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) - .setProductionFactions(F_CCY); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) + .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) + .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) + .setProductionFactions(F_CCY); return ammo; } @@ -5785,10 +5788,10 @@ private static AmmoType createCLIATM12HEAmmo() { ammo.setModes("", "HotLoad"); ammo.rulesRefs = "65, IO"; ammo.techAdvancement.setTechBase(TECH_BASE_CLAN).setIntroLevel(false).setUnofficial(false) - .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) - .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) - .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) - .setProductionFactions(F_CCY); + .setTechRating(RATING_F).setAvailability(RATING_X, RATING_X, RATING_D, RATING_D) + .setClanAdvancement(3054, 3070, DATE_NONE, DATE_NONE, DATE_NONE) + .setClanApproximate(true, false, false, false, false).setPrototypeFactions(F_CCY) + .setProductionFactions(F_CCY); return ammo; } @@ -6146,12 +6149,12 @@ private static AmmoType createISEnhancedLRM5Ammo() { ammo.cost = 31000; ammo.rulesRefs = "326, TO"; // Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS - ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_E) - .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setISAdvancement(3058,DATE_NONE, 3082,DATE_NONE,DATE_NONE) - .setPrototypeFactions(F_FS) - .setProductionFactions(F_FS) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_E) + .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) + .setISAdvancement(3058, DATE_NONE, 3082, DATE_NONE, DATE_NONE) + .setPrototypeFactions(F_FS) + .setProductionFactions(F_FS) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -6172,12 +6175,12 @@ private static AmmoType createISEnhancedLRM10Ammo() { ammo.cost = 31000; ammo.rulesRefs = "326, TO"; // Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS - ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_E) - .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setISAdvancement(3058,DATE_NONE, 3082,DATE_NONE,DATE_NONE) - .setPrototypeFactions(F_FS) - .setProductionFactions(F_FS) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_E) + .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) + .setISAdvancement(3058, DATE_NONE, 3082, DATE_NONE, DATE_NONE) + .setPrototypeFactions(F_FS) + .setProductionFactions(F_FS) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -6197,12 +6200,12 @@ private static AmmoType createISEnhancedLRM15Ammo() { ammo.cost = 31000; ammo.rulesRefs = "326, TO"; // Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS - ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_E) - .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setISAdvancement(3058,DATE_NONE, 3082,DATE_NONE,DATE_NONE) - .setPrototypeFactions(F_FS) - .setProductionFactions(F_FS) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_E) + .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) + .setISAdvancement(3058, DATE_NONE, 3082, DATE_NONE, DATE_NONE) + .setPrototypeFactions(F_FS) + .setProductionFactions(F_FS) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -6222,12 +6225,12 @@ private static AmmoType createISEnhancedLRM20Ammo() { ammo.cost = 31000; ammo.rulesRefs = "326, TO"; // Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS - ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_E) - .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setISAdvancement(3058,DATE_NONE, 3082,DATE_NONE,DATE_NONE) - .setPrototypeFactions(F_FS) - .setProductionFactions(F_FS) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_E) + .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) + .setISAdvancement(3058, DATE_NONE, 3082, DATE_NONE, DATE_NONE) + .setPrototypeFactions(F_FS) + .setProductionFactions(F_FS) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -6255,7 +6258,7 @@ private static AmmoType createISExtendedLRM5Ammo() { // Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_E) .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setISAdvancement(DATE_NONE, 3054, 3080,DATE_NONE, DATE_NONE) + .setISAdvancement(DATE_NONE, 3054, 3080, DATE_NONE, DATE_NONE) .setPrototypeFactions(F_FS, F_LC).setProductionFactions(F_LC) .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; @@ -6284,7 +6287,7 @@ private static AmmoType createISExtendedLRM10Ammo() { // Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_E) .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setISAdvancement(DATE_NONE, 3054, 3080,DATE_NONE, DATE_NONE) + .setISAdvancement(DATE_NONE, 3054, 3080, DATE_NONE, DATE_NONE) .setPrototypeFactions(F_FS, F_LC).setProductionFactions(F_LC) .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; @@ -6313,7 +6316,7 @@ private static AmmoType createISExtendedLRM15Ammo() { // Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_E) .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setISAdvancement(DATE_NONE, 3054, 3080,DATE_NONE, DATE_NONE) + .setISAdvancement(DATE_NONE, 3054, 3080, DATE_NONE, DATE_NONE) .setPrototypeFactions(F_FS, F_LC).setProductionFactions(F_LC) .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; @@ -6342,7 +6345,7 @@ private static AmmoType createISExtendedLRM20Ammo() { // Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS ammo.techAdvancement.setTechBase(TECH_BASE_IS).setTechRating(RATING_E) .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) - .setISAdvancement(DATE_NONE, 3054, 3080,DATE_NONE, DATE_NONE) + .setISAdvancement(DATE_NONE, 3054, 3080, DATE_NONE, DATE_NONE) .setPrototypeFactions(F_FS, F_LC).setProductionFactions(F_LC) .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; @@ -7402,7 +7405,7 @@ private static AmmoType createCLLRM19Ammo() { } -//Standard MRMs + //Standard MRMs private static AmmoType createISMRM10Ammo() { AmmoType ammo = new AmmoType(); @@ -12273,7 +12276,7 @@ private static AmmoType createISCoolantPod() { ammo.cost = 50000; // TODO : modes is a bodge because there is no proper end phase - String[] theModes = { "safe", "efficient", "off", "dump" }; + String[] theModes = {"safe", "efficient", "off", "dump"}; ammo.setModes(theModes); ammo.setInstantModeSwitch(true); ammo.rulesRefs = "303, TO"; @@ -12768,7 +12771,6 @@ private static AmmoType createISRailGunAmmo() { } - private static AmmoType createISAC10iAmmo() { AmmoType ammo = new AmmoType(); @@ -13037,8 +13039,8 @@ public static boolean canClearMinefield(AmmoType at) { || (at.getAmmoType() == T_ROCKET_LAUNCHER)) && (at.getRackSize() >= 20) && ((at.getMunitionType().contains(Munitions.M_STANDARD)) || (at.getMunitionType().contains(Munitions.M_ARTEMIS_CAPABLE)) - || (at.getMunitionType().contains(Munitions.M_ARTEMIS_V_CAPABLE)) - || (at.getMunitionType().contains(Munitions.M_NARC_CAPABLE)))) { + || (at.getMunitionType().contains(Munitions.M_ARTEMIS_V_CAPABLE)) + || (at.getMunitionType().contains(Munitions.M_NARC_CAPABLE)))) { return true; } // ATMs @@ -13060,11 +13062,11 @@ public static boolean canClearMinefield(AmmoType at) { public static boolean canDeliverMinefield(AmmoType at) { return (at != null) && ((at.getAmmoType() == T_LRM) || (at.getAmmoType() == AmmoType.T_LRM_IMP) - || (at.getAmmoType() == AmmoType.T_MML)) + || (at.getAmmoType() == AmmoType.T_MML)) && ((at.getMunitionType().contains(Munitions.M_THUNDER)) || (at.getMunitionType().contains(Munitions.M_THUNDER_INFERNO)) - || (at.getMunitionType().contains(Munitions.M_THUNDER_AUGMENTED)) - || (at.getMunitionType().contains(Munitions.M_THUNDER_VIBRABOMB)) - || (at.getMunitionType().contains(Munitions.M_THUNDER_ACTIVE))); + || (at.getMunitionType().contains(Munitions.M_THUNDER_AUGMENTED)) + || (at.getMunitionType().contains(Munitions.M_THUNDER_VIBRABOMB)) + || (at.getMunitionType().contains(Munitions.M_THUNDER_ACTIVE))); } private void addToEnd(AmmoType base, String modifier) { @@ -13481,7 +13483,7 @@ public AmmoType createMunitionType(AmmoType base) { || (munition.getAmmoType() == AmmoType.T_MML) || (munition.getAmmoType() == AmmoType.T_SRM) || (munition.getAmmoType() == AmmoType.T_SRM_IMP) || (munition.getAmmoType() == AmmoType.T_NLRM)) && ((munition.getMunitionType().contains(Munitions.M_ANTI_TSM)) - || (munition.getMunitionType().contains(Munitions.M_FRAGMENTATION)))) { + || (munition.getMunitionType().contains(Munitions.M_FRAGMENTATION)))) { cost *= 2; } @@ -13496,7 +13498,7 @@ public AmmoType createMunitionType(AmmoType base) { if (((munition.getAmmoType() == AmmoType.T_MML) || (munition.getAmmoType() == AmmoType.T_SRM) || (munition.getAmmoType() == AmmoType.T_SRM_IMP)) && ((munition.getMunitionType().contains(Munitions.M_TANDEM_CHARGE)) - || (munition.getMunitionType().contains(Munitions.M_ARTEMIS_V_CAPABLE)))) { + || (munition.getMunitionType().contains(Munitions.M_ARTEMIS_V_CAPABLE)))) { cost *= 5; bv *= 2; } @@ -13505,7 +13507,7 @@ public AmmoType createMunitionType(AmmoType base) { || (munition.getAmmoType() == AmmoType.T_MML) || (munition.getAmmoType() == AmmoType.T_SRM) || (munition.getAmmoType() == AmmoType.T_SRM_IMP) || (munition.getAmmoType() == AmmoType.T_NLRM)) && ((munition.getMunitionType().contains(Munitions.M_HEAT_SEEKING)) - || (munition.getMunitionType().contains(Munitions.M_FOLLOW_THE_LEADER)))) { + || (munition.getMunitionType().contains(Munitions.M_FOLLOW_THE_LEADER)))) { cost *= 2; bv *= 1.5; } diff --git a/megamek/src/megamek/common/ConvFighter.java b/megamek/src/megamek/common/ConvFighter.java index 65d26666126..63c37a96db3 100644 --- a/megamek/src/megamek/common/ConvFighter.java +++ b/megamek/src/megamek/common/ConvFighter.java @@ -21,7 +21,7 @@ * @author Jay Lawson * @since Jun 12, 2008 */ -public class ConvFighter extends Aero { +public class ConvFighter extends AeroSpaceFighter { private static final long serialVersionUID = 6297668284292929409L; @Override @@ -53,7 +53,7 @@ public boolean doomedInSpace() { public int getHeatCapacity() { return DOES_NOT_TRACK_HEAT; } - + @Override public boolean tracksHeat() { return false; @@ -84,12 +84,12 @@ public int getFuelUsed(int thrust) { .setAdvancement(DATE_NONE, 2470, 2490).setProductionFactions(F_TH) .setTechRating(RATING_D).setAvailability(RATING_C, RATING_D, RATING_C, RATING_B) .setStaticTechLevel(SimpleTechLevel.STANDARD); - + @Override public TechAdvancement getConstructionTechAdvancement() { return TA_CONV_FIGHTER; } - + @Override public double getBVTypeModifier() { return 1.1; diff --git a/megamek/src/megamek/common/Dropship.java b/megamek/src/megamek/common/Dropship.java index 073dcc54c8e..62412655b36 100644 --- a/megamek/src/megamek/common/Dropship.java +++ b/megamek/src/megamek/common/Dropship.java @@ -752,12 +752,4 @@ public boolean canLandVertically() { public boolean canTakeOffVertically() { return (isSpheroid() || game.getPlanetaryConditions().isVacuum()) && (getCurrentThrust() > 2); } - - @Override - public void autoSetMaxBombPoints() { - // Only count free whole tons per bay - maxBombPoints = getTransportBays().stream().mapToInt( - tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused() / 5) : 0 - ).sum(); - } } diff --git a/megamek/src/megamek/common/FighterSquadron.java b/megamek/src/megamek/common/FighterSquadron.java index 7a95e66ce43..27aef2eaad4 100644 --- a/megamek/src/megamek/common/FighterSquadron.java +++ b/megamek/src/megamek/common/FighterSquadron.java @@ -33,7 +33,7 @@ * Fighter squadrons are basically "containers" for a bunch of fighters. * @author Jay Lawson */ -public class FighterSquadron extends Aero { +public class FighterSquadron extends AeroSpaceFighter { private static final long serialVersionUID = 3491212296982370726L; public static final int MAX_SIZE = 6; @@ -42,7 +42,7 @@ public class FighterSquadron extends Aero { public static final int ALTERNATE_MAX_SIZE = 10; private static final Predicate ACTIVE_CHECK = ent -> !((ent == null) || ent.isDestroyed() || ent.isDoomed()); - + private final List fighters = new ArrayList<>(); // fighter squadrons need to keep track of heat capacity apart from their fighters @@ -145,7 +145,7 @@ public int getFuel() { .min() .orElse(0); } - + @Override public int getCurrentFuel() { return getActiveSubEntities().stream() @@ -253,7 +253,7 @@ public int doBattleValueCalculation(boolean ignoreC3, boolean ignoreSkill, Calcu public int getHeatSinks() { return getActiveSubEntities().stream().mapToInt(ent -> ((IAero) ent).getHeatSinks()).sum(); } - + @Override public int getHeatCapacity(final boolean includeRadicalHeatSink) { return includeRadicalHeatSink ? heatCapacity : heatCapacityNoRHS; @@ -274,13 +274,13 @@ public double getWeight() { public HitData rollHitLocation(int table, int side, int aimedLocation, AimingMode aimingMode, int cover) { List activeFighters = getActiveSubEntities(); - + // If this squadron is doomed or is of size 1 then just return the first one if (isDoomed() || (activeFighters.size() <= 1)) { return new HitData(0); } - // Pick a random number between 0 and the number of fighters in the squadron. + // Pick a random number between 0 and the number of fighters in the squadron. int hit = Compute.randomInt(activeFighters.size()); return new HitData(hit); } @@ -299,7 +299,7 @@ public void newRound(int roundNumber) { updateSkills(); resetHeatCapacity(); } - + /** * Update sensors. Use the active sensor of the first fighter in the squadron that hasn't taken 3 sensor hits * BAPs don't count as active sensors in space, but they do make detection rolls easier @@ -322,7 +322,7 @@ public void updateSensors() { } setNextSensor(getSensors().firstElement()); break; - } + } } } } @@ -492,7 +492,7 @@ public void applyBombs() { * This method looks at the bombs equipped on all the fighters in the * squadron and determines what possible bombing attacks the squadrons * can make. - * + * * TODO: Make this into a generic "clean up bomb loadout" method */ public void computeSquadronBombLoadout() { @@ -684,14 +684,14 @@ public int getCargoMpReduction(Entity carrier) { @Override public long getEntityType() { - return Entity.ETYPE_AERO | Entity.ETYPE_FIGHTER_SQUADRON; + return super.getEntityType() | Entity.ETYPE_FIGHTER_SQUADRON; } @Override public Engine getEngine() { return null; } - + @Override public boolean hasEngine() { return false; @@ -700,11 +700,11 @@ public boolean hasEngine() { @Override public EntityMovementMode getMovementMode() { List entities = getSubEntities(); - + if (entities.size() < 1) { return EntityMovementMode.NONE; } - + EntityMovementMode moveMode = entities.get(0).getMovementMode(); for (Entity fighter : entities) { if (moveMode != fighter.getMovementMode()) { @@ -714,14 +714,14 @@ public EntityMovementMode getMovementMode() { } return moveMode; } - + @Override public List getSubEntities() { return fighters.stream().map(fid -> game.getEntity(fid)) .filter(Objects::nonNull) .collect(Collectors.toList()); } - + @Override public List getActiveSubEntities() { return fighters.stream().map(fid -> game.getEntity(fid)) diff --git a/megamek/src/megamek/common/FixedWingSupport.java b/megamek/src/megamek/common/FixedWingSupport.java index a4e878c9a6a..d2b63bfb36f 100644 --- a/megamek/src/megamek/common/FixedWingSupport.java +++ b/megamek/src/megamek/common/FixedWingSupport.java @@ -13,6 +13,7 @@ import megamek.client.ui.swing.calculationReport.CalculationReport; import megamek.common.cost.FixedWingSupportCostCalculator; +import megamek.common.options.OptionsConstants; /** * @author Jason Tighe @@ -234,13 +235,23 @@ protected int calculateWalk() { @Override public void autoSetMaxBombPoints() { - // fixed wing support craft need external stores hardpoints to be able to carry bombs + // fixed wing support craft need external stores hardpoints or the Internal Bomb Bay quirk + // to be able to carry bombs. int bombpoints = 0; for (Mounted misc : getMisc()) { if (misc.getType().hasFlag(MiscType.F_EXTERNAL_STORES_HARDPOINT)) { bombpoints++; } } + + // fixed-wing support craft may also use internal transport bays as bomb bays with Internal Bomb Bay quirk. + // Only count free whole tons per bay + if (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) { + bombpoints += getTransportBays().stream().mapToInt( + tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused()) : 0 + ).sum(); + } + maxBombPoints = bombpoints; } diff --git a/megamek/src/megamek/common/IBomber.java b/megamek/src/megamek/common/IBomber.java index ea42e39496a..aa7720e7785 100644 --- a/megamek/src/megamek/common/IBomber.java +++ b/megamek/src/megamek/common/IBomber.java @@ -26,11 +26,11 @@ /** * Common interface for all entities capable of carrying bombs and making bomb attacks, includig Aero, * LandAirMech, and VTOL. - * + * * @author Neoancient */ public interface IBomber { - + String SPACE_BOMB_ATTACK = "SpaceBombAttack"; String DIVE_BOMB_ATTACK = "DiveBombAttack"; String ALT_BOMB_ATTACK = "AltBombAttack"; @@ -39,29 +39,29 @@ public interface IBomber { * @return The total number of bomb points that the bomber can carry. */ int getMaxBombPoints(); - + /** * Fighters and VTOLs can carry any size bomb up to the maximum number of points, but LAMs are limited * to the number of bays in a single location. - * + * * @return The largest single bomb that can be carried */ default int getMaxBombSize() { return getMaxBombPoints(); } - + /** * @return The number of each bomb type that was selected prior to deployment */ int[] getBombChoices(); - + /** * Sets the bomb type selections prior to deployment. - * + * * @param bc An array with the count of each bomb type as the value of the bomb type's index */ void setBombChoices(int[] bc); - + /** * Sets the count of each bomb to zero */ @@ -71,7 +71,7 @@ default int getMaxBombSize() { * @return The calculates movement factoring in the load of bombs currently on unit, t is current movement */ int reduceMPByBombLoad(int t); - + /** * @param cost The cost of the bomb to be mounted * @return A location with sufficient space to mount the bomb, or Entity.LOC_NONE if the unit does not have the space. @@ -96,18 +96,37 @@ default boolean isVTOLBombing() { List getBombs(); /** - * @return The number of points taken up by all mounted bombs or other external stores. + * + * @return the number of total bomb points for this unit */ default int getBombPoints() { + return getBombPoints(false); + } + + /** + * + * @return the number of externally-mounted ordnance points (useful for MP calculations) + */ + default int getExternalBombPoints() { + return getBombPoints(true); + } + + /** + * @return The number of points taken up by all mounted bombs, or just external + */ + default int getBombPoints(boolean externalOnly) { int points = 0; for (Mounted bomb : getBombs()) { if (bomb.getUsableShotsLeft() > 0) { - points += BombType.getBombCost(((BombType) bomb.getType()).getBombType()); + // Add points if A) not external only, and any kind of bomb, or B) external only, and not internal bomb + points += !(externalOnly && bomb.isInternalBomb()) ? + BombType.getBombCost(((BombType) bomb.getType()).getBombType()) : 0; } } return points; } + /** * Iterate through the bomb choices that were configured prior to deployment and add the corresponding * equipment. @@ -148,7 +167,7 @@ default void applyBombs() { ammo.setShotsLeft(1); m.setLinked(ammo); ((Entity) this).addEquipment(ammo, loc, false); - + } } catch (LocationFullException ignored) { diff --git a/megamek/src/megamek/common/MechFileParser.java b/megamek/src/megamek/common/MechFileParser.java index 48631799441..5cae8f93f0a 100644 --- a/megamek/src/megamek/common/MechFileParser.java +++ b/megamek/src/megamek/common/MechFileParser.java @@ -123,7 +123,9 @@ public void parse(InputStream is, String fileName) throws Exception { } else if (sType.equals("SupportVTOL")) { loader = new BLKSupportVTOLFile(bb); } else if (sType.equals("Aero")) { - loader = new BLKAeroFile(bb); + loader = new BLKAeroSpaceFighterFile(bb); + } else if (sType.equals("AeroSpaceFighter")) { + loader = new BLKAeroSpaceFighterFile(bb); } else if (sType.equals("FixedWingSupport")) { loader = new BLKFixedWingSupportFile(bb); } else if (sType.equals("ConvFighter")) { @@ -191,7 +193,7 @@ public static void postLoadInit(Entity ent) throws EntityLoadingException { // Conventional Fighters get a combined sensor suite ent.getSensors().add(new Sensor(Sensor.TYPE_AERO_SENSOR)); ent.setNextSensor(ent.getSensors().firstElement()); - } else if (ent.hasETypeFlag(Entity.ETYPE_DROPSHIP) + } else if (ent.hasETypeFlag(Entity.ETYPE_DROPSHIP) || ent.hasETypeFlag(Entity.ETYPE_SPACE_STATION) || ent.hasETypeFlag(Entity.ETYPE_JUMPSHIP) || ent.hasETypeFlag(Entity.ETYPE_WARSHIP)) { @@ -414,7 +416,7 @@ else if (m.getType().hasFlag(MiscType.F_APOLLO) ent.setNextSensor(ent.getSensors().lastElement()); } else if (m.getType().getInternalName().equals(Sensor.BAPP)) { ent.getSensors().add(new Sensor(Sensor.TYPE_BAPP)); - ent.setNextSensor(ent.getSensors().lastElement()); + ent.setNextSensor(ent.getSensors().lastElement()); } else if (m.getType().getInternalName().equals(Sensor.BLOODHOUND)) { ent.getSensors().add(new Sensor(Sensor.TYPE_BLOODHOUND)); ent.setNextSensor(ent.getSensors().lastElement()); @@ -568,7 +570,7 @@ else if (m.getType().hasFlag(MiscType.F_APOLLO) || (mWeapon.getType() instanceof ISSnubNosePPC) || (mWeapon.getType() instanceof CLEnhancedPPC) || (mWeapon.getType() instanceof CLImprovedPPC) - || (mWeapon.getType() instanceof ISKinsSlaughterPPC) + || (mWeapon.getType() instanceof ISKinsSlaughterPPC) || (mWeapon.getType() instanceof CLERPPC && ent.getYear() >= 3101)) { m.setCrossLinked(mWeapon); @@ -751,7 +753,7 @@ else if ((ent instanceof Infantry) && ((Infantry) ent).canMakeAntiMekAttacks()) throw new EntityLoadingException(ex.getMessage()); } } - + // Check if it's canon; if it is, mark it as such. ent.setCanon(false);// Guilty until proven innocent try { @@ -782,7 +784,7 @@ else if ((ent instanceof Infantry) && ((Infantry) ent).canMakeAntiMekAttacks()) int index = Collections.binarySearch(canonUnitNames, ent.getShortNameRaw()); if (index >= 0) { ent.setCanon(true); - } + } ent.initMilitary(); linkDumpers(ent); } diff --git a/megamek/src/megamek/common/Mounted.java b/megamek/src/megamek/common/Mounted.java index fe9076d7c6b..a70f1c95d57 100644 --- a/megamek/src/megamek/common/Mounted.java +++ b/megamek/src/megamek/common/Mounted.java @@ -1631,6 +1631,15 @@ public boolean isGroundBomb() { getType().hasFlag(AmmoType.F_GROUND_BOMB); } + /** + * Convenience method to determine if a bomb munition is mounted EXternally (reduces MP) or INternally (no + * MP reduction). + * @return True if + */ + public boolean isInternalBomb() { + return getType().hasFlag(AmmoType.F_INTERNAL_BOMB); + } + // is ammo in the same bay as the weapon public boolean ammoInBay(int mAmmoId) { for (int nextAmmoId : bayAmmo) { diff --git a/megamek/src/megamek/common/SmallCraft.java b/megamek/src/megamek/common/SmallCraft.java index a64c21ce3ff..26d68eac0d5 100644 --- a/megamek/src/megamek/common/SmallCraft.java +++ b/megamek/src/megamek/common/SmallCraft.java @@ -897,6 +897,17 @@ public boolean isFighter() { return false; } + /** + * Fighters may carry external ordnance; + * Other Aerospace units with cargo bays and the Internal Bomb Bay quirk may carry bombs internally. + * @return boolean + */ + @Override + public boolean isBomber() { + return (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)); + } + + @Override public boolean isAerospaceFighter() { return false; @@ -924,7 +935,7 @@ public int getLandingLength() { public void autoSetMaxBombPoints() { // Only count free whole tons per bay maxBombPoints = getTransportBays().stream().mapToInt( - tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused() / 5) : 0 + tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused()) : 0 ).sum(); } } \ No newline at end of file diff --git a/megamek/src/megamek/common/loaders/BLKAeroFile.java b/megamek/src/megamek/common/loaders/BLKAeroSpaceFighterFile.java similarity index 94% rename from megamek/src/megamek/common/loaders/BLKAeroFile.java rename to megamek/src/megamek/common/loaders/BLKAeroSpaceFighterFile.java index 7593c43671d..00a88066b5e 100644 --- a/megamek/src/megamek/common/loaders/BLKAeroFile.java +++ b/megamek/src/megamek/common/loaders/BLKAeroSpaceFighterFile.java @@ -26,7 +26,7 @@ * * @author taharqa */ -public class BLKAeroFile extends BLKFile implements IMechLoader { +public class BLKAeroSpaceFighterFile extends BLKFile implements IMechLoader { // armor locatioms public static final int NOSE = 0; @@ -34,14 +34,14 @@ public class BLKAeroFile extends BLKFile implements IMechLoader { public static final int LW = 2; public static final int AFT = 3; - public BLKAeroFile(BuildingBlock bb) { + public BLKAeroSpaceFighterFile(BuildingBlock bb) { dataFile = bb; } @Override public Entity getEntity() throws EntityLoadingException { - Aero a = new Aero(); + AeroSpaceFighter a = new AeroSpaceFighter(); setBasicEntityData(a); @@ -50,9 +50,6 @@ public Entity getEntity() throws EntityLoadingException { } a.setWeight(dataFile.getDataAsDouble("tonnage")[0]); - // how many bombs can it carry - a.autoSetMaxBombPoints(); - // get a movement mode - lets try Aerodyne EntityMovementMode nMotion = EntityMovementMode.AERODYNE; a.setMovementMode(nMotion); @@ -148,10 +145,10 @@ public Entity getEntity() throws EntityLoadingException { throw new EntityLoadingException("Incorrect armor array length"); } - a.initializeArmor(armor[BLKAeroFile.NOSE], Aero.LOC_NOSE); - a.initializeArmor(armor[BLKAeroFile.RW], Aero.LOC_RWING); - a.initializeArmor(armor[BLKAeroFile.LW], Aero.LOC_LWING); - a.initializeArmor(armor[BLKAeroFile.AFT], Aero.LOC_AFT); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.NOSE], Aero.LOC_NOSE); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.RW], Aero.LOC_RWING); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.LW], Aero.LOC_LWING); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.AFT], Aero.LOC_AFT); a.initializeArmor(0, Aero.LOC_WINGS); a.initializeArmor(IArmorState.ARMOR_NA, Aero.LOC_FUSELAGE); @@ -176,6 +173,10 @@ public Entity getEntity() throws EntityLoadingException { } addTransports(a); + + // how many bombs can it carry; dependent on transport bays as well as total mass. + a.autoSetMaxBombPoints(); + a.setArmorTonnage(a.getArmorWeight()); loadQuirks(a); return a; @@ -237,10 +238,10 @@ protected void loadEquipment(Entity t, String sName, int nLoc) throws EntityLoad facing = 2; equipName = equipName.substring(0, equipName.length() - 4) .trim(); - } + } EquipmentType etype = EquipmentType.get(equipName); - + if ((etype instanceof MiscType) && etype.hasFlag(MiscType.F_CASE)) { if (etype.isClan() || addedCase) { continue; @@ -267,7 +268,7 @@ protected void loadEquipment(Entity t, String sName, int nLoc) throws EntityLoad Mounted mount = t.addEquipment(etype, useLoc, rearMount); mount.setOmniPodMounted(omniMounted); // Need to set facing for VGLs - if ((etype instanceof WeaponType) + if ((etype instanceof WeaponType) && etype.hasFlag(WeaponType.F_VGL)) { if (facing == -1) { mount.setFacing(defaultAeroVGLFacing(useLoc, rearMount)); diff --git a/megamek/src/megamek/common/loaders/BLKConvFighterFile.java b/megamek/src/megamek/common/loaders/BLKConvFighterFile.java index 33abea1effc..46c9bfe72c5 100644 --- a/megamek/src/megamek/common/loaders/BLKConvFighterFile.java +++ b/megamek/src/megamek/common/loaders/BLKConvFighterFile.java @@ -116,10 +116,10 @@ public Entity getEntity() throws EntityLoadingException { throw new EntityLoadingException("Incorrect armor array length"); } - a.initializeArmor(armor[BLKAeroFile.NOSE], Aero.LOC_NOSE); - a.initializeArmor(armor[BLKAeroFile.RW], Aero.LOC_RWING); - a.initializeArmor(armor[BLKAeroFile.LW], Aero.LOC_LWING); - a.initializeArmor(armor[BLKAeroFile.AFT], Aero.LOC_AFT); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.NOSE], Aero.LOC_NOSE); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.RW], Aero.LOC_RWING); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.LW], Aero.LOC_LWING); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.AFT], Aero.LOC_AFT); a.autoSetInternal(); a.recalculateTechAdvancement(); @@ -197,7 +197,7 @@ protected void loadEquipment(Entity t, String sName, int nLoc) throws EntityLoad facing = 2; equipName = equipName.substring(0, equipName.length() - 4) .trim(); - } + } EquipmentType etype = EquipmentType.get(equipName); @@ -214,7 +214,7 @@ protected void loadEquipment(Entity t, String sName, int nLoc) throws EntityLoad int useLoc = TestEntity.eqRequiresLocation(t, etype) ? nLoc : Aero.LOC_FUSELAGE; Mounted mount = t.addEquipment(etype, useLoc, rearMount); // Need to set facing for VGLs - if ((etype instanceof WeaponType) + if ((etype instanceof WeaponType) && etype.hasFlag(WeaponType.F_VGL)) { // If no facing specified, assume front if (facing == -1) { diff --git a/megamek/src/megamek/common/loaders/BLKDropshipFile.java b/megamek/src/megamek/common/loaders/BLKDropshipFile.java index 5e6caa93d15..6ae85d7d205 100644 --- a/megamek/src/megamek/common/loaders/BLKDropshipFile.java +++ b/megamek/src/megamek/common/loaders/BLKDropshipFile.java @@ -13,7 +13,6 @@ */ package megamek.common.loaders; -import com.sun.mail.util.DecodingException; import megamek.common.*; import megamek.common.util.BuildingBlock; @@ -167,10 +166,10 @@ public Entity getEntity() throws EntityLoadingException { throw new EntityLoadingException("Incorrect armor array length"); } - a.initializeArmor(armor[BLKAeroFile.NOSE], Dropship.LOC_NOSE); - a.initializeArmor(armor[BLKAeroFile.RW], Dropship.LOC_RWING); - a.initializeArmor(armor[BLKAeroFile.LW], Dropship.LOC_LWING); - a.initializeArmor(armor[BLKAeroFile.AFT], Dropship.LOC_AFT); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.NOSE], Dropship.LOC_NOSE); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.RW], Dropship.LOC_RWING); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.LW], Dropship.LOC_LWING); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.AFT], Dropship.LOC_AFT); a.initializeArmor(IArmorState.ARMOR_NA, Dropship.LOC_HULL); a.autoSetInternal(); diff --git a/megamek/src/megamek/common/loaders/BLKFile.java b/megamek/src/megamek/common/loaders/BLKFile.java index 26ad00ea777..5b29a1b7322 100644 --- a/megamek/src/megamek/common/loaders/BLKFile.java +++ b/megamek/src/megamek/common/loaders/BLKFile.java @@ -587,8 +587,8 @@ public static BuildingBlock getBlock(Entity t) { blk.writeBlockData("UnitType", "Tank"); } else if (t instanceof Infantry) { blk.writeBlockData("UnitType", "Infantry"); - } else if (t instanceof Aero) { - blk.writeBlockData("UnitType", "Aero"); + } else if (t instanceof AeroSpaceFighter) { + blk.writeBlockData("UnitType", "AeroSpaceFighter"); } blk.writeBlockData("Name", t.getChassis()); diff --git a/megamek/src/megamek/common/loaders/BLKFixedWingSupportFile.java b/megamek/src/megamek/common/loaders/BLKFixedWingSupportFile.java index 64313fb5a7e..78eff20cc1c 100644 --- a/megamek/src/megamek/common/loaders/BLKFixedWingSupportFile.java +++ b/megamek/src/megamek/common/loaders/BLKFixedWingSupportFile.java @@ -118,11 +118,11 @@ public Entity getEntity() throws EntityLoadingException { throw new EntityLoadingException("Incorrect armor array length"); } - a.initializeArmor(armor[BLKAeroFile.NOSE], Aero.LOC_NOSE); - a.initializeArmor(armor[BLKAeroFile.RW], Aero.LOC_RWING); - a.initializeArmor(armor[BLKAeroFile.LW], Aero.LOC_LWING); - a.initializeArmor(armor[BLKAeroFile.AFT], Aero.LOC_AFT); - + a.initializeArmor(armor[BLKAeroSpaceFighterFile.NOSE], Aero.LOC_NOSE); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.RW], Aero.LOC_RWING); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.LW], Aero.LOC_LWING); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.AFT], Aero.LOC_AFT); + // Set the structural tech rating if (!dataFile.exists("structural_tech_rating")) { throw new EntityLoadingException("Could not find " + @@ -133,12 +133,12 @@ public Entity getEntity() throws EntityLoadingException { // Set armor tech rating, if it exists (defaults to structural tr) if (dataFile.exists("armor_tech_rating")) { a.setArmorTechRating(dataFile - .getDataAsInt("armor_tech_rating")[0]); + .getDataAsInt("armor_tech_rating")[0]); } - // Set engine tech rating, if it exists (defaults to structural tr) + // Set engine tech rating, if it exists (defaults to structural tr) if (dataFile.exists("engine_tech_rating")) { a.setEngineTechRating(dataFile - .getDataAsInt("engine_tech_rating")[0]); + .getDataAsInt("engine_tech_rating")[0]); } a.autoSetInternal(); @@ -225,7 +225,7 @@ protected void loadEquipment(Entity t, String sName, int nLoc) throws EntityLoad facing = 2; equipName = equipName.substring(0, equipName.length() - 4) .trim(); - } + } EquipmentType etype = EquipmentType.get(equipName); @@ -242,7 +242,7 @@ protected void loadEquipment(Entity t, String sName, int nLoc) throws EntityLoad Mounted mount = t.addEquipment(etype, nLoc, rearMount); mount.setOmniPodMounted(omniMounted); // Need to set facing for VGLs - if ((etype instanceof WeaponType) + if ((etype instanceof WeaponType) && etype.hasFlag(WeaponType.F_VGL)) { if (facing == -1) { mount.setFacing(defaultAeroVGLFacing(nLoc, rearMount)); diff --git a/megamek/src/megamek/common/loaders/BLKSmallCraftFile.java b/megamek/src/megamek/common/loaders/BLKSmallCraftFile.java index f81b77e7d4b..788397fc95d 100644 --- a/megamek/src/megamek/common/loaders/BLKSmallCraftFile.java +++ b/megamek/src/megamek/common/loaders/BLKSmallCraftFile.java @@ -149,10 +149,10 @@ public Entity getEntity() throws EntityLoadingException { throw new EntityLoadingException("Incorrect armor array length"); } - a.initializeArmor(armor[BLKAeroFile.NOSE], SmallCraft.LOC_NOSE); - a.initializeArmor(armor[BLKAeroFile.RW], SmallCraft.LOC_RWING); - a.initializeArmor(armor[BLKAeroFile.LW], SmallCraft.LOC_LWING); - a.initializeArmor(armor[BLKAeroFile.AFT], SmallCraft.LOC_AFT); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.NOSE], SmallCraft.LOC_NOSE); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.RW], SmallCraft.LOC_RWING); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.LW], SmallCraft.LOC_LWING); + a.initializeArmor(armor[BLKAeroSpaceFighterFile.AFT], SmallCraft.LOC_AFT); a.initializeArmor(IArmorState.ARMOR_NA, SmallCraft.LOC_HULL); a.autoSetInternal(); From 2a8b4d633ee25ee5c52eb036b41004f6d8b1ec16 Mon Sep 17 00:00:00 2001 From: sleet01 Date: Wed, 6 Dec 2023 23:33:48 -0800 Subject: [PATCH 04/22] A lot of rework but now ASF bombs are busted. --- .../mechfiles/fighters/.Manbat MBT-23.blk.swp | Bin 0 -> 12288 bytes .../data/mechfiles/fighters/Manbat MBT-23.blk | 105 +++++++++++++++ .../mechfiles/fighters/Manbat MBT-23A.blk | 109 ++++++++++++++++ .../fighters/TRO3055U/.Visigoth D.blk.swp | Bin 0 -> 4096 bytes .../i18n/megamek/common/messages.properties | 3 +- .../ratgenerator/AbstractUnitRecord.java | 2 + .../client/ratgenerator/ForceDescriptor.java | 14 +- .../client/ui/swing/AdvancedSearchDialog.java | 3 +- .../client/ui/swing/BombChoicePanel.java | 13 +- .../megamek/client/ui/swing/ClientGUI.java | 2 +- .../ui/swing/ForceGenerationOptionsPanel.java | 102 ++++++++------- megamek/src/megamek/common/Aero.java | 45 +++++-- .../src/megamek/common/AeroSpaceFighter.java | 33 ++--- .../src/megamek/common/EntityWeightClass.java | 4 +- .../src/megamek/common/FighterSquadron.java | 20 ++- .../src/megamek/common/FixedWingSupport.java | 12 +- megamek/src/megamek/common/IBomber.java | 122 +++++++++++++----- megamek/src/megamek/common/LandAirMech.java | 37 ++++-- megamek/src/megamek/common/MechSummary.java | 52 ++++---- megamek/src/megamek/common/SmallCraft.java | 4 +- megamek/src/megamek/common/UnitType.java | 13 +- megamek/src/megamek/common/VTOL.java | 59 ++++++--- .../loaders/BLKAeroSpaceFighterFile.java | 10 +- .../src/megamek/common/loaders/BLKFile.java | 7 + .../common/verifier/EntityVerifier.java | 27 ++-- .../src/megamek/common/verifier/TestAero.java | 2 +- megamek/src/megamek/server/GameManager.java | 4 +- .../megamek/utilities/RATGeneratorEditor.java | 100 +++++++------- 28 files changed, 634 insertions(+), 270 deletions(-) create mode 100644 megamek/data/mechfiles/fighters/.Manbat MBT-23.blk.swp create mode 100644 megamek/data/mechfiles/fighters/Manbat MBT-23.blk create mode 100644 megamek/data/mechfiles/fighters/Manbat MBT-23A.blk create mode 100644 megamek/data/mechfiles/fighters/TRO3055U/.Visigoth D.blk.swp diff --git a/megamek/data/mechfiles/fighters/.Manbat MBT-23.blk.swp b/megamek/data/mechfiles/fighters/.Manbat MBT-23.blk.swp new file mode 100644 index 0000000000000000000000000000000000000000..2346ac8231751b95e5d62802738fdf6d558e8587 GIT binary patch literal 12288 zcmeI2J!~6Q9Kc^%u@nlZ-6)lg2Bb1zI}SlbYNhiq6|m~iJ=9$A)yX10uu|r^Es)VgtD-Jo~57e{vY@J-tV4= zD6d&qTk)LvTv713SBT3W_SNy%Z-_ttm=b}EjSkbo?LW1~lR?y0w{N0)J{vIKqB-xW zuJqJKKHwURUE9(~lC54x`zpzIbhl?zoa8+jHl=aAa^sQWY_92V{NvZz25Z0?m@sfy z%ob+~X=?h!1J2PS&rVcrU=3IU)_^r&4Oj!#fHhzZSOfoW1ExPEwpikU(K7YuI<@OM z3hl)jum-FFYrqpt@Eoi{4VIw{3vdb^0tfDdLvRp&x?6}J;RpB*zJ@FC z1$+u0!+UTM-i9~fHQ0g#`p|+VtU&{o;WRt}kHah!;Xyb7N8t$E0SDms!$SN9H{oZv z4%gsY_y(@Rm+(1!2AAOyd;}lB`|u8Ifq@rc1)hQ;9E1DdApDB!U*HB@fluHPT!aho z1{hd}RX7Fnkb{$O9Mbo74D81mum-FFYrq#Ngj5V&h=HS(=97~Hud(pqgb6ZuEZ$Gaj&Sz)lQmuOne8J{eM|Mxr<8V82vUBRf9fcHX9CIf>JjTvj9!8|sINZ(( zsgyk2?1a$nek*|x|yyh R-S^G3(Mb>4JLv&?@fR79q&WZp literal 0 HcmV?d00001 diff --git a/megamek/data/mechfiles/fighters/Manbat MBT-23.blk b/megamek/data/mechfiles/fighters/Manbat MBT-23.blk new file mode 100644 index 00000000000..8a7193eddc9 --- /dev/null +++ b/megamek/data/mechfiles/fighters/Manbat MBT-23.blk @@ -0,0 +1,105 @@ +#building block data file + +1 + + +##Write the version number just in case... + +MAM0 + + + +Manbat + + + +MBT-23 + + + +3151 + + + +3151 + + + +Mixed (Clan Chassis) Experimental + + + +Striker + + + +None + + + +6 + + + +10 + + + +1 + + + +800 + + + +2 + + + +16 + + + +6 + + + +1 + + + +126 +105 +105 +84 + + + +BeagleActiveProbe +CLTAG:OMNI +CLLaserAntiMissileSystem + + + + + + + + + +CLLaserAntiMissileSystem + + + + + + +Cargo:OMNI:SIZE:25.0 +CLECMSuite:OMNI + + + +100.0 + + diff --git a/megamek/data/mechfiles/fighters/Manbat MBT-23A.blk b/megamek/data/mechfiles/fighters/Manbat MBT-23A.blk new file mode 100644 index 00000000000..adf3896a227 --- /dev/null +++ b/megamek/data/mechfiles/fighters/Manbat MBT-23A.blk @@ -0,0 +1,109 @@ +#building block data file + +1 + + +##Write the version number just in case... + +MAM0 + + + +Aero + + + +Manbat + + + +MBT-23A + + + +3145 + + + +3145 + + + +Mixed (Clan Chassis) Experimental + + + +Striker + + + +None + + + +6 + + + +10 + + + +1 + + + +800 + + + +2 + + + +16 + + + +6 + + + +1 + + + +126 +105 +105 +84 + + + +CLECMSuite:OMNI +CLTAG +CLLaserAntiMissileSystem +BeagleActiveProbe:OMNI + + + + + + + + + +CLLaserAntiMissileSystem + + + + + + +Cargo:OMNI:SIZE:25.0 + + + +100.0 + + diff --git a/megamek/data/mechfiles/fighters/TRO3055U/.Visigoth D.blk.swp b/megamek/data/mechfiles/fighters/TRO3055U/.Visigoth D.blk.swp new file mode 100644 index 0000000000000000000000000000000000000000..4af84da811aeb1a2cda4cc6ff34f1bfd919af0d0 GIT binary patch literal 4096 zcmYc?2=nw+u+TGN00IF92Kx&ssj=L*7!tlQGUO%}m1O3DB=7^j5SQHilvMnRfU subs) { ModelRecord baseModel = null; /* Generate base model using weight class of entire formation */ if (ut != null) { - if (!(ut == UnitType.MEK || (ut == UnitType.AERO && subs.size() > 3))) { + if (!(ut == UnitType.MEK || (ut == UnitType.AEROSPACEFIGHTER && subs.size() > 3))) { baseModel = subs.get(0).generate(); } - if (ut == UnitType.AERO || ut == UnitType.CONV_FIGHTER) { + if (ut == UnitType.AEROSPACEFIGHTER || ut == UnitType.CONV_FIGHTER || ut == UnitType.AERO ) { target -= 3; } if (roles.contains(MissionRole.ARTILLERY)) { @@ -711,7 +711,7 @@ public void setUnit(ModelRecord unit) { if (null == unitType) { unitType = unit.getUnitType(); } - if (((unitType == UnitType.MEK) || (unitType == UnitType.AERO) + if (((unitType == UnitType.MEK) || (unitType == UnitType.AEROSPACEFIGHTER) || (unitType == UnitType.TANK)) && unit.isOmni()) { flags.add("omni"); @@ -826,7 +826,7 @@ public void loadEntities(Ruleset.ProgressListener l, double progress) { l.updateProgress(progress, "Loading entities"); } } - + /** Generates a force string for exporting these units to MUL / adding to the game. */ private String getForceString() { var ancestors = new ArrayList(); @@ -955,7 +955,7 @@ public void assignCommanders() { for (ForceDescriptor fd : subforces) { movementModes.addAll(fd.getMovementModes()); if ((fd.getUnitType() == null || - !((UnitType.MEK == fd.getUnitType()) || (UnitType.AERO == fd.getUnitType()) + !((UnitType.MEK == fd.getUnitType()) || (UnitType.AEROSPACEFIGHTER == fd.getUnitType()) || (UnitType.TANK == fd.getUnitType()))) || !fd.getFlags().contains("omni")) { isOmni = false; @@ -989,7 +989,7 @@ public void assignCommanders() { for (ForceDescriptor sub : subforces) { if (sub.useWeightClass()) { if (sub.getWeightClass() == null) { - LogManager.getLogger().error("Weight class == null for " + LogManager.getLogger().error("Weight class == null for " + sub.getUnitType() + " with " + sub.getSubforces().size() + " subforces."); } else { wt += sub.getWeightClass(); @@ -1193,7 +1193,7 @@ private boolean useWeightClass(Integer ut) { return ut != null && !(roles.contains(MissionRole.ARTILLERY) || roles.contains(MissionRole.MISSILE_ARTILLERY)) && (ut == UnitType.MEK || - ut == UnitType.AERO || + ut == UnitType.AEROSPACEFIGHTER || ut == UnitType.TANK || ut == UnitType.BATTLE_ARMOR); } diff --git a/megamek/src/megamek/client/ui/swing/AdvancedSearchDialog.java b/megamek/src/megamek/client/ui/swing/AdvancedSearchDialog.java index 969f484d794..94c69ebaec2 100644 --- a/megamek/src/megamek/client/ui/swing/AdvancedSearchDialog.java +++ b/megamek/src/megamek/client/ui/swing/AdvancedSearchDialog.java @@ -264,6 +264,7 @@ public void mouseClicked(MouseEvent e) { unitTypeModel.addElement(UnitType.getTypeDisplayableName(UnitType.BATTLE_ARMOR)); unitTypeModel.addElement(UnitType.getTypeDisplayableName(UnitType.INFANTRY)); unitTypeModel.addElement(UnitType.getTypeDisplayableName(UnitType.PROTOMEK)); + unitTypeModel.addElement(UnitType.getTypeDisplayableName(UnitType.AEROSPACEFIGHTER)); unitTypeModel.addElement(UnitType.getTypeDisplayableName(UnitType.AERO)); unitTypeModel.setSelectedItem(Messages.getString("MechSelectorDialog.All")); @@ -1158,7 +1159,7 @@ public void setValueAt(Object value, int row, int col) { /** * A table model for displaying weapon types */ - + /** * A table model for displaying equipment */ diff --git a/megamek/src/megamek/client/ui/swing/BombChoicePanel.java b/megamek/src/megamek/client/ui/swing/BombChoicePanel.java index c0c2f8c6f53..90a7442374f 100644 --- a/megamek/src/megamek/client/ui/swing/BombChoicePanel.java +++ b/megamek/src/megamek/client/ui/swing/BombChoicePanel.java @@ -43,7 +43,7 @@ public class BombChoicePanel extends JPanel implements Serializable, ItemListene private int maxPoints = 0; private int maxSize = 0; private int maxRows = (int) Math.ceil(BombType.B_NUM / 2.0); - + //Variable for MekHQ functionality private int[] typeMax = null; @@ -64,10 +64,11 @@ public BombChoicePanel(IBomber bomber, boolean at2Nukes, boolean allowAdvancedAm this.typeMax = typeMax; initPanel(); } - + @SuppressWarnings("unchecked") private void initPanel() { maxPoints = bomber.getMaxBombPoints(); + maxSize = bomber.getMaxBombSize(); int[] bombChoices = bomber.getBombChoices(); @@ -98,7 +99,7 @@ private void initPanel() { if ((bombChoices[type] * BombType.getBombCost(type)) > maxSize) { bombChoices[type] = maxSize / BombType.getBombCost(type); } - + if (typeMax != null) { if ((maxNumBombs > 0) && (maxNumBombs > typeMax[type])) { maxNumBombs = typeMax[type]; @@ -152,6 +153,12 @@ private void initPanel() { } } + private void initIntFrame(int intBombPoints) { + } + + private void initExtFrame(int extBombPoints) { + } + @Override @SuppressWarnings("unchecked") public void itemStateChanged(ItemEvent ie) { diff --git a/megamek/src/megamek/client/ui/swing/ClientGUI.java b/megamek/src/megamek/client/ui/swing/ClientGUI.java index 91c2ff1f4a3..51fbbabd247 100644 --- a/megamek/src/megamek/client/ui/swing/ClientGUI.java +++ b/megamek/src/megamek/client/ui/swing/ClientGUI.java @@ -1880,7 +1880,7 @@ public void loadListFile(Player player) { * Allow the player to select a MegaMek Unit List file to load. The * Entitys in the file will replace any that the player has * already selected. As such, this method should only be called in the chat - * lounge. The file can record damage sustained, non- standard munitions + * lounge. The file can record damage sustained, non-standard munitions * selected, and ammunition expended in a prior engagement. * * @param player diff --git a/megamek/src/megamek/client/ui/swing/ForceGenerationOptionsPanel.java b/megamek/src/megamek/client/ui/swing/ForceGenerationOptionsPanel.java index e0cfdf888e0..8210082d979 100644 --- a/megamek/src/megamek/client/ui/swing/ForceGenerationOptionsPanel.java +++ b/megamek/src/megamek/client/ui/swing/ForceGenerationOptionsPanel.java @@ -81,7 +81,7 @@ public enum Use { private static final int[] UNIT_TYPES = { UnitType.MEK, UnitType.TANK, UnitType.BATTLE_ARMOR, UnitType.INFANTRY, UnitType.PROTOMEK, - UnitType.VTOL, UnitType.NAVAL, UnitType.CONV_FIGHTER, UnitType.AERO, UnitType.SMALL_CRAFT, + UnitType.VTOL, UnitType.NAVAL, UnitType.CONV_FIGHTER, UnitType.AEROSPACEFIGHTER, UnitType.AERO, UnitType.SMALL_CRAFT, UnitType.DROPSHIP, UnitType.JUMPSHIP, UnitType.WARSHIP, UnitType.SPACE_STATION }; private static final int EARLIEST_YEAR = 2398; @@ -91,7 +91,7 @@ public enum Use { //region Constructors public ForceGenerationOptionsPanel(Use use) { setLayout(new GridBagLayout()); - + GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.gridy = 0; @@ -445,7 +445,7 @@ public void focusLost(FocusEvent e) { RATGenerator.getInstance().loadYear(ratGenYear); } } - + public void updateGeneratedUnits(List list) { panUnitTypeOptions.updateGeneratedUnits(list); } @@ -464,7 +464,7 @@ public Component getListCellRendererComponent(JList list, Object value, int i return this; } }; - + private Comparator factionSorter = new Comparator<>() { @Override public int compare(FactionRecord o1, FactionRecord o2) { @@ -479,31 +479,31 @@ abstract static class UnitTypeOptions extends JPanel { private static final long serialVersionUID = -7141802206126462796L; abstract public void optionsChanged(); - + public Integer getIntegerVal(String key) { return null; } - + public Boolean getBooleanVal(String key) { return null; } - + public String getStringVal(String key) { return null; } - + public List getListVal(String key) { return new ArrayList<>(); } - + public abstract void updateGeneratedUnits(List list); } - + public class RATGenUnitTypeOptions extends UnitTypeOptions { private static final long serialVersionUID = 6536972747395725718L; private Map cardMap = new HashMap<>(); - + public RATGenUnitTypeOptions() { setLayout(new CardLayout()); for (int i = 0; i < cbUnitType.getItemCount(); i++) { @@ -513,17 +513,17 @@ public RATGenUnitTypeOptions() { add(card, cbUnitType.getItemAt(i)); } } - + @Override public void optionsChanged() { ((CardLayout) getLayout()).show(this, (String) cbUnitType.getSelectedItem()); } - + private RATGenUnitTypeCard currentCard() { String selectedCard = (String) cbUnitType.getSelectedItem(); return cardMap.get(selectedCard); } - + @Override public Integer getIntegerVal(String key) { switch (key) { @@ -535,7 +535,7 @@ public Integer getIntegerVal(String key) { return null; } } - + @Override public List getListVal(String key) { switch (key) { @@ -568,7 +568,7 @@ private static class RATGenUnitTypeCard extends JPanel { private List roleChecks = new ArrayList<>(); private ButtonGroup networkButtons = new ButtonGroup(); private List subtypeChecks = new ArrayList<>(); - + public RATGenUnitTypeCard(int unitType) { setLayout(new BorderLayout()); @@ -576,11 +576,11 @@ public RATGenUnitTypeCard(int unitType) { panWeightClass.setBorder(BorderFactory.createTitledBorder(Messages .getString("RandomArmyDialog.WeightClass"))); add(panWeightClass, BorderLayout.WEST); - + JPanel panRoles = new JPanel(new GridBagLayout()); panRoles.setBorder(BorderFactory.createTitledBorder(Messages .getString("RandomArmyDialog.MissionRole"))); - + JPanel panStrictness = new JPanel(); panStrictness.add(new JLabel(Messages.getString("RandomArmyDialog.Strictness"))); cbRoleStrictness.setToolTipText(Messages.getString("RandomArmyDialog.Strictness.tooltip")); @@ -589,7 +589,7 @@ public RATGenUnitTypeCard(int unitType) { cbRoleStrictness.addItem(Messages.getString("RandomArmyDialog.High")); cbRoleStrictness.setSelectedIndex(1); panStrictness.add(cbRoleStrictness); - + GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.gridy = 0; @@ -599,17 +599,17 @@ public RATGenUnitTypeCard(int unitType) { c.weightx = 0.0; c.weighty = 0.0; panRoles.add(panStrictness, c); - + add(panRoles, BorderLayout.CENTER); JPanel panNetwork = new JPanel(new GridBagLayout()); panNetwork.setBorder(BorderFactory.createTitledBorder(Messages .getString("RandomArmyDialog.Network"))); add(panNetwork, BorderLayout.EAST); - + JPanel panMotive = new JPanel(); add(panMotive, BorderLayout.NORTH); - + switch (unitType) { case UnitType.MEK: addWeightClasses(panWeightClass, EntityWeightClass.WEIGHT_ULTRA_LIGHT, @@ -631,6 +631,7 @@ public RATGenUnitTypeCard(int unitType) { addWeightClasses(panWeightClass, EntityWeightClass.WEIGHT_ULTRA_LIGHT, EntityWeightClass.WEIGHT_ASSAULT, true); break; + case UnitType.AEROSPACEFIGHTER: case UnitType.AERO: addWeightClasses(panWeightClass, EntityWeightClass.WEIGHT_LIGHT, EntityWeightClass.WEIGHT_HEAVY, false); @@ -646,7 +647,7 @@ public RATGenUnitTypeCard(int unitType) { default: panWeightClass.setVisible(false); } - + for (MissionRole role : MissionRole.values()) { if (role.fitsUnitType(unitType)) { JCheckBox chk = new JCheckBox(Messages.getString("MissionRole." + role)); @@ -674,7 +675,7 @@ public RATGenUnitTypeCard(int unitType) { } panRoles.add(roleChecks.get(i), c); } - + c = new GridBagConstraints(); c.gridx = 0; c.gridy = 0; @@ -690,6 +691,7 @@ public RATGenUnitTypeCard(int unitType) { case UnitType.VTOL: case UnitType.NAVAL: case UnitType.CONV_FIGHTER: + case UnitType.AEROSPACEFIGHTER: case UnitType.AERO: addNetworkButton(panNetwork, c, networkButtons, Messages.getString("RandomArmyDialog.NoNetwork"), ModelRecord.NETWORK_NONE); @@ -733,7 +735,7 @@ public RATGenUnitTypeCard(int unitType) { ModelRecord.NETWORK_NAVAL_C3); break; } - + switch (unitType) { case UnitType.TANK: panMotive.add(createSubtypeCheck("hover", true)); @@ -782,7 +784,7 @@ private void addWeightClasses(JPanel panel, int start, int end, boolean all) { c.weightx = 0.0; c.weighty = 0.0; panel.add(cbWeightClass); - + c.gridx = 0; c.gridwidth = 2; for (int i = start; i <= end; i++) { @@ -869,22 +871,22 @@ public List getMotiveTypes() { .map(chk -> EntityMovementMode.parseFromString(chk.getName())).collect(Collectors.toList()); } } - + private class FormationUnitTypeOptions extends UnitTypeOptions { private static final long serialVersionUID = -6448946137013919069L; FormationTypesCard groundCard; FormationTypesCard airCard; - + public FormationUnitTypeOptions() { setLayout(new CardLayout()); - + groundCard = new FormationTypesCard(true); airCard = new FormationTypesCard(false); add(groundCard, "Ground"); add(airCard, "Air"); } - + @Override public void optionsChanged() { if (getUnitType() != null) { @@ -893,7 +895,7 @@ public void optionsChanged() { } currentCard().updateUnitType(getUnitType()); } - + private FormationTypesCard currentCard() { if ((getUnitType() != null) && (getUnitType() >= UnitType.CONV_FIGHTER)) { return airCard; @@ -901,7 +903,7 @@ private FormationTypesCard currentCard() { return groundCard; } } - + @Override public Integer getIntegerVal(String key) { switch (key) { @@ -915,7 +917,7 @@ public Integer getIntegerVal(String key) { return null; } } - + @Override public Boolean getBooleanVal(String key) { switch (key) { @@ -927,7 +929,7 @@ public Boolean getBooleanVal(String key) { return null; } } - + @Override public String getStringVal(String key) { if ("formationType".equals(key)) { @@ -936,7 +938,7 @@ public String getStringVal(String key) { return null; } } - + @Override public void updateGeneratedUnits(List list) { currentCard().setGeneratedUnits(list); @@ -958,13 +960,13 @@ private class FormationTypesCard extends JPanel { private Map networkOptions = new LinkedHashMap<>(); private JTextArea txtNoFormation = new JTextArea(); private List generatedUnits = null; - + public FormationTypesCard(boolean groundUnit) { setLayout(new GridBagLayout()); - + JPanel panFormations = new JPanel(new GridBagLayout()); JPanel panOtherOptions = new JPanel(new GridBagLayout()); - + GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.gridy = 0; @@ -972,14 +974,14 @@ public FormationTypesCard(boolean groundUnit) { c.weightx = 0.5; c.weighty = 1.0; add(panFormations, c); - + c.gridx = 1; c.gridy = 0; c.anchor = GridBagConstraints.NORTHWEST; c.weightx = 0.5; c.weighty = 0.0; add(panOtherOptions, c); - + // Sort main types alphabetically, and subtypes alphabetically within the main. List formations = FormationType.getAllFormations().stream() .filter(ft -> ft.isGround() == groundUnit).collect(Collectors.toList()); @@ -987,7 +989,7 @@ public FormationTypesCard(boolean groundUnit) { .collect(Collectors.groupingBy(FormationType::getCategory, TreeMap::new, Collectors.mapping(FormationType::getName, Collectors.toCollection(TreeSet::new)))); - + int rows = (formations.size() + 1) / 2; c = new GridBagConstraints(); @@ -998,7 +1000,7 @@ public FormationTypesCard(boolean groundUnit) { c.fill = GridBagConstraints.NONE; c.weightx = 0.0; c.weighty = 1.0; - + Insets mainInsets = new Insets(0, 10, 0, 10); Insets subInsets = new Insets(0, 30, 0, 10); for (String group : formationGroups.keySet()) { @@ -1034,8 +1036,8 @@ public FormationTypesCard(boolean groundUnit) { c.gridy = 0; } } - - ButtonGroup btnGroup = new ButtonGroup(); + + ButtonGroup btnGroup = new ButtonGroup(); c.gridx = 0; c.gridy = 0; c.anchor = GridBagConstraints.NORTHWEST; @@ -1046,7 +1048,7 @@ public FormationTypesCard(boolean groundUnit) { panOtherOptions.add(bSimpleFormation, c); bSimpleFormation.addItemListener(ev -> tNumUnits.setEnabled(!bSimpleFormation.isSelected())); btnGroup.add(bSimpleFormation); - + c.gridx = 0; c.gridy++; c.anchor = GridBagConstraints.NORTHWEST; @@ -1154,7 +1156,7 @@ public int numOtherUnits() { return 0; } } - + public int getOtherUnitType() { String otherUnitType = (String) cbOtherUnitType.getSelectedItem(); if (!bOtherUnitType.isSelected() || (otherUnitType == null)) { @@ -1162,7 +1164,7 @@ public int getOtherUnitType() { } return ModelRecord.parseUnitType(otherUnitType); } - + public void updateUnitType(int ut) { boolean selectionDisabled = false; for (Enumeration e = formationBtnGroup.getElements(); e.hasMoreElements();) { @@ -1186,13 +1188,13 @@ public void updateUnitType(int ut) { //We shouldn't reach this point, but if we do the previous selection doesn't change. } } - + public int getNetwork() { String networkOption = (String) cbNetwork.getSelectedItem(); return (networkOptions.get(networkOption) != null) ? networkOptions.get((networkOption)) : ModelRecord.NETWORK_NONE; } - + private void showAnalysis() { List params = new ArrayList<>(); FormationType ft = FormationType.getFormationType(getFormation()); @@ -1217,7 +1219,7 @@ private void showAnalysis() { params, numUnits, getNetwork()); afd.setVisible(true); } - + public void setGeneratedUnits(List list) { generatedUnits = list; txtNoFormation.setVisible(list == null || list.isEmpty()); diff --git a/megamek/src/megamek/common/Aero.java b/megamek/src/megamek/common/Aero.java index d80763052f4..8557ce1d572 100644 --- a/megamek/src/megamek/common/Aero.java +++ b/megamek/src/megamek/common/Aero.java @@ -154,8 +154,10 @@ public String[] getLocationNames() { // fixed and pod-mounted. private int podHeatSinks; - protected int maxBombPoints = 0; - protected int[] bombChoices = new int[BombType.B_NUM]; + protected int maxIntBombPoints = 0; + protected int maxExtBombPoints = 0; + protected int[] intBombChoices = new int[BombType.B_NUM]; + protected int[] extBombChoices = new int[BombType.B_NUM]; // fuel - number of fuel points private int fuel = 0; @@ -477,35 +479,56 @@ public void setAccLast(boolean b) { @Override public int getMaxBombPoints() { - return maxBombPoints; + return maxExtBombPoints + maxIntBombPoints; + } + + public int getMaxIntBombPoints() { + return (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) ? maxIntBombPoints : 0; + } + + public int getMaxExtBombPoints() { + return maxExtBombPoints; } public void autoSetMaxBombPoints() { // Stock Aerospace units cannot carry bombs - maxBombPoints = 0; + maxExtBombPoints = maxIntBombPoints = 0; + } + + @Override + public int[] getIntBombChoices() { + return intBombChoices.clone(); + } + + @Override + public void setIntBombChoices(int[] bc) { + if (bc.length == intBombChoices.length) { + intBombChoices = bc; + } } @Override - public int[] getBombChoices() { - return bombChoices.clone(); + public int[] getExtBombChoices() { + return extBombChoices.clone(); } @Override - public void setBombChoices(int[] bc) { - if (bc.length == bombChoices.length) { - bombChoices = bc; + public void setExtBombChoices(int[] bc) { + if (bc.length == extBombChoices.length) { + extBombChoices = bc; } } @Override public void clearBombChoices() { - Arrays.fill(bombChoices, 0); + Arrays.fill(intBombChoices, 0); + Arrays.fill(extBombChoices, 0); } @Override public int reduceMPByBombLoad(int t) { // The base Aero cannot carry bombs so no MP reduction - return 0; + return t; } public void setWhoFirst() { diff --git a/megamek/src/megamek/common/AeroSpaceFighter.java b/megamek/src/megamek/common/AeroSpaceFighter.java index 334fdc8d36d..32d69b1d416 100644 --- a/megamek/src/megamek/common/AeroSpaceFighter.java +++ b/megamek/src/megamek/common/AeroSpaceFighter.java @@ -30,15 +30,19 @@ public AeroSpaceFighter() { super(); } + @Override + public int getUnitType() { + return UnitType.AEROSPACEFIGHTER; + } + public void autoSetMaxBombPoints() { // Aerospace fighters can carry both external and internal ordnances, if configured and quirked // appropriately - maxBombPoints = (int) Math.round(getWeight() / 5); - if (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) { - maxBombPoints += getTransportBays().stream().mapToInt( - tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused()) : 0 - ).sum(); - } + maxExtBombPoints = (int) Math.round(getWeight() / 5); + // Can't check quirk here, as they don't exist in unit files yet. + maxIntBombPoints = getTransportBays().stream().mapToInt( + tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused()) : 0 + ).sum(); } @Override @@ -46,23 +50,6 @@ public boolean isInASquadron() { return game.getEntity(getTransportId()) instanceof FighterSquadron; } - @Override - public int[] getBombChoices() { - return bombChoices.clone(); - } - - @Override - public void setBombChoices(int[] bc) { - if (bc.length == bombChoices.length) { - bombChoices = bc; - } - } - - @Override - public void clearBombChoices() { - Arrays.fill(bombChoices, 0); - } - @Override public int reduceMPByBombLoad(int t) { return Math.max(0, t - (int) Math.ceil(getExternalBombPoints() / 5.0)); diff --git a/megamek/src/megamek/common/EntityWeightClass.java b/megamek/src/megamek/common/EntityWeightClass.java index 60a72307d0a..542939b6c73 100644 --- a/megamek/src/megamek/common/EntityWeightClass.java +++ b/megamek/src/megamek/common/EntityWeightClass.java @@ -71,6 +71,8 @@ public class EntityWeightClass { public static double[] getWeightLimitByType(String type) { if (type.equals(UnitType.getTypeName(UnitType.MEK))) { return mechWeightLimits; + } else if (type.equals(UnitType.getTypeName(UnitType.AEROSPACEFIGHTER))) { + return ASFWeightLimits; } else if (type.equals(UnitType.getTypeName(UnitType.AERO))) { return ASFWeightLimits; } else if (type.equals(UnitType.getTypeName(UnitType.BATTLE_ARMOR))) { @@ -150,7 +152,7 @@ public static int getWeightClass(double tonnage, String type) { } } else if (type.equals(UnitType.getTypeName(UnitType.SMALL_CRAFT))) { return WEIGHT_SMALL_CRAFT; - } else if (type.equals("Aero") || type.equals("Conventional Fighter")) { + } else if (type.equals("AeroSpaceFighter") || type.equals("Aero") || type.equals("Conventional Fighter")) { for (i = WEIGHT_LIGHT; i < (ASFWeightLimits.length - 1); i++) { // Started late to bypass padding & save a loop execution if (tonnage <= ASFWeightLimits[i]) { break; diff --git a/megamek/src/megamek/common/FighterSquadron.java b/megamek/src/megamek/common/FighterSquadron.java index 27aef2eaad4..c0dc1dbd8d0 100644 --- a/megamek/src/megamek/common/FighterSquadron.java +++ b/megamek/src/megamek/common/FighterSquadron.java @@ -440,18 +440,24 @@ public void useFuel(int fuel) { @Override public void autoSetMaxBombPoints() { - maxBombPoints = Integer.MAX_VALUE; + maxExtBombPoints = maxIntBombPoints = Integer.MAX_VALUE; for (Entity fighter : getSubEntities()) { + // External bomb points int currBombPoints = (int) Math.round(fighter.getWeight() / 5); - maxBombPoints = Math.min(maxBombPoints, currBombPoints); + maxExtBombPoints = Math.min(maxExtBombPoints, currBombPoints); + // Internal (cargo bay) bomb points; requires IBB to utilize + currBombPoints = getTransportBays().stream().mapToInt( + tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused()) : 0 + ).sum(); + maxIntBombPoints = Math.min(maxIntBombPoints, currBombPoints); } } @Override public void setBombChoices(int... bc) { // Set the bombs for the squadron - if (bc.length == bombChoices.length) { - bombChoices = bc; + if (bc.length == extBombChoices.length) { + extBombChoices = bc; } // Update each fighter in the squadron for (Entity bomber : getSubEntities()) { @@ -515,13 +521,13 @@ public void computeSquadronBombLoadout() { } maxBombCount = Math.max(bombCount, maxBombCount); } - bombChoices[btype] = maxBombCount; + extBombChoices[btype] = maxBombCount; } // Now that we know our bomb choices, load 'em int gameTL = TechConstants.getSimpleLevel(game.getOptions().stringOption("techlevel")); for (int type = 0; type < BombType.B_NUM; type++) { - for (int i = 0; i < bombChoices[type]; i++) { + for (int i = 0; i < extBombChoices[type]; i++) { if ((type == BombType.B_ALAMO) && !game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AT2_NUKES)) { continue; @@ -551,7 +557,7 @@ public void computeSquadronBombLoadout() { } } // Clear out the bomb choice once the bombs are loaded - bombChoices[type] = 0; + extBombChoices[type] = 0; } // add the space bomb attack if (game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_STRATOPS_SPACE_BOMB) diff --git a/megamek/src/megamek/common/FixedWingSupport.java b/megamek/src/megamek/common/FixedWingSupport.java index d2b63bfb36f..2b28db6685d 100644 --- a/megamek/src/megamek/common/FixedWingSupport.java +++ b/megamek/src/megamek/common/FixedWingSupport.java @@ -243,16 +243,12 @@ public void autoSetMaxBombPoints() { bombpoints++; } } + maxExtBombPoints = bombpoints; // fixed-wing support craft may also use internal transport bays as bomb bays with Internal Bomb Bay quirk. - // Only count free whole tons per bay - if (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) { - bombpoints += getTransportBays().stream().mapToInt( - tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused()) : 0 - ).sum(); - } - - maxBombPoints = bombpoints; + maxIntBombPoints = getTransportBays().stream().mapToInt( + tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused()) : 0 + ).sum(); } @Override diff --git a/megamek/src/megamek/common/IBomber.java b/megamek/src/megamek/common/IBomber.java index aa7720e7785..4d12245d20e 100644 --- a/megamek/src/megamek/common/IBomber.java +++ b/megamek/src/megamek/common/IBomber.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.List; +import java.util.stream.IntStream; import megamek.common.options.OptionsConstants; @@ -53,14 +54,31 @@ default int getMaxBombSize() { /** * @return The number of each bomb type that was selected prior to deployment */ - int[] getBombChoices(); + int[] getIntBombChoices(); + int[] getExtBombChoices(); /** * Sets the bomb type selections prior to deployment. * * @param bc An array with the count of each bomb type as the value of the bomb type's index */ - void setBombChoices(int[] bc); + void setIntBombChoices(int[] bc); + void setExtBombChoices(int[] bc); + + /** + * @return summed combination of internal and external choices + */ + default int[] getBombChoices(){ + int[] intArr = getIntBombChoices(); + int[] extArr = getExtBombChoices(); + IntStream range = IntStream.range(0, Math.min(intArr.length, extArr.length)); + IntStream stream3 = range.map(i -> intArr[i] + extArr[i]); + return stream3.toArray(); + } + + default void setBombChoices(int[] ebc) { + setExtBombChoices(ebc); + } /** * Sets the count of each bomb to zero @@ -134,15 +152,20 @@ default int getBombPoints(boolean externalOnly) { default void applyBombs() { Game game = ((Entity) this).getGame(); int gameTL = TechConstants.getSimpleLevel(game.getOptions().stringOption("techlevel")); - Integer[] sorted = new Integer[BombType.B_NUM]; + Integer[] iSorted = new Integer[BombType.B_NUM]; // Apply the largest bombs first because we need to fit larger bombs into a single location // in LAMs. - for (int i = 0; i < sorted.length; i++) { - sorted[i] = i; + for (int i = 0; i < iSorted.length; i++) { + iSorted[i] = i; } - Arrays.sort(sorted, (a, b) -> BombType.bombCosts[b] - BombType.bombCosts[a]); - for (int type : sorted) { - for (int i = 0; i < getBombChoices()[type]; i++) { + Integer[] eSorted = iSorted.clone(); + + Arrays.sort(iSorted, (a, b) -> BombType.bombCosts[b] - BombType.bombCosts[a]); + Arrays.sort(eSorted, (a, b) -> BombType.bombCosts[b] - BombType.bombCosts[a]); + + // First, internal bombs + for (int type : iSorted) { + for (int i = 0; i < getIntBombChoices()[type]; i++) { int loc = availableBombLocation(BombType.bombCosts[type]); if ((type == BombType.B_ALAMO) && !game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AT2_NUKES)) { @@ -156,34 +179,75 @@ default void applyBombs() { // some bombs need an associated weapon and if so // they need a weapon for each bomb if (null != BombType.getBombWeaponName(type)) { - Mounted m; - try { - m = ((Entity) this).addBomb(EquipmentType.get(BombType - .getBombWeaponName(type)), loc); - // Add bomb itself as single-shot ammo. - if (type != BombType.B_TAG) { - Mounted ammo = new Mounted((Entity) this, - EquipmentType.get(BombType.getBombInternalName(type))); - ammo.setShotsLeft(1); - m.setLinked(ammo); - ((Entity) this).addEquipment(ammo, loc, false); - - } - } catch (LocationFullException ignored) { - - } + applyBombWeapons(type, loc, true); } else { - try { - ((Entity) this).addEquipment(EquipmentType.get(BombType.getBombInternalName(type)), - loc, false); - } catch (LocationFullException ignored) { + applyBombEquipment(type, loc, true); + } + } + } + // Now external bombs + for (int type : eSorted) { + for (int i = 0; i < getIntBombChoices()[type]; i++) { + int loc = availableBombLocation(BombType.bombCosts[type]); + if ((type == BombType.B_ALAMO) + && !game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AT2_NUKES)) { + continue; + } + if ((type > BombType.B_TAG) + && (gameTL < TechConstants.T_SIMPLE_ADVANCED)) { + continue; + } - } + // some bombs need an associated weapon and if so + // they need a weapon for each bomb + if (null != BombType.getBombWeaponName(type)) { + applyBombWeapons(type, loc, false); + } else { + applyBombEquipment(type, loc, false); } } } clearBombChoices(); } + private void applyBombEquipment(int type, int loc, boolean internal){ + try { + EquipmentType et = EquipmentType.get(BombType.getBombInternalName(type)); + if (internal) { + et.setFlags(BombType.F_INTERNAL_BOMB); + } + ((Entity) this).addEquipment(et, loc, false); + } catch (LocationFullException ignored) { + + } + + } + + private void applyBombWeapons(int type, int loc, boolean internal){ + Mounted m; + try { + EquipmentType et = EquipmentType.get(BombType.getBombWeaponName(type)); + if (internal) { + et.setFlags(BombType.F_INTERNAL_BOMB); + } + m = ((Entity) this).addBomb(et, loc); + // Add bomb itself as single-shot ammo. + if (type != BombType.B_TAG) { + Mounted ammo = new Mounted((Entity) this, + EquipmentType.get(BombType.getBombInternalName(type))); + ammo.setShotsLeft(1); + m.setLinked(ammo); + ((Entity) this).addEquipment(ammo, loc, false); + + } + } catch (LocationFullException ignored) { + + } + } + void clearBombs(); + + int getMaxExtBombPoints(); + + int getMaxIntBombPoints(); } diff --git a/megamek/src/megamek/common/LandAirMech.java b/megamek/src/megamek/common/LandAirMech.java index 3954afcdb50..84d1e936ef2 100644 --- a/megamek/src/megamek/common/LandAirMech.java +++ b/megamek/src/megamek/common/LandAirMech.java @@ -125,7 +125,11 @@ public String[] getLocationAbbrs() { //Autoejection private boolean critThresh = false; - private int[] bombChoices = new int[BombType.B_NUM]; + // Bomb choices + + protected int[] intBombChoices = new int[BombType.B_NUM]; + protected int[] extBombChoices = new int[BombType.B_NUM]; + private Targetable airmechBombTarget = null; private int fuel; @@ -1083,8 +1087,16 @@ public int getWhoFirst() { return whoFirst; } - @Override public int getMaxBombPoints() { + return getMaxExtBombPoints() + getMaxIntBombPoints(); + } + + @Override + public int getMaxExtBombPoints() { + return 0; + } + @Override + public int getMaxIntBombPoints() { return countWorkingMisc(MiscType.F_BOMB_BAY); } @@ -1094,20 +1106,29 @@ public int getMaxBombSize() { } @Override - public int[] getBombChoices() { - return bombChoices.clone(); + public int[] getIntBombChoices() { + return intBombChoices.clone(); } @Override - public void setBombChoices(int[] bc) { - if (bc.length == bombChoices.length) { - bombChoices = bc; + public void setIntBombChoices(int[] bc) { + if (bc.length == intBombChoices.length) { + intBombChoices = bc; } } + @Override + public int[] getExtBombChoices() { + return extBombChoices; + } + + @Override + public void setExtBombChoices(int[] bc) { + } + @Override public void clearBombChoices() { - Arrays.fill(bombChoices, 0); + Arrays.fill(intBombChoices, 0); } @Override diff --git a/megamek/src/megamek/common/MechSummary.java b/megamek/src/megamek/common/MechSummary.java index 24e797accd5..6818559a7f5 100644 --- a/megamek/src/megamek/common/MechSummary.java +++ b/megamek/src/megamek/common/MechSummary.java @@ -129,16 +129,16 @@ public class MechSummary implements Serializable, ASCardDisplayable { /** The type of internal structure on this unit **/ private int internalsType; - + /** * Each location can have a separate armor type, but this is used for search purposes. We really * only care about which types are present. */ private final HashSet armorTypeSet; - + /** The armor type for each location. */ private int[] armorLoc; - + /** The armor tech type for each location. */ private int[] armorLocTech; @@ -257,15 +257,17 @@ public static String determineETypeName(MechSummary ms) { case "Jumpship": case "Dropship": case "Small Craft": - case "Conventional Fighter": case "Aero": return Entity.getEntityMajorTypeName(Entity.ETYPE_AERO); + case "Conventional Fighter": + case "AeroSpaceFighter": + return Entity.getEntityMajorTypeName(Entity.ETYPE_AEROSPACEFIGHTER); case "Unknown": return Entity.getEntityMajorTypeName(-1); } return Entity.getEntityMajorTypeName(-1); } - + // This is here for legacy purposes to not break the API @Deprecated public static String determineUnitType(Entity e) { @@ -307,11 +309,11 @@ public int getYear() { public int getType() { return type; } - + public int[] getAltTypes() { return altTypes; } - + public int getType(int year) { if (year >= stdTechYear) { return altTypes[0]; @@ -529,7 +531,7 @@ public long getModified() { public String getLevel() { return level; } - + public int getAdvancedTechYear() { return advTechYear; } @@ -537,7 +539,7 @@ public int getAdvancedTechYear() { public int getStandardTechYear() { return stdTechYear; } - + public String getLevel(int year) { if (level.equals("F")) { return level; @@ -871,11 +873,11 @@ public void setYear(int nYear) { public void setType(int nType) { this.type = nType; } - + public void setAltTypes(int[] altTypes) { this.altTypes = altTypes; } - + public void setTons(double nTons) { this.tons = nTons; } @@ -911,11 +913,11 @@ public void setModified(long lModified) { public void setLevel(String level) { this.level = level; } - + public void setAdvancedYear(int year) { advTechYear = year; } - + public void setStandardYear(int year) { stdTechYear = year; } @@ -996,11 +998,11 @@ public int getJumpMp() { public void setJumpMp(int jumpMp) { this.jumpMp = jumpMp; } - + /** * Given the list of equipment mounted on this unit, parse it into a unique * list of names and the number of times that name appears. - * + * * @param mountedList A collection of Mounted equipment */ public void setEquipment(List mountedList) @@ -1020,14 +1022,14 @@ public void setEquipment(List mountedList) equipmentQuantities.add(1); } else { // We've seen this before, update count equipmentQuantities.set(index, equipmentQuantities.get(index)+1); - } + } } } - + public Vector getEquipmentNames() { return equipmentNames; } - + public Vector getEquipmentQuantities() { return equipmentQuantities; } @@ -1084,9 +1086,9 @@ public int getInternalsType() { } /** - * Takes the armor type at all locations and creates a set of the armor + * Takes the armor type at all locations and creates a set of the armor * types. - * + * * @param locsArmor An array that stores the armor type at each location. */ public void setArmorType(int[] locsArmor) { @@ -1099,19 +1101,19 @@ public void setArmorType(int[] locsArmor) { public HashSet getArmorType() { return armorTypeSet; } - + public int[] getArmorTypes() { return armorLoc; } - + public void setArmorTypes(int[] al) { armorLoc = al; } - + public int[] getArmorTechTypes() { return armorLocTech; } - + public void setArmorTechTypes(int[] att) { armorLocTech = att; } @@ -1249,7 +1251,7 @@ public boolean equals(Object obj) { return Objects.equals(chassis, other.chassis) && Objects.equals(model, other.model) && Objects.equals(unitType, other.unitType) && Objects.equals(sourceFile, other.sourceFile); } - + @Override public int hashCode() { return Objects.hash(chassis, model, unitType, sourceFile); diff --git a/megamek/src/megamek/common/SmallCraft.java b/megamek/src/megamek/common/SmallCraft.java index 26d68eac0d5..ef7a811b951 100644 --- a/megamek/src/megamek/common/SmallCraft.java +++ b/megamek/src/megamek/common/SmallCraft.java @@ -933,8 +933,8 @@ public int getLandingLength() { @Override public void autoSetMaxBombPoints() { - // Only count free whole tons per bay - maxBombPoints = getTransportBays().stream().mapToInt( + // Only internal cargo bays can be considered for this type of unit. + maxIntBombPoints = getTransportBays().stream().mapToInt( tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused()) : 0 ).sum(); } diff --git a/megamek/src/megamek/common/UnitType.java b/megamek/src/megamek/common/UnitType.java index 6eebef72d07..de389fc5ab1 100644 --- a/megamek/src/megamek/common/UnitType.java +++ b/megamek/src/megamek/common/UnitType.java @@ -34,11 +34,12 @@ public class UnitType { public static final int JUMPSHIP = 12; public static final int WARSHIP = 13; public static final int SPACE_STATION = 14; + public static final int AEROSPACEFIGHTER = 15; private static String[] names = { "Mek", "Tank", "BattleArmor", "Infantry", "ProtoMek", "VTOL", "Naval", "Gun Emplacement", "Conventional Fighter", - "Aero", "Small Craft", "Dropship", - "Jumpship", "Warship", "Space Station" }; + "Aero", "Small Craft", "Dropship", + "Jumpship", "Warship", "Space Station", "AeroSpaceFighter" }; public static final int SIZE = names.length; @@ -50,7 +51,7 @@ public static String determineUnitType(Entity e) { /** * Reverse lookup for type integer constant from name - * + * * @param name Unit type name * @return The unit type constant. If no match can be found, returns -1. */ @@ -82,9 +83,9 @@ public static String getTypeDisplayableName(int type) { } throw new IllegalArgumentException("Unknown unit type"); } - + // series of convenience methods to shorten unit type determination - + /** * Whether the given entity is a VTOL * @param e the entity to examine @@ -93,7 +94,7 @@ public static String getTypeDisplayableName(int type) { public static boolean isVTOL(Entity e) { return e.getEntityType() == Entity.ETYPE_VTOL; } - + /** * Whether the given entity is a Spheroid dropship * @param e the entity to examine diff --git a/megamek/src/megamek/common/VTOL.java b/megamek/src/megamek/common/VTOL.java index 9cf3106d2b9..78a6c54d31c 100644 --- a/megamek/src/megamek/common/VTOL.java +++ b/megamek/src/megamek/common/VTOL.java @@ -52,8 +52,8 @@ public VTOL() { // need to set elevation to something different than entity elevation = 1; } - - + + @Override public int getUnitType() { return UnitType.VTOL; @@ -73,13 +73,15 @@ public String[] getLocationNames() { public int getLocTurret() { return LOC_TURRET; } - + @Override public int getLocTurret2() { return LOC_TURRET_2; } - private int[] bombChoices = new int[BombType.B_NUM]; + protected int[] intBombChoices = new int[BombType.B_NUM]; + protected int[] extBombChoices = new int[BombType.B_NUM]; + private Targetable bombTarget = null; private List strafingCoords = new ArrayList<>(); @@ -240,38 +242,57 @@ public boolean doomedInVacuum() { public boolean doomedInAtmosphere() { return true; } - + @Override public boolean isBomber() { return (game != null) - && game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_VTOL_ATTACKS); + && (game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_VTOL_ATTACKS)); } - + @Override public int availableBombLocation(int cost) { return LOC_FRONT; } - + @Override - public int getMaxBombPoints() { + public int getMaxExtBombPoints() { return (int) Math.round(getWeight() / 5); } + @Override + public int getMaxIntBombPoints() { + return 0; + } + + + @Override + public int getMaxBombPoints() { + return getMaxExtBombPoints(); + } @Override - public int[] getBombChoices() { - return bombChoices.clone(); + public int[] getIntBombChoices() { + return intBombChoices.clone(); } @Override - public void setBombChoices(int... bc) { - if (bc.length == bombChoices.length) { - bombChoices = bc; + public void setIntBombChoices(int[] bc) { + } + + @Override + public int[] getExtBombChoices() { + return extBombChoices.clone(); + } + + @Override + public void setExtBombChoices(int[] bc) { + if (bc.length == extBombChoices.length) { + extBombChoices = bc; } } - + @Override public void clearBombChoices() { - Arrays.fill(bombChoices, 0); + Arrays.fill(extBombChoices, 0); } @Override @@ -284,12 +305,12 @@ public int reduceMPByBombLoad(int t) { public Targetable getVTOLBombTarget() { return bombTarget; } - + @Override public void setVTOLBombTarget(Targetable t) { bombTarget = t; } - + public List getStrafingCoords() { return strafingCoords; } @@ -565,7 +586,7 @@ public PilotingRollData addEntityBonuses(PilotingRollData prd) { @Override public void newRound(int roundNumber) { super.newRound(roundNumber); - + bombTarget = null; strafingCoords.clear(); } diff --git a/megamek/src/megamek/common/loaders/BLKAeroSpaceFighterFile.java b/megamek/src/megamek/common/loaders/BLKAeroSpaceFighterFile.java index 00a88066b5e..d0a28609c65 100644 --- a/megamek/src/megamek/common/loaders/BLKAeroSpaceFighterFile.java +++ b/megamek/src/megamek/common/loaders/BLKAeroSpaceFighterFile.java @@ -161,6 +161,9 @@ public Entity getEntity() throws EntityLoadingException { // This is not working right for arrays for some reason a.autoSetThresh(); + // add Transporters prior to equipment to simplify F_CARGO bay assignment + addTransports(a); + for (int loc = 0; loc < a.locations(); loc++) { loadEquipment(a, a.getLocationName(loc), loc); } @@ -172,8 +175,6 @@ public Entity getEntity() throws EntityLoadingException { a.setOmni(true); } - addTransports(a); - // how many bombs can it carry; dependent on transport bays as well as total mass. a.autoSetMaxBombPoints(); @@ -282,6 +283,11 @@ protected void loadEquipment(Entity t, String sName, int nLoc) throws EntityLoad } mount.setSize(size); } + if (etype.hasFlag(MiscType.F_CARGO)) { + // Treat F_CARGO equipment as cargo bays with 1 door, e.g. for ASF with IBB. + int idx = t.getTransportBays().size(); + t.addTransporter(new CargoBay(mount.getSize(), 1, idx), omniMounted); + } } catch (LocationFullException ex) { throw new EntityLoadingException(ex.getMessage()); } diff --git a/megamek/src/megamek/common/loaders/BLKFile.java b/megamek/src/megamek/common/loaders/BLKFile.java index 5b29a1b7322..94eb8719c8a 100644 --- a/megamek/src/megamek/common/loaders/BLKFile.java +++ b/megamek/src/megamek/common/loaders/BLKFile.java @@ -310,6 +310,11 @@ protected void loadEquipment(Entity t, String sName, int nLoc) * ((InfantryWeapon) mount.getType()).getShots()); mount.getLinked().setShotsLeft(mount.getLinked().getOriginalShots()); } + if (etype.hasFlag(MiscType.F_CARGO)) { + // Treat F_CARGO equipment as cargo bays with 1 door, e.g. for ASF with IBB. + int idx = t.getTransportBays().size(); + t.addTransporter(new CargoBay(mount.getSize(), 1, idx), isOmniMounted); + } } catch (LocationFullException ex) { throw new EntityLoadingException(ex.getMessage()); } @@ -589,6 +594,8 @@ public static BuildingBlock getBlock(Entity t) { blk.writeBlockData("UnitType", "Infantry"); } else if (t instanceof AeroSpaceFighter) { blk.writeBlockData("UnitType", "AeroSpaceFighter"); + } else if (t instanceof Aero) { + blk.writeBlockData("UnitType", "Aero"); } blk.writeBlockData("Name", t.getChassis()); diff --git a/megamek/src/megamek/common/verifier/EntityVerifier.java b/megamek/src/megamek/common/verifier/EntityVerifier.java index 516da80d207..13092d60500 100755 --- a/megamek/src/megamek/common/verifier/EntityVerifier.java +++ b/megamek/src/megamek/common/verifier/EntityVerifier.java @@ -34,10 +34,10 @@ import java.util.Map; /** - * Performs verification of the validity of different types of + * Performs verification of the validity of different types of * Entity subclasses. Most of the actual validation is performed - * by TestEntity and its subclasses. - * + * by TestEntity and its subclasses. + * * @author Reinhard Vicinus */ @XmlRootElement(name = "entityverifier") @@ -59,7 +59,7 @@ public class EntityVerifier implements MechSummaryCache.Listener { public TestXMLOption baOption = new TestXMLOption(); @XmlElement(name = "infantry") public TestXMLOption infOption = new TestXMLOption(); - + private boolean loadingVerbosity = false; private boolean failsOnly = false; @@ -68,16 +68,16 @@ public class EntityVerifier implements MechSummaryCache.Listener { */ private EntityVerifier() { } - + /** * Creates and return a new instance of EntityVerifier. - * + * * @param config a File that contains an XML representation of the configuration settings * @return an EntityVerifier with the configuration loaded from XML */ public static EntityVerifier getInstance(final File config) { EntityVerifier ev; - + try { JAXBContext jc = JAXBContext.newInstance(EntityVerifier.class); @@ -89,7 +89,7 @@ public static EntityVerifier getInstance(final File config) { ev = new EntityVerifier(); } - + return ev; } @@ -101,7 +101,7 @@ public boolean checkEntity(Entity entity, String fileString, boolean verbose, int ammoTechLvl) { return checkEntity(entity, fileString, verbose, ammoTechLvl, false); } - + public boolean checkEntity(Entity entity, String fileString, boolean verbose, int ammoTechLvl, boolean failsOnly) { final NumberFormat FMT = NumberFormat.getNumberInstance(Locale.getDefault()); @@ -113,7 +113,7 @@ public boolean checkEntity(Entity entity, String fileString, testEntity = new TestProtomech((Protomech) entity, protomechOption, fileString); } else if (entity.isSupportVehicle()) { testEntity = new TestSupportVehicle(entity, tankOption, null); - } else if ((entity instanceof Tank) && + } else if ((entity instanceof Tank) && !(entity instanceof GunEmplacement)) { testEntity = new TestTank((Tank) entity, tankOption, null); } else if (entity.hasETypeFlag(Entity.ETYPE_SMALL_CRAFT)) { @@ -144,7 +144,7 @@ public boolean checkEntity(Entity entity, String fileString, } else { System.out.println("---Entity INVALID---"); } - System.out.print(testEntity.printEntity()); + System.out.print(testEntity.printEntity()); System.out.println("BV: " + entity.calculateBattleValue() + " Cost: " + FMT.format(entity.getCost(false))); } @@ -221,7 +221,8 @@ public void doneLoading() { System.out.println("\t Failed Tanks: " + failedByType.getOrDefault(UnitType.TANK, 0)); System.out.println("\t Failed VTOLs: " + failedByType.getOrDefault(UnitType.VTOL, 0)); System.out.println("\t Failed Naval: " + failedByType.getOrDefault(UnitType.NAVAL, 0)); - System.out.println("\t Failed ASFs: " + failedByType.getOrDefault(UnitType.AERO, 0)); + System.out.println("\t Failed ASFs: " + failedByType.getOrDefault(UnitType.AEROSPACEFIGHTER, 0)); + System.out.println("\t Failed Aerospaces: " + failedByType.getOrDefault(UnitType.AERO, 0)); System.out.println("\t Failed CFs: " + failedByType.getOrDefault(UnitType.CONV_FIGHTER, 0)); System.out.println("\t Failed Small Craft: " + failedByType.getOrDefault(UnitType.SMALL_CRAFT, 0)); System.out.println("\t Failed DropShips: " + failedByType.getOrDefault(UnitType.DROPSHIP, 0)); @@ -272,7 +273,7 @@ public static void main(String[] args) { "-file \t Specify a file to validate,\n"+ " \t else the data directory is checked\n" + "-v \t Verbose -- print detailed report\n" + - "-unofficial \t Consider unofficial units in data dir\n"+ + "-unofficial \t Consider unofficial units in data dir\n"+ "-valid \t Print verbose reports for valid units\n"); return; } diff --git a/megamek/src/megamek/common/verifier/TestAero.java b/megamek/src/megamek/common/verifier/TestAero.java index 9729b2a5ecc..ba40edc268c 100644 --- a/megamek/src/megamek/common/verifier/TestAero.java +++ b/megamek/src/megamek/common/verifier/TestAero.java @@ -246,7 +246,7 @@ public static int maxArmorPoints(Entity aero, double tonnage) { return TestSmallCraft.maxArmorPoints((SmallCraft) aero); } else if (aero.hasETypeFlag(Entity.ETYPE_CONV_FIGHTER)) { return (int) (tonnage * 1); - } else if (eType == Entity.ETYPE_AERO) { + } else if (aero.hasETypeFlag(Entity.ETYPE_AERO)) { return (int) (tonnage * 8); } else { return 0; diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index 83bdc075e12..4646159b310 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -3695,7 +3695,7 @@ private void loadUnit(Entity loader, Entity unit, int bayNumber) { // is loaded into the squadron, the squadrons bombing attacks are // adjusted based on the bomb loadout on the fighter. if (getGame().getPhase().isLounge() && (loader instanceof FighterSquadron)) { - ((IBomber) unit).setBombChoices(((FighterSquadron) loader).getBombChoices()); + ((IBomber) unit).setBombChoices(((FighterSquadron) loader).getExtBombChoices()); ((FighterSquadron) loader).updateSkills(); ((FighterSquadron) loader).updateWeaponGroups(); } @@ -29635,7 +29635,7 @@ private void receiveSquadronAdd(Packet c, int connIndex) { fighter.setTransportId(fs.getId()); // If this is the lounge, we want to configure bombs if (getGame().getPhase().isLounge()) { - ((IBomber) fighter).setBombChoices(fs.getBombChoices()); + ((IBomber) fighter).setBombChoices(fs.getExtBombChoices()); } entityUpdate(fighter.getId()); } diff --git a/megamek/src/megamek/utilities/RATGeneratorEditor.java b/megamek/src/megamek/utilities/RATGeneratorEditor.java index 000ec522915..2a9d4ef68f7 100644 --- a/megamek/src/megamek/utilities/RATGeneratorEditor.java +++ b/megamek/src/megamek/utilities/RATGeneratorEditor.java @@ -51,7 +51,7 @@ public class RATGeneratorEditor extends JFrame { private static final FactionRecord GENERAL_FACTION = new FactionRecord("General", "General"); private static RATGenerator rg; - + private static Integer[] ERAS; private final JComboBox cbUnitType = new JComboBox<>(); @@ -699,7 +699,7 @@ public Object getValueAt(int row, int col) { return data.get(row).getModel() + "[C]"; } else if (data.get(row).isSL()) { return data.get(row).getModel() + "[*]"; - } + } return data.get(row).getModel(); case COL_UNIT_TYPE: if (data.get(row).getMechSummary() == null) { @@ -713,13 +713,13 @@ public Object getValueAt(int row, int col) { System.err.println("Could not find mechsummary for " + data.get(row).getKey()); } return data.get(row).getMechSummary().getYear(); - + case COL_EXTINCT_RANGE: if (data.get(row).getMechSummary() == null) { System.err.println("Could not find mechsummary for " + data.get(row).getKey()); } return data.get(row).getMechSummary().getExtinctRange(); - + case COL_ROLE: return data.get(row).getRoles().stream().map(Object::toString).collect(Collectors.joining(",")); case COL_CANON_ROLE: @@ -746,7 +746,7 @@ public void setValueAt(Object val, int row, int col) { if (!((String) val).isBlank()) { for (String unit : ((String) val).split(",")) { if (unit.startsWith("req:")) { - data.get(row).getRequiredUnits().add(unit); + data.get(row).getRequiredUnits().add(unit); } else { data.get(row).getDeployedWith().add(unit); } @@ -867,7 +867,7 @@ public Object getValueAt(int row, int col) { return factions.get(row); } else { return data.get(factions.get(row)).get(col - 1); - } + } } @Override @@ -894,13 +894,13 @@ public void setValueAt(Object value, int row, int col) { data.get(factions.get(row)).set(col - 1, stringValue); if (rg.getChassisRecord(getUnitKey()) != null) { if (ar == null) { - rg.removeChassisFactionRating(era, getUnitKey(), factions.get(row)); + rg.removeChassisFactionRating(era, getUnitKey(), factions.get(row)); } else { rg.setChassisFactionRating(era, getUnitKey(), ar); } } else { if (ar == null) { - rg.removeModelFactionRating(era, getUnitKey(), factions.get(row)); + rg.removeModelFactionRating(era, getUnitKey(), factions.get(row)); } else { rg.setModelFactionRating(era, getUnitKey(), ar); } @@ -961,9 +961,9 @@ public boolean addEntry(RowData rowData) { public void removeEntry(int row) { for (int era : ERAS) { if (mode == MODE_CHASSIS) { - rg.removeChassisFactionRating(era, getUnitKey(), factions.get(row)); + rg.removeChassisFactionRating(era, getUnitKey(), factions.get(row)); } else { - rg.removeModelFactionRating(era, getUnitKey(), factions.get(row)); + rg.removeModelFactionRating(era, getUnitKey(), factions.get(row)); } } data.remove(factions.get(row)); @@ -982,7 +982,7 @@ public void copyRow(int row, String newFaction) { List copyTo = new ArrayList<>(); for (int i = 0; i < ERAS.length; i++) { copyTo.add(copyFrom.get(i)); - } + } data.put(newFaction, copyTo); for (int i = 0; i < ERAS.length; i++) { if (!copyFrom.get(i).isBlank()) { @@ -1082,30 +1082,30 @@ private class FactionListTableModel extends DefaultTableModel { public static final int COL_RATINGS = 6; public static final int COL_USE_ALT_FACTION = 7; public static final int NUM_COLS = 8; - + public final String[] colNames = {"Code", "Name", "Years", "Minor", "Clan", "Periphery", "Ratings", "Use Alt"}; - + private final ArrayList data; - + public FactionListTableModel(Collection factionList) { data = new ArrayList<>(); data.addAll(factionList); fillFactionChoosers(); } - + public void addRecord(FactionRecord rec) { data.add(rec); fireTableDataChanged(); fillFactionChoosers(); } - + public void delRecord(FactionRecord rec) { data.remove(rec); fireTableDataChanged(); fillFactionChoosers(); } - + @Override public String getColumnName(int column) { return colNames[column]; @@ -1115,7 +1115,7 @@ public String getColumnName(int column) { public int getColumnCount() { return NUM_COLS; } - + @Override public int getRowCount() { if (data == null) { @@ -1123,12 +1123,12 @@ public int getRowCount() { } return data.size(); } - + @Override public boolean isCellEditable(int row, int col) { return col > COL_CODE; } - + @Override public Class getColumnClass(int col) { if (col == COL_MINOR || col == COL_CLAN || col == COL_PERIPHERY) { @@ -1136,7 +1136,7 @@ public Class getColumnClass(int col) { } return String.class; } - + @Override public Object getValueAt(int row, int col) { switch (col) { @@ -1160,7 +1160,7 @@ public Object getValueAt(int row, int col) { return "?"; } } - + @Override public void setValueAt(Object val, int row, int col) { switch (col) { @@ -1190,13 +1190,13 @@ public void setValueAt(Object val, int row, int col) { data.get(row).setParentFactions((String) val); } } - + public FactionRecord getFactionRecord(int row) { return data.get(row); } - + } - + private static class FactionEditorTableModel extends DefaultTableModel { private static final int CAT_OMNI_PCT = 0; private static final int CAT_CLAN_PCT = 1; @@ -1212,13 +1212,13 @@ private static class FactionEditorTableModel extends DefaultTableModel { "Omni % (Aero)", "Clan % (Aero)", "SL % (Aero)", "Clan % (Vee)", "SL % (Vee)" }; - + private static final int[] WEIGHT_DIST_UNIT_TYPES = { - UnitType.MEK, UnitType.TANK, UnitType.AERO + UnitType.MEK, UnitType.TANK, UnitType.AEROSPACEFIGHTER, UnitType.AERO }; - + private FactionRecord factionRec; - + public FactionEditorTableModel(FactionRecord rec) { factionRec = rec; } @@ -1226,12 +1226,12 @@ public FactionEditorTableModel(FactionRecord rec) { public void clearData() { setData(null); } - + public void setData(FactionRecord rec) { factionRec = rec; fireTableDataChanged(); } - + @Override public String getColumnName(int column) { if (column == 0) { @@ -1245,7 +1245,7 @@ public String getColumnName(int column) { public int getColumnCount() { return ERAS.length + 1; } - + @Override public int getRowCount() { if (factionRec == null) { @@ -1254,17 +1254,17 @@ public int getRowCount() { return 1 + CATEGORIES.length * factionRec.getRatingLevels().size() + WEIGHT_DIST_UNIT_TYPES.length; } - + @Override public boolean isCellEditable(int row, int col) { return factionRec != null && col > 0; } - + @Override public Class getColumnClass(int col) { return String.class; } - + @Override public Object getValueAt(int row, int col) { if (factionRec == null) { @@ -1281,7 +1281,7 @@ public Object getValueAt(int row, int col) { } } int era = ERAS[col - 1]; - + if (row == 0) { Integer pct = factionRec.getPctSalvage(era); return (pct == null) ? "" : pct.toString(); @@ -1311,7 +1311,7 @@ public Object getValueAt(int row, int col) { return "?"; } } - + @Override public void setValueAt(Object val, int row, int col) { int era = ERAS[col - 1]; @@ -1366,14 +1366,14 @@ public void setValueAt(Object val, int row, int col) { } } } - + } private static class SalvageEditorTableModel extends DefaultTableModel { ArrayList factions; HashMap> data; private FactionRecord factionRec; - + public SalvageEditorTableModel() { factions = new ArrayList<>(); data = new HashMap<>(); @@ -1417,7 +1417,7 @@ public String getColumnName(int col) { } return ERAS[col - 1] + " (" + getEra(ERAS[col - 1]) + ")"; } - + @Override public int getColumnCount() { if (data == null) { @@ -1425,7 +1425,7 @@ public int getColumnCount() { } return ERAS.length + 1; } - + @Override public int getRowCount() { if (data == null) { @@ -1433,26 +1433,26 @@ public int getRowCount() { } return data.size(); } - + @Override public Class getColumnClass(int col) { return String.class; } - + @Override public Object getValueAt(int row, int col) { if (col == 0) { return factions.get(row); } else { return data.get(factions.get(row)).get(col - 1); - } + } } - + @Override public boolean isCellEditable(int row, int col) { return col > 0; } - + @Override public void setValueAt(Object value, int row, int col) { Integer wt; @@ -1471,7 +1471,7 @@ public void setValueAt(Object value, int row, int col) { factionRec.setSalvage(era, factions.get(row), wt); } } - + public void addEntry(String faction) { factions.add(faction); ArrayList list = new ArrayList<>(); @@ -1481,7 +1481,7 @@ public void addEntry(String faction) { data.put(faction, list); fireTableDataChanged(); } - + public void removeEntry(int row) { for (int era : ERAS) { factionRec.removeSalvage(era, factions.get(row)); @@ -1490,14 +1490,14 @@ public void removeEntry(int row) { factions.remove(row); fireTableDataChanged(); } - + public void copyRow(int row, String newFaction) { ArrayList copyFrom = data.get(factions.get(row)); factions.add(newFaction); ArrayList copyTo = new ArrayList<>(); for (int i = 0; i < ERAS.length; i++) { copyTo.add(copyFrom.get(i)); - } + } data.put(newFaction, copyTo); for (int i = 0; i < ERAS.length; i++) { if (!copyFrom.get(i).isBlank()) { From 6ec6796eb12e8de570536457d76dc7c3a143f73d Mon Sep 17 00:00:00 2001 From: sleet01 Date: Thu, 7 Dec 2023 10:28:47 -0800 Subject: [PATCH 05/22] cleanup temp test files --- .../mechfiles/fighters/.Manbat MBT-23.blk.swp | Bin 12288 -> 0 bytes .../data/mechfiles/fighters/Manbat MBT-23.blk | 105 ------------------ .../fighters/TRO3055U/.Visigoth D.blk.swp | Bin 4096 -> 0 bytes 3 files changed, 105 deletions(-) delete mode 100644 megamek/data/mechfiles/fighters/.Manbat MBT-23.blk.swp delete mode 100644 megamek/data/mechfiles/fighters/Manbat MBT-23.blk delete mode 100644 megamek/data/mechfiles/fighters/TRO3055U/.Visigoth D.blk.swp diff --git a/megamek/data/mechfiles/fighters/.Manbat MBT-23.blk.swp b/megamek/data/mechfiles/fighters/.Manbat MBT-23.blk.swp deleted file mode 100644 index 2346ac8231751b95e5d62802738fdf6d558e8587..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2J!~6Q9Kc^%u@nlZ-6)lg2Bb1zI}SlbYNhiq6|m~iJ=9$A)yX10uu|r^Es)VgtD-Jo~57e{vY@J-tV4= zD6d&qTk)LvTv713SBT3W_SNy%Z-_ttm=b}EjSkbo?LW1~lR?y0w{N0)J{vIKqB-xW zuJqJKKHwURUE9(~lC54x`zpzIbhl?zoa8+jHl=aAa^sQWY_92V{NvZz25Z0?m@sfy z%ob+~X=?h!1J2PS&rVcrU=3IU)_^r&4Oj!#fHhzZSOfoW1ExPEwpikU(K7YuI<@OM z3hl)jum-FFYrqpt@Eoi{4VIw{3vdb^0tfDdLvRp&x?6}J;RpB*zJ@FC z1$+u0!+UTM-i9~fHQ0g#`p|+VtU&{o;WRt}kHah!;Xyb7N8t$E0SDms!$SN9H{oZv z4%gsY_y(@Rm+(1!2AAOyd;}lB`|u8Ifq@rc1)hQ;9E1DdApDB!U*HB@fluHPT!aho z1{hd}RX7Fnkb{$O9Mbo74D81mum-FFYrq#Ngj5V&h=HS(=97~Hud(pqgb6ZuEZ$Gaj&Sz)lQmuOne8J{eM|Mxr<8V82vUBRf9fcHX9CIf>JjTvj9!8|sINZ(( zsgyk2?1a$nek*|x|yyh R-S^G3(Mb>4JLv&?@fR79q&WZp diff --git a/megamek/data/mechfiles/fighters/Manbat MBT-23.blk b/megamek/data/mechfiles/fighters/Manbat MBT-23.blk deleted file mode 100644 index 8a7193eddc9..00000000000 --- a/megamek/data/mechfiles/fighters/Manbat MBT-23.blk +++ /dev/null @@ -1,105 +0,0 @@ -#building block data file - -1 - - -##Write the version number just in case... - -MAM0 - - - -Manbat - - - -MBT-23 - - - -3151 - - - -3151 - - - -Mixed (Clan Chassis) Experimental - - - -Striker - - - -None - - - -6 - - - -10 - - - -1 - - - -800 - - - -2 - - - -16 - - - -6 - - - -1 - - - -126 -105 -105 -84 - - - -BeagleActiveProbe -CLTAG:OMNI -CLLaserAntiMissileSystem - - - - - - - - - -CLLaserAntiMissileSystem - - - - - - -Cargo:OMNI:SIZE:25.0 -CLECMSuite:OMNI - - - -100.0 - - diff --git a/megamek/data/mechfiles/fighters/TRO3055U/.Visigoth D.blk.swp b/megamek/data/mechfiles/fighters/TRO3055U/.Visigoth D.blk.swp deleted file mode 100644 index 4af84da811aeb1a2cda4cc6ff34f1bfd919af0d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmYc?2=nw+u+TGN00IF92Kx&ssj=L*7!tlQGUO%}m1O3DB=7^j5SQHilvMnRfU Date: Mon, 11 Dec 2023 23:57:47 -0800 Subject: [PATCH 06/22] 80% working for ASFs with cargo bays --- .../mechfiles/fighters/Manbat MBT-23A.blk | 109 -------- .../megamek/common/report-messages.properties | 5 + .../client/ui/swing/BombChoicePanel.java | 233 +++++++++++------- megamek/src/megamek/common/Aero.java | 17 +- megamek/src/megamek/common/IAero.java | 3 +- megamek/src/megamek/common/IBomber.java | 46 +++- megamek/src/megamek/common/LandAirMech.java | 17 +- megamek/src/megamek/common/Mounted.java | 7 +- megamek/src/megamek/common/VTOL.java | 9 + .../common/actions/WeaponAttackAction.java | 4 +- .../common/battlevalue/BVCalculator.java | 2 +- .../common/weapons/BombAttackHandler.java | 5 +- megamek/src/megamek/server/GameManager.java | 72 ++++++ megamek/src/megamek/server/ServerHelper.java | 93 ++++--- 14 files changed, 360 insertions(+), 262 deletions(-) delete mode 100644 megamek/data/mechfiles/fighters/Manbat MBT-23A.blk diff --git a/megamek/data/mechfiles/fighters/Manbat MBT-23A.blk b/megamek/data/mechfiles/fighters/Manbat MBT-23A.blk deleted file mode 100644 index adf3896a227..00000000000 --- a/megamek/data/mechfiles/fighters/Manbat MBT-23A.blk +++ /dev/null @@ -1,109 +0,0 @@ -#building block data file - -1 - - -##Write the version number just in case... - -MAM0 - - - -Aero - - - -Manbat - - - -MBT-23A - - - -3145 - - - -3145 - - - -Mixed (Clan Chassis) Experimental - - - -Striker - - - -None - - - -6 - - - -10 - - - -1 - - - -800 - - - -2 - - - -16 - - - -6 - - - -1 - - - -126 -105 -105 -84 - - - -CLECMSuite:OMNI -CLTAG -CLLaserAntiMissileSystem -BeagleActiveProbe:OMNI - - - - - - - - - -CLLaserAntiMissileSystem - - - - - - -Cargo:OMNI:SIZE:25.0 - - - -100.0 - - diff --git a/megamek/i18n/megamek/common/report-messages.properties b/megamek/i18n/megamek/common/report-messages.properties index 9b8446dc4d3..2374797a1bc 100755 --- a/megamek/i18n/megamek/common/report-messages.properties +++ b/megamek/i18n/megamek/common/report-messages.properties @@ -726,6 +726,11 @@ 5543=success! 5550=External heat reduced due to intact heat-dissipating armor! 5560= takes over as of (). +5600=Bomb Bay Explosions------------------- +5601= took damage while dropping internal bay bombs. +5602= must roll under 10+ to avoid bomb bay explosion, rolls . +5603=\ takes damage when remaining bombs explode! +5604=\ avoids bomb bay explosion; internal bombs remaining. #6000's -- Damage Related 6005=\ no effect. diff --git a/megamek/src/megamek/client/ui/swing/BombChoicePanel.java b/megamek/src/megamek/client/ui/swing/BombChoicePanel.java index 90a7442374f..867f5898590 100644 --- a/megamek/src/megamek/client/ui/swing/BombChoicePanel.java +++ b/megamek/src/megamek/client/ui/swing/BombChoicePanel.java @@ -13,15 +13,20 @@ */ package megamek.client.ui.swing; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; +import java.awt.*; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.Serializable; +import java.util.Arrays; +import java.util.HashMap; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import javax.swing.border.TitledBorder; import megamek.common.BombType; import megamek.common.IBomber; @@ -38,15 +43,20 @@ public class BombChoicePanel extends JPanel implements Serializable, ItemListene private static final long serialVersionUID = 483782753790544050L; @SuppressWarnings("rawtypes") - private JComboBox[] b_choices = new JComboBox[BombType.B_NUM]; - private JLabel[] b_labels = new JLabel[BombType.B_NUM]; - private int maxPoints = 0; - private int maxSize = 0; + private JPanel interiorPanel; + private JPanel exteriorPanel; + private HashMap b_choices = new HashMap(); + private HashMap b_labels = new HashMap(); + private HashMap maxPoints = new HashMap(); + private HashMap maxSize = new HashMap(); private int maxRows = (int) Math.ceil(BombType.B_NUM / 2.0); //Variable for MekHQ functionality private int[] typeMax = null; + private final String INTNAME = "Internal"; + private final String EXTNAME = "External"; + //private BombChoicePanel m_bombs; //private JPanel panBombs = new JPanel(); @@ -54,50 +64,99 @@ public BombChoicePanel(IBomber bomber, boolean at2Nukes, boolean allowAdvancedAm this.bomber = bomber; this.at2Nukes = at2Nukes; this.allowAdvancedAmmo = allowAdvancedAmmo; + + initArrays(); initPanel(); } + //Constructor to call from MekHQ to pass in typeMax public BombChoicePanel(IBomber bomber, boolean at2Nukes, boolean allowAdvancedAmmo, int[] typeMax) { this.bomber = bomber; this.at2Nukes = at2Nukes; this.allowAdvancedAmmo = allowAdvancedAmmo; this.typeMax = typeMax; + + initArrays(); initPanel(); } + private void initArrays(){ + // Initialize control arrays + b_choices.put(INTNAME, new JComboBox[BombType.B_NUM]); + b_choices.put(EXTNAME, new JComboBox[BombType.B_NUM]); + b_labels.put(INTNAME, new JLabel[BombType.B_NUM]); + b_labels.put(EXTNAME, new JLabel[BombType.B_NUM]); + maxSize.put(INTNAME, 0); + maxSize.put(EXTNAME, 0); + } + + private int compileBombPoints(int[] choices) { + int currentPoints = 0; + for (int i = 0; i < choices.length; i++) { + currentPoints += choices[i] * BombType.getBombCost(i); + } + return currentPoints; + } + @SuppressWarnings("unchecked") private void initPanel() { - maxPoints = bomber.getMaxBombPoints(); + maxPoints.put(INTNAME, bomber.getMaxIntBombPoints()); + maxPoints.put(EXTNAME, bomber.getMaxExtBombPoints()); + + maxSize.put(INTNAME, bomber.getMaxIntBombSize()); + maxSize.put(EXTNAME, bomber.getMaxExtBombSize()); + + int[] intBombChoices = bomber.getIntBombChoices(); + int[] extBombChoices = bomber.getExtBombChoices(); + + JPanel outer = new JPanel(); + outer.setLayout(new GridLayout(0, 2)); + TitledBorder titledBorder = new TitledBorder(new LineBorder(Color.blue), "Bombs"); + Font font2 = new Font("Verdana", Font.BOLD + Font.ITALIC, 12); + titledBorder.setTitleFont(font2); + EmptyBorder emptyBorder = new EmptyBorder(10, 10, 10, 10); + CompoundBorder compoundBorder = new CompoundBorder(titledBorder, emptyBorder); + outer.setBorder(compoundBorder); + + interiorPanel = initSubPanel(compileBombPoints(intBombChoices), intBombChoices, INTNAME); + exteriorPanel = initSubPanel(compileBombPoints(extBombChoices), extBombChoices, EXTNAME); + + outer.add(interiorPanel); + outer.add(exteriorPanel); + add(outer); + } - maxSize = bomber.getMaxBombSize(); - int[] bombChoices = bomber.getBombChoices(); + private JPanel initSubPanel(int availBombPoints, int[] bombChoices, String title){ - // how many bomb points am I currently using? - int curBombPoints = 0; - for (int i = 0; i < bombChoices.length; i++) { - curBombPoints += bombChoices[i] * BombType.getBombCost(i); - } - int availBombPoints = bomber.getMaxBombPoints() - curBombPoints; + // Set up sub-panel + JPanel inner = new JPanel(); + TitledBorder titledBorder = new TitledBorder(new LineBorder(Color.blue), title); + Font font3 = new Font("Verdana", Font.BOLD + Font.ITALIC, 10); + titledBorder.setTitleFont(font3); + EmptyBorder emptyBorder = new EmptyBorder(10, 10, 10, 10); + CompoundBorder compoundBorder = new CompoundBorder(titledBorder, emptyBorder); + inner.setBorder(compoundBorder); GridBagLayout g = new GridBagLayout(); - setLayout(g); + inner.setLayout(g); GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; int column = 0; int row = 0; for (int type = 0; type < BombType.B_NUM; type++) { - b_labels[type] = new JLabel(); - b_choices[type] = new JComboBox(); + b_labels.get(title)[type] = new JLabel(); + b_choices.get(title)[type] = new JComboBox(); int maxNumBombs = Math.round(availBombPoints / BombType.getBombCost(type)) + bombChoices[type]; - if (BombType.getBombCost(type) > maxSize) { + if (BombType.getBombCost(type) > maxSize.get(title)) { maxNumBombs = 0; } // somehow too many bombs were added - if ((bombChoices[type] * BombType.getBombCost(type)) > maxSize) { - bombChoices[type] = maxSize / BombType.getBombCost(type); + if ((bombChoices[type] * BombType.getBombCost(type)) > maxSize.get(title)) { + bombChoices[type] = maxSize.get(title) / BombType.getBombCost(type); } if (typeMax != null) { @@ -114,23 +173,23 @@ private void initPanel() { maxNumBombs = 0; } - if (maxNumBombs > maxSize) { - maxNumBombs = maxSize; + if (maxNumBombs > maxSize.get(title)) { + maxNumBombs = maxSize.get(title); } for (int x = 0; x <= maxNumBombs; x++) { - b_choices[type].addItem(Integer.toString(x)); + b_choices.get(title)[type].addItem(Integer.toString(x)); } - b_choices[type].setSelectedIndex(bombChoices[type]); - b_labels[type].setText(BombType.getBombName(type)); - b_choices[type].addItemListener(this); + b_choices.get(title)[type].setSelectedIndex(bombChoices[type]); + b_labels.get(title)[type].setText(BombType.getBombName(type)); + b_choices.get(title)[type].addItemListener(this); if ((type == BombType.B_ALAMO) && !at2Nukes) { - b_choices[type].setEnabled(false); + b_choices.get(title)[type].setEnabled(false); } if ((type > BombType.B_TAG) && !allowAdvancedAmmo) { - b_choices[type].setEnabled(false); + b_choices.get(title)[type].setEnabled(false); } if (row >= maxRows) { @@ -141,100 +200,106 @@ private void initPanel() { c.gridx = column; c.gridy = row; c.anchor = GridBagConstraints.EAST; - g.setConstraints(b_labels[type], c); - add(b_labels[type]); + g.setConstraints(b_labels.get(title)[type], c); + inner.add(b_labels.get(title)[type]); c.gridx = column + 1; c.gridy = row; c.anchor = GridBagConstraints.WEST; - g.setConstraints(b_choices[type], c); - add(b_choices[type]); + g.setConstraints(b_choices.get(title)[type], c); + inner.add(b_choices.get(title)[type]); row++; } - } - - private void initIntFrame(int intBombPoints) { - } - - private void initExtFrame(int extBombPoints) { + return inner; } @Override @SuppressWarnings("unchecked") public void itemStateChanged(ItemEvent ie) { - int[] current = new int[BombType.B_NUM]; - int curPoints = 0; - for (int type = 0; type < BombType.B_NUM; type++) { - current[type] = b_choices[type].getSelectedIndex(); - curPoints += current[type] * BombType.getBombCost(type); - } + for (String title: new String[]{INTNAME, EXTNAME}){ + int[] current = new int[BombType.B_NUM]; + int curPoints = 0; + for (int type = 0; type < BombType.B_NUM; type++) { + current[type] = b_choices.get(title)[type].getSelectedIndex(); + curPoints += current[type] * BombType.getBombCost(type); + } - int availBombPoints = maxPoints - curPoints; + int availBombPoints = maxPoints.get(title) - curPoints; - for (int type = 0; type < BombType.B_NUM; type++) { - b_choices[type].removeItemListener(this); - b_choices[type].removeAllItems(); - int maxNumBombs = Math.round(availBombPoints / BombType.getBombCost(type)) + current[type]; + for (int type = 0; type < BombType.B_NUM; type++) { + b_choices.get(title)[type].removeItemListener(this); + b_choices.get(title)[type].removeAllItems(); + int maxNumBombs = Math.round(availBombPoints / BombType.getBombCost(type)) + current[type]; - if (typeMax != null) { - if ((maxNumBombs > 0) && (maxNumBombs > typeMax[type])) { - maxNumBombs = typeMax[type]; + if (typeMax != null) { + if ((maxNumBombs > 0) && (maxNumBombs > typeMax[type])) { + maxNumBombs = typeMax[type]; + } } - } - if (current[type] > maxNumBombs) { - maxNumBombs = current[type]; - } + if (current[type] > maxNumBombs) { + maxNumBombs = current[type]; + } - if (maxNumBombs < 0) { - maxNumBombs = 0; - } + if (maxNumBombs < 0) { + maxNumBombs = 0; + } - if (maxNumBombs > maxSize) { - maxNumBombs = maxSize; - } + if (maxNumBombs > maxSize.get(title)) { + maxNumBombs = maxSize.get(title); + } - for (int x = 0; x <= maxNumBombs; x++) { - b_choices[type].addItem(Integer.toString(x)); + for (int x = 0; x <= maxNumBombs; x++) { + b_choices.get(title)[type].addItem(Integer.toString(x)); + } + b_choices.get(title)[type].setSelectedIndex(current[type]); + b_choices.get(title)[type].addItemListener(this); } - b_choices[type].setSelectedIndex(current[type]); - b_choices[type].addItemListener(this); } } public void applyChoice() { int[] choices = new int[BombType.B_NUM]; + // Internal bombs for (int type = 0; type < BombType.B_NUM; type++) { - choices[type] = b_choices[type].getSelectedIndex(); + choices[type] = b_choices.get(INTNAME)[type].getSelectedIndex(); } - - bomber.setBombChoices(choices); + bomber.setIntBombChoices(choices); + // External bombs + for (int type = 0; type < BombType.B_NUM; type++) { + choices[type] = b_choices.get(EXTNAME)[type].getSelectedIndex(); + } + bomber.setExtBombChoices(choices); } public int[] getChoice() { int[] choices = new int[BombType.B_NUM]; + Arrays.fill(choices, 0); + for (int type = 0; type < BombType.B_NUM; type++) { - choices[type] = b_choices[type].getSelectedIndex(); + choices[type] += b_choices.get(INTNAME)[type].getSelectedIndex() + b_choices.get(EXTNAME)[type].getSelectedIndex(); } return choices; } @Override public void setEnabled(boolean enabled) { - for (int type = 0; type < BombType.B_NUM; type++) { - if ((type == BombType.B_ALAMO) - && !at2Nukes) { - b_choices[type].setEnabled(false); - } else if ((type > BombType.B_TAG) - && !allowAdvancedAmmo) { - b_choices[type].setEnabled(false); - } else if ((type == BombType.B_ASEW) - || (type == BombType.B_ALAMO) - || (type == BombType.B_TAG)) { - b_choices[type].setEnabled(false); - } else { - b_choices[type].setEnabled(enabled); + for (String title : new String[]{INTNAME, EXTNAME}) { + for (int type = 0; type < BombType.B_NUM; type++) { + if ((type == BombType.B_ALAMO) + && !at2Nukes) { + b_choices.get(title)[type].setEnabled(false); + } else if ((type > BombType.B_TAG) + && !allowAdvancedAmmo) { + b_choices.get(title)[type].setEnabled(false); + } else if ((type == BombType.B_ASEW) + || (type == BombType.B_ALAMO) + || (type == BombType.B_TAG)) { + b_choices.get(title)[type].setEnabled(false); + } else { + b_choices.get(title)[type].setEnabled(enabled); + } } } } diff --git a/megamek/src/megamek/common/Aero.java b/megamek/src/megamek/common/Aero.java index 8557ce1d572..34ed6986741 100644 --- a/megamek/src/megamek/common/Aero.java +++ b/megamek/src/megamek/common/Aero.java @@ -159,6 +159,8 @@ public String[] getLocationNames() { protected int[] intBombChoices = new int[BombType.B_NUM]; protected int[] extBombChoices = new int[BombType.B_NUM]; + protected boolean usedInternalBombs = false; + // fuel - number of fuel points private int fuel = 0; private int currentfuel = 0; @@ -503,7 +505,7 @@ public int[] getIntBombChoices() { @Override public void setIntBombChoices(int[] bc) { if (bc.length == intBombChoices.length) { - intBombChoices = bc; + intBombChoices = bc.clone(); } } @@ -515,7 +517,7 @@ public int[] getExtBombChoices() { @Override public void setExtBombChoices(int[] bc) { if (bc.length == extBombChoices.length) { - extBombChoices = bc; + extBombChoices = bc.clone(); } } @@ -531,6 +533,14 @@ public int reduceMPByBombLoad(int t) { return t; } + public void setUsedInternalBombs(boolean b){ + usedInternalBombs = b; + } + + public boolean getUsedInternalBombs() { + return usedInternalBombs; + } + public void setWhoFirst() { whoFirst = Compute.randomInt(500); } @@ -1010,6 +1020,9 @@ public void newRound(int roundNumber) { setWhoFirst(); resetAltLossThisRound(); + + // Reset usedInternalBombs + setUsedInternalBombs(false); } /** diff --git a/megamek/src/megamek/common/IAero.java b/megamek/src/megamek/common/IAero.java index baf5da77177..6c902f85af4 100644 --- a/megamek/src/megamek/common/IAero.java +++ b/megamek/src/megamek/common/IAero.java @@ -170,9 +170,10 @@ default boolean requiresFuel() { void autoSetCapArmor(); void autoSetFatalThresh(); - + int getAltitude(); + /** * Iterate through current weapons and count the number in each capital * fighter location. diff --git a/megamek/src/megamek/common/IBomber.java b/megamek/src/megamek/common/IBomber.java index 4d12245d20e..1725f419efd 100644 --- a/megamek/src/megamek/common/IBomber.java +++ b/megamek/src/megamek/common/IBomber.java @@ -36,19 +36,30 @@ public interface IBomber { String DIVE_BOMB_ATTACK = "DiveBombAttack"; String ALT_BOMB_ATTACK = "AltBombAttack"; + void setUsedInternalBombs(boolean b); + boolean getUsedInternalBombs(); + /** * @return The total number of bomb points that the bomber can carry. */ int getMaxBombPoints(); /** - * Fighters and VTOLs can carry any size bomb up to the maximum number of points, but LAMs are limited - * to the number of bays in a single location. + * Fighters and VTOLs can carry any size bomb up to the maximum number of points per location (internal/external), + * but LAMs are limited to the number of bays in a single location. + * + * @return The largest single bomb that can be carried internally. + */ + default int getMaxIntBombSize() { + return getMaxIntBombPoints(); + } + + /** * - * @return The largest single bomb that can be carried + * @return The largest single bomb that can be carried externally. */ - default int getMaxBombSize() { - return getMaxBombPoints(); + default int getMaxExtBombSize() { + return getMaxExtBombPoints(); } /** @@ -129,6 +140,17 @@ default int getExternalBombPoints() { return getBombPoints(true); } + /** + * + * @return total damage from remaining bombs + */ + default int getInternalBombsDamageTotal() { + int total = getBombs().stream().filter( + b -> b.isInternalBomb() + ).mapToInt(b -> b.getExplosionDamage() * b.getUsableShotsLeft()).sum(); + return total; + } + /** * @return The number of points taken up by all mounted bombs, or just external */ @@ -185,9 +207,10 @@ default void applyBombs() { } } } + // Now external bombs for (int type : eSorted) { - for (int i = 0; i < getIntBombChoices()[type]; i++) { + for (int i = 0; i < getExtBombChoices()[type]; i++) { int loc = availableBombLocation(BombType.bombCosts[type]); if ((type == BombType.B_ALAMO) && !game.getOptions().booleanOption(OptionsConstants.ADVAERORULES_AT2_NUKES)) { @@ -213,10 +236,8 @@ default void applyBombs() { private void applyBombEquipment(int type, int loc, boolean internal){ try { EquipmentType et = EquipmentType.get(BombType.getBombInternalName(type)); - if (internal) { - et.setFlags(BombType.F_INTERNAL_BOMB); - } - ((Entity) this).addEquipment(et, loc, false); + Mounted m = ((Entity) this).addEquipment(et, loc, false); + m.setInternalBomb(internal); } catch (LocationFullException ignored) { } @@ -227,15 +248,14 @@ private void applyBombWeapons(int type, int loc, boolean internal){ Mounted m; try { EquipmentType et = EquipmentType.get(BombType.getBombWeaponName(type)); - if (internal) { - et.setFlags(BombType.F_INTERNAL_BOMB); - } m = ((Entity) this).addBomb(et, loc); + m.setInternalBomb(internal); // Add bomb itself as single-shot ammo. if (type != BombType.B_TAG) { Mounted ammo = new Mounted((Entity) this, EquipmentType.get(BombType.getBombInternalName(type))); ammo.setShotsLeft(1); + ammo.setInternalBomb(internal); m.setLinked(ammo); ((Entity) this).addEquipment(ammo, loc, false); diff --git a/megamek/src/megamek/common/LandAirMech.java b/megamek/src/megamek/common/LandAirMech.java index 84d1e936ef2..9d949c18efe 100644 --- a/megamek/src/megamek/common/LandAirMech.java +++ b/megamek/src/megamek/common/LandAirMech.java @@ -1100,8 +1100,12 @@ public int getMaxIntBombPoints() { return countWorkingMisc(MiscType.F_BOMB_BAY); } + /** + * + * @return Largest empty bay size + */ @Override - public int getMaxBombSize() { + public int getMaxIntBombSize() { return Math.max(emptyBaysInLoc(LOC_CT), Math.max(emptyBaysInLoc(LOC_RT), emptyBaysInLoc(LOC_LT))); } @@ -1117,6 +1121,17 @@ public void setIntBombChoices(int[] bc) { } } + public void setUsedInternalBombs(boolean b){ + // Do nothing + } + + public boolean getUsedInternalBombs() { + // Currently not possible + return false; + } + + + @Override public int[] getExtBombChoices() { return extBombChoices; diff --git a/megamek/src/megamek/common/Mounted.java b/megamek/src/megamek/common/Mounted.java index a70f1c95d57..fad9c5262e4 100644 --- a/megamek/src/megamek/common/Mounted.java +++ b/megamek/src/megamek/common/Mounted.java @@ -110,6 +110,7 @@ public class Mounted implements Serializable, RoundUpdated, PhaseUpdated { // bomb stuff private boolean bombMounted = false; + private boolean isInternalBomb = false; // mine type private int mineType = MINE_NONE; @@ -1631,13 +1632,17 @@ public boolean isGroundBomb() { getType().hasFlag(AmmoType.F_GROUND_BOMB); } + public void setInternalBomb(boolean internal) { + isInternalBomb = internal; + } + /** * Convenience method to determine if a bomb munition is mounted EXternally (reduces MP) or INternally (no * MP reduction). * @return True if */ public boolean isInternalBomb() { - return getType().hasFlag(AmmoType.F_INTERNAL_BOMB); + return isInternalBomb; } // is ammo in the same bay as the weapon diff --git a/megamek/src/megamek/common/VTOL.java b/megamek/src/megamek/common/VTOL.java index 78a6c54d31c..971935648f7 100644 --- a/megamek/src/megamek/common/VTOL.java +++ b/megamek/src/megamek/common/VTOL.java @@ -301,6 +301,15 @@ public int reduceMPByBombLoad(int t) { return Math.max(0, (t - (int) this.getBombs().stream().filter(m -> (m.getUsableShotsLeft() > 0)).count())); } + public void setUsedInternalBombs(boolean b){ + // Do nothing + } + + public boolean getUsedInternalBombs() { + // Currently not possible + return false; + } + @Override public Targetable getVTOLBombTarget() { return bombTarget; diff --git a/megamek/src/megamek/common/actions/WeaponAttackAction.java b/megamek/src/megamek/common/actions/WeaponAttackAction.java index d7e20f18d30..97300e73bc3 100644 --- a/megamek/src/megamek/common/actions/WeaponAttackAction.java +++ b/megamek/src/megamek/common/actions/WeaponAttackAction.java @@ -1615,7 +1615,7 @@ private static String toHitIsImpossible(Game game, Entity ae, int attackerId, Ta if ((ae.getAltitude() > 3) && isStrafing) { return Messages.getString("WeaponAttackAction.AttackerTooHigh"); } - // Additional Nape-of-Earth restrictions for strafing + // Additional Nap-of-Earth restrictions for strafing if ((ae.getAltitude() == 1) && isStrafing) { Vector passedThrough = ae.getPassedThrough(); if (passedThrough.isEmpty() || passedThrough.get(0).equals(target.getPosition())) { @@ -3593,7 +3593,7 @@ private static ToHitData compileAeroAttackerToHitMods(Game game, Entity ae, Targ // So it's here instead of with other weapon mods that apply across the board if ((wtype != null) && ((wtype.ammoType == AmmoType.T_GAUSS_HEAVY) || - (wtype.ammoType == AmmoType.T_IGAUSS_HEAVY)) && + (wtype.ammoType == AmmoType.T_IGAUSS_HEAVY)) && !(ae instanceof Dropship) && !(ae instanceof Jumpship)) { toHit.addModifier(+1, Messages.getString("WeaponAttackAction.FighterHeavyGauss")); diff --git a/megamek/src/megamek/common/battlevalue/BVCalculator.java b/megamek/src/megamek/common/battlevalue/BVCalculator.java index 9c9f2332c3d..62ec6b1ddca 100644 --- a/megamek/src/megamek/common/battlevalue/BVCalculator.java +++ b/megamek/src/megamek/common/battlevalue/BVCalculator.java @@ -711,7 +711,7 @@ protected double arcFactor(Mounted equipment) { } /** - * Forwards to {@link #processWeapon(Mounted, boolean, boolean, int)} with a weaponCount + * Forwards to {@link #maintainUsedSearchlight(Mounted, boolean, boolean, int)} with a weaponCount * parameter of 1 (single weapon). */ protected double processWeapon(Mounted weapon, boolean showInReport, diff --git a/megamek/src/megamek/common/weapons/BombAttackHandler.java b/megamek/src/megamek/common/weapons/BombAttackHandler.java index a110981265b..5161936bfe0 100644 --- a/megamek/src/megamek/common/weapons/BombAttackHandler.java +++ b/megamek/src/megamek/common/weapons/BombAttackHandler.java @@ -63,6 +63,9 @@ protected void useAmmo() { && (bomb.getUsableShotsLeft() > 0) && (((BombType) bomb.getType()).getBombType() == type)) { bomb.setShotsLeft(0); + if (bomb.isInternalBomb()) { + ((IBomber) ae).setUsedInternalBombs(true); + } break; } } @@ -73,7 +76,7 @@ protected void useAmmo() { /* * (non-Javadoc) - * + * * @see megamek.common.weapons.AttackHandler#handle(int, java.util.Vector) */ @Override diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index 4646159b310..1b42c755c70 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -2027,6 +2027,7 @@ public boolean accept(Entity entity) { addReport(checkForTraitors()); // write End Phase header addReport(new Report(5005, Report.PUBLIC)); + addReport(resolveInternalBombHits()); checkLayExplosives(); resolveHarJelRepairs(); resolveEmergencyCoolantSystem(); @@ -20887,6 +20888,77 @@ private Vector resolveControl(Entity e) { return vReport; } + /** + * Check all + * @return + */ + private Vector resolveInternalBombHits() { + Vector vFullReport = new Vector<>(); + vFullReport.add(new Report(5600, Report.PUBLIC)); + for (Iterator i = game.getEntities(); i.hasNext(); ) { + vFullReport.addAll(resolveInternalBombHit(i.next())); + } + return vFullReport; + } + + /** + * Resolves and reports all control skill rolls for a single aero or airborne LAM in airmech mode. + */ + private Vector resolveInternalBombHit(Entity e) { + Vector vReport = new Vector<>(); + if (e.isDoomed() || e.isDestroyed() || !e.isDeployed()) { + return vReport; + } + + Report r; + Aero b = null; + + if (e.isAero() && (e.isAirborne() || e.isSpaceborne())) { + b = (Aero) e; + + if (b.getUsedInternalBombs()) { + int id = e.getId(); + Vector rolls = new Vector<>(); + + // Header + r = new Report(5601); + r.subject = id; + r.addDesc(e); + vReport.add(r); + + // Roll + int rollTarget = 10; + int roll = Compute.d6(2); + boolean explosion = roll >= rollTarget; + r = new Report(5602); + r.indent(); + r.subject = id; + r.addDesc(e); + r.add(roll); + vReport.add(r); + + // Outcome + r = (explosion) ? new Report(5603) : new Report(5604); + r.indent(); + r.subject = id; + r.addDesc(e); + int bombsLeft = b.getBombs().stream().mapToInt(Mounted::getUsableShotsLeft).sum(); + int bombDamage = b.getInternalBombsDamageTotal(); + if (explosion) { + r.add(bombDamage); + } + r.add(bombsLeft); + vReport.add(r); + // Deal damage + if (explosion) { + HitData ht = null; + vReport.addAll(damageEntity(e, ht, bombDamage, true, DamageType.NONE,true)); + } + } + } + return vReport; + } + /** * Inflict damage on a pilot * diff --git a/megamek/src/megamek/server/ServerHelper.java b/megamek/src/megamek/server/ServerHelper.java index ae7e9350725..6130a790b6b 100644 --- a/megamek/src/megamek/server/ServerHelper.java +++ b/megamek/src/megamek/server/ServerHelper.java @@ -26,12 +26,12 @@ /** * This class contains computations carried out by the Server class. - * Methods put in here should be static and self-contained. + * Methods put in here should be static and self-contained. * @author NickAragua */ public class ServerHelper { /** - * Determines if the given entity is an infantry unit in the given hex is "in the open" + * Determines if the given entity is an infantry unit in the given hex is "in the open" * (and thus subject to double damage from attacks) * @param te Target entity. * @param te_hex Hex where target entity is located. @@ -41,9 +41,9 @@ public class ServerHelper { * @param ignoreInfantryDoubleDamage Whether we should ignore double damage to infantry. * @return Whether the infantry unit can be considered to be "in the open" */ - public static boolean infantryInOpen(Entity te, Hex te_hex, Game game, - boolean isPlatoon, boolean ammoExplosion, boolean ignoreInfantryDoubleDamage) { - + public static boolean infantryInOpen(Entity te, Hex te_hex, Game game, + boolean isPlatoon, boolean ammoExplosion, boolean ignoreInfantryDoubleDamage) { + if (isPlatoon && !te.isDestroyed() && !te.isDoomed() && !ignoreInfantryDoubleDamage && (((Infantry) te).getDugIn() != Infantry.DUG_IN_COMPLETE)) { @@ -61,17 +61,17 @@ public static boolean infantryInOpen(Entity te, Hex te_hex, Game game, return true; } } - + return false; } - + /** * Worker function that handles heat as applied to aerospace fighter */ - public static void resolveAeroHeat(Game game, Entity entity, Vector vPhaseReport, Vector rhsReports, - int radicalHSBonus, int hotDogMod, GameManager s) { + public static void resolveAeroHeat(Game game, Entity entity, Vector vPhaseReport, Vector rhsReports, + int radicalHSBonus, int hotDogMod, GameManager s) { Report r; - + // If this aero is part of a squadron, we will deal with its // heat with the fighter squadron if (game.getEntity(entity.getTransportId()) instanceof FighterSquadron) { @@ -80,8 +80,8 @@ public static void resolveAeroHeat(Game game, Entity entity, Vector vPha // should we even bother? if (entity.isDestroyed() || entity.isDoomed() - || entity.getCrew().isDoomed() - || entity.getCrew().isDead()) { + || entity.getCrew().isDoomed() + || entity.getCrew().isDead()) { return; } @@ -405,7 +405,7 @@ else if ((entity.heat >= 14) && !entity.isShutDown()) { } } - public static void adjustHeatExtremeTemp(Game game, Entity entity, Vector vPhaseReport) { + public static void adjustHeatExtremeTemp(Game game, Entity entity, Vector vPhaseReport) { Report r; int tempDiff = game.getPlanetaryConditions().getTemperatureDifference(50, -30); boolean heatArmor = false; @@ -451,15 +451,15 @@ public static void sinkToBottom(Entity entity) { if ((entity == null) || !entity.getGame().getBoard().contains(entity.getPosition())) { return; } - + Hex fallHex = entity.getGame().getBoard().getHex(entity.getPosition()); int waterDepth = 0; - + // we're going hull down, we still sink to the bottom if appropriate if (fallHex.containsTerrain(Terrains.WATER)) { boolean hexHasBridge = fallHex.containsTerrain(Terrains.BRIDGE_CF); boolean entityOnTopOfBridge = hexHasBridge && (entity.getElevation() == fallHex.ceiling()); - + if (!entityOnTopOfBridge) { // *Only* use this if there actually is water in the hex, otherwise // we get Terrain.LEVEL_NONE, i.e. Integer.minValue... @@ -468,21 +468,20 @@ public static void sinkToBottom(Entity entity) { } } } - + public static void checkAndApplyMagmaCrust(Hex hex, int elevation, Entity entity, Coords curPos, - boolean jumpLanding, Vector vPhaseReport, GameManager gameManager) { - + boolean jumpLanding, Vector vPhaseReport, GameManager gameManager) { + if ((hex.terrainLevel(Terrains.MAGMA) == 1) && (elevation == 0) && (entity.getMovementMode() != EntityMovementMode.HOVER)) { int reportID = jumpLanding ? 2396 : 2395; - Roll diceRoll = Compute.rollD6(1); Report r = new Report(reportID); r.addDesc(entity); r.add(diceRoll); r.subject = entity.getId(); vPhaseReport.add(r); - + int rollTarget = jumpLanding ? 4 : 6; if (diceRoll.getIntValue() >= rollTarget) { @@ -513,7 +512,7 @@ public static void checkEnteringMagma(Hex hex, int elevation, Entity entity, Gam */ public static void detectMinefields(Game game, Vector vPhaseReport, GameManager gameManager) { boolean tacOpsBap = game.getOptions().booleanOption(OptionsConstants.ADVANCED_TACOPS_BAP); - + // if the entity is on the board // and it either a) hasn't moved or b) we're not using TacOps BAP rules // if we are not using the TacOps BAP rules, that means we only check the entity's final hex @@ -526,90 +525,90 @@ public static void detectMinefields(Game game, Vector vPhaseReport, Game } } } - + /** * Checks for minefields within the entity's active probe range. * @return True if any minefields have been detected. */ - public static boolean detectMinefields(Game game, Entity entity, Coords coords, - Vector vPhaseReport, GameManager gameManager) { + public static boolean detectMinefields(Game game, Entity entity, Coords coords, + Vector vPhaseReport, GameManager gameManager) { if (!game.getOptions().booleanOption(OptionsConstants.ADVANCED_MINEFIELDS)) { return false; } - + // can't detect minefields if the coordinates are invalid if (coords == null) { return false; } - + // can't detect minefields if there aren't any to detect if (!game.getMinedCoords().hasMoreElements()) { return false; } - + // can't detect minefields if we have no probe int probeRange = entity.getBAPRange(); if (probeRange <= 0) { return false; } - + boolean minefieldDetected = false; - + for (int distance = 1; distance <= probeRange; distance++) { for (Coords potentialMineCoords : coords.allAtDistance(distance)) { if (!game.getBoard().contains(potentialMineCoords)) { continue; } - + for (Minefield minefield : game.getMinefields(potentialMineCoords)) { // no need to roll for already revealed minefields if (entity.getOwner().containsMinefield(minefield)) { continue; } - + int roll = Compute.d6(2); - + if (roll >= minefield.getBAPDetectionTarget()) { minefieldDetected = true; - + Report r = new Report(2163); r.subject = entity.getId(); r.add(entity.getShortName(), true); r.add(potentialMineCoords.toFriendlyString()); vPhaseReport.add(r); - + gameManager.revealMinefield(entity.getOwner(), minefield); } } } } - + return minefieldDetected; } - + /** * Checks to see if any units can detected hidden units. */ public static boolean detectHiddenUnits(Game game, Entity detector, Coords detectorCoords, - Vector vPhaseReport, GameManager gameManager) { + Vector vPhaseReport, GameManager gameManager) { // If hidden units aren't on, nothing to do if (!game.getOptions().booleanOption(OptionsConstants.ADVANCED_HIDDEN_UNITS)) { return false; } - + // Units without a position won't be able to detect // check for this before calculating BAP range, as that's expensive if ((detector.getPosition() == null) || (detectorCoords == null)) { return false; } - + int probeRange = detector.getBAPRange(); - + // if no probe, save ourselves a few loops if (probeRange <= 0) { return false; } - + // Get all hidden units in probe range List hiddenUnits = new ArrayList<>(); for (Coords coords : detectorCoords.allAtDistanceOrLess(probeRange)) { @@ -629,8 +628,8 @@ public static boolean detectHiddenUnits(Game game, Entity detector, Coords detec boolean detectorHasBloodhound = detector.hasWorkingMisc(MiscType.F_BLOODHOUND); boolean hiddenUnitFound = false; - - for (Entity detected : hiddenUnits) { + + for (Entity detected : hiddenUnits) { // Can only detect units within the probes range int dist = detector.getPosition().distance(detected.getPosition()); boolean beyondPointBlankRange = dist > 1; @@ -674,7 +673,7 @@ public static boolean detectHiddenUnits(Game game, Entity detector, Coords detec Report.addNewline(vPhaseReport); reportPlayers.add(detector.getOwnerId()); reportPlayers.add(detected.getOwnerId()); - + hiddenUnitFound = true; } } @@ -685,10 +684,10 @@ public static boolean detectHiddenUnits(Game game, Entity detector, Coords detec gameManager.send(playerId, gameManager.createSpecialReportPacket()); } } - + return hiddenUnitFound; } - + /** * Loop through the game and clear 'blood stalker' flag for * any entities that have the given unit as the blood stalker target. From 505f452439ec9747384e59c98447d8adfe35cd53 Mon Sep 17 00:00:00 2001 From: sleet01 Date: Thu, 14 Dec 2023 12:13:04 -0800 Subject: [PATCH 07/22] Post-rebase to quirks-in-unit-files --- megamek/build.gradle | 20 +++--- .../megamek/common/report-messages.properties | 8 +-- .../client/ui/swing/BombPayloadDialog.java | 2 + .../client/ui/swing/FiringDisplay.java | 64 +++++++++++++------ megamek/src/megamek/common/Entity.java | 26 +++++++- megamek/src/megamek/common/IBomber.java | 13 +++- .../common/weapons/AmmoWeaponHandler.java | 25 +++++--- .../common/weapons/BombAttackHandler.java | 3 +- .../megamek/common/weapons/WeaponHandler.java | 1 + megamek/src/megamek/server/GameManager.java | 24 ++++--- 10 files changed, 125 insertions(+), 61 deletions(-) diff --git a/megamek/build.gradle b/megamek/build.gradle index eb97a2ba420..6f8528ee265 100644 --- a/megamek/build.gradle +++ b/megamek/build.gradle @@ -46,7 +46,7 @@ dependencies { mainClassName = 'megamek.MegaMek' ext { - mmJvmOptions = ['-Xmx2048m', '--add-opens', 'java.base/java.util=ALL-UNNAMED', '--add-opens', 'java.base/java.util.concurrent=ALL-UNNAMED'] + mmJvmOptions = ['-Xmx4096m', '--add-opens', 'java.base/java.util=ALL-UNNAMED', '--add-opens', 'java.base/java.util.concurrent=ALL-UNNAMED', '-Dsun.awt.disablegrab=true'] data = 'data' unitFiles = "${data}/mechfiles" rats = "${data}/rat" @@ -101,12 +101,12 @@ task equipmentList(type: JavaExec, dependsOn: jar) { task copyFiles(type: Copy) { description = 'Stages files that are to be copied into the distribution.' - + dependsOn officialUnitList dependsOn equipmentList - + from projectDir - + include "${data}/**" include "${docs}/**" include "${mmconf}/**" @@ -116,9 +116,9 @@ task copyFiles(type: Copy) { exclude { it.file.isDirectory() && (it.file in file(unitFiles).listFiles()) } exclude "${rats}/**" include "${userdata}/" - + into fileStagingDir - + inputs.dir "${data}" inputs.dir "${docs}" inputs.dir "${mmconf}" @@ -176,7 +176,7 @@ task stageFiles { dependsOn unitFilesZip dependsOn ratZip dependsOn deleteAtlasedImages - + doLast { mkdir "${fileStagingDir}/${log}" } @@ -249,7 +249,7 @@ createExe { new File("${buildDir}/${outputDir}/${iniFile}").text = """# Launch4j runtime config # you can add arguments here that will be processed by the JVM at runtime ${project.ext.mmJvmOptions.join('\n')} -""" +""" } } @@ -320,7 +320,7 @@ task buildFromRepo (type: GradleBuild) { description = 'Assembles the distribution packages in the clean repository copy' group = 'distribution' dependsOn cloneMMRepo - + dir = "${mmRepoDir}" tasks = [ ':megamek:assembleDist' ] } @@ -329,7 +329,7 @@ task release (type: Copy) { description = 'Builds the release packages from the repository and copies them into the project build directory' group = 'distribution' dependsOn buildFromRepo - + from (buildFromRepo) into "${distributionDir}" } diff --git a/megamek/i18n/megamek/common/report-messages.properties b/megamek/i18n/megamek/common/report-messages.properties index 2374797a1bc..572f8b7ace7 100755 --- a/megamek/i18n/megamek/common/report-messages.properties +++ b/megamek/i18n/megamek/common/report-messages.properties @@ -727,10 +727,10 @@ 5550=External heat reduced due to intact heat-dissipating armor! 5560= takes over as of (). 5600=Bomb Bay Explosions------------------- -5601= took damage while dropping internal bay bombs. -5602= must roll under 10+ to avoid bomb bay explosion, rolls . -5603=\ takes damage when remaining bombs explode! -5604=\ avoids bomb bay explosion; internal bombs remaining. +5601= () took damage while dropping internal bay bombs. +5602= () must roll under + to avoid bomb bay explosion, rolls . +5603=\ () takes damage when remaining bombs explode! +5604=\ () avoids bomb bay explosion; internal bombs remaining. #6000's -- Damage Related 6005=\ no effect. diff --git a/megamek/src/megamek/client/ui/swing/BombPayloadDialog.java b/megamek/src/megamek/client/ui/swing/BombPayloadDialog.java index e71703357fd..f538c77984a 100644 --- a/megamek/src/megamek/client/ui/swing/BombPayloadDialog.java +++ b/megamek/src/megamek/client/ui/swing/BombPayloadDialog.java @@ -47,6 +47,8 @@ public class BombPayloadDialog extends JDialog implements ActionListener, ItemLi private boolean confirm = false; private int limit; + private int internalBombLimit=6; + private int internalBombCount=0; private int[] bombs; private JPanel panButtons = new JPanel(); diff --git a/megamek/src/megamek/client/ui/swing/FiringDisplay.java b/megamek/src/megamek/client/ui/swing/FiringDisplay.java index 5e9e943fd3e..445a4cd5f63 100644 --- a/megamek/src/megamek/client/ui/swing/FiringDisplay.java +++ b/megamek/src/megamek/client/ui/swing/FiringDisplay.java @@ -1501,31 +1501,53 @@ private int[] getBombPayload(boolean isSpace, int limit) { if (!ce().isBomber()) { return payload; } - int[] loadout = ce().getBombLoadout(); - // this part is ugly, but we need to find any other bombing attacks by - // this - // entity in the attack list and subtract those payloads from the - // loadout - for (EntityAction o : attacks) { - if (o instanceof WeaponAttackAction) { - WeaponAttackAction waa = (WeaponAttackAction) o; - if (waa.getEntityId() == ce().getId()) { - int[] priorLoad = waa.getBombPayload(); - for (int i = 0; i < priorLoad.length; i++) { - loadout[i] = loadout[i] - priorLoad[i]; + + HashMap loadouts = new HashMap(); + loadouts.put("external", ce().getExternalBombLoadout()); + loadouts.put("internal", ce().getInternalBombLoadout()); + String[] titles = new String[] {"internal", "external"}; + + for (String title: titles){ + int[] loadout = loadouts.get(title); + + // this part is ugly, but we need to find any other bombing attacks by + // this + // entity in the attack list and subtract those payloads from the + // loadout + for (EntityAction o : attacks) { + if (o instanceof WeaponAttackAction) { + WeaponAttackAction waa = (WeaponAttackAction) o; + if (waa.getEntityId() == ce().getId()) { + int[] priorLoad = waa.getBombPayload(); + for (int i = 0; i < priorLoad.length; i++) { + loadout[i] = loadout[i] - priorLoad[i]; + } } } } - } - int numFighters = ce().getActiveSubEntities().size(); - BombPayloadDialog bombsDialog = new BombPayloadDialog( - clientgui.frame, - Messages.getString("FiringDisplay.BombNumberDialog" + ".title"), - loadout, isSpace, false, limit, numFighters); - bombsDialog.setVisible(true); - if (bombsDialog.getAnswer()) { - payload = bombsDialog.getChoices(); + // Don't bother preparing a dialog for bombs that don't exist. + if (Arrays.stream(loadout).sum() == 0){ + continue; + } + + // Internal bay dive bombing is limited to 6 bombs at a time, but other limits may apply + if ("internal".equals(title)) { + limit = (limit <= -1) ? 6 : Math.min(6, limit); + } + + int numFighters = ce().getActiveSubEntities().size(); + BombPayloadDialog bombsDialog = new BombPayloadDialog( + clientgui.frame, + Messages.getString("FiringDisplay.BombNumberDialog" + ".title") + ", " + title, + loadout, isSpace, false, limit, numFighters); + bombsDialog.setVisible(true); + if (bombsDialog.getAnswer()) { + int[] choices = bombsDialog.getChoices(); + for (int i = 0; i < choices.length; i++) { + payload[i] += choices[i]; + } + } } return payload; } diff --git a/megamek/src/megamek/common/Entity.java b/megamek/src/megamek/common/Entity.java index 829e75ecc2f..45008d50067 100644 --- a/megamek/src/megamek/common/Entity.java +++ b/megamek/src/megamek/common/Entity.java @@ -12976,17 +12976,39 @@ public void setInitialBV(int bv) { * @return */ public int[] getBombLoadout() { + return getBombLoadout(false); + } + + public int[] getBombLoadout(boolean internalOnly) { int[] loadout = new int[BombType.B_NUM]; for (Mounted bomb : getBombs()) { if ((bomb.getUsableShotsLeft() > 0) && (bomb.getType() instanceof BombType)) { - int type = ((BombType) bomb.getType()).getBombType(); - loadout[type] = loadout[type] + 1; + // Either count all bombs, or just internal bombs + if (internalOnly && !bomb.isInternalBomb()) { + continue; + } else { + int type = ((BombType) bomb.getType()).getBombType(); + loadout[type] = loadout[type] + 1; + } } } return loadout; } + public int[] getInternalBombLoadout() { + return getBombLoadout(true); + } + + public int[] getExternalBombLoadout() { + int[] allBombs = getBombLoadout(); + int[] intBombs = getBombLoadout(true); + for (int i = 0; i < allBombs.length; i++) { + allBombs[i] -= intBombs[i]; + } + return allBombs; + } + @Override public Map getSecondaryPositions() { return secondaryPositions; diff --git a/megamek/src/megamek/common/IBomber.java b/megamek/src/megamek/common/IBomber.java index 1725f419efd..26eac8365fc 100644 --- a/megamek/src/megamek/common/IBomber.java +++ b/megamek/src/megamek/common/IBomber.java @@ -145,9 +145,16 @@ default int getExternalBombPoints() { * @return total damage from remaining bombs */ default int getInternalBombsDamageTotal() { - int total = getBombs().stream().filter( - b -> b.isInternalBomb() - ).mapToInt(b -> b.getExplosionDamage() * b.getUsableShotsLeft()).sum(); + int total = 0; + for (Mounted bomb: getBombs()) { + if (bomb.isInternalBomb()) { + total += bomb.getExplosionDamage(); + } + } + + // int total = getBombs().stream().filter( + // b -> b.isInternalBomb() + // ).mapToInt(b -> b.getExplosionDamage()).sum(); return total; } diff --git a/megamek/src/megamek/common/weapons/AmmoWeaponHandler.java b/megamek/src/megamek/common/weapons/AmmoWeaponHandler.java index 8ee0e900e15..46f13625ba3 100644 --- a/megamek/src/megamek/common/weapons/AmmoWeaponHandler.java +++ b/megamek/src/megamek/common/weapons/AmmoWeaponHandler.java @@ -53,6 +53,11 @@ protected void useAmmo() { ammo = weapon.getLinked(); } ammo.setShotsLeft(ammo.getBaseShotsLeft() - 1); + + if (weapon.isInternalBomb()) { + ((IBomber) ae).setUsedInternalBombs(true); + } + super.useAmmo(); } @@ -63,11 +68,11 @@ protected void checkAmmo() { ammo = weapon.getLinked(); } } - + /** * For ammo weapons, this number can be less than the full number if the * amount of ammo is not high enough - * + * * @return the number of weapons of this type firing (for squadron weapon groups) */ @Override @@ -82,12 +87,12 @@ protected int getNumberWeapons() { (int) Math.floor((double) totalShots / (double) weapon.getCurrentShots())); } - + @Override protected boolean doChecks(Vector vPhaseReport) { return doAmmoFeedProblemCheck(vPhaseReport); } - + /** * Carry out an 'ammo feed problems' check on the weapon. Return true if it blew up. */ @@ -111,7 +116,7 @@ protected boolean doAmmoFeedProblemCheck(Vector vPhaseReport) { r = new Report(3163); r.subject = subjectId; vPhaseReport.addElement(r); - + explodeRoundInBarrel(vPhaseReport); } else if (diceRoll.getIntValue() >= 10) { // plain old weapon jam @@ -130,17 +135,17 @@ protected boolean doAmmoFeedProblemCheck(Vector vPhaseReport) { } else { return false; } - + return true; } - + /** * Worker function that explodes a round in the barrel of the attack's weapon */ protected void explodeRoundInBarrel(Vector vPhaseReport) { weapon.setJammed(true); weapon.setHit(true); - + int wloc = weapon.getLocation(); for (int i = 0; i < ae.getNumberOfCriticals(wloc); i++) { CriticalSlot slot1 = ae.getCritical(wloc, i); @@ -153,8 +158,8 @@ protected void explodeRoundInBarrel(Vector vPhaseReport) { break; } } - - // if we're here, the weapon is going to explode whether it's flagged as explosive or not + + // if we're here, the weapon is going to explode whether it's flagged as explosive or not vPhaseReport.addAll(gameManager.explodeEquipment(ae, wloc, weapon, true)); } } diff --git a/megamek/src/megamek/common/weapons/BombAttackHandler.java b/megamek/src/megamek/common/weapons/BombAttackHandler.java index 5161936bfe0..c7e3ec8a489 100644 --- a/megamek/src/megamek/common/weapons/BombAttackHandler.java +++ b/megamek/src/megamek/common/weapons/BombAttackHandler.java @@ -61,7 +61,8 @@ protected void useAmmo() { for (Mounted bomb : ae.getBombs()) { if (!bomb.isDestroyed() && (bomb.getUsableShotsLeft() > 0) - && (((BombType) bomb.getType()).getBombType() == type)) { + && (((BombType) bomb.getType()).getBombType() == type) + ) { bomb.setShotsLeft(0); if (bomb.isInternalBomb()) { ((IBomber) ae).setUsedInternalBombs(true); diff --git a/megamek/src/megamek/common/weapons/WeaponHandler.java b/megamek/src/megamek/common/weapons/WeaponHandler.java index 23790c3ff16..ea00d38c8e3 100644 --- a/megamek/src/megamek/common/weapons/WeaponHandler.java +++ b/megamek/src/megamek/common/weapons/WeaponHandler.java @@ -1824,6 +1824,7 @@ protected void useAmmo() { } else if (wtype.hasFlag(WeaponType.F_ONESHOT)) { weapon.setFired(true); } + setDone(); } diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index 1b42c755c70..509c47df75e 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -20889,7 +20889,8 @@ private Vector resolveControl(Entity e) { } /** - * Check all + * Check all aircraft that may have used internal bomb bays for incidental explosions + * caused by ground fire. * @return */ private Vector resolveInternalBombHits() { @@ -20898,7 +20899,8 @@ private Vector resolveInternalBombHits() { for (Iterator i = game.getEntities(); i.hasNext(); ) { vFullReport.addAll(resolveInternalBombHit(i.next())); } - return vFullReport; + // Return empty Vector if no reports are added. + return (vFullReport.isEmpty()) ? new Vector<>() : vFullReport; } /** @@ -20906,15 +20908,15 @@ private Vector resolveInternalBombHits() { */ private Vector resolveInternalBombHit(Entity e) { Vector vReport = new Vector<>(); - if (e.isDoomed() || e.isDestroyed() || !e.isDeployed()) { + // Only applies to surviving bombing craft that took damage this last round + if (!e.isBomber() || e.damageThisRound <= 0 || e.isDoomed() || e.isDestroyed() || !e.isDeployed()) { return vReport; } Report r; - Aero b = null; - if (e.isAero() && (e.isAirborne() || e.isSpaceborne())) { - b = (Aero) e; + if (e.isAero()) { + Aero b = (Aero) e; if (b.getUsedInternalBombs()) { int id = e.getId(); @@ -20927,14 +20929,15 @@ private Vector resolveInternalBombHit(Entity e) { vReport.add(r); // Roll - int rollTarget = 10; + int rollTarget = 10; //Testing purposes int roll = Compute.d6(2); boolean explosion = roll >= rollTarget; r = new Report(5602); r.indent(); r.subject = id; r.addDesc(e); - r.add(roll); + r.add(rollTarget); + r.add(roll, false); vReport.add(r); // Outcome @@ -20951,8 +20954,9 @@ private Vector resolveInternalBombHit(Entity e) { vReport.add(r); // Deal damage if (explosion) { - HitData ht = null; - vReport.addAll(damageEntity(e, ht, bombDamage, true, DamageType.NONE,true)); + HitData hd = new HitData(b.getBodyLocation(), false, HitData.EFFECT_NONE); + vReport.addAll(damageEntity(e, hd, bombDamage, true, DamageType.NONE,true)); + e.applyDamage(); } } } From 0075f694657c4e594ac81e95e4ba5edfe0a96570 Mon Sep 17 00:00:00 2001 From: sleet01 Date: Sat, 16 Dec 2023 23:52:07 -0800 Subject: [PATCH 08/22] All functionality complete, added to allowed classes --- .../i18n/megamek/client/messages.properties | 1 + .../common/options/messages.properties | 2 +- .../client/ui/swing/BombChoicePanel.java | 31 +- .../client/ui/swing/FiringDisplay.java | 82 +++-- .../client/ui/swing/MovementDisplay.java | 322 +++++++++--------- .../megamek/client/ui/swing/QuirksPanel.java | 8 + megamek/src/megamek/common/Aero.java | 12 +- .../src/megamek/common/AeroSpaceFighter.java | 1 + megamek/src/megamek/common/Entity.java | 5 + megamek/src/megamek/common/IBomber.java | 6 +- megamek/src/megamek/common/LandAirMech.java | 12 +- megamek/src/megamek/common/VTOL.java | 10 +- .../common/actions/WeaponAttackAction.java | 3 + .../common/loaders/BLKConvFighterFile.java | 7 +- .../common/loaders/BLKDropshipFile.java | 3 + .../common/weapons/AmmoWeaponHandler.java | 8 +- .../common/weapons/BombAttackHandler.java | 2 +- megamek/src/megamek/server/GameManager.java | 11 +- 18 files changed, 315 insertions(+), 211 deletions(-) diff --git a/megamek/i18n/megamek/client/messages.properties b/megamek/i18n/megamek/client/messages.properties index a79527a4506..ce14a7f0307 100644 --- a/megamek/i18n/megamek/client/messages.properties +++ b/megamek/i18n/megamek/client/messages.properties @@ -3950,6 +3950,7 @@ WeaponAttackAction.AttackerNotReady=Attacker is in no condition to fire weapons. WeaponAttackAction.AttackerTooHigh=attacker is too high. WeaponAttackAction.BusyAltBombing=Already altitude bombing. WeaponAttackAction.BusyDiveBombing=Already dive bombing. +WeaponAttackAction.AlreadyUsedMaxInternalBombs=Already used maximum internal bombs (6). WeaponAttackAction.BusyLayingMines=Can't fire weapons when laying mines. WeaponAttackAction.BusySpaceBombing=Already space bombing. WeaponAttackAction.CantShootAndFastMove=Infantry fast moved this turn and so can not shoot. diff --git a/megamek/i18n/megamek/common/options/messages.properties b/megamek/i18n/megamek/common/options/messages.properties index 894be944f70..c7d9c1292f9 100644 --- a/megamek/i18n/megamek/common/options/messages.properties +++ b/megamek/i18n/megamek/common/options/messages.properties @@ -826,7 +826,7 @@ QuirksInfo.option.good_rep_2.description=Unit costs 25% more C-bills.\nincluded QuirksInfo.option.hyper_actuator.displayableName=Hyper-Extending Actuators QuirksInfo.option.hyper_actuator.description=This unit can flip arms,\neven with lower arm and hand actuators. (SO pg 194) QuirksInfo.option.internal_bomb.displayableName=Internal Bomb Bay -QuirksInfo.option.internal_bomb.description=No game effect,\ncurrently. (SO pg 195) +QuirksInfo.option.internal_bomb.description=Allows some units to utilize cargo space as bomb bays, but risk internal explosions. (SO pg 195) QuirksInfo.option.low_profile.displayableName=Narrow/Low Profile QuirksInfo.option.low_profile.description=This unit is may take less damage because of a narrow/low profile. (BMM pg 85) QuirksInfo.option.imp_com.displayableName=Improved Communications diff --git a/megamek/src/megamek/client/ui/swing/BombChoicePanel.java b/megamek/src/megamek/client/ui/swing/BombChoicePanel.java index 867f5898590..e2b1a690253 100644 --- a/megamek/src/megamek/client/ui/swing/BombChoicePanel.java +++ b/megamek/src/megamek/client/ui/swing/BombChoicePanel.java @@ -40,6 +40,8 @@ public class BombChoicePanel extends JPanel implements Serializable, ItemListene private final boolean at2Nukes; private final boolean allowAdvancedAmmo; + private boolean empty = false; + private static final long serialVersionUID = 483782753790544050L; @SuppressWarnings("rawtypes") @@ -109,8 +111,15 @@ private void initPanel() { int[] intBombChoices = bomber.getIntBombChoices(); int[] extBombChoices = bomber.getExtBombChoices(); + int columns = (maxPoints.get(INTNAME) > 0 ? 1 : 0) + (maxPoints.get(EXTNAME) > 0 ? 1 : 0); + // Should not occur! + if (columns == 0){ + empty = true; + return; + } + JPanel outer = new JPanel(); - outer.setLayout(new GridLayout(0, 2)); + outer.setLayout(new GridLayout(0, columns)); TitledBorder titledBorder = new TitledBorder(new LineBorder(Color.blue), "Bombs"); Font font2 = new Font("Verdana", Font.BOLD + Font.ITALIC, 12); titledBorder.setTitleFont(font2); @@ -118,11 +127,15 @@ private void initPanel() { CompoundBorder compoundBorder = new CompoundBorder(titledBorder, emptyBorder); outer.setBorder(compoundBorder); - interiorPanel = initSubPanel(compileBombPoints(intBombChoices), intBombChoices, INTNAME); - exteriorPanel = initSubPanel(compileBombPoints(extBombChoices), extBombChoices, EXTNAME); + interiorPanel = initSubPanel(maxPoints.get(INTNAME) - compileBombPoints(intBombChoices), intBombChoices, INTNAME); + exteriorPanel = initSubPanel(maxPoints.get(EXTNAME) - compileBombPoints(extBombChoices), extBombChoices, EXTNAME); - outer.add(interiorPanel); - outer.add(exteriorPanel); + if (maxPoints.get(INTNAME) != 0) { + outer.add(interiorPanel); + } + if (maxPoints.get(EXTNAME) != 0) { + outer.add(exteriorPanel); + } add(outer); } @@ -261,6 +274,11 @@ public void itemStateChanged(ItemEvent ie) { } public void applyChoice() { + // Return cleanly if bomber never had any capacity but e.g. Internal Bomb Bay tried add bomb capacity. + if (empty) { + return; + } + int[] choices = new int[BombType.B_NUM]; // Internal bombs for (int type = 0; type < BombType.B_NUM; type++) { @@ -276,6 +294,9 @@ public void applyChoice() { public int[] getChoice() { int[] choices = new int[BombType.B_NUM]; Arrays.fill(choices, 0); + if (empty) { + return choices; + } for (int type = 0; type < BombType.B_NUM; type++) { choices[type] += b_choices.get(INTNAME)[type].getSelectedIndex() + b_choices.get(EXTNAME)[type].getSelectedIndex(); diff --git a/megamek/src/megamek/client/ui/swing/FiringDisplay.java b/megamek/src/megamek/client/ui/swing/FiringDisplay.java index 445a4cd5f63..15575575466 100644 --- a/megamek/src/megamek/client/ui/swing/FiringDisplay.java +++ b/megamek/src/megamek/client/ui/swing/FiringDisplay.java @@ -188,6 +188,8 @@ public String getHotKeyDesc() { protected boolean isStrafing = false; + protected int phaseInternalBombs = 0; + /** * Keeps track of the Coords that are in a strafing run. */ @@ -1203,27 +1205,37 @@ public void ready() { } } - // We need to nag for overheat on capital fighters - if ((ce() != null) && ce().isCapitalFighter() && GUIP.getNagForOverheat()) { - int totalheat = 0; - for (EntityAction action : attacks) { - if (action instanceof WeaponAttackAction) { - Mounted weapon = ce().getEquipment(((WeaponAttackAction) action).getWeaponId()); - totalheat += weapon.getCurrentHeat(); + // Handle some entity bookkeeping + if (ce() != null) { + // Add internal bombs used this phase to all internal bombs used this round + if (ce().isBomber()) { + if (phaseInternalBombs > 0) { + ((IBomber) ce()).increaseUsedInternalBombs(phaseInternalBombs); } - } - if (totalheat > ce().getHeatCapacity()) { - // confirm this action - String title = Messages.getString("FiringDisplay.OverheatNag.title"); - String body = Messages.getString("FiringDisplay.OverheatNag.message"); - ConfirmDialog response = clientgui.doYesNoBotherDialog(title, body); - if (!response.getShowAgain()) { - GUIP.setNagForOverheat(false); + } + // We need to nag for overheat on capital fighters + if (ce().isCapitalFighter() && GUIP.getNagForOverheat()) { + int totalheat = 0; + for (EntityAction action : attacks) { + if (action instanceof WeaponAttackAction) { + Mounted weapon = ce().getEquipment(((WeaponAttackAction) action).getWeaponId()); + totalheat += weapon.getCurrentHeat(); + } } - if (!response.getAnswer()) { - return; + if (totalheat > ce().getHeatCapacity()) { + // confirm this action + String title = Messages.getString("FiringDisplay.OverheatNag.title"); + String body = Messages.getString("FiringDisplay.OverheatNag.message"); + ConfirmDialog response = clientgui.doYesNoBotherDialog(title, body); + if (!response.getShowAgain()) { + GUIP.setNagForOverheat(false); + } + + if (!response.getAnswer()) { + return; + } } } } @@ -1531,9 +1543,10 @@ private int[] getBombPayload(boolean isSpace, int limit) { continue; } - // Internal bay dive bombing is limited to 6 bombs at a time, but other limits may apply + // Internal bay dive-bombing is limited to 6 bombs at a time, but other limits may apply if ("internal".equals(title)) { - limit = (limit <= -1) ? 6 : Math.min(6, limit); + int usedBombs = ((IBomber) ce()).getUsedInternalBombs(); + limit = (limit <= -1) ? 6 - usedBombs : Math.min(6 - usedBombs, limit); } int numFighters = ce().getActiveSubEntities().size(); @@ -1686,6 +1699,9 @@ void fire() { waa.setStrafingFirstShot(firstShot); firstShot = false; + // Handle incrementing internal-bay weapons that are not used in bomb bay attacks + incrementInternalBombs(waa); + // Temporarily add attack into the game. On turn done // this will be recomputed from the local // @attacks EntityAttackLog, but Game actions @@ -1829,6 +1845,11 @@ protected void clearAttacks() { // restore any other movement to default ce().setSecondaryFacing(ce().getFacing()); ce().setArmsFlipped(false); + + // restore count of internal bombs dropped this phase. + if(ce().isBomber()) { + phaseInternalBombs = ((IBomber)ce()).getUsedInternalBombs(); + } } /** @@ -1849,6 +1870,7 @@ protected void removeLastFiring() { if (o instanceof WeaponAttackAction) { WeaponAttackAction waa = (WeaponAttackAction) o; ce().getEquipment(waa.getWeaponId()).setUsedThisRound(false); + decrementInternalBombs(waa); removeAttack(o); clientgui.getUnitDisplay().wPan.displayMech(ce()); clientgui.getClient().getGame().removeAction(o); @@ -1988,6 +2010,9 @@ public void updateTarget() { } else if (m.isInBearingsOnlyMode()) { clientgui.getUnitDisplay().wPan.setToHit(Messages.getString("FiringDisplay.bearingsOnlyWrongPhase")); setFireEnabled(false); + } else if (m.isInternalBomb() && phaseInternalBombs >= 6) { + clientgui.getUnitDisplay().wPan.setToHit(Messages.getString("WeaponAttackAction.AlreadyUsedMaxInternalBombs")); + setFireEnabled(false); } else if (toHit.getValue() == TargetRoll.IMPOSSIBLE) { clientgui.getUnitDisplay().wPan.setToHit(toHit); setFireEnabled(false); @@ -2696,4 +2721,23 @@ private boolean validStrafingCoord(Coords newCoord) { } return isConsecutive && isInaLine; } + + private void incrementInternalBombs(WeaponAttackAction waa) { + updateInternalBombs(waa, 1); + } + private void decrementInternalBombs(WeaponAttackAction waa) { + updateInternalBombs(waa, -1); + } + private void updateInternalBombs(WeaponAttackAction waa, int amt) { + if (ce().isBomber()) { + if (ce().getEquipment(waa.getWeaponId()).isInternalBomb()) { + int usedInternalBombs = ((IBomber) ce()).getUsedInternalBombs(); + phaseInternalBombs += amt; + if (phaseInternalBombs < usedInternalBombs) { + phaseInternalBombs = usedInternalBombs; + } + } + } + + } } diff --git a/megamek/src/megamek/client/ui/swing/MovementDisplay.java b/megamek/src/megamek/client/ui/swing/MovementDisplay.java index f74ca9d3e16..2e2414f49e1 100644 --- a/megamek/src/megamek/client/ui/swing/MovementDisplay.java +++ b/megamek/src/megamek/client/ui/swing/MovementDisplay.java @@ -196,6 +196,7 @@ public enum MoveCommand implements PhaseCommand { * Priority that determines this buttons order */ public int priority; + MoveCommand(String c, int f) { cmd = c; flag = f; @@ -225,7 +226,7 @@ public String toString() { public String getHotKeyDesc() { String result = ""; - String msg_next= Messages.getString("Next"); + String msg_next = Messages.getString("Next"); String msg_previous = Messages.getString("Previous"); String msg_left = Messages.getString("Left"); String msg_right = Messages.getString("Right"); @@ -598,7 +599,7 @@ public void performAction() { } computeMovementEnvelope(ce); } - }); + }); // Register the action for mode conversion controller.registerCommandAction(KeyCommandBind.TOGGLE_CONVERSIONMODE.cmd, @@ -794,31 +795,33 @@ private boolean isEnabled(MoveCommand c) { /** * Signals Unit Display to update later on the AWT event stack. - * See Issue:#4876 and #4444. This is done to prevent blank general tab when switching + * See Issue:#4876 and #4444. This is done to prevent blank general tab when switching * units when the map is zoomed all the way out. */ private void updateUnitDisplayLater(Entity ce) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - clientgui.getUnitDisplay().displayEntity(ce); - clientgui.getUnitDisplay().showPanel("movement"); + clientgui.getUnitDisplay().displayEntity(ce); + clientgui.getUnitDisplay().showPanel("movement"); } }); } /** - * Sets buttons to their proper state, but lets Swing do this later after all the + * Sets buttons to their proper state, but lets Swing do this later after all the * current BoardView repaints and updates are complete. This is done to prevent some * buttons from painting correctly when the maps is zoomed way out. See Issue: #4444 */ private void updateButtonsLater() { SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - updateButtons(); - }; + @Override + public void run() { + updateButtons(); + } + + ; }); } @@ -904,7 +907,7 @@ private void updateButtons() { // Infantry and Tank - Fortify if (isInfantry - && ce.hasWorkingMisc(MiscType.F_TRENCH_CAPABLE)) { + && ce.hasWorkingMisc(MiscType.F_TRENCH_CAPABLE)) { // Crews adrift in space or atmosphere can't do this if (ce instanceof EjectedCrew && (ce.isSpaceborne() || ce.isAirborne())) { getBtn(MoveCommand.MOVE_DIG_IN).setEnabled(false); @@ -949,7 +952,7 @@ private void updateButtons() { getBtn(MoveCommand.MOVE_SHAKE_OFF).setEnabled( (ce instanceof Tank) - && (ce.getSwarmAttackerId() != Entity.NONE)); + && (ce.getSwarmAttackerId() != Entity.NONE)); setFleeEnabled(ce.canFlee()); if (gOpts.booleanOption(OptionsConstants.ADVGRNDMOV_VEHICLES_CAN_EJECT) && (ce instanceof Tank)) { @@ -997,7 +1000,7 @@ private void addStepToMovePath(MoveStepType moveStep) { updateMove(); } - private void addStepsToMovePath(MoveStepType ... moveSteps ) { + private void addStepsToMovePath(MoveStepType... moveSteps) { for (MoveStepType moveStep : moveSteps) { cmd.addStep(moveStep); } @@ -1009,7 +1012,7 @@ private void addStepToMovePath(MoveStepType moveStep, Entity entity) { updateMove(); } - private void addStepToMovePath(MoveStepType moveStep, TreeMap> targets) { + private void addStepToMovePath(MoveStepType moveStep, TreeMap> targets) { cmd.addStep(moveStep, targets); updateMove(); } @@ -1019,12 +1022,12 @@ private void addStepToMovePath(MoveStepType moveStep, boolean noCost) { updateMove(); } - private void addStepToMovePath(MoveStepType moveStep, boolean noCost, boolean isManeuver, int maneuverType) { + private void addStepToMovePath(MoveStepType moveStep, boolean noCost, boolean isManeuver, int maneuverType) { cmd.addStep(moveStep, noCost, isManeuver, maneuverType); updateMove(); } - private void addStepsToMovePath(boolean noCost, boolean isManeuver, int maneuverType, MoveStepType ... moveSteps) { + private void addStepsToMovePath(boolean noCost, boolean isManeuver, int maneuverType, MoveStepType... moveSteps) { for (MoveStepType moveStep : moveSteps) { cmd.addStep(moveStep, noCost, isManeuver, maneuverType); } @@ -1046,7 +1049,7 @@ private void addStepToMovePath(MoveStepType moveStep, int additionalIntData) { updateMove(); } - private void addStepToMovePath(MoveStepType moveStep, int recover ,int mineToLay) { + private void addStepToMovePath(MoveStepType moveStep, int recover, int mineToLay) { cmd.addStep(moveStep, recover, mineToLay); updateMove(); } @@ -1105,7 +1108,7 @@ protected void updateDonePanel() { } } - private List computeTurnDetails(){ + private List computeTurnDetails() { String validTextColor = AbstractBoardViewOverlay.colorToHex(AbstractBoardViewOverlay.getTextColor()); String invalidTextColor = AbstractBoardViewOverlay.colorToHex(AbstractBoardViewOverlay.getTextColor(), 0.7f); @@ -1116,7 +1119,7 @@ private List computeTurnDetails(){ boolean accumLegal = true; String unicodeIcon = ""; ArrayList turnDetails = new ArrayList<>(); - for( final Enumeration step = cmd.getSteps(); step.hasMoreElements();) { + for (final Enumeration step = cmd.getSteps(); step.hasMoreElements(); ) { MoveStep currentStep = step.nextElement(); MoveStepType currentType = currentStep.getType(); int currentDanger = currentStep.isDanger() ? 1 : 0; @@ -1442,7 +1445,7 @@ public synchronized void ready() { return; } - if ((ce().canUnjamRAC()) && (GUIP.getNagForNoUnJamRAC()) && (!isUnJammingRAC)){ + if ((ce().canUnjamRAC()) && (GUIP.getNagForNoUnJamRAC()) && (!isUnJammingRAC)) { // confirm this action String title = Messages.getString("MovementDisplay.ConfirmUnJamRACDlg.title"); String body = Messages.getString("MovementDisplay.ConfirmUnJamRACDlg.message"); @@ -1457,7 +1460,7 @@ public synchronized void ready() { } cmd.clipToPossible(); - if ( (cmd.length() == 0) && (!ce().isAirborne()) && needNagForNoAction()) { + if ((cmd.length() == 0) && (!ce().isAirborne()) && needNagForNoAction()) { // Hmm... no movement steps, confirm this action String title = Messages.getString("MovementDisplay.ConfirmNoMoveDlg.title"); String body = Messages.getString("MovementDisplay.ConfirmNoMoveDlg.message"); @@ -1508,9 +1511,9 @@ public synchronized void ready() { && GUIP.getNagForSprint() // no need to nag for vehicles using overdrive if they already get a PSR nag && !((cmd.getEntity() instanceof Tank - || (cmd.getEntity() instanceof QuadVee - && cmd.getEntity().getConversionMode() == QuadVee.CONV_MODE_VEHICLE) - && GUIP.getNagForPSR()))) { + || (cmd.getEntity() instanceof QuadVee + && cmd.getEntity().getConversionMode() == QuadVee.CONV_MODE_VEHICLE) + && GUIP.getNagForPSR()))) { ConfirmDialog nag = new ConfirmDialog(clientgui.frame, Messages.getString("MovementDisplay.areYouSure"), Messages.getString("MovementDisplay.ConfirmSprint"), true); @@ -1640,7 +1643,7 @@ public synchronized void ready() { boolean flyoff = false; if ((null != cmd) && (cmd.contains(MoveStepType.OFF) || cmd - .contains(MoveStepType.RETURN))) { + .contains(MoveStepType.RETURN))) { flyoff = true; } boolean landing = false; @@ -1703,9 +1706,9 @@ public synchronized void ready() { && !cmd.contains(MoveStepType.FORWARDS) && !cmd.contains(MoveStepType.FLEE) && cmd.getFinalElevation() > 0 && ce().getGame().getBoard().getHex(cmd.getFinalCoords()) - .terrainLevel(Terrains.BLDG_ELEV) < cmd.getFinalElevation() + .terrainLevel(Terrains.BLDG_ELEV) < cmd.getFinalElevation() && ce().getGame().getBoard().getHex(cmd.getFinalCoords()) - .terrainLevel(Terrains.BRIDGE_ELEV) < cmd.getFinalElevation()) { + .terrainLevel(Terrains.BRIDGE_ELEV) < cmd.getFinalElevation()) { String title = Messages.getString("MovementDisplay.MicroliteMove.title"); String body = Messages.getString("MovementDisplay.MicroliteMove.message"); clientgui.doAlertDialog(title, body); @@ -1796,7 +1799,7 @@ private void currentMove(Coords dest) { if (shiftheld || (gear == GEAR_TURN)) { cmd.rotatePathfinder(cmd.getFinalCoords().direction(dest), false, ManeuverType.MAN_NONE); } else if ((gear == GEAR_JUMP) - && (ce().getJumpType() == Mech.JUMP_BOOSTER)) { + && (ce().getJumpType() == Mech.JUMP_BOOSTER)) { // Jumps with mechanical jump boosters are special Coords src; if (cmd.getLastStep() != null) { @@ -1866,7 +1869,7 @@ private void currentMove(Coords dest) { cmd.findPathTo(dest, MoveStepType.CHARGE); // The path planner shouldn't actually add the charge step if (cmd.getFinalCoords().equals(dest) - && (cmd.getLastStep().getType() != MoveStepType.CHARGE)) { + && (cmd.getLastStep().getType() != MoveStepType.CHARGE)) { cmd.removeLastStep(); addStepToMovePath(MoveStepType.CHARGE); } @@ -1874,7 +1877,7 @@ private void currentMove(Coords dest) { cmd.findPathTo(dest, MoveStepType.DFA); // The path planner shouldn't actually add the DFA step if (cmd.getFinalCoords().equals(dest) - && (cmd.getLastStep().getType() != MoveStepType.DFA)) { + && (cmd.getLastStep().getType() != MoveStepType.DFA)) { cmd.removeLastStep(); addStepToMovePath(MoveStepType.DFA); } @@ -2085,9 +2088,9 @@ public synchronized void hexMoused(BoardViewEvent b) { toDefender = AirmechRamAttackAction.getDamageFor(ce, cmd.getHexesMoved()); } else { toDefender = ChargeAttackAction.getDamageFor( - ce, clientgui.getClient().getGame().getOptions() - .booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_CHARGE_DAMAGE), - cmd.getHexesMoved()); + ce, clientgui.getClient().getGame().getOptions() + .booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_CHARGE_DAMAGE), + cmd.getHexesMoved()); if (target.getTargetType() == Targetable.TYPE_ENTITY) { Entity te = (Entity) target; toAttacker = ChargeAttackAction.getDamageTakenBy(ce, te, @@ -2095,7 +2098,7 @@ public synchronized void hexMoused(BoardViewEvent b) { .booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_CHARGE_DAMAGE), cmd.getHexesMoved()); } else if ((target.getTargetType() == Targetable.TYPE_FUEL_TANK) - || (target.getTargetType() == Targetable.TYPE_BUILDING)) { + || (target.getTargetType() == Targetable.TYPE_BUILDING)) { Building bldg = clientgui.getClient().getGame().getBoard().getBuildingAt(moveto); toAttacker = ChargeAttackAction.getDamageTakenBy(ce, bldg, moveto); } @@ -2226,8 +2229,7 @@ private void updateTakeCoverButton() { private synchronized void updateChaffButton() { Entity ce = ce(); - if (ce == null ) - { + if (ce == null) { return; } @@ -2256,7 +2258,7 @@ private synchronized void updateProneButtons() { } else if (cmd.getFinalHullDown()) { if (isMech) { setGetUpEnabled(!ce.isImmobile() && !ce.isStuck() - && !((Mech) ce).cannotStandUpFromHullDown()); + && !((Mech) ce).cannotStandUpFromHullDown()); } else { setGetUpEnabled(!ce.isImmobile() && !ce.isStuck()); } @@ -2265,7 +2267,7 @@ private synchronized void updateProneButtons() { } else { setGetUpEnabled(false); setGoProneEnabled(!ce.isImmobile() && isMech && !ce.isStuck() - && !(getBtn(MoveCommand.MOVE_GET_UP).isEnabled())); + && !(getBtn(MoveCommand.MOVE_GET_UP).isEnabled())); if (!(ce instanceof Tank) && !(ce instanceof QuadVee && ce.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)) { setHullDownEnabled(ce.canGoHullDown()); @@ -2274,11 +2276,11 @@ private synchronized void updateProneButtons() { // check if it's moved into a fortified position if (cmd.getLastStep() != null) { boolean hullDownEnabled = clientgui.getClient() - .getGame().getOptions() - .booleanOption(OptionsConstants.ADVGRNDMOV_TACOPS_HULL_DOWN); + .getGame().getOptions() + .booleanOption(OptionsConstants.ADVGRNDMOV_TACOPS_HULL_DOWN); Hex occupiedHex = clientgui.getClient().getGame() - .getBoard() - .getHex(cmd.getLastStep().getPosition()); + .getBoard() + .getHex(cmd.getLastStep().getPosition()); boolean fortifiedHex = occupiedHex .containsTerrain(Terrains.FORTIFIED); setHullDownEnabled(hullDownEnabled && fortifiedHex); @@ -2301,14 +2303,14 @@ private void updateRACButton() { GameOptions opts = clientgui.getClient().getGame().getOptions(); setUnjamEnabled(ce.canUnjamRAC() && ((gear == MovementDisplay.GEAR_LAND) - || (gear == MovementDisplay.GEAR_TURN) - || (gear == MovementDisplay.GEAR_BACKUP)) + || (gear == MovementDisplay.GEAR_TURN) + || (gear == MovementDisplay.GEAR_BACKUP)) && ((cmd.getMpUsed() <= ce.getWalkMP()) - || (cmd.getLastStep().isOnlyPavement() - && (cmd.getMpUsed() <= (ce.getWalkMP() + 1)))) + || (cmd.getLastStep().isOnlyPavement() + && (cmd.getMpUsed() <= (ce.getWalkMP() + 1)))) && !(opts.booleanOption(OptionsConstants.ADVANCED_TACOPS_TANK_CREWS) - && (cmd.getMpUsed() > 0) && (ce instanceof Tank) - && (ce.getCrew().getSize() < 2))); + && (cmd.getMpUsed() > 0) && (ce instanceof Tank) + && (ce.getCrew().getSize() < 2))); } private void updateSearchlightButton() { @@ -2589,9 +2591,9 @@ private void updateFlyOffButton() { if (a.isSpheroid() && !board.inSpace()) { setFlyOffEnabled((position != null) && (ce.getWalkMP() > 0) && ((position.getX() == 0) - || (position.getX() == (board.getWidth() - 1)) - || (position.getY() == 0) - || (position.getY() == (board.getHeight() - 1)))); + || (position.getX() == (board.getWidth() - 1)) + || (position.getY() == 0) + || (position.getY() == (board.getHeight() - 1)))); return; } @@ -2603,13 +2605,13 @@ private void updateFlyOffButton() { boolean evenx = (position.getX() % 2) == 0; if ((velocityLeft > 0) && (((position.getX() == 0) && ((facing == 5) || (facing == 4))) || ((position.getX() == (board.getWidth() - 1)) - && ((facing == 1) || (facing == 2))) + && ((facing == 1) || (facing == 2))) || ((position.getY() == 0) && ((facing == 1) || (facing == 5) || (facing == 0)) && evenx) || ((position.getY() == 0) && (facing == 0)) || ((position.getY() == (board.getHeight() - 1)) - && ((facing == 2) || (facing == 3) || (facing == 4)) && !evenx) + && ((facing == 2) || (facing == 3) || (facing == 4)) && !evenx) || ((position.getY() == (board.getHeight() - 1)) - && (facing == 3)))) { + && (facing == 3)))) { setFlyOffEnabled(true); } else { setFlyOffEnabled(false); @@ -2665,7 +2667,7 @@ private void updateBootleggerButton() { } if (!clientgui.getClient().getGame().getOptions() - .booleanOption(OptionsConstants.ADVGRNDMOV_VEHICLE_ADVANCED_MANEUVERS)) { + .booleanOption(OptionsConstants.ADVGRNDMOV_VEHICLE_ADVANCED_MANEUVERS)) { return; } @@ -2709,7 +2711,7 @@ private void updateStartupButton() { } if (!clientgui.getClient().getGame().getOptions() - .booleanOption(OptionsConstants.RPG_MANUAL_SHUTDOWN)) { + .booleanOption(OptionsConstants.RPG_MANUAL_SHUTDOWN)) { return; } @@ -2728,7 +2730,7 @@ private void updateSelfDestructButton() { } if (!clientgui.getClient().getGame().getOptions() - .booleanOption(OptionsConstants.ADVANCED_TACOPS_SELF_DESTRUCT)) { + .booleanOption(OptionsConstants.ADVANCED_TACOPS_SELF_DESTRUCT)) { return; } @@ -2897,8 +2899,8 @@ private void updateBombButton() { if (ce().isBomber() && ((ce() instanceof LandAirMech) - || clientgui.getClient().getGame().getOptions() - .booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_VTOL_ATTACKS)) + || clientgui.getClient().getGame().getOptions() + .booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_VTOL_ATTACKS)) && ((IBomber) ce()).getBombPoints() > 0) { setBombEnabled(true); } @@ -3018,8 +3020,8 @@ private void updateLayMineButton() { setLayMineEnabled(false); } else if (ce instanceof BattleArmor) { setLayMineEnabled(cmd.getLastStep() == null - || cmd.isJumping() - || cmd.getLastStepMovementType().equals(EntityMovementType.MOVE_VTOL_WALK)); + || cmd.isJumping() + || cmd.getLastStepMovementType().equals(EntityMovementType.MOVE_VTOL_WALK)); } else { setLayMineEnabled(true); } @@ -3068,7 +3070,7 @@ private Entity getMountedUnit() { int i = 0; for (Integer bn : bayChoices) { retVal[i++] = bn.toString() + " (Free Slots: " - + (int) choice.getBayById(bn).getUnused() + ")"; + + (int) choice.getBayById(bn).getUnused() + ")"; } if (bayChoices.size() > 1) { String bayString = (String) JOptionPane @@ -3081,7 +3083,7 @@ private Entity getMountedUnit() { JOptionPane.QUESTION_MESSAGE, null, retVal, null); ce.setTargetBay(Integer.parseInt(bayString.substring(0, - bayString.indexOf(" ")))); + bayString.indexOf(" ")))); // We need to update the entity here so that the server knows // about our target bay clientgui.getClient().sendUpdateEntity(ce); @@ -3103,7 +3105,7 @@ private Entity getMountedUnit() { Vector choices = new Vector<>(); for (Entity other : game.getEntitiesVector(cmd.getFinalCoords())) { if (other.isLoadableThisTurn() && (ce() != null) - && ce().canLoad(other, false)) { + && ce().canLoad(other, false)) { choices.addElement(other); } } @@ -3118,12 +3120,12 @@ && ce().canLoad(other, false)) { if (choices.size() > 1) { String input = (String) JOptionPane .showInputDialog(clientgui, - Messages.getString( - "DeploymentDisplay.loadUnitDialog.message", - new Object[]{ce().getShortName(), - ce().getUnusedString()}), - Messages.getString("DeploymentDisplay.loadUnitDialog.title"), - JOptionPane.QUESTION_MESSAGE, null, SharedUtility + Messages.getString( + "DeploymentDisplay.loadUnitDialog.message", + new Object[]{ce().getShortName(), + ce().getUnusedString()}), + Messages.getString("DeploymentDisplay.loadUnitDialog.title"), + JOptionPane.QUESTION_MESSAGE, null, SharedUtility .getDisplayArray(choices), null); choice = (Entity) SharedUtility.getTargetPicked(choices, input); } else { @@ -3163,7 +3165,7 @@ && ce().canLoad(other, false)) { bayChoices = new ArrayList<>(); for (Transporter t : ce().getTransports()) { if ((t instanceof ProtomechClampMount) - && t.canLoad(choice)) { + && t.canLoad(choice)) { bayChoices.add(((ProtomechClampMount) t).isRear() ? 1 : 0); } } @@ -3171,9 +3173,9 @@ && ce().canLoad(other, false)) { String[] retVal = new String[bayChoices.size()]; int i = 0; for (Integer bn : bayChoices) { - retVal[i++] = bn > 0? + retVal[i++] = bn > 0 ? Messages.getString("MovementDisplay.loadProtoClampMountDialog.rear") : - Messages.getString("MovementDisplay.loadProtoClampMountDialog.front"); + Messages.getString("MovementDisplay.loadProtoClampMountDialog.front"); } String bayString = (String) JOptionPane .showInputDialog( @@ -3233,11 +3235,11 @@ private Entity getTowedUnit() { if (choices.size() > 1) { String input = (String) JOptionPane .showInputDialog(clientgui, - Messages.getString( - "DeploymentDisplay.towUnitDialog.message", - new Object[]{ce().getShortName()}), - Messages.getString("DeploymentDisplay.towUnitDialog.title"), - JOptionPane.QUESTION_MESSAGE, null, SharedUtility + Messages.getString( + "DeploymentDisplay.towUnitDialog.message", + new Object[]{ce().getShortName()}), + Messages.getString("DeploymentDisplay.towUnitDialog.title"), + JOptionPane.QUESTION_MESSAGE, null, SharedUtility .getDisplayArray(choices), null); choice = (Entity) SharedUtility.getTargetPicked(choices, input); } else { @@ -3313,7 +3315,7 @@ public String toString() { } String selection = (String) JOptionPane.showInputDialog(clientgui, Messages.getString("MovementDisplay.loadUnitHitchDialog.message", - new Object[]{ce().getShortName()}), + new Object[]{ce().getShortName()}), Messages.getString("MovementDisplay.loadUnitHitchDialog.title"), JOptionPane.QUESTION_MESSAGE, null, retVal, null); HitchChoice hc = null; @@ -3367,7 +3369,7 @@ private Entity getDisconnectedUnit() { clientgui, Messages.getString( "MovementDisplay.DisconnectUnitDialog.message", new Object[]{ - ce.getShortName(), ce.getUnusedString()}), + ce.getShortName(), ce.getUnusedString()}), Messages.getString("MovementDisplay.DisconnectUnitDialog.title"), JOptionPane.QUESTION_MESSAGE, null, SharedUtility .getDisplayArray(towedUnits), null); @@ -3465,12 +3467,12 @@ private Coords getUnloadPosition(Entity unloaded) { choices[i++] = c.toString(); } String selected = (String) JOptionPane.showInputDialog(clientgui, - Messages.getString( - "MovementDisplay.ChooseHex" + ".message", new Object[]{ - ce.getShortName(), ce.getUnusedString()}), Messages - .getString("MovementDisplay.ChooseHex.title"), + Messages.getString( + "MovementDisplay.ChooseHex" + ".message", new Object[]{ + ce.getShortName(), ce.getUnusedString()}), Messages + .getString("MovementDisplay.ChooseHex.title"), - JOptionPane.QUESTION_MESSAGE, null, choices, null); + JOptionPane.QUESTION_MESSAGE, null, choices, null); Coords choice = null; if (selected == null) { return choice; @@ -3516,12 +3518,12 @@ private Coords getEjectPosition(Entity abandoned) { choices[i++] = c.toString(); } String selected = (String) JOptionPane.showInputDialog(clientgui, - Messages.getString( - "MovementDisplay.ChooseEjectHex.message", new Object[]{ - abandoned.getShortName(), abandoned.getUnusedString()}), Messages - .getString("MovementDisplay.ChooseHex.title"), + Messages.getString( + "MovementDisplay.ChooseEjectHex.message", new Object[]{ + abandoned.getShortName(), abandoned.getUnusedString()}), Messages + .getString("MovementDisplay.ChooseHex.title"), - JOptionPane.QUESTION_MESSAGE, null, choices, null); + JOptionPane.QUESTION_MESSAGE, null, choices, null); Coords choice = null; if (selected == null) { return choice; @@ -3629,7 +3631,7 @@ private synchronized void updateJoinButton() { if (game.useVectorMove()) { // not where you are, but where you will be loadeePos = Compute.getFinalPosition(ce.getPosition(), - cmd.getFinalVectors()); + cmd.getFinalVectors()); } boolean isGood = false; for (Entity other : game.getEntitiesVector(loadeePos)) { @@ -3715,7 +3717,7 @@ private TreeMap> getLaunchedUnits() { String question = Messages .getString( "MovementDisplay.LaunchFighterDialog.message", new Object[]{ - ce.getShortName(), doors * 2, bayNum}); + ce.getShortName(), doors * 2, bayNum}); for (int loop = 0; loop < names.length; loop++) { names[loop] = currentFighters.get(loop).getShortName(); } @@ -3727,7 +3729,7 @@ private TreeMap> getLaunchedUnits() { clientgui.frame, Messages.getString( "MovementDisplay.LaunchFighterDialog.title", new Object[]{ - currentBay.getType(), bayNum}), question, + currentBay.getType(), bayNum}), question, names); choiceDialog.setVisible(true); if (choiceDialog.getChoices() == null) { @@ -3875,42 +3877,42 @@ private TreeMap> getUndockedUnits() { * * @param craft The launching entity, which has already been tested to see if it's a small craft */ - private void loadPassengerAtLaunch(SmallCraft craft) { - final Entity currentEntity = ce(); - if (currentEntity == null) { - LogManager.getLogger().error("Cannot load passenger at launch for a null current entity."); - return; - } - - int space = 0; - for (Bay b : craft.getTransportBays()) { - if ((b instanceof CargoBay) || (b instanceof InfantryBay) || (b instanceof BattleArmorBay)) { - // Assume a passenger takes up 0.1 tons per single infantryman weight calculations - space += (int) Math.round(b.getUnused() / 0.1); - } - } - // Passengers don't actually 'load' into bays to consume space, so update what's available for anyone - // already aboard - space -= ((craft.getTotalOtherCrew() + craft.getTotalPassengers()) * 0.1); - // Make sure the text displays either the carrying capacity or the number of passengers left aboard - space = Math.min(space, currentEntity.getNPassenger()); - ConfirmDialog takePassenger = new ConfirmDialog(clientgui.frame, - Messages.getString("MovementDisplay.FillSmallCraftPassengerDialog.Title"), - Messages.getString("MovementDisplay.FillSmallCraftPassengerDialog.message", - craft.getShortName(), space, currentEntity.getShortName() + "'", - currentEntity.getNPassenger()), false); - takePassenger.setVisible(true); - if (takePassenger.getAnswer()) { - // Move the passengers - currentEntity.setNPassenger(currentEntity.getNPassenger() - space); - if (currentEntity instanceof Aero) { - ((Aero) currentEntity).addEscapeCraft(craft.getExternalIdAsString()); - } - clientgui.getClient().sendUpdateEntity(currentEntity); - craft.addPassengers(currentEntity.getExternalIdAsString(), space); - clientgui.getClient().sendUpdateEntity(craft); - } - } + private void loadPassengerAtLaunch(SmallCraft craft) { + final Entity currentEntity = ce(); + if (currentEntity == null) { + LogManager.getLogger().error("Cannot load passenger at launch for a null current entity."); + return; + } + + int space = 0; + for (Bay b : craft.getTransportBays()) { + if ((b instanceof CargoBay) || (b instanceof InfantryBay) || (b instanceof BattleArmorBay)) { + // Assume a passenger takes up 0.1 tons per single infantryman weight calculations + space += (int) Math.round(b.getUnused() / 0.1); + } + } + // Passengers don't actually 'load' into bays to consume space, so update what's available for anyone + // already aboard + space -= ((craft.getTotalOtherCrew() + craft.getTotalPassengers()) * 0.1); + // Make sure the text displays either the carrying capacity or the number of passengers left aboard + space = Math.min(space, currentEntity.getNPassenger()); + ConfirmDialog takePassenger = new ConfirmDialog(clientgui.frame, + Messages.getString("MovementDisplay.FillSmallCraftPassengerDialog.Title"), + Messages.getString("MovementDisplay.FillSmallCraftPassengerDialog.message", + craft.getShortName(), space, currentEntity.getShortName() + "'", + currentEntity.getNPassenger()), false); + takePassenger.setVisible(true); + if (takePassenger.getAnswer()) { + // Move the passengers + currentEntity.setNPassenger(currentEntity.getNPassenger() - space); + if (currentEntity instanceof Aero) { + ((Aero) currentEntity).addEscapeCraft(craft.getExternalIdAsString()); + } + clientgui.getClient().sendUpdateEntity(currentEntity); + craft.addPassengers(currentEntity.getExternalIdAsString(), space); + clientgui.getClient().sendUpdateEntity(craft); + } + } /** * Get the unit that the player wants to drop. This method will remove the @@ -4595,18 +4597,18 @@ public synchronized void actionPerformed(ActionEvent ev) { || (gear == MovementDisplay.GEAR_CHARGE) || (gear == MovementDisplay.GEAR_DFA) || ((cmd.getMpUsed() > ce.getWalkMP()) - && !(cmd.getLastStep().isOnlyPavement() - && (cmd.getMpUsed() <= (ce.getWalkMP() + 1)))) + && !(cmd.getLastStep().isOnlyPavement() + && (cmd.getMpUsed() <= (ce.getWalkMP() + 1)))) || (opts.booleanOption("tacops_tank_crews") - && (cmd.getMpUsed() > 0) && (ce instanceof Tank) - && (ce.getCrew().getSize() < 2)) + && (cmd.getMpUsed() > 0) && (ce instanceof Tank) + && (ce.getCrew().getSize() < 2)) || (gear == MovementDisplay.GEAR_SWIM) || (gear == MovementDisplay.GEAR_RAM)) { // in the wrong gear // clearAllMoves(); // gear = Compute.GEAR_LAND; setUnjamEnabled(false); - } else if (clientgui.doYesNoDialog(title, msg)) { + } else if (clientgui.doYesNoDialog(title, msg)) { addStepToMovePath(MoveStepType.UNJAM_RAC); isUnJammingRAC = true; ready(); @@ -4633,8 +4635,8 @@ public synchronized void actionPerformed(ActionEvent ev) { } else if (actionCmd.equals(MoveCommand.MOVE_JUMP.getCmd())) { if ((gear != MovementDisplay.GEAR_JUMP) && !((cmd.getLastStep() != null) - && cmd.getLastStep().isFirstStep() - && (cmd.getLastStep().getType() == MoveStepType.LAY_MINE))) { + && cmd.getLastStep().isFirstStep() + && (cmd.getLastStep().getType() == MoveStepType.LAY_MINE))) { clear(); } if (!cmd.isJumping()) { @@ -4712,7 +4714,7 @@ public synchronized void actionPerformed(ActionEvent ev) { if (!clientgui.getClient().getGame() .containsMinefield(ce.getPosition())) { clientgui.doAlertDialog(Messages - .getString("MovementDisplay.CantClearMinefield"), + .getString("MovementDisplay.CantClearMinefield"), Messages.getString("MovementDisplay.NoMinefield")); return; } @@ -4840,9 +4842,9 @@ public synchronized void actionPerformed(ActionEvent ev) { // Dialog for choosing which location to brace String option = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - "Choose the location to brace:", - "Choose Brace Location", JOptionPane.QUESTION_MESSAGE, null, - locationNames, locationNames[0]); + "Choose the location to brace:", + "Choose Brace Location", JOptionPane.QUESTION_MESSAGE, null, + locationNames, locationNames[0]); // Verify that we have a valid option... if (option != null) { @@ -4853,20 +4855,20 @@ public synchronized void actionPerformed(ActionEvent ev) { } } else if (actionCmd.equals(MoveCommand.MOVE_FLEE.getCmd()) && clientgui.doYesNoDialog( - Messages.getString("MovementDisplay.EscapeDialog.title"), - Messages.getString("MovementDisplay.EscapeDialog.message"))) { + Messages.getString("MovementDisplay.EscapeDialog.title"), + Messages.getString("MovementDisplay.EscapeDialog.message"))) { clear(); addStepToMovePath(MoveStepType.FLEE); ready(); } else if (actionCmd.equals(MoveCommand.MOVE_FLY_OFF.getCmd()) && clientgui.doYesNoDialog( - Messages.getString("MovementDisplay.FlyOffDialog.title"), - Messages.getString("MovementDisplay.FlyOffDialog.message"))) { + Messages.getString("MovementDisplay.FlyOffDialog.title"), + Messages.getString("MovementDisplay.FlyOffDialog.message"))) { if (opts.booleanOption(OptionsConstants.ADVAERORULES_RETURN_FLYOVER) && clientgui.doYesNoDialog( - Messages.getString("MovementDisplay.ReturnFly.title"), - Messages.getString("MovementDisplay.ReturnFly.message"))) { + Messages.getString("MovementDisplay.ReturnFly.title"), + Messages.getString("MovementDisplay.ReturnFly.message"))) { addStepToMovePath(MoveStepType.RETURN); } else { addStepToMovePath(MoveStepType.OFF); @@ -4959,7 +4961,7 @@ public synchronized void actionPerformed(ActionEvent ev) { && (cmd.getLastStep().getNDown() == 1) && (cmd.getLastStep().getVelocity() < 12) && !(((IAero) ce).isSpheroid() || clientgui.getClient() - .getGame().getPlanetaryConditions().isVacuum())) { + .getGame().getPlanetaryConditions().isVacuum())) { addStepToMovePath(MoveStepType.ACC, true); } addStepToMovePath(MoveStepType.DOWN); @@ -4967,7 +4969,7 @@ public synchronized void actionPerformed(ActionEvent ev) { MoveStep ms = cmd.getLastStep(); if ((ms != null) && ((ms.getType() == MoveStepType.CLIMB_MODE_ON) || (ms - .getType() == MoveStepType.CLIMB_MODE_OFF))) { + .getType() == MoveStepType.CLIMB_MODE_OFF))) { MoveStep lastStep = cmd.getLastStep(); cmd.removeLastStep(); // Add another climb mode step @@ -5154,8 +5156,8 @@ public synchronized void actionPerformed(ActionEvent ev) { String title = Messages.getString("MovementDisplay.NoTakeOffDialog.title"); String body = Messages.getString( "MovementDisplay.NoTakeOffDialog.message", - new Object[] { ((IAero) ce()) - .hasRoomForHorizontalTakeOff() }); + new Object[]{((IAero) ce()) + .hasRoomForHorizontalTakeOff()}); clientgui.doAlertDialog(title, body); } else { if (clientgui.doYesNoDialog( @@ -5232,15 +5234,15 @@ public synchronized void actionPerformed(ActionEvent ev) { if (idx == 0) { JOptionPane.showMessageDialog(clientgui.getFrame(), "No players available. Units cannot be traitored to players " - + "that aren't assigned to a team."); + + "that aren't assigned to a team."); return; } // Dialog for choosing which player to transfer to String option = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - "Choose the player to gain ownership of this unit when it turns traitor", - "Traitor", JOptionPane.QUESTION_MESSAGE, null, - options, options[0]); + "Choose the player to gain ownership of this unit when it turns traitor", + "Traitor", JOptionPane.QUESTION_MESSAGE, null, + options, options[0]); // Verify that we have a valid option... if (option != null) { @@ -5250,18 +5252,18 @@ public synchronized void actionPerformed(ActionEvent ev) { // And now we perform the actual transfer int confirm = JOptionPane.showConfirmDialog( - clientgui.getFrame(), - e.getDisplayName() + " will switch to " + name - + "'s side at the end of this turn. Are you sure?", - "Confirm", - JOptionPane.YES_NO_OPTION); + clientgui.getFrame(), + e.getDisplayName() + " will switch to " + name + + "'s side at the end of this turn. Are you sure?", + "Confirm", + JOptionPane.YES_NO_OPTION); if (confirm == JOptionPane.YES_OPTION) { e.setTraitorId(id); clientgui.getClient().sendUpdateEntity(e); } } } else if (actionCmd.equals(MoveCommand.MOVE_CHAFF.getCmd())) { - if(clientgui.doYesNoDialog( + if (clientgui.doYesNoDialog( Messages.getString("MovementDisplay.ConfirmChaff.title"), Messages.getString("MovementDisplay.ConfirmChaff.message"))) { isUsingChaff = true; diff --git a/megamek/src/megamek/client/ui/swing/QuirksPanel.java b/megamek/src/megamek/client/ui/swing/QuirksPanel.java index d18f2f23804..1ee827ea79f 100644 --- a/megamek/src/megamek/client/ui/swing/QuirksPanel.java +++ b/megamek/src/megamek/client/ui/swing/QuirksPanel.java @@ -26,8 +26,10 @@ import megamek.client.ui.GBC; import megamek.client.ui.Messages; +import megamek.common.Aero; import megamek.common.Entity; import megamek.common.Mounted; +import megamek.common.VTOL; import megamek.common.options.IOption; import megamek.common.options.IOptionGroup; import megamek.common.options.Quirks; @@ -126,6 +128,12 @@ public void setQuirks() { option = comp.getOption(); if ((comp.getValue() == Messages.getString("CustomMechDialog.None"))) { entity.getQuirks().getOption(option.getName()).setValue("None"); + } else if (option.getName().equals("internal_bomb")) { + // Need to set the quirk, and only then force re-computing bomb bay space for Aero-derived units + entity.getQuirks().getOption(option.getName()).setValue(comp.getValue()); + if (entity.isAero()) { + ((Aero) entity).autoSetMaxBombPoints(); + } } else { entity.getQuirks().getOption(option.getName()).setValue(comp.getValue()); } diff --git a/megamek/src/megamek/common/Aero.java b/megamek/src/megamek/common/Aero.java index 34ed6986741..d08b105b33e 100644 --- a/megamek/src/megamek/common/Aero.java +++ b/megamek/src/megamek/common/Aero.java @@ -159,7 +159,7 @@ public String[] getLocationNames() { protected int[] intBombChoices = new int[BombType.B_NUM]; protected int[] extBombChoices = new int[BombType.B_NUM]; - protected boolean usedInternalBombs = false; + protected int usedInternalBombs = 0; // fuel - number of fuel points private int fuel = 0; @@ -533,11 +533,15 @@ public int reduceMPByBombLoad(int t) { return t; } - public void setUsedInternalBombs(boolean b){ + public void setUsedInternalBombs(int b){ usedInternalBombs = b; } - public boolean getUsedInternalBombs() { + public void increaseUsedInternalBombs(int b){ + usedInternalBombs += b; + } + + public int getUsedInternalBombs() { return usedInternalBombs; } @@ -1022,7 +1026,7 @@ public void newRound(int roundNumber) { resetAltLossThisRound(); // Reset usedInternalBombs - setUsedInternalBombs(false); + setUsedInternalBombs(0); } /** diff --git a/megamek/src/megamek/common/AeroSpaceFighter.java b/megamek/src/megamek/common/AeroSpaceFighter.java index 32d69b1d416..abf1333440a 100644 --- a/megamek/src/megamek/common/AeroSpaceFighter.java +++ b/megamek/src/megamek/common/AeroSpaceFighter.java @@ -35,6 +35,7 @@ public int getUnitType() { return UnitType.AEROSPACEFIGHTER; } + @Override public void autoSetMaxBombPoints() { // Aerospace fighters can carry both external and internal ordnances, if configured and quirked // appropriately diff --git a/megamek/src/megamek/common/Entity.java b/megamek/src/megamek/common/Entity.java index 45008d50067..409257323d7 100644 --- a/megamek/src/megamek/common/Entity.java +++ b/megamek/src/megamek/common/Entity.java @@ -4183,6 +4183,11 @@ protected void resetBombAttacks() { if (eq.getType().equals(spaceBomb) || eq.getType().equals(altBomb) || eq.getType().equals(diveBomb)) { bombAttacksToRemove.add(eq); + } else if (eq.getLinked() != null && eq.getLinked().isInternalBomb()){ // Does not do what's intended! + // Remove any used internal bombs + if (eq.getLinked().getUsableShotsLeft() <= 0) { + bombAttacksToRemove.add(eq); + } } } equipmentList.removeAll(bombAttacksToRemove); diff --git a/megamek/src/megamek/common/IBomber.java b/megamek/src/megamek/common/IBomber.java index 26eac8365fc..7d50d1c42f5 100644 --- a/megamek/src/megamek/common/IBomber.java +++ b/megamek/src/megamek/common/IBomber.java @@ -36,8 +36,10 @@ public interface IBomber { String DIVE_BOMB_ATTACK = "DiveBombAttack"; String ALT_BOMB_ATTACK = "AltBombAttack"; - void setUsedInternalBombs(boolean b); - boolean getUsedInternalBombs(); + void setUsedInternalBombs(int b); + void increaseUsedInternalBombs(int b); + int getUsedInternalBombs(); + /** * @return The total number of bomb points that the bomber can carry. diff --git a/megamek/src/megamek/common/LandAirMech.java b/megamek/src/megamek/common/LandAirMech.java index 9d949c18efe..612ea694136 100644 --- a/megamek/src/megamek/common/LandAirMech.java +++ b/megamek/src/megamek/common/LandAirMech.java @@ -1121,16 +1121,18 @@ public void setIntBombChoices(int[] bc) { } } - public void setUsedInternalBombs(boolean b){ + public void setUsedInternalBombs(int b){ // Do nothing } - public boolean getUsedInternalBombs() { - // Currently not possible - return false; + public void increaseUsedInternalBombs(int b){ + // Do nothing } - + public int getUsedInternalBombs() { + // Currently not possible + return 0; + } @Override public int[] getExtBombChoices() { diff --git a/megamek/src/megamek/common/VTOL.java b/megamek/src/megamek/common/VTOL.java index 971935648f7..58694d779a8 100644 --- a/megamek/src/megamek/common/VTOL.java +++ b/megamek/src/megamek/common/VTOL.java @@ -301,13 +301,17 @@ public int reduceMPByBombLoad(int t) { return Math.max(0, (t - (int) this.getBombs().stream().filter(m -> (m.getUsableShotsLeft() > 0)).count())); } - public void setUsedInternalBombs(boolean b){ + public void setUsedInternalBombs(int b){ // Do nothing } - public boolean getUsedInternalBombs() { + public void increaseUsedInternalBombs(int b){ + // Do nothing + } + + public int getUsedInternalBombs() { // Currently not possible - return false; + return 0; } @Override diff --git a/megamek/src/megamek/common/actions/WeaponAttackAction.java b/megamek/src/megamek/common/actions/WeaponAttackAction.java index 97300e73bc3..4a382581b7d 100644 --- a/megamek/src/megamek/common/actions/WeaponAttackAction.java +++ b/megamek/src/megamek/common/actions/WeaponAttackAction.java @@ -1606,6 +1606,9 @@ private static String toHitIsImpossible(Game game, Entity ae, int attackerId, Ta // Air-to-ground attacks if (Compute.isAirToGround(ae, target) && !isArtilleryIndirect && !ae.isDropping()) { + if (ae.isBomber() && weapon.isInternalBomb() && ((IBomber)ae).getUsedInternalBombs() >= 6) { + return Messages.getString("WeaponAttackAction.AlreadyUsedMaxInternalBombs"); + } // Can't strike from above altitude 5. Dive bombing uses a different test below if ((ae.getAltitude() > 5) && !wtype.hasFlag(WeaponType.F_DIVE_BOMB) && !wtype.hasFlag(WeaponType.F_ALT_BOMB)) { diff --git a/megamek/src/megamek/common/loaders/BLKConvFighterFile.java b/megamek/src/megamek/common/loaders/BLKConvFighterFile.java index 46c9bfe72c5..ce8e9bf143b 100644 --- a/megamek/src/megamek/common/loaders/BLKConvFighterFile.java +++ b/megamek/src/megamek/common/loaders/BLKConvFighterFile.java @@ -49,9 +49,6 @@ public Entity getEntity() throws EntityLoadingException { } a.setWeight(dataFile.getDataAsDouble("tonnage")[0]); - // how many bombs can it carry - a.autoSetMaxBombPoints(); - // get a movement mode - lets try Aerodyne EntityMovementMode nMotion = EntityMovementMode.AERODYNE; a.setMovementMode(nMotion); @@ -140,6 +137,10 @@ public Entity getEntity() throws EntityLoadingException { } addTransports(a); + + // how many bombs can it carry; depends on transport space as well. + a.autoSetMaxBombPoints(); + a.setArmorTonnage(a.getArmorWeight()); loadQuirks(a); return a; diff --git a/megamek/src/megamek/common/loaders/BLKDropshipFile.java b/megamek/src/megamek/common/loaders/BLKDropshipFile.java index 6ae85d7d205..36dc5aa6cc2 100644 --- a/megamek/src/megamek/common/loaders/BLKDropshipFile.java +++ b/megamek/src/megamek/common/loaders/BLKDropshipFile.java @@ -187,6 +187,9 @@ public Entity getEntity() throws EntityLoadingException { addTransports(a); + // how many bombs can it carry; depends on transport bays + a.autoSetMaxBombPoints(); + a.setArmorTonnage(a.getArmorWeight()); loadQuirks(a); return a; diff --git a/megamek/src/megamek/common/weapons/AmmoWeaponHandler.java b/megamek/src/megamek/common/weapons/AmmoWeaponHandler.java index 46f13625ba3..d391140f987 100644 --- a/megamek/src/megamek/common/weapons/AmmoWeaponHandler.java +++ b/megamek/src/megamek/common/weapons/AmmoWeaponHandler.java @@ -55,7 +55,7 @@ protected void useAmmo() { ammo.setShotsLeft(ammo.getBaseShotsLeft() - 1); if (weapon.isInternalBomb()) { - ((IBomber) ae).setUsedInternalBombs(true); + ((IBomber) ae).increaseUsedInternalBombs(1); } super.useAmmo(); @@ -104,13 +104,13 @@ protected boolean doAmmoFeedProblemCheck(Vector vPhaseReport) { // attack roll was a 2, may explode } else if (roll.getIntValue() <= 2) { Roll diceRoll = Compute.rollD6(2); - + Report r = new Report(3173); r.subject = subjectId; r.newlines = 0; r.add(diceRoll); - vPhaseReport.addElement(r); - + vPhaseReport.addElement(r); + if (diceRoll.getIntValue() == 12) { // round explodes in weapon r = new Report(3163); diff --git a/megamek/src/megamek/common/weapons/BombAttackHandler.java b/megamek/src/megamek/common/weapons/BombAttackHandler.java index c7e3ec8a489..3c426db4109 100644 --- a/megamek/src/megamek/common/weapons/BombAttackHandler.java +++ b/megamek/src/megamek/common/weapons/BombAttackHandler.java @@ -65,7 +65,7 @@ protected void useAmmo() { ) { bomb.setShotsLeft(0); if (bomb.isInternalBomb()) { - ((IBomber) ae).setUsedInternalBombs(true); + ((IBomber) ae).increaseUsedInternalBombs(1); } break; } diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index 509c47df75e..fa2784a1687 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -20897,10 +20897,13 @@ private Vector resolveInternalBombHits() { Vector vFullReport = new Vector<>(); vFullReport.add(new Report(5600, Report.PUBLIC)); for (Iterator i = game.getEntities(); i.hasNext(); ) { - vFullReport.addAll(resolveInternalBombHit(i.next())); + Vector interim = resolveInternalBombHit(i.next()); + if (!interim.isEmpty()) { + vFullReport.addAll(interim); + } } - // Return empty Vector if no reports are added. - return (vFullReport.isEmpty()) ? new Vector<>() : vFullReport; + // Return empty Vector if no reports (besides the header) are added. + return (vFullReport.size() == 1) ? new Vector<>() : vFullReport; } /** @@ -20918,7 +20921,7 @@ private Vector resolveInternalBombHit(Entity e) { if (e.isAero()) { Aero b = (Aero) e; - if (b.getUsedInternalBombs()) { + if (b.getUsedInternalBombs() > 0) { int id = e.getId(); Vector rolls = new Vector<>(); From 2907d8ee8a659172effeb6e9f898ad32f53c2eda Mon Sep 17 00:00:00 2001 From: sleet01 Date: Sun, 17 Dec 2023 00:10:36 -0800 Subject: [PATCH 09/22] Re-order UnitType indices for unit selection box --- megamek/src/megamek/common/UnitType.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/megamek/src/megamek/common/UnitType.java b/megamek/src/megamek/common/UnitType.java index de389fc5ab1..bfb064f7244 100644 --- a/megamek/src/megamek/common/UnitType.java +++ b/megamek/src/megamek/common/UnitType.java @@ -28,18 +28,18 @@ public class UnitType { public static final int NAVAL = 6; public static final int GUN_EMPLACEMENT = 7; public static final int CONV_FIGHTER = 8; - public static final int AERO = 9; + public static final int AEROSPACEFIGHTER = 9; public static final int SMALL_CRAFT = 10; public static final int DROPSHIP = 11; public static final int JUMPSHIP = 12; public static final int WARSHIP = 13; public static final int SPACE_STATION = 14; - public static final int AEROSPACEFIGHTER = 15; + public static final int AERO = 15; // Non-differentiated Aerospace, like Escape Pods / Life Boats private static String[] names = { "Mek", "Tank", "BattleArmor", "Infantry", "ProtoMek", "VTOL", "Naval", "Gun Emplacement", "Conventional Fighter", - "Aero", "Small Craft", "Dropship", - "Jumpship", "Warship", "Space Station", "AeroSpaceFighter" }; + "AeroSpaceFighter", "Small Craft", "Dropship", + "Jumpship", "Warship", "Space Station", "Aero"}; public static final int SIZE = names.length; From ab7d1bb393532075880b8e18886bc17e3b790977 Mon Sep 17 00:00:00 2001 From: sleet01 Date: Sun, 17 Dec 2023 13:45:49 -0800 Subject: [PATCH 10/22] Adding Internal Bay indicator to summary, TRO, Weapons display, Systems display (pt. 1) --- .../i18n/megamek/client/messages.properties | 1 + .../ui/swing/unitDisplay/SystemPanel.java | 47 ++++++++++--------- .../src/megamek/common/templates/TROView.java | 14 ++++-- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/megamek/i18n/megamek/client/messages.properties b/megamek/i18n/megamek/client/messages.properties index ce14a7f0307..12697ca5c4d 100644 --- a/megamek/i18n/megamek/client/messages.properties +++ b/megamek/i18n/megamek/client/messages.properties @@ -1933,6 +1933,7 @@ MechDisplay.attached=attached MechDisplay.AV=AV: MechDisplay.Bombs=Bomb Loadout MechDisplay.BombDumpDialog.title=Which bombs will be dumped next turn? +MechDisplay.isInternalBomb=(Int.) MechDisplay.Breached=\ Breached MechDisplay.CancelBombDumping.message=Do you want to cancel bomb dumping? MechDisplay.CancelBombDumping.title=Cancel Bomb Dump? diff --git a/megamek/src/megamek/client/ui/swing/unitDisplay/SystemPanel.java b/megamek/src/megamek/client/ui/swing/unitDisplay/SystemPanel.java index c9c892c6114..9ca6ae8dfc3 100644 --- a/megamek/src/megamek/client/ui/swing/unitDisplay/SystemPanel.java +++ b/megamek/src/megamek/client/ui/swing/unitDisplay/SystemPanel.java @@ -28,7 +28,7 @@ * This class shows the critical hits and systems for a mech */ class SystemPanel extends PicMap implements ItemListener, ActionListener, ListSelectionListener, IPreferenceChangeListener { - + private static int LOC_ALL_EQUIP = 0; private static int LOC_ALL_WEAPS = 1; private static int LOC_SPACER = 2; @@ -193,7 +193,7 @@ class SystemPanel extends PicMap implements ItemListener, ActionListener, ListSe setBackGround(); onResize(); - + addListeners(); } @@ -229,20 +229,20 @@ private CriticalSlot getSelectedCritical() { private Mounted getSelectedEquipment() { if ((locList.getSelectedIndex() == LOC_ALL_EQUIP)) { - if (slotList.getSelectedIndex() != -1) { + if (slotList.getSelectedIndex() != -1) { return en.getMisc().get(slotList.getSelectedIndex()); } else { return null; } } if (locList.getSelectedIndex() == LOC_ALL_WEAPS) { - if (slotList.getSelectedIndex() != -1) { + if (slotList.getSelectedIndex() != -1) { return en.getWeaponList().get(slotList.getSelectedIndex()); } else { return null; } } - + final CriticalSlot cs = getSelectedCritical(); if ((cs == null) || (unitDisplay.getClientGUI() == null)) { return null; @@ -307,7 +307,7 @@ public void displayMech(Entity newEntity) { displayLocations(); addListeners(); } - + public void selectLocation(int loc) { locList.setSelectedIndex(loc + LOC_OFFSET); } @@ -333,10 +333,10 @@ private void displayLocations() { private void displaySlots() { int loc = locList.getSelectedIndex(); - DefaultListModel slotModel = - ((DefaultListModel) slotList.getModel()); - slotModel.removeAllElements(); - + DefaultListModel slotModel = + ((DefaultListModel) slotList.getModel()); + slotModel.removeAllElements(); + // Display all Equipment if (loc == LOC_ALL_EQUIP) { for (Mounted m : en.getMisc()) { @@ -344,7 +344,7 @@ private void displaySlots() { } return; } - + // Display all Weapons if (loc == LOC_ALL_WEAPS) { for (Mounted m : en.getWeaponList()) { @@ -352,11 +352,11 @@ private void displaySlots() { } return; } - + // Display nothing for a spacer if (loc == LOC_SPACER) { return; - } + } // Standard location handling loc -= LOC_OFFSET; @@ -388,22 +388,22 @@ private void displaySlots() { default: } if (cs.isArmored()) { - sb.append(" (armored)"); + sb.append(" (armored)"); } } slotModel.addElement(sb.toString()); } onResize(); } - + private String getMountedDisplay(Mounted m, int loc) { return getMountedDisplay(m, loc, null); } - + private String getMountedDisplay(Mounted m, int loc, CriticalSlot cs) { String hotLoaded = Messages.getString("MechDisplay.isHotLoaded"); StringBuffer sb = new StringBuffer(); - + sb.append(m.getDesc()); if ((cs != null) && cs.getMount2() != null) { @@ -433,6 +433,9 @@ private String getMountedDisplay(Mounted m, int loc, CriticalSlot cs) { .append(m.getCurrentDamageCapacity(en, m.getLocation())).append(')'); } } + if (m.isInternalBomb()) { + sb.append(" " + Messages.getString("MechDisplay.isInternalBomb")); + } return sb.toString(); } @@ -739,7 +742,7 @@ public void valueChanged(ListSelectionEvent event) { && (m.getUsableShotsLeft() > 0) && !m.isDumping() && en.isActive() - && (client.getGame().getOptions().intOption(OptionsConstants.BASE_DUMPING_FROM_ROUND) + && (client.getGame().getOptions().intOption(OptionsConstants.BASE_DUMPING_FROM_ROUND) <= client.getGame().getRoundCount()) && !carryingBAsOnBack && !invalidEnvironment) { m_bDumpAmmo.setEnabled(true); @@ -822,21 +825,21 @@ public void valueChanged(ListSelectionEvent event) { addListeners(); } } - + private void addListeners() { locList.addListSelectionListener(this); slotList.addListSelectionListener(this); unitList.addListSelectionListener(this); - + m_chMode.addItemListener(this); m_bDumpAmmo.addActionListener(this); } - + private void removeListeners() { locList.removeListSelectionListener(this); slotList.removeListSelectionListener(this); unitList.removeListSelectionListener(this); - + m_chMode.removeItemListener(this); m_bDumpAmmo.removeActionListener(this); } diff --git a/megamek/src/megamek/common/templates/TROView.java b/megamek/src/megamek/common/templates/TROView.java index 7b5fa393f27..b9aa374e8e1 100644 --- a/megamek/src/megamek/common/templates/TROView.java +++ b/megamek/src/megamek/common/templates/TROView.java @@ -404,6 +404,7 @@ protected int addEquipment(Entity entity, boolean includeAmmo) { final int structure = entity.getStructureType(); final Map> equipment = new HashMap<>(); int nameWidth = 20; + EquipmentKey eqk; for (final Mounted m : entity.getEquipment()) { if (skipMount(m, includeAmmo)) { continue; @@ -422,8 +423,8 @@ protected int addEquipment(Entity entity, boolean includeAmmo) { if (m.isOmniPodMounted() || !entity.isOmni()) { final String loc = formatLocationTableEntry(entity, m); equipment.putIfAbsent(loc, new HashMap<>()); - equipment.get(loc).merge(new EquipmentKey(m.getType(), m.getSize(), m.isArmored()), - 1, Integer::sum); + eqk = new EquipmentKey(m.getType(), m.getSize(), m.isArmored(), m.isInternalBomb()); + equipment.get(loc).merge(eqk,1, Integer::sum); } } final List> eqList = new ArrayList<>(); @@ -435,6 +436,9 @@ protected int addEquipment(Entity entity, boolean includeAmmo) { if (entry.getKey().isArmored()) { name += " (Armored)"; } + if (entry.getKey().internalBomb) { + name += " (Int. Bay)"; + } if (eq instanceof AmmoType) { name = String.format("%s (%d)", name, ((AmmoType) eq).getShots() * count); } else if (count > 1) { @@ -782,15 +786,17 @@ static final class EquipmentKey { private final EquipmentType etype; private final double size; private final boolean armored; + private final boolean internalBomb; EquipmentKey(EquipmentType etype, double size) { - this(etype, size, false); + this(etype, size, false, false); } - EquipmentKey(EquipmentType etype, double size, boolean armored) { + EquipmentKey(EquipmentType etype, double size, boolean armored, boolean internal) { this.etype = etype; this.size = size; this.armored = armored; + this.internalBomb = internal; } String name() { From 49eb3dae307bffee229a4dbef395ed7655c23dea Mon Sep 17 00:00:00 2001 From: sleet01 Date: Mon, 18 Dec 2023 12:41:00 -0800 Subject: [PATCH 11/22] Further cleanup, make ASF with bomb bays report correct weight in verification --- .../i18n/megamek/client/messages.properties | 1 - megamek/src/megamek/MegaMek.java | 1 + .../ui/swing/unitDisplay/SystemPanel.java | 3 - megamek/src/megamek/common/MechView.java | 156 +++++++++--------- megamek/src/megamek/common/Mounted.java | 4 + .../src/megamek/common/verifier/TestAero.java | 14 ++ 6 files changed, 100 insertions(+), 79 deletions(-) diff --git a/megamek/i18n/megamek/client/messages.properties b/megamek/i18n/megamek/client/messages.properties index 12697ca5c4d..ce14a7f0307 100644 --- a/megamek/i18n/megamek/client/messages.properties +++ b/megamek/i18n/megamek/client/messages.properties @@ -1933,7 +1933,6 @@ MechDisplay.attached=attached MechDisplay.AV=AV: MechDisplay.Bombs=Bomb Loadout MechDisplay.BombDumpDialog.title=Which bombs will be dumped next turn? -MechDisplay.isInternalBomb=(Int.) MechDisplay.Breached=\ Breached MechDisplay.CancelBombDumping.message=Do you want to cancel bomb dumping? MechDisplay.CancelBombDumping.title=Cancel Bomb Dump? diff --git a/megamek/src/megamek/MegaMek.java b/megamek/src/megamek/MegaMek.java index 1160d109f68..1e2b766d550 100644 --- a/megamek/src/megamek/MegaMek.java +++ b/megamek/src/megamek/MegaMek.java @@ -62,6 +62,7 @@ public static void main(String... args) { Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { LogManager.getLogger().error("Uncaught Exception Detected", t); final String name = t.getClass().getName(); + final StackTraceElement[] stack = t.getStackTrace(); JOptionPane.showMessageDialog(null, String.format("Uncaught %s detected. Please open up an issue containing all logs, the game save file, and customs at https://github.com/MegaMek/megamek/issues", name), "Uncaught " + name, JOptionPane.ERROR_MESSAGE); diff --git a/megamek/src/megamek/client/ui/swing/unitDisplay/SystemPanel.java b/megamek/src/megamek/client/ui/swing/unitDisplay/SystemPanel.java index 9ca6ae8dfc3..28f01b7b1ea 100644 --- a/megamek/src/megamek/client/ui/swing/unitDisplay/SystemPanel.java +++ b/megamek/src/megamek/client/ui/swing/unitDisplay/SystemPanel.java @@ -433,9 +433,6 @@ private String getMountedDisplay(Mounted m, int loc, CriticalSlot cs) { .append(m.getCurrentDamageCapacity(en, m.getLocation())).append(')'); } } - if (m.isInternalBomb()) { - sb.append(" " + Messages.getString("MechDisplay.isInternalBomb")); - } return sb.toString(); } diff --git a/megamek/src/megamek/common/MechView.java b/megamek/src/megamek/common/MechView.java index 71e20165469..da062e0f5c0 100644 --- a/megamek/src/megamek/common/MechView.java +++ b/megamek/src/megamek/common/MechView.java @@ -33,18 +33,18 @@ /** * A utility class for retrieving unit information in a formatted string. - * + * * The information is encoded in a series of classes that implement a common {@link ViewElement} * interface, which can format the element either in html or in plain text. * @author Ryan McConnell * @since January 20, 2003 */ public class MechView { - + /** * Provides common interface for various ways to present data that can be formatted * either as HTML or as plain text. - * + * * @see SingleLine * @see LabeledElement * @see TableElement @@ -78,24 +78,24 @@ interface ViewElement { private List sBasic = new ArrayList<>(); private List sLoadout = new ArrayList<>(); private List sFluff = new ArrayList<>(); - + private final boolean html; /** * Compiles information about an {@link Entity} useful for showing a summary of its abilities. * Produced output formatted in html. - * + * * @param entity The entity to summarize * @param showDetail If true, shows individual weapons that make up weapon bays. */ public MechView(Entity entity, boolean showDetail) { this(entity, showDetail, false, true); } - + /** * Compiles information about an {@link Entity} useful for showing a summary of its abilities. * Produced output formatted in html. - * + * * @param entity The entity to summarize * @param showDetail If true, shows individual weapons that make up weapon bays. * @param useAlternateCost If true, uses alternate cost calculation. This primarily provides an @@ -122,7 +122,7 @@ public MechView(final Entity entity, final boolean showDetail, final boolean use /** * Compiles information about an {@link Entity} useful for showing a summary of its abilities. - * + * * @param entity The entity to summarize * @param showDetail If true, shows individual weapons that make up weapon bays. * @param useAlternateCost If true, uses alternate cost calculation. This primarily provides an @@ -192,7 +192,7 @@ public MechView(final Entity entity, final boolean showDetail, final boolean use } sLoadout.add(specList); } - + if (inf.getCrew() != null) { ArrayList augmentations = new ArrayList<>(); for (Enumeration e = inf.getCrew().getOptions(PilotOptions.MD_ADVANTAGES); @@ -232,7 +232,7 @@ public MechView(final Entity entity, final boolean showDetail, final boolean use if (!entity.isDesignValid()) { sHead.add(new SingleLine(Messages.getString("MechView.DesignInvalid"))); } - + TableElement tpTable = new TableElement(3); String tableSpacer = " "; tpTable.setColNames(Messages.getString("MechView.Level"), tableSpacer, @@ -259,7 +259,7 @@ public MechView(final Entity entity, final boolean showDetail, final boolean use tpTable.addRow(Messages.getString("MechView.Extinct"), tableSpacer, extinctRange); } sHead.add(tpTable); - + sHead.add(new LabeledElement(Messages.getString("MechView.TechRating"), entity.getFullRatingName())); sHead.add(new SingleLine()); @@ -416,7 +416,7 @@ public MechView(final Entity entity, final boolean showDetail, final boolean use .append(" damaged)").append(warningEnd()); } sBasic.add(new LabeledElement(Messages.getString("MechView.HeatSinks"), hsString.toString())); - + sBasic.add(new LabeledElement(Messages.getString("MechView.Cockpit"), a.getCockpitTypeString())); } @@ -455,7 +455,7 @@ public MechView(final Entity entity, final boolean showDetail, final boolean use sBasic.add(new LabeledElement(Messages.getString("MechView.SystemDamage"), warningStart() + a.getCritDamageString() + warningEnd())); } - + String fuel = String.valueOf(a.getCurrentFuel()); if (a.getCurrentFuel() < a.getFuel()) { fuel += "/" + a.getFuel(); @@ -546,10 +546,10 @@ private String eraText(int startYear, int endYear) { } return eraText; } - + /** * Converts a list of {@link ViewElement}s to a String using the selected format. - * + * * @param section The elements to format. * @return The formatted data. */ @@ -558,7 +558,7 @@ private String getReadout(List section) { ViewElement::toHTML : ViewElement::toPlainText; return section.stream().map(mapper).collect(Collectors.joining()); } - + /** * The head section includes the title (unit name), tech level and availability, tonnage, bv, and cost. * @return The data from the head section. @@ -592,9 +592,9 @@ public String getMechReadoutLoadout() { public String getMechReadoutFluff() { return getReadout(sFluff); } - + /** - * @return A summary including all four sections. + * @return A summary including all four sections. */ public String getMechReadout() { return getMechReadout(null); @@ -617,7 +617,7 @@ public String getMechReadout(@Nullable String fontName) { private List getInternalAndArmor() { List retVal = new ArrayList<>(); - + int maxArmor = (entity.getTotalInternal() * 2) + 3; if (isInf && !isBA) { Infantry inf = (Infantry) entity; @@ -685,7 +685,7 @@ private List getInternalAndArmor() { String[] row = {entity.getLocationName(loc), renderArmor(entity.getInternalForReal(loc), entity.getOInternal(loc), html), "", "", "" }; - + if (IArmorState.ARMOR_NA != entity.getArmorForReal(loc)) { row[2] = renderArmor(entity.getArmorForReal(loc), entity.getOArmor(loc), html); @@ -831,7 +831,7 @@ private List getWeapons(boolean showDetail) { if (entity.getWeaponList().isEmpty()) { return retVal; } - + TableElement wpnTable = new TableElement(4); wpnTable.setColNames("Weapons ", " Loc ", " Heat ", entity.isOmni() ? " Omni " : ""); wpnTable.setJustification(TableElement.JUSTIFIED_LEFT, TableElement.JUSTIFIED_CENTER, @@ -873,7 +873,7 @@ private List getWeapons(boolean showDetail) { } } row[2] = String.valueOf(heat); - + if (entity.isOmni()) { row[3] = Messages.getString(mounted.isOmniPodMounted() ? "MechView.Pod" : "MechView.Fixed"); } else if (wtype instanceof BayWeapon && bWeapDamaged > 0 && !showDetail) { @@ -890,16 +890,16 @@ private List getWeapons(boolean showDetail) { wpnTable.addRow(row); } - // if this is a weapon bay, then cycle through weapons and ammo + // if this is a weapon bay, then cycle through weapons and ammo if ((wtype instanceof BayWeapon) && showDetail) { - for (int wId : mounted.getBayWeapons()) { + for (int wId : mounted.getBayWeapons()) { Mounted m = entity.getEquipment(wId); - if (null == m) { - continue; + if (null == m) { + continue; } - + row = new String[] { m.getDesc(), "", "", "" }; - + if (entity.isClan() && (mounted.getType().getTechBase() == ITechnology.TECH_BASE_IS)) { row[0] += Messages.getString("MechView.IS"); @@ -920,8 +920,8 @@ private List getWeapons(boolean showDetail) { } for (int aId : mounted.getBayAmmo()) { Mounted m = entity.getEquipment(aId); - if (null == m) { - continue; + if (null == m) { + continue; } // Ignore ammo for one-shot launchers if ((m.getLinkedBy() != null) @@ -965,7 +965,7 @@ private ViewElement getAmmo() { if (mounted.getSize() == 0) { continue; } - + if (mounted.getLocation() == Entity.LOC_NONE) { continue; } @@ -1014,7 +1014,13 @@ private ViewElement getAmmo() { private List getBombs() { List retVal = new ArrayList<>(); IBomber b = (IBomber) entity; - int[] choices = b.getBombChoices(); + int[] choices = b.getIntBombChoices(); + for (int type = 0; type < BombType.B_NUM; type++) { + if (choices[type] > 0) { + retVal.add(new SingleLine(BombType.getBombName(type) + " (" + choices[type] + ") [Int. Bay]")); + } + } + choices = b.getExtBombChoices(); for (int type = 0; type < BombType.B_NUM; type++) { if (choices[type] > 0) { retVal.add(new SingleLine(BombType.getBombName(type) + " (" + choices[type] + ")")); @@ -1025,7 +1031,7 @@ private List getBombs() { private List getMisc() { List retVal = new ArrayList<>(); - + TableElement miscTable = new TableElement(3); miscTable.setColNames("Equipment", "Loc", entity.isOmni() ? "Omni" : ""); miscTable.setJustification(TableElement.JUSTIFIED_LEFT, TableElement.JUSTIFIED_CENTER, @@ -1040,7 +1046,7 @@ private List getMisc() { || (name.contains("CASE") && !name.contains("II") && entity.isClan()) - || (name.contains("Heat Sink") + || (name.contains("Heat Sink") && !name.contains("Radical")) || EquipmentType.isArmorType(mounted.getType()) || EquipmentType.isStructureType(mounted.getType())) { @@ -1048,7 +1054,7 @@ private List getMisc() { continue; } nEquip++; - + String[] row = { mounted.getDesc(), entity.joinLocationAbbr(mounted.allLocations(), 3), "" }; if (entity.isClan() && (mounted.getType().getTechBase() == ITechnology.TECH_BASE_IS)) { @@ -1058,7 +1064,7 @@ private List getMisc() { && (mounted.getType().getTechBase() == ITechnology.TECH_BASE_CLAN)) { row[0] += Messages.getString("MechView.Clan"); } - + if (entity.isOmni()) { row[2] = Messages.getString(mounted.isOmniPodMounted() ? "MechView.Pod" : "MechView.Fixed"); } @@ -1168,9 +1174,9 @@ public String toPlainText() { public String toHTML() { return ""; } - + } - + /** * Basic one-line entry consisting of a label, a colon, and a value. In html the label is bold. * @@ -1178,12 +1184,12 @@ public String toHTML() { private static class LabeledElement implements ViewElement { private final String label; private final String value; - + LabeledElement(String label, String value) { this.label = label; this.value = value; } - + @Override public String toPlainText() { String htmlCleanedText = value.replaceAll("<[Bb][Rr]> *", "\n") @@ -1192,13 +1198,13 @@ public String toPlainText() { .replaceAll("<[^>]*>", ""); return label + ": " + htmlCleanedText + "\n"; } - + @Override public String toHTML() { return "" + label + ": " + value + "
"; } } - + /** * Data laid out in a table with named columns. The columns are left-justified by default, * but justification can be set for columns individually. Plain text output requires a monospace @@ -1206,23 +1212,23 @@ public String toHTML() { * */ private static class TableElement implements ViewElement { - + static final int JUSTIFIED_LEFT = 0; static final int JUSTIFIED_CENTER = 1; static final int JUSTIFIED_RIGHT = 2; - + private final int[] justification; private final String[] colNames; private final List data = new ArrayList<>(); private final Map colWidth = new HashMap<>(); private final Map colors = new HashMap<>(); - + TableElement(int colCount) { justification = new int[colCount]; colNames = new String[colCount]; Arrays.fill(colNames, ""); } - + void setColNames(String... colNames) { Arrays.fill(this.colNames, ""); System.arraycopy(colNames, 0, this.colNames, 0, @@ -1232,25 +1238,25 @@ void setColNames(String... colNames) { colWidth.put(i, colNames[i].length()); } } - + void setJustification(int... justification) { Arrays.fill(this.justification, JUSTIFIED_LEFT); System.arraycopy(justification, 0, this.justification, 0, Math.min(justification.length, this.justification.length)); } - + void addRow(String... row) { data.add(row); for (int i = 0; i < row.length; i++) { colWidth.merge(i, row[i].length(), Math::max); } } - + void addRowWithColor(String color, String... row) { addRow(row); colors.put(data.size() - 1, color); } - + private String leftPad(String s, int fieldSize) { if (fieldSize > 0) { return String.format("%" + fieldSize + "s", s); @@ -1258,7 +1264,7 @@ private String leftPad(String s, int fieldSize) { return ""; } } - + private String rightPad(String s, int fieldSize) { if (fieldSize > 0) { return String.format("%-" + fieldSize + "s", s); @@ -1266,12 +1272,12 @@ private String rightPad(String s, int fieldSize) { return ""; } } - + private String center(String s, int fieldSize) { int rightPadding = Math.max(fieldSize - s.length(), 0) / 2; return rightPad(leftPad(s, fieldSize - rightPadding), fieldSize); } - + private String justify(int justification, String s, int fieldSize) { if (justification == JUSTIFIED_CENTER) { return center(s, fieldSize); @@ -1281,7 +1287,7 @@ private String justify(int justification, String s, int fieldSize) { return leftPad(s, fieldSize); } } - + @Override public String toPlainText() { final String COL_PADDING = " "; @@ -1309,7 +1315,7 @@ public String toPlainText() { } return sb.toString(); } - + @Override public String toHTML() { StringBuilder sb = new StringBuilder(""); @@ -1364,7 +1370,7 @@ public String toHTML() { return sb.toString(); } } - + /** * Displays a label (bold for html output) followed by a column of items * @@ -1372,15 +1378,15 @@ public String toHTML() { private static class ItemList implements ViewElement { private final String heading; private final List data = new ArrayList<>(); - + ItemList(String heading) { this.heading = heading; } - + void addItem(String item) { data.add(item); } - + @Override public String toPlainText() { StringBuilder sb = new StringBuilder(); @@ -1394,7 +1400,7 @@ public String toPlainText() { } return sb.toString(); } - + @Override public String toHTML() { StringBuilder sb = new StringBuilder(); @@ -1407,27 +1413,27 @@ public String toHTML() { return sb.toString(); } } - + /** * Displays a single line of text. The default constructor is used to insert a new line. */ private static class SingleLine implements ViewElement { - + private final String value; - + SingleLine(String value) { this.value = value; } - + SingleLine() { this(""); } - + @Override public String toPlainText() { return value + "\n"; } - + @Override public String toHTML() { return value + "
\n"; @@ -1467,29 +1473,29 @@ public String toHTML() { return result + "" + displayText + "
"; } } - + /** * Displays a single line in bold in a larger font in html. In plain text simply displays a single line. */ private static class Title implements ViewElement { - + private final String title; - + Title(String title) { this.title = title; } - + @Override public String toPlainText() { return title + "\n"; } - + @Override public String toHTML() { return "" + title + "
\n"; } } - + /** * Marks warning text; in html the text is displayed in red. In plain text it is preceded and followed * by an asterisk. @@ -1502,7 +1508,7 @@ private String warningStart() { return "*"; } } - + /** * Returns the end element of the warning text. * @return A String that is used to mark the end of a warning. @@ -1514,7 +1520,7 @@ private String warningEnd() { return "*"; } } - + /** * Marks the beginning of a section of italicized text if using html output. For plain text * returns an empty String. @@ -1527,7 +1533,7 @@ private String italicsStart() { return ""; } } - + /** * Marks the end of a section of italicized text. * @return The ending element for italicized text. diff --git a/megamek/src/megamek/common/Mounted.java b/megamek/src/megamek/common/Mounted.java index fad9c5262e4..c91a4a19b75 100644 --- a/megamek/src/megamek/common/Mounted.java +++ b/megamek/src/megamek/common/Mounted.java @@ -551,6 +551,10 @@ public String getDesc() { if (isArmored()) { desc.append(" (armored)"); } + + if (isInternalBomb()) { + desc.append(" (Int. Bay)"); + } return desc.toString(); } diff --git a/megamek/src/megamek/common/verifier/TestAero.java b/megamek/src/megamek/common/verifier/TestAero.java index ba40edc268c..7299e8e37e9 100644 --- a/megamek/src/megamek/common/verifier/TestAero.java +++ b/megamek/src/megamek/common/verifier/TestAero.java @@ -16,6 +16,7 @@ import megamek.common.*; import megamek.common.annotations.Nullable; +import megamek.common.options.OptionsConstants; import megamek.common.util.StringUtil; import megamek.common.weapons.bayweapons.BayWeapon; import megamek.common.weapons.capitalweapons.ScreenLauncherWeapon; @@ -1100,6 +1101,19 @@ public String printWeightCalculation() { + printMiscEquip() + printWeapon() + printAmmo(); } + @Override + public double getWeightMiscEquip() { + double weightSum = super.getWeightMiscEquip(); + for (Mounted m : getEntity().getMisc()) { + MiscType mt = (MiscType) m.getType(); + if (mt.hasFlag(MiscType.F_CARGO) && aero.hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)){ + // This equipment will get counted as a cargo bay later, for IBB compatibility. + weightSum -= m.getTonnage(); + } + } + return weightSum; + } + @Override public String printLocations() { StringBuffer buff = new StringBuffer(); From 90ff570c1648c2e00e606314d86a553b1bde68ea Mon Sep 17 00:00:00 2001 From: sleet01 Date: Mon, 18 Dec 2023 16:31:09 -0800 Subject: [PATCH 12/22] Continuing refinement of internal bomb handling and verification --- .../common/verifier/TestSmallCraft.java | 108 +++++++++--------- megamek/src/megamek/server/GameManager.java | 9 +- 2 files changed, 59 insertions(+), 58 deletions(-) diff --git a/megamek/src/megamek/common/verifier/TestSmallCraft.java b/megamek/src/megamek/common/verifier/TestSmallCraft.java index 2cc19e2d119..a74a6bae31a 100644 --- a/megamek/src/megamek/common/verifier/TestSmallCraft.java +++ b/megamek/src/megamek/common/verifier/TestSmallCraft.java @@ -30,7 +30,7 @@ * */ public class TestSmallCraft extends TestAero { - + // Indices used to specify firing arcs with aliases for aerodyne and spheroid public static final int ARC_NOSE = SmallCraft.LOC_NOSE; public static final int ARC_LWING = SmallCraft.LOC_LWING; @@ -41,42 +41,42 @@ public class TestSmallCraft extends TestAero { public static final int ARC_FWD_RIGHT = SmallCraft.LOC_RWING; public static final int ARC_AFT_LEFT = SmallCraft.LOC_LWING + REAR_ARC_OFFSET; public static final int ARC_AFT_RIGHT = SmallCraft.LOC_RWING + REAR_ARC_OFFSET; - + private final SmallCraft smallCraft; public enum AerospaceArmor{ - STANDARD(EquipmentType.T_ARMOR_AEROSPACE, false), + STANDARD(EquipmentType.T_ARMOR_AEROSPACE, false), CLAN_STANDARD(EquipmentType.T_ARMOR_AEROSPACE, true), IS_FERRO_ALUM(EquipmentType.T_ARMOR_ALUM, false), CLAN_FERRO_ALUM(EquipmentType.T_ARMOR_ALUM, true), - FERRO_PROTO(EquipmentType.T_ARMOR_FERRO_ALUM_PROTO, false), + FERRO_PROTO(EquipmentType.T_ARMOR_FERRO_ALUM_PROTO, false), HEAVY_FERRO_ALUM(EquipmentType.T_ARMOR_HEAVY_ALUM, false), LIGHT_FERRO_ALUM(EquipmentType.T_ARMOR_LIGHT_ALUM, false), - PRIMITIVE(EquipmentType.T_ARMOR_PRIMITIVE_AERO, false); + PRIMITIVE(EquipmentType.T_ARMOR_PRIMITIVE_AERO, false); /** - * The type, corresponding to types defined in + * The type, corresponding to types defined in * EquipmentType. */ public int type; - + /** * Denotes whether this armor is Clan or not. */ public boolean isClan; - + AerospaceArmor(int t, boolean c) { type = t; isClan = c; } - + /** * Given an armor type, return the AerospaceArmor instance that * represents that type. - * + * * @param t The armor type. * @param c Whether this armor type is Clan or not. - * @return The AeroArmor that corresponds to the given + * @return The AeroArmor that corresponds to the given * type or null if no match was found. */ public static AerospaceArmor getArmor(int t, boolean c) { @@ -87,18 +87,18 @@ public static AerospaceArmor getArmor(int t, boolean c) { } return null; } - + /** * Calculates and returns the points per ton of the armor type given the * weight and shape of a small craft/dropship - * + * * @param sc The small craft/dropship * @return The number of points of armor per ton */ public double pointsPerTon(SmallCraft sc) { return SmallCraft.armorPointsPerTon(sc.getWeight(), sc.isSpheroid(), type, isClan); } - + /** * @return The MiscType for this armor. */ @@ -110,7 +110,7 @@ public EquipmentType getArmorEqType() { /** * Filters all small craft/dropship armor according to given tech constraints - * + * * @param techManager Used to check the tech constraints * @return A list of all armors that meet the tech constraints */ @@ -124,7 +124,7 @@ public static List legalArmorsFor(ITechManager techManager) { } return retVal; } - + public static int maxArmorPoints(SmallCraft sc) { AerospaceArmor a = AerospaceArmor.getArmor(sc.getArmorType(0), TechConstants.isClan(sc.getArmorTechLevel(0))); @@ -135,10 +135,10 @@ public static int maxArmorPoints(SmallCraft sc) { return 0; } } - + /** * Computes the maximum number armor level in tons - * + * */ public static double maxArmorWeight(SmallCraft smallCraft) { if (smallCraft.isSpheroid()) { @@ -147,16 +147,16 @@ public static double maxArmorWeight(SmallCraft smallCraft) { return floor(smallCraft.get0SI() * 4.5, Ceil.HALFTON); } } - + /** * Computes the amount of weight required for fire control systems and power distribution * systems for exceeding the base limit of weapons per firing arc. - * + * * Spheroid aft side arcs are implemented as rear-mounted; the return value uses the index * of forward side + 3 for the aft side arcs. - * + * * @param sc The small craft/dropship in question - * @return Returns a double array, where each element corresponds to a + * @return Returns a double array, where each element corresponds to a * location and the value is the extra tonnage required by exceeding the base * allotment */ @@ -193,10 +193,10 @@ public static double[] extraSlotCost(SmallCraft sc) { } return retVal; } - + /** * Computes the weight of the engine. - * + * * @param clan Whether the unit is a Clan design * @param tonnage The weight of the unit * @param desiredSafeThrust The safe thrust value @@ -204,7 +204,7 @@ public static double[] extraSlotCost(SmallCraft sc) { * @param year The original construction year (only relevant for primitives) * @return The weight of the engine in tons */ - public static double calculateEngineTonnage(boolean clan, double tonnage, + public static double calculateEngineTonnage(boolean clan, double tonnage, int desiredSafeThrust, boolean dropship, int year) { double multiplier; if (clan) { @@ -216,7 +216,7 @@ public static double calculateEngineTonnage(boolean clan, double tonnage, } return ceil(tonnage * desiredSafeThrust * multiplier, Ceil.HALFTON); } - + public static int weightFreeHeatSinks(SmallCraft sc) { double engineTonnage = calculateEngineTonnage(sc.isClan(), sc.getWeight(), sc.getOriginalWalkMP(), sc.hasETypeFlag(Entity.ETYPE_DROPSHIP), sc.getOriginalBuildYear()); @@ -240,7 +240,7 @@ public static int weightFreeHeatSinks(SmallCraft sc) { } } } - + public static double smallCraftEngineMultiplier(int year) { if (year >= 2500) { return 0.065; @@ -258,7 +258,7 @@ public static double smallCraftEngineMultiplier(int year) { return 0.143; } } - + public static double smallCraftControlMultiplier(int year) { if (year >= 2500) { return 0.0075; @@ -276,7 +276,7 @@ public static double smallCraftControlMultiplier(int year) { return 0.01575; } } - + public static double dropshipEngineMultiplier(int year) { if (year >= 2500) { return 0.065; @@ -294,7 +294,7 @@ public static double dropshipEngineMultiplier(int year) { return 0.13; } } - + public static double dropshipControlMultiplier(int year) { if (year >= 2500) { return 0.0075; @@ -312,7 +312,7 @@ public static double dropshipControlMultiplier(int year) { return 0.015; } } - + /** * @return Minimum crew requirements based on unit type and equipment crew requirements. */ @@ -329,10 +329,10 @@ public static int minimumBaseCrew(SmallCraft sc) { } return crew; } - + public TestSmallCraft(SmallCraft sc, TestEntityOption option, String fs) { super(sc, option, fs); - + smallCraft = sc; } @@ -350,12 +350,12 @@ public boolean isTank() { public boolean isMech() { return false; } - + @Override public boolean isAero() { return true; } - + @Override public boolean isSmallCraft() { return true; @@ -401,7 +401,7 @@ public int getCountHeatSinks() { @Override public double getWeightHeatSinks() { - return Math.max(smallCraft.getHeatSinks() - weightFreeHeatSinks(smallCraft), 0); + return Math.max(smallCraft.getHeatSinks() - weightFreeHeatSinks(smallCraft), 0); } // Bays can store multiple tons of ammo in a single slot. @@ -444,7 +444,7 @@ public String printWeightMisc() { } return ""; } - + @Override public StringBuffer printWeapon() { if (!getEntity().usesWeaponBays()) { @@ -505,7 +505,7 @@ public String printWeightFuel() { public Aero getAero() { return smallCraft; } - + public SmallCraft getSmallCraft() { return smallCraft; } @@ -517,7 +517,7 @@ public String printArmorLocProp(int loc, int wert) { /** * Checks to see if this unit has valid armor assignment. - * + * * @param buff A buffer that collects messages about validation failures * @return Whether the unit's armor is valid */ @@ -533,7 +533,7 @@ public boolean correctArmor(StringBuffer buff) { return correct ; } - + /** * Checks that the heatsink type is a legal value. * @@ -542,7 +542,7 @@ public boolean correctArmor(StringBuffer buff) { */ @Override public boolean correctHeatSinks(StringBuffer buff) { - if ((smallCraft.getHeatType() != Aero.HEAT_SINGLE) + if ((smallCraft.getHeatType() != Aero.HEAT_SINGLE) && (smallCraft.getHeatType() != Aero.HEAT_DOUBLE)) { buff.append("Invalid heatsink type! Valid types are ").append(Aero.HEAT_SINGLE) .append(" and ").append(Aero.HEAT_DOUBLE).append(". Found ") @@ -555,7 +555,7 @@ public boolean correctHeatSinks(StringBuffer buff) { @Override public boolean correctEntity(StringBuffer buff, int ammoTechLvl) { boolean correct = true; - + if (skip()) { return true; } @@ -569,28 +569,28 @@ public boolean correctEntity(StringBuffer buff, int ammoTechLvl) { buff.append(" Total ").append(getCountHeatSinks()).append("\n"); buff.append(" Required ").append(weightFreeHeatSinks(smallCraft)).append("\n"); correct = false; - } - + } + if (showCorrectArmor() && !correctArmor(buff)) { correct = false; } if (showFailedEquip() && hasFailedEquipment(buff)) { correct = false; } - + correct &= !hasIllegalTechLevels(buff, ammoTechLvl); correct &= !hasIllegalEquipmentCombinations(buff); correct &= correctHeatSinks(buff); correct &= correctCrew(buff); correct &= correctCriticals(buff); - + return correct; } @Override public boolean hasIllegalEquipmentCombinations(StringBuffer buff) { boolean illegal = false; - + // For DropShips, make sure all bays have at least one weapon and that there are at least // ten shots of ammo for each ammo-using weapon in the bay. for (Mounted bay : smallCraft.getWeaponBayList()) { @@ -659,7 +659,7 @@ public boolean hasIllegalEquipmentCombinations(StringBuffer buff) { ? MiscType.F_DS_EQUIPMENT : MiscType.F_SC_EQUIPMENT; for (Mounted m : smallCraft.getEquipment()) { if (m.getType() instanceof MiscType) { - if (!m.getType().hasFlag(typeFlag)) { + if (!m.getType().hasFlag(typeFlag) && !m.getType().hasFlag(MiscType.F_SINGLE_HEX_ECM)) { buff.append("Cannot mount ").append(m.getType().getName()).append("\n"); illegal = true; } @@ -749,7 +749,7 @@ public boolean hasIllegalEquipmentCombinations(StringBuffer buff) { return illegal; } - + /** * Checks that the unit meets minimum crew and quarters requirements. * @param buffer Where to write messages explaining failures. @@ -790,7 +790,7 @@ public boolean correctCrew(StringBuffer buffer) { public StringBuffer printEntity() { StringBuffer buff = new StringBuffer(); buff.append("Small Craft / DropShip: ").append(smallCraft.getDisplayName()).append("\n"); - buff.append("Found in: ").append(fileString).append("\n"); + buff.append("Found in: ").append(fileString).append("\n"); buff.append(printTechLevel()); buff.append("Intro year: ").append(getEntity().getYear()).append("\n"); buff.append(printSource()); @@ -806,7 +806,7 @@ public StringBuffer printEntity() { printFailedEquipment(buff); return buff; } - + @Override public double calculateWeightExact() { double weight = 0; @@ -831,7 +831,7 @@ public double calculateWeightExact() { @Override public String printWeightCalculation() { return printWeightEngine() - + printWeightControls() + printWeightFuel() + + printWeightControls() + printWeightFuel() + printWeightHeatSinks() + printWeightArmor() + printWeightMisc() + printWeightCarryingSpace() @@ -839,7 +839,7 @@ public String printWeightCalculation() { + "Equipment:\n" + printMiscEquip() + printWeapon() + printAmmo(); } - + @Override public String printLocations() { StringBuilder buff = new StringBuilder(); @@ -850,7 +850,7 @@ public String printLocations() { for (int j = 0; j < getEntity().getNumberOfCriticals(i); j++) { CriticalSlot slot = getEntity().getCritical(i, j); if (slot == null) { - j = getEntity().getNumberOfCriticals(i); + j = getEntity().getNumberOfCriticals(i); } else if (slot.getType() == CriticalSlot.TYPE_SYSTEM) { buff.append(j).append(". UNKNOWN SYSTEM NAME"); buff.append("\n"); diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index fa2784a1687..64f9f05d369 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -25044,10 +25044,10 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in break; case Aero.CRIT_BOMB: // bomb destroyed - // go through bomb list and choose one + // go through bomb list and choose one (internal bay munitions are handled separately) List bombs = new ArrayList<>(); for (Mounted bomb : aero.getBombs()) { - if (bomb.getType().isHittable() && (bomb.getHittableShotsLeft() > 0)) { + if (bomb.getType().isHittable() && (bomb.getHittableShotsLeft() > 0) && !bomb.isInternalBomb()) { bombs.add(bomb); } } @@ -25165,11 +25165,12 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in weapons.add(weapon); } } - // add in in hittable misc equipment + // add in in hittable misc equipment; internal bay munitions are handled separately. for (Mounted misc : aero.getMisc()) { if (misc.getType().isHittable() && (misc.getLocation() == loc) - && !misc.isDestroyed()) { + && !misc.isDestroyed() + && !misc.isInternalBomb()) { weapons.add(misc); } } From c3d916a755e795f086611a36447ce45b119ca1c4 Mon Sep 17 00:00:00 2001 From: sleet01 Date: Tue, 19 Dec 2023 13:50:22 -0800 Subject: [PATCH 13/22] Updated ground fire check and first pass at proper MUL reinforcement for two bomb location types --- .../client/bot/princess/FireControl.java | 23 +++-- .../client/bot/princess/WeaponFireInfo.java | 93 ++++++++++--------- .../client/ui/swing/FiringDisplay.java | 47 +++++----- .../ui/swing/PointblankShotDisplay.java | 4 +- megamek/src/megamek/common/Entity.java | 10 ++ .../src/megamek/common/EntityListFile.java | 20 +++- megamek/src/megamek/common/MULParser.java | 13 ++- .../common/actions/WeaponAttackAction.java | 21 ++++- .../megamek/common/weapons/TAGHandler.java | 18 ++-- .../megamek/common/weapons/WeaponHandler.java | 3 + megamek/src/megamek/server/GameManager.java | 8 +- 11 files changed, 159 insertions(+), 101 deletions(-) diff --git a/megamek/src/megamek/client/bot/princess/FireControl.java b/megamek/src/megamek/client/bot/princess/FireControl.java index 2a22d5c578d..76d1981f5d7 100644 --- a/megamek/src/megamek/client/bot/princess/FireControl.java +++ b/megamek/src/megamek/client/bot/princess/FireControl.java @@ -1439,7 +1439,7 @@ WeaponFireInfo buildWeaponFireInfo(final Entity shooter, final boolean assumeUnderFlightPath, final boolean guessToHit) { return new WeaponFireInfo(shooter, flightPath, target, targetState, - weapon, game, assumeUnderFlightPath, guessToHit, owner, new int[0]); + weapon, game, assumeUnderFlightPath, guessToHit, owner, null); } /** @@ -1465,9 +1465,9 @@ private WeaponFireInfo buildWeaponFireInfo(final Entity shooter, final Game game, final boolean assumeUnderFlightPath, final boolean guessToHit, - final int[] bombPayload) { + final HashMap bombPayloads) { return new WeaponFireInfo(shooter, flightPath, target, targetState, - weapon, game, assumeUnderFlightPath, guessToHit, owner, bombPayload); + weapon, game, assumeUnderFlightPath, guessToHit, owner, bombPayloads); } /** @@ -1694,11 +1694,22 @@ private FiringPlan getDiveBombPlan(final Entity shooter, while (weaponIter.hasNext()) { final Mounted weapon = weaponIter.next(); if (weapon.getType().hasFlag(WeaponType.F_DIVE_BOMB)) { - final int[] bombPayload = new int[BombType.B_NUM]; + final HashMap bombPayloads = new HashMap(); + bombPayloads.put("internal", new int[BombType.B_NUM]); + bombPayloads.put("external", new int[BombType.B_NUM]); + // load up all droppable bombs, yeah baby! Mix thunder bombs and infernos 'cause why the hell not. // seriously, though, TODO: more intelligent bomb drops for (final Mounted bomb : shooter.getBombs(BombType.F_GROUND_BOMB)) { - bombPayload[((BombType) bomb.getType()).getBombType()]++; + int bType = ((BombType) bomb.getType()).getBombType(); + if (bomb.isInternalBomb()) { + // Can only drop 6 internal bombs in one turn. + if (bombPayloads.get("internal")[bType] < 6) { + bombPayloads.get("internal")[bType]++; + } + } else { + bombPayloads.get("external")[bType]++; + } } final WeaponFireInfo diveBomb = buildWeaponFireInfo(shooter, @@ -1709,7 +1720,7 @@ private FiringPlan getDiveBombPlan(final Entity shooter, game, passedOverTarget, guess, - bombPayload); + bombPayloads); diveBombPlan.add(diveBomb); } } diff --git a/megamek/src/megamek/client/bot/princess/WeaponFireInfo.java b/megamek/src/megamek/client/bot/princess/WeaponFireInfo.java index 93e57bc29de..e0382971585 100644 --- a/megamek/src/megamek/client/bot/princess/WeaponFireInfo.java +++ b/megamek/src/megamek/client/bot/princess/WeaponFireInfo.java @@ -27,6 +27,7 @@ import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; /** @@ -80,7 +81,7 @@ protected WeaponFireInfo(final Princess owner) { final Game game, final boolean guess, final Princess owner) { - this(shooter, null, null, target, null, weapon, game, false, guess, owner, new int[0]); + this(shooter, null, null, target, null, weapon, game, false, guess, owner, null); } /** @@ -102,7 +103,7 @@ protected WeaponFireInfo(final Princess owner) { final Game game, final boolean guess, final Princess owner) { - this(shooter, shooterState, null, target, targetState, weapon, game, false, guess, owner, new int[0]); + this(shooter, shooterState, null, target, targetState, weapon, game, false, guess, owner, null); } /** @@ -128,8 +129,8 @@ protected WeaponFireInfo(final Princess owner) { final boolean assumeUnderFlightPath, final boolean guess, final Princess owner, - final int[] bombPayload) { - this(shooter, null, shooterPath, target, targetState, weapon, game, assumeUnderFlightPath, guess, owner, bombPayload); + final HashMap bombPayloads) { + this(shooter, null, shooterPath, target, targetState, weapon, game, assumeUnderFlightPath, guess, owner, bombPayloads); } /** @@ -159,7 +160,7 @@ private WeaponFireInfo(final Entity shooter, final boolean assumeUnderFlightPath, final boolean guess, final Princess owner, - final int[] bombPayload) { + final HashMap bombPayloads) { this.owner = owner; setShooter(shooter); @@ -168,7 +169,7 @@ private WeaponFireInfo(final Entity shooter, setTargetState(targetState); setWeapon(weapon); setGame(game); - initDamage(shooterPath, assumeUnderFlightPath, guess, bombPayload); + initDamage(shooterPath, assumeUnderFlightPath, guess, bombPayloads); } protected WeaponAttackAction getAction() { @@ -276,7 +277,7 @@ shooterPath, getWeapon(), getGame(), } private ToHitData calcRealToHit(final WeaponAttackAction weaponAttackAction) { - return weaponAttackAction.toHit(getGame(), + return weaponAttackAction.toHit(getGame(), owner.getPrecognition().getECMInfo()); } @@ -331,7 +332,7 @@ public double getExpectedDamage() { } WeaponAttackAction buildWeaponAttackAction() { - if (!(getWeapon().getType().hasFlag(WeaponType.F_ARTILLERY) + if (!(getWeapon().getType().hasFlag(WeaponType.F_ARTILLERY) || (getWeapon().getType() instanceof CapitalMissileWeapon && Compute.isGroundToGround(shooter, target)))) { return new WeaponAttackAction(getShooter().getId(), getTarget().getTargetType(), getTarget().getId(), @@ -342,14 +343,14 @@ WeaponAttackAction buildWeaponAttackAction() { } } - private WeaponAttackAction buildBombAttackAction(final int[] bombPayload) { + private WeaponAttackAction buildBombAttackAction(final HashMap bombPayloads) { final WeaponAttackAction diveBomb = new WeaponAttackAction(getShooter().getId(), getTarget().getTargetType(), getTarget().getId(), getShooter().getEquipmentNum(getWeapon())); - - diveBomb.setBombPayload(bombPayload); - + + diveBomb.setBombPayloads(bombPayloads); + return diveBomb; } @@ -358,7 +359,7 @@ private WeaponAttackAction buildBombAttackAction(final int[] bombPayload) { if (weapon.isGroundBomb()) { return computeExpectedBombDamage(getShooter(), weapon, getTarget().getPosition()); } - + // bay weapons require special consideration, by looping through all weapons and adding up the damage // A bay's weapons may have different ranges, most noticeable in laser bays, where the damage potential // varies with distance to target. @@ -370,9 +371,9 @@ private WeaponAttackAction buildBombAttackAction(final int[] bombPayload) { int maxRange = game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_RANGE) ? weaponType.getExtremeRange() : weaponType.getLongRange(); int targetDistance = getShooter().getPosition().distance(getTarget().getPosition()); - + // if the particular weapon is within range or we're an aircraft strafing a ground unit - // then we can count it. Otherwise, it's not going to contribute to damage, and we want + // then we can count it. Otherwise, it's not going to contribute to damage, and we want // to avoid grossly overestimating damage. if (targetDistance <= maxRange || shooter.isAirborne() && !target.isAirborne()) { bayDamage += weaponType.getDamage(); @@ -390,27 +391,27 @@ private WeaponAttackAction buildBombAttackAction(final int[] bombPayload) { } // artillery and cluster table use the rack size as the base damage amount - // a little inaccurate, but better than ignoring those weapons entirely + // a little inaccurate, but better than ignoring those weapons entirely if ((weaponType.getDamage() == WeaponType.DAMAGE_BY_CLUSTERTABLE) || (weaponType.getDamage() == WeaponType.DAMAGE_ARTILLERY)) { return weaponType.getRackSize(); } - // infantry weapons use number of troopers multiplied by weapon damage, + // infantry weapons use number of troopers multiplied by weapon damage, // with # troopers counting as 1 for support vehicles if ((weaponType.getDamage() == WeaponType.DAMAGE_VARIABLE) && (weaponType instanceof InfantryWeapon)) { - int numTroopers = (shooter instanceof Infantry) ? + int numTroopers = (shooter instanceof Infantry) ? ((Infantry) shooter).getShootingStrength() : 1; return InfantryWeaponHandler.calculateBaseDamage(shooter, weapon, weaponType) * numTroopers; } - + // this is a special case - if we're considering hitting a swarmed target // that's basically our only option if (weaponType.getInternalName() == Infantry.SWARM_WEAPON_MEK) { return 1; } - + if (getTarget() instanceof Entity) { double dmg = Compute.getExpectedDamage(getGame(), getAction(), true, owner.getPrecognition().getECMInfo()); @@ -419,10 +420,10 @@ private WeaponAttackAction buildBombAttackAction(final int[] bombPayload) { } return dmg; } - + return weaponType.getDamage(); } - + /** * Compute the heat output by firing a given weapon. * Contains special logic for bay weapons when using individual bay heat. @@ -441,13 +442,13 @@ int computeHeat(Mounted weapon) { WeaponType weaponType = (WeaponType) bayWeapon.getType(); bayHeat += weaponType.getHeat(); } - + return bayHeat; } else { return weapon.getType().getHeat(); } } - + /** * Worker function to compute expected bomb damage given the shooter * @param shooter The unit making the attack. @@ -458,35 +459,35 @@ int computeHeat(Mounted weapon) { private double computeExpectedBombDamage(final Entity shooter, final Mounted weapon, final Coords bombedHex) { double damage = 0D; //lol double damage I wish - + // for dive attacks, we can pretty much assume that we're going to drop everything we've got on the poor scrubs in this hex if (weapon.getType().hasFlag(WeaponType.F_DIVE_BOMB)) { for (final Mounted bomb : shooter.getBombs(BombType.F_GROUND_BOMB)) { final int damagePerShot = ((BombType) bomb.getType()).getDamagePerShot(); - + // some bombs affect a blast radius, so we take that into account final List affectedHexes = new ArrayList<>(); - - int blastRadius = BombType.getBombBlastRadius(bomb.getType().getInternalName()); + + int blastRadius = BombType.getBombBlastRadius(bomb.getType().getInternalName()); for (int radius = 0; radius <= blastRadius; radius++) { affectedHexes.addAll(bombedHex.allAtDistance(radius)); } - + // now we go through all affected hexes and add up the damage done for (final Coords coords : affectedHexes) { - for (final Entity currentVictim : game.getEntitiesVector(coords)) { + for (final Entity currentVictim : game.getEntitiesVector(coords)) { if (currentVictim.getOwner().getTeam() != shooter.getOwner().getTeam()) { damage += damagePerShot; } else { // we prefer not to blow up friendlies if we can help it damage -= damagePerShot; - } + } } } } } - + damage = damage * getProbabilityToHit(); - + return damage; } @@ -501,9 +502,9 @@ private double computeExpectedBombDamage(final Entity shooter, final Mounted wea void initDamage(@Nullable final MovePath shooterPath, final boolean assumeUnderFlightPath, final boolean guess, - final int[] bombPayload) { + final HashMap bombPayloads) { boolean debugging = false; - + final StringBuilder msg = debugging ? new StringBuilder("Initializing Damage for ").append(getShooter().getDisplayName()) @@ -513,12 +514,12 @@ void initDamage(@Nullable final MovePath shooterPath, null; // Set up the attack action and calculate the chance to hit. - if ((null == bombPayload) || (0 == bombPayload.length)) { + if ((null == bombPayloads) || (0 == bombPayloads.get("external").length)) { setAction(buildWeaponAttackAction()); } else { - setAction(buildBombAttackAction(bombPayload)); + setAction(buildBombAttackAction(bombPayloads)); } - + if (!guess) { setToHit(calcRealToHit(getWeaponAttackAction())); } else if (null != shooterPath) { @@ -539,13 +540,13 @@ void initDamage(@Nullable final MovePath shooterPath, setExpectedDamageOnHit(0); return; } - + if (debugging && getShooterState().hasNaturalAptGun()) { msg.append("\n\tAttacker has Natural Aptitude Gunnery"); } - + setProbabilityToHit(Compute.oddsAbove(getToHit().getValue(), getShooterState().hasNaturalAptGun()) / 100); - + if (debugging) { msg.append("\n\tHit Chance: ").append(LOG_PER.format(getProbabilityToHit())); } @@ -557,16 +558,16 @@ void initDamage(@Nullable final MovePath shooterPath, if (!currentFireMode.equals(getWeapon().curMode().getName())) { setUpdatedFiringMode(spinMode); } - + setHeat(computeHeat(weapon)); - + if (debugging) { msg.append("\n\tHeat: ").append(getHeat()); } setExpectedDamageOnHit(computeExpectedDamage()); setMaxDamage(getExpectedDamageOnHit()); - + if (debugging) { msg.append("\n\tMax Damage: ").append(LOG_DEC.format(maxDamage)); } @@ -637,7 +638,7 @@ void initDamage(@Nullable final MovePath shooterPath, LogManager.getLogger().debug(msg.toString()); } } - + WeaponAttackAction getWeaponAttackAction() { if (null != getAction()) { return getAction(); @@ -677,7 +678,7 @@ String getDebugDescription() { public Integer getUpdatedFiringMode() { return updatedFiringMode; } - + public void setUpdatedFiringMode(int mode) { updatedFiringMode = mode; } diff --git a/megamek/src/megamek/client/ui/swing/FiringDisplay.java b/megamek/src/megamek/client/ui/swing/FiringDisplay.java index 15575575466..786d2c10675 100644 --- a/megamek/src/megamek/client/ui/swing/FiringDisplay.java +++ b/megamek/src/megamek/client/ui/swing/FiringDisplay.java @@ -1269,7 +1269,7 @@ public void ready() { waa2.setAmmoId(waa.getAmmoId()); waa2.setAmmoMunitionType(waa.getAmmoMunitionType()); waa2.setAmmoCarrier(waa.getAmmoCarrier()); - waa2.setBombPayload(waa.getBombPayload()); + waa2.setBombPayloads(waa.getBombPayloads()); waa2.setStrafing(waa.isStrafing()); waa2.setStrafingFirstShot(waa.isStrafingFirstShot()); newAttacks.addElement(waa2); @@ -1299,7 +1299,7 @@ public void ready() { waa2.setAmmoId(waa.getAmmoId()); waa2.setAmmoMunitionType(waa.getAmmoMunitionType()); waa2.setAmmoCarrier(waa.getAmmoCarrier()); - waa2.setBombPayload(waa.getBombPayload()); + waa2.setBombPayloads(waa.getBombPayloads()); waa2.setStrafing(waa.isStrafing()); waa2.setStrafingFirstShot(waa.isStrafingFirstShot()); newAttacks.addElement(waa2); @@ -1508,29 +1508,32 @@ private void updateStrafingTargets() { clientgui.getUnitDisplay().wPan.setToHit(toHitBuff.toString()); } - private int[] getBombPayload(boolean isSpace, int limit) { - int[] payload = new int[BombType.B_NUM]; + private HashMap getBombPayloads(boolean isSpace, int limit) { + HashMap payloads = new HashMap(); + HashMap loadouts = new HashMap(); + String[] titles = new String[] {"internal", "external"}; + for (String title: titles) { + payloads.put(title, new int[BombType.B_NUM]); + } + + // Have to return after map is filled in, not before if (!ce().isBomber()) { - return payload; + return payloads; } - HashMap loadouts = new HashMap(); - loadouts.put("external", ce().getExternalBombLoadout()); loadouts.put("internal", ce().getInternalBombLoadout()); - String[] titles = new String[] {"internal", "external"}; + loadouts.put("external", ce().getExternalBombLoadout()); for (String title: titles){ int[] loadout = loadouts.get(title); - // this part is ugly, but we need to find any other bombing attacks by - // this - // entity in the attack list and subtract those payloads from the - // loadout + // this part is ugly, but we need to find any other bombing attacks by this + // entity in the attack list and subtract those payloads from the relevant loadout for (EntityAction o : attacks) { if (o instanceof WeaponAttackAction) { WeaponAttackAction waa = (WeaponAttackAction) o; if (waa.getEntityId() == ce().getId()) { - int[] priorLoad = waa.getBombPayload(); + int[] priorLoad = waa.getBombPayloads().get(title); for (int i = 0; i < priorLoad.length; i++) { loadout[i] = loadout[i] - priorLoad[i]; } @@ -1539,11 +1542,11 @@ private int[] getBombPayload(boolean isSpace, int limit) { } // Don't bother preparing a dialog for bombs that don't exist. - if (Arrays.stream(loadout).sum() == 0){ + if (Arrays.stream(loadout).sum() <= 0){ continue; } - // Internal bay dive-bombing is limited to 6 bombs at a time, but other limits may apply + // Internal bay bombing is limited to 6 items per turn, but other limits may also apply if ("internal".equals(title)) { int usedBombs = ((IBomber) ce()).getUsedInternalBombs(); limit = (limit <= -1) ? 6 - usedBombs : Math.min(6 - usedBombs, limit); @@ -1558,11 +1561,11 @@ private int[] getBombPayload(boolean isSpace, int limit) { if (bombsDialog.getAnswer()) { int[] choices = bombsDialog.getChoices(); for (int i = 0; i < choices.length; i++) { - payload[i] += choices[i]; + payloads.get(title)[i] += choices[i]; } } } - return payload; + return payloads; } /** @@ -1656,15 +1659,11 @@ void fire() { // check for a bomb payload dialog if (mounted.getType().hasFlag(WeaponType.F_SPACE_BOMB)) { - int[] payload = getBombPayload(true, -1); - waa.setBombPayload(payload); + waa.setBombPayloads(getBombPayloads(true, -1)); } else if (mounted.getType().hasFlag(WeaponType.F_DIVE_BOMB)) { - int[] payload = getBombPayload(false, -1); - waa.setBombPayload(payload); + waa.setBombPayloads(getBombPayloads(false, -1)); } else if (mounted.getType().hasFlag(WeaponType.F_ALT_BOMB)) { - // if the user cancels, then return - int[] payload = getBombPayload(false, 2); - waa.setBombPayload(payload); + waa.setBombPayloads(getBombPayloads(false, 2)); } if ((mounted.getLinked() != null) diff --git a/megamek/src/megamek/client/ui/swing/PointblankShotDisplay.java b/megamek/src/megamek/client/ui/swing/PointblankShotDisplay.java index 7cfd329c342..eeeea2152fc 100644 --- a/megamek/src/megamek/client/ui/swing/PointblankShotDisplay.java +++ b/megamek/src/megamek/client/ui/swing/PointblankShotDisplay.java @@ -635,7 +635,7 @@ public void ready() { waa2.setAmmoId(waa.getAmmoId()); waa2.setAmmoMunitionType(waa.getAmmoMunitionType()); waa2.setAmmoCarrier(waa.getAmmoCarrier()); - waa2.setBombPayload(waa.getBombPayload()); + waa2.setBombPayloads(waa.getBombPayloads()); waa2.setStrafing(waa.isStrafing()); waa2.setStrafingFirstShot(waa.isStrafingFirstShot()); waa2.setPointblankShot(waa.isPointblankShot()); @@ -669,7 +669,7 @@ public void ready() { waa2.setAmmoId(waa.getAmmoId()); waa2.setAmmoMunitionType(waa.getAmmoMunitionType()); waa2.setAmmoCarrier(waa.getAmmoCarrier()); - waa2.setBombPayload(waa.getBombPayload()); + waa2.setBombPayloads(waa.getBombPayloads()); waa2.setStrafing(waa.isStrafing()); waa2.setStrafingFirstShot(waa.isStrafingFirstShot()); waa2.setPointblankShot(waa.isPointblankShot()); diff --git a/megamek/src/megamek/common/Entity.java b/megamek/src/megamek/common/Entity.java index 409257323d7..1975508ca82 100644 --- a/megamek/src/megamek/common/Entity.java +++ b/megamek/src/megamek/common/Entity.java @@ -770,6 +770,7 @@ public abstract class Entity extends TurnOrdered implements Transporter, Targeta protected int consecutiveRHSUses = 0; private final Set attackedByThisTurn = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Set groundAttackedByThisTurn = Collections.newSetFromMap(new ConcurrentHashMap<>()); /** * Determines the sort order for weapons in the UnitDisplay weapon list. @@ -14486,14 +14487,23 @@ public void addAttackedByThisTurn(int entityId) { attackedByThisTurn.add(entityId); } + public void addGroundAttackedByThisTurn(int entityId) { + groundAttackedByThisTurn.add(entityId); + } + public void clearAttackedByThisTurn() { attackedByThisTurn.clear(); + groundAttackedByThisTurn.clear(); } public Collection getAttackedByThisTurn() { return new HashSet<>(attackedByThisTurn); } + public Collection getGroundAttackedByThisTurn() { + return new HashSet<>(groundAttackedByThisTurn); + } + public WeaponSortOrder getWeaponSortOrder() { return (weaponSortOrder == null) ? WeaponSortOrder.DEFAULT : weaponSortOrder; } diff --git a/megamek/src/megamek/common/EntityListFile.java b/megamek/src/megamek/common/EntityListFile.java index 331c27ea563..6121971f525 100644 --- a/megamek/src/megamek/common/EntityListFile.java +++ b/megamek/src/megamek/common/EntityListFile.java @@ -858,16 +858,26 @@ private static void writeEntityList(Writer output, ArrayList list) throw // Write the Bomb Data if needed if (entity.isBomber()) { IBomber b = (IBomber) entity; - int[] bombChoices = b.getBombChoices(); - if (bombChoices.length > 0) { + int[] intBombChoices = b.getIntBombChoices(); + int[] extBombChoices = b.getExtBombChoices(); + if (intBombChoices.length > 0 || extBombChoices.length > 0) { output.write(indentStr(indentLvl + 1) + "\n"); for (int type = 0; type < BombType.B_NUM; type++) { String typeName = BombType.getBombInternalName(type); - if (bombChoices[type] > 0) { + if (intBombChoices[type] > 0) { output.write(indentStr(indentLvl + 2) + "\n"); + } + if (extBombChoices[type] > 0) { + output.write(indentStr(indentLvl + 2) + "\n"); } } @@ -879,6 +889,8 @@ private static void writeEntityList(Writer output, ArrayList list) throw output.write(m.getType().getShortName()); output.write("\" load=\""); output.write(String.valueOf(m.getBaseShotsLeft())); + output.write("\" Internal=\""); + output.write(String.valueOf(m.isInternalBomb())); output.write("\"/>\n"); } output.write(indentStr(indentLvl + 1) + "\n"); diff --git a/megamek/src/megamek/common/MULParser.java b/megamek/src/megamek/common/MULParser.java index 37d5595d371..c38416f11f2 100644 --- a/megamek/src/megamek/common/MULParser.java +++ b/megamek/src/megamek/common/MULParser.java @@ -2250,17 +2250,24 @@ private void parseBombs(Element bombsTag, Entity entity) { Element currEle = (Element) currNode; String nodeName = currNode.getNodeName(); if (nodeName.equalsIgnoreCase(BOMB)) { - int[] bombChoices = ((IBomber) entity).getBombChoices(); + int[] intBombChoices = ((IBomber) entity).getIntBombChoices(); + int[] extBombChoices = ((IBomber) entity).getExtBombChoices(); String type = currEle.getAttribute(TYPE); String load = currEle.getAttribute(LOAD); + boolean internal = Boolean.parseBoolean(currEle.getAttribute(INTERNAL)); if (!type.isBlank() && !load.isBlank()) { int bombType = BombType.getBombTypeFromInternalName(type); if ((bombType <= BombType.B_NONE) || (bombType >= BombType.B_NUM)) { continue; } - bombChoices[bombType] += Integer.parseInt(load); - ((IBomber) entity).setBombChoices(bombChoices); + if (internal) { + intBombChoices[bombType] += Integer.parseInt(load); + ((IBomber) entity).setIntBombChoices(intBombChoices); + } else { + extBombChoices[bombType] += Integer.parseInt(load); + ((IBomber) entity).setExtBombChoices(extBombChoices); + } } } } diff --git a/megamek/src/megamek/common/actions/WeaponAttackAction.java b/megamek/src/megamek/common/actions/WeaponAttackAction.java index 4a382581b7d..6320eab2d3a 100644 --- a/megamek/src/megamek/common/actions/WeaponAttackAction.java +++ b/megamek/src/megamek/common/actions/WeaponAttackAction.java @@ -80,7 +80,7 @@ public class WeaponAttackAction extends AbstractAttackAction implements Serializ private int swarmMissiles = 0; // bomb stuff - private int[] bombPayload = new int[BombType.B_NUM]; + private HashMap bombPayloads = new HashMap(); // equipment that affects this attack (AMS, ECM?, etc) // only used server-side @@ -116,11 +116,15 @@ public class WeaponAttackAction extends AbstractAttackAction implements Serializ public WeaponAttackAction(int entityId, int targetId, int weaponId) { super(entityId, targetId); this.weaponId = weaponId; + this.bombPayloads.put("internal", new int[BombType.B_NUM]); + this.bombPayloads.put("external", new int[BombType.B_NUM]); } public WeaponAttackAction(int entityId, int targetType, int targetId, int weaponId) { super(entityId, targetType, targetId); this.weaponId = weaponId; + this.bombPayloads.put("internal", new int[BombType.B_NUM]); + this.bombPayloads.put("external", new int[BombType.B_NUM]); } public int getWeaponId() { @@ -2745,16 +2749,25 @@ public void setSwarmMissiles(int swarmMissiles) { } public int[] getBombPayload() { + int[] bombPayload = new int[BombType.B_NUM]; + for (int i=0; i getBombPayloads() { + return bombPayloads; + } + /** * - * @param load This is the "bomb payload". It's an array indexed by the constants declared in BombType. + * @param bpls These are the "bomb payload" for internal and external bomb stores. + * It's a HashMap of two arrays, each indexed by the constants declared in BombType. * Each element indicates how many types of that bomb should be fired. */ - public void setBombPayload(int[] load) { - bombPayload = load; + public void setBombPayloads(HashMap bpls) { + bombPayloads = (HashMap) bpls.clone(); } public boolean isStrafing() { diff --git a/megamek/src/megamek/common/weapons/TAGHandler.java b/megamek/src/megamek/common/weapons/TAGHandler.java index 7b2f17c756e..cd259ef7b48 100644 --- a/megamek/src/megamek/common/weapons/TAGHandler.java +++ b/megamek/src/megamek/common/weapons/TAGHandler.java @@ -21,14 +21,7 @@ import java.util.Vector; -import megamek.common.Building; -import megamek.common.Entity; -import megamek.common.EquipmentMode; -import megamek.common.Game; -import megamek.common.Report; -import megamek.common.TagInfo; -import megamek.common.Targetable; -import megamek.common.ToHitData; +import megamek.common.*; import megamek.common.actions.WeaponAttackAction; import megamek.common.enums.GamePhase; import megamek.server.GameManager; @@ -58,11 +51,16 @@ protected void handleEntityDamage(Entity entityTarget, Vector vPhaseRepo entityTarget, false); game.addTagInfo(info); entityTarget.setTaggedBy(ae.getId()); - + + if (weapon.isInternalBomb()) { + // Firing an internally-mounted TAG pod counts for bomb bay explosion check + ((IBomber) ae).increaseUsedInternalBombs(1); + } + // per errata, being painted by a TAG also spots the target for indirect fire ae.setSpotting(true); ae.setSpotTargetId(entityTarget.getId()); - + Report r = new Report(3188); r.subject = subjectId; vPhaseReport.addElement(r); diff --git a/megamek/src/megamek/common/weapons/WeaponHandler.java b/megamek/src/megamek/common/weapons/WeaponHandler.java index ea00d38c8e3..577ae9417e8 100644 --- a/megamek/src/megamek/common/weapons/WeaponHandler.java +++ b/megamek/src/megamek/common/weapons/WeaponHandler.java @@ -1780,6 +1780,9 @@ public WeaponHandler(ToHitData t, WeaponAttackAction w, Game g, GameManager m) { if (target instanceof Entity) { ((Entity) target).addAttackedByThisTurn(w.getEntityId()); + if (!ae.isAirborne()) { + ((Entity) target).addGroundAttackedByThisTurn(w.getEntityId()); + } } } diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index 64f9f05d369..069262e8a85 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -20916,10 +20916,14 @@ private Vector resolveInternalBombHit(Entity e) { return vReport; } - Report r; - if (e.isAero()) { + // Only ground fire can hit internal bombs + if (e.getGroundAttackedByThisTurn().isEmpty()) { + return vReport; + } + Aero b = (Aero) e; + Report r; if (b.getUsedInternalBombs() > 0) { int id = e.getId(); From b819db4f593bf5281dba7247f77ae6c0070963a3 Mon Sep 17 00:00:00 2001 From: sleet01 Date: Tue, 19 Dec 2023 17:40:43 -0800 Subject: [PATCH 14/22] Fixed broken bomb loads in MUL reinforce: should only apply bombs in-game. --- megamek/src/megamek/client/ui/swing/ClientGUI.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/megamek/src/megamek/client/ui/swing/ClientGUI.java b/megamek/src/megamek/client/ui/swing/ClientGUI.java index 51fbbabd247..fa8b123886f 100644 --- a/megamek/src/megamek/client/ui/swing/ClientGUI.java +++ b/megamek/src/megamek/client/ui/swing/ClientGUI.java @@ -1945,7 +1945,9 @@ public String getDescription() { // the movement turn are considered selectable entity.setDone(true); entity.setUnloaded(true); - if (entity instanceof IBomber) { + if (entity instanceof IBomber && (client.getGame().getPhase() != GamePhase.LOUNGE)) { + // Only apply bombs when we're going straight into the game; doing this in the lounge + // breaks the bombs completely. ((IBomber) entity).applyBombs(); } } From f46b01cb13db3295cc89beb98b09dcd5e6ef86db Mon Sep 17 00:00:00 2001 From: sleet01 Date: Wed, 20 Dec 2023 08:23:50 -0800 Subject: [PATCH 15/22] Fixed some NPEs, including one causing Princess hangs, and clarified IBB reason message --- .../megamek/common/report-messages.properties | 2 +- .../princess/ArtilleryTargetingControl.java | 8 +- .../client/bot/princess/PathRanker.java | 109 ++++++++++-------- megamek/src/megamek/common/Entity.java | 4 +- 4 files changed, 69 insertions(+), 54 deletions(-) diff --git a/megamek/i18n/megamek/common/report-messages.properties b/megamek/i18n/megamek/common/report-messages.properties index 572f8b7ace7..cfb2d1a3c19 100755 --- a/megamek/i18n/megamek/common/report-messages.properties +++ b/megamek/i18n/megamek/common/report-messages.properties @@ -727,7 +727,7 @@ 5550=External heat reduced due to intact heat-dissipating armor! 5560= takes over as of (). 5600=Bomb Bay Explosions------------------- -5601= () took damage while dropping internal bay bombs. +5601= () took ground fire damage while dropping internal bay bombs. 5602= () must roll under + to avoid bomb bay explosion, rolls . 5603=\ () takes damage when remaining bombs explode! 5604=\ () avoids bomb bay explosion; internal bombs remaining. diff --git a/megamek/src/megamek/client/bot/princess/ArtilleryTargetingControl.java b/megamek/src/megamek/client/bot/princess/ArtilleryTargetingControl.java index fcf39db98da..a630e9f2185 100644 --- a/megamek/src/megamek/client/bot/princess/ArtilleryTargetingControl.java +++ b/megamek/src/megamek/client/bot/princess/ArtilleryTargetingControl.java @@ -449,8 +449,14 @@ public static double evaluateIncomingArtilleryDamage(Coords coords, Princess ope if ((aaa.getTurnsTilHit() == 0) && (aaa.getTarget(operator.getGame()) != null)) { // damage for artillery weapons is, for some reason, derived from the weapon type's rack size + int damage = 0; Mounted weapon = aaa.getEntity(operator.getGame()).getEquipment(aaa.getWeaponId()); - int damage = ((WeaponType) weapon.getType()).getRackSize(); + if (weapon.getType() instanceof BombType){ + damage = (weapon.getExplosionDamage()); + } else { + damage = ((WeaponType) weapon.getType()).getRackSize(); + } + // distance from given coordinates reduces damage Coords attackDestination = aaa.getTarget(operator.getGame()).getPosition(); diff --git a/megamek/src/megamek/client/bot/princess/PathRanker.java b/megamek/src/megamek/client/bot/princess/PathRanker.java index a56eb5d8cc8..a0fbb9dfcb3 100644 --- a/megamek/src/megamek/client/bot/princess/PathRanker.java +++ b/megamek/src/megamek/client/bot/princess/PathRanker.java @@ -35,7 +35,7 @@ public abstract class PathRanker implements IPathRanker { // TODO: Introduce PathRankerCacheHelper class that contains "global" path ranker state // TODO: Introduce FireControlCacheHelper class that contains "global" Fire Control state // PathRanker classes should be pretty stateless, except pointers to princess and such - + /** * The possible path ranker types. * If you're adding a new one, add it here then make sure to add it to Princess.InitializePathRankers @@ -45,7 +45,7 @@ public enum PathRankerType { Infantry, NewtonianAerospace } - + private Princess owner; public PathRanker(Princess princess) { @@ -67,7 +67,7 @@ public ArrayList rankPaths(List movePaths, Game game, int // the cached path probability data is really only relevant for one iteration through this method getPathRankerState().getPathSuccessProbabilities().clear(); - + // Let's try to whittle down this list. List validPaths = validatePaths(movePaths, game, maxRange, fallTolerance); LogManager.getLogger().debug("Validated " + validPaths.size() + " out of " + movePaths.size() + " possible paths."); @@ -75,46 +75,53 @@ public ArrayList rankPaths(List movePaths, Game game, int Coords allyCenter = calcAllyCenter(movePaths.get(0).getEntity().getId(), friends, game); ArrayList returnPaths = new ArrayList<>(validPaths.size()); - final BigDecimal numberPaths = new BigDecimal(validPaths.size()); - BigDecimal count = BigDecimal.ZERO; - BigDecimal interval = new BigDecimal(5); - - boolean pathsHaveExpectedDamage = false; - - for (MovePath path : validPaths) { - count = count.add(BigDecimal.ONE); - - RankedPath rankedPath = rankPath(path, game, maxRange, fallTolerance, enemies, allyCenter); - - returnPaths.add(rankedPath); - - // we want to keep track of if any of the paths we've considered have some kind of damage potential - pathsHaveExpectedDamage |= (rankedPath.getExpectedDamage() > 0); - - BigDecimal percent = count.divide(numberPaths, 2, RoundingMode.DOWN).multiply(new BigDecimal(100)) - .round(new MathContext(0, RoundingMode.DOWN)); - if (percent.compareTo(interval) >= 0) { - if (LogManager.getLogger().getLevel().isLessSpecificThan(Level.INFO)) { - getOwner().sendChat("... " + percent.intValue() + "% complete."); + + try { + final BigDecimal numberPaths = new BigDecimal(validPaths.size()); + BigDecimal count = BigDecimal.ZERO; + BigDecimal interval = new BigDecimal(5); + + boolean pathsHaveExpectedDamage = false; + + for (MovePath path : validPaths) { + count = count.add(BigDecimal.ONE); + + RankedPath rankedPath = rankPath(path, game, maxRange, fallTolerance, enemies, allyCenter); + + returnPaths.add(rankedPath); + + // we want to keep track of if any of the paths we've considered have some kind of damage potential + pathsHaveExpectedDamage |= (rankedPath.getExpectedDamage() > 0); + + BigDecimal percent = count.divide(numberPaths, 2, RoundingMode.DOWN).multiply(new BigDecimal(100)) + .round(new MathContext(0, RoundingMode.DOWN)); + if (percent.compareTo(interval) >= 0) { + if (LogManager.getLogger().getLevel().isLessSpecificThan(Level.INFO)) { + getOwner().sendChat("... " + percent.intValue() + "% complete."); + } + interval = percent.add(new BigDecimal(5)); } - interval = percent.add(new BigDecimal(5)); } + + Entity mover = movePaths.get(0).getEntity(); + UnitBehavior behaviorTracker = getOwner().getUnitBehaviorTracker(); + boolean noDamageButCanDoDamage = !pathsHaveExpectedDamage + && (FireControl.getMaxDamageAtRange(mover, 1, false, false) > 0); + + // if we're trying to fight, but aren't going to be doing any damage no matter how we move + // then let's try to get closer + if (noDamageButCanDoDamage + && (behaviorTracker.getBehaviorType(mover, getOwner()) == BehaviorType.Engaged)) { + behaviorTracker.overrideBehaviorType(mover, BehaviorType.MoveToContact); + return rankPaths(getOwner().getMovePathsAndSetNecessaryTargets(mover, true), + game, maxRange, fallTolerance, enemies, friends); + } + } catch (Exception ignored) { + LogManager.getLogger().error(ignored.toString()); + return returnPaths; } - - Entity mover = movePaths.get(0).getEntity(); - UnitBehavior behaviorTracker = getOwner().getUnitBehaviorTracker(); - boolean noDamageButCanDoDamage = !pathsHaveExpectedDamage - && (FireControl.getMaxDamageAtRange(mover, 1, false, false) > 0); - - // if we're trying to fight, but aren't going to be doing any damage no matter how we move - // then let's try to get closer - if (noDamageButCanDoDamage - && (behaviorTracker.getBehaviorType(mover, getOwner()) == BehaviorType.Engaged)) { - behaviorTracker.overrideBehaviorType(mover, BehaviorType.MoveToContact); - return rankPaths(getOwner().getMovePathsAndSetNecessaryTargets(mover, true), - game, maxRange, fallTolerance, enemies, friends); - } - + + return returnPaths; } @@ -138,7 +145,7 @@ private List validatePaths(List startingPathList, Game game, boolean isAirborneAeroOnGroundMap = mover.isAirborneAeroOnGroundMap(); boolean needToUnjamRAC = mover.canUnjamRAC(); int walkMP = mover.getWalkMP(); - + for (MovePath path : startingPathList) { // just in case if ((path == null) || !path.isMoveLegal()) { @@ -191,7 +198,7 @@ private List validatePaths(List startingPathList, Game game, msg.append("\n\tINADVISABLE: Want to unjam autocannon but path involves running or jumping"); continue; } - + // If all the above checks have passed, this is a valid path. msg.append("\n\tVALID."); returnPaths.add(path); @@ -210,7 +217,7 @@ private List validatePaths(List startingPathList, Game game, /** * Returns the best path of a list of ranked paths. - * + * * @param ps The list of ranked paths to process * @return "Best" out of those paths */ @@ -231,7 +238,7 @@ public void initUnitTurn(Entity unit, Game game) { public Targetable findClosestEnemy(Entity me, Coords position, Game game) { return findClosestEnemy(me, position, game, true); } - + /** * Find the closest enemy to a unit with a path */ @@ -245,7 +252,7 @@ public Targetable findClosestEnemy(Entity me, Coords position, Game game, // Skip airborne aero units as they're further away than they seem and hard to catch. // Also, skip withdrawing enemy bot units, to avoid humping disabled tanks and ejected // MechWarriors - if (e.isAirborneAeroOnGroundMap() || + if (e.isAirborneAeroOnGroundMap() || getOwner().getHonorUtil().isEnemyBroken(e.getId(), e.getOwnerId(), getOwner().getForcedWithdrawal())) { continue; @@ -263,7 +270,7 @@ public Targetable findClosestEnemy(Entity me, Coords position, Game game, closest = e; } } - + // if specified, we also consider strategic targets if (includeStrategicTargets) { for (Targetable t : getOwner().getFireControlState().getAdditionalTargets()) { @@ -274,7 +281,7 @@ public Targetable findClosestEnemy(Entity me, Coords position, Game game, } } } - + return closest; } @@ -286,7 +293,7 @@ protected double getMovePathSuccessProbability(MovePath movePath, StringBuilder if (getPathRankerState().getPathSuccessProbabilities().containsKey(movePath.getKey())) { return getPathRankerState().getPathSuccessProbabilities().get(movePath.getKey()); } - + MovePath pathCopy = movePath.clone(); List pilotingRolls = getPSRList(pathCopy); double successProbability = 1.0; @@ -332,7 +339,7 @@ protected double getMovePathSuccessProbability(MovePath movePath, StringBuilder msg.append("\n\t\tTotal = ").append(NumberFormat.getPercentInstance().format(successProbability)); getPathRankerState().getPathSuccessProbabilities().put(movePath.getKey(), successProbability); - + return successProbability; } @@ -419,7 +426,7 @@ private boolean willBuildingCollapse(MovePath path, Game game) { if (path.getEntity().isAero() || path.getEntity().hasETypeFlag(Entity.ETYPE_VTOL)) { return false; } - + // If we're jumping onto a building, make sure it can support our weight. if (path.isJumping()) { final Coords finalCoords = path.getFinalCoords(); @@ -505,7 +512,7 @@ private boolean willBuildingCollapse(MovePath path, Game game) { protected Princess getOwner() { return owner; } - + /** * Convenience property to access bot-wide state information. * @return the owner's path ranker state diff --git a/megamek/src/megamek/common/Entity.java b/megamek/src/megamek/common/Entity.java index 1975508ca82..f6cd5be95c2 100644 --- a/megamek/src/megamek/common/Entity.java +++ b/megamek/src/megamek/common/Entity.java @@ -14493,7 +14493,9 @@ public void addGroundAttackedByThisTurn(int entityId) { public void clearAttackedByThisTurn() { attackedByThisTurn.clear(); - groundAttackedByThisTurn.clear(); + if (groundAttackedByThisTurn != null) { + groundAttackedByThisTurn.clear(); + } } public Collection getAttackedByThisTurn() { From 6d651658e0a9ea8e3b1fee6a60bce46a977c0ff9 Mon Sep 17 00:00:00 2001 From: sleet01 Date: Wed, 20 Dec 2023 15:12:47 -0800 Subject: [PATCH 16/22] Final cleanup and fixes for LAM/VTOL bombing --- .../client/ui/swing/BombPayloadDialog.java | 31 ++++++------------- megamek/src/megamek/common/Compute.java | 9 ++++++ megamek/src/megamek/common/LandAirMech.java | 4 +-- .../common/actions/WeaponAttackAction.java | 3 ++ megamek/src/megamek/server/GameManager.java | 6 ++-- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/megamek/src/megamek/client/ui/swing/BombPayloadDialog.java b/megamek/src/megamek/client/ui/swing/BombPayloadDialog.java index f538c77984a..7ddb34a1b39 100644 --- a/megamek/src/megamek/client/ui/swing/BombPayloadDialog.java +++ b/megamek/src/megamek/client/ui/swing/BombPayloadDialog.java @@ -15,24 +15,17 @@ */ package megamek.client.ui.swing; -import java.awt.Dimension; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.awt.geom.Dimension2D; import java.util.StringTokenizer; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; +import javax.swing.*; import megamek.client.ui.Messages; import megamek.common.BombType; @@ -88,7 +81,7 @@ public class BombPayloadDialog extends JDialog implements ActionListener, ItemLi @SuppressWarnings("unchecked") private void initialize(JFrame parent, String title, int[] b, boolean spaceBomb, boolean bombDump, int lim, int numFighters) { - super.setResizable(false); + // super.setResizable(false); this.numFighters = numFighters; bombs = b; @@ -185,22 +178,16 @@ public void windowClosing(WindowEvent e) { pack(); Dimension size = getSize(); - boolean updateSize = false; - if (size.width < GUIPreferences.getInstance().getMinimumSizeWidth()) { - size.width = GUIPreferences.getInstance().getMinimumSizeWidth(); - } - if (size.height < GUIPreferences.getInstance().getMinimumSizeHeight()) { - size.height = GUIPreferences.getInstance().getMinimumSizeHeight(); - } - if (updateSize) { - setSize(size); - size = getSize(); - } setLocation((parent.getLocation().x + (parent.getSize().width / 2)) - (size.width / 2), (parent.getLocation().y + (parent.getSize().height / 2)) - (size.height / 2)); } + @Override + public Dimension getPreferredSize() { + return new Dimension(500,200); + } + private void setupButtons() { butOK.addActionListener(this); butCancel.addActionListener(this); diff --git a/megamek/src/megamek/common/Compute.java b/megamek/src/megamek/common/Compute.java index a0edc3e7c6f..684e38bf8b3 100644 --- a/megamek/src/megamek/common/Compute.java +++ b/megamek/src/megamek/common/Compute.java @@ -22,6 +22,7 @@ import megamek.common.enums.BasementType; import megamek.common.enums.IlluminationLevel; import megamek.common.options.OptionsConstants; +import megamek.common.weapons.DiveBombAttack; import megamek.common.weapons.InfantryAttack; import megamek.common.weapons.Weapon; import megamek.common.weapons.artillery.ArtilleryCannonWeapon; @@ -3848,6 +3849,14 @@ public static boolean isInArc(Game game, int attackerId, int weaponId, } } + // Allow dive-bombing VTOLs to attack the hex they are in, if they didn't select one for bombing while moving. + if ((ae.getMovementMode() == EntityMovementMode.VTOL) + && aPos.equals(tPos)) { + if (ae.getEquipment(weaponId).getType().hasFlag(WeaponType.F_DIVE_BOMB)) { + return true; + } + } + // if using advanced AA options, then ground-to-air fire determines arc // by closest position if (isGroundToAir(ae, t) && (t instanceof Entity)) { diff --git a/megamek/src/megamek/common/LandAirMech.java b/megamek/src/megamek/common/LandAirMech.java index 612ea694136..0695fb8fd63 100644 --- a/megamek/src/megamek/common/LandAirMech.java +++ b/megamek/src/megamek/common/LandAirMech.java @@ -1117,12 +1117,12 @@ public int[] getIntBombChoices() { @Override public void setIntBombChoices(int[] bc) { if (bc.length == intBombChoices.length) { - intBombChoices = bc; + intBombChoices = bc.clone(); } } public void setUsedInternalBombs(int b){ - // Do nothing + // Do nothing; LAMs don't take internal bomb bay hits like this } public void increaseUsedInternalBombs(int b){ diff --git a/megamek/src/megamek/common/actions/WeaponAttackAction.java b/megamek/src/megamek/common/actions/WeaponAttackAction.java index 6320eab2d3a..636e92d7dca 100644 --- a/megamek/src/megamek/common/actions/WeaponAttackAction.java +++ b/megamek/src/megamek/common/actions/WeaponAttackAction.java @@ -19,6 +19,7 @@ import megamek.common.*; import megamek.common.enums.AimingMode; import megamek.common.options.OptionsConstants; +import megamek.common.weapons.DiveBombAttack; import megamek.common.weapons.InfantryAttack; import megamek.common.weapons.Weapon; import megamek.common.weapons.artillery.ArtilleryCannonWeapon; @@ -2158,7 +2159,9 @@ private static String toHitIsImpossible(Game game, Entity ae, int attackerId, Ta // Capital weapons fire by grounded units if (wtype.isSubCapital() || wtype.isCapital()) { // Can't fire any but capital/subcapital missiles surface to surface + // (but VTOL dive bombing is allowed) if (Compute.isGroundToGround(ae, target) + && !((ae.getMovementMode() == EntityMovementMode.VTOL) && (wtype instanceof DiveBombAttack)) && !(wtype instanceof CapitalMissileWeapon)) { return Messages.getString("WeaponAttackAction.NoS2SCapWeapons"); } diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index 069262e8a85..72df9929229 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -25163,13 +25163,14 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in r = new Report(9150); r.subject = aero.getId(); List weapons = new ArrayList<>(); + // Ignore internal bomb bay-mounted weapons for (Mounted weapon : aero.getWeaponList()) { - if ((weapon.getLocation() == loc) && !weapon.isDestroyed() + if ((weapon.getLocation() == loc) && !weapon.isDestroyed() && !weapon.isInternalBomb() && weapon.getType().isHittable()) { weapons.add(weapon); } } - // add in in hittable misc equipment; internal bay munitions are handled separately. + // add in hittable misc equipment; internal bay munitions are handled separately. for (Mounted misc : aero.getMisc()) { if (misc.getType().isHittable() && (misc.getLocation() == loc) @@ -25627,6 +25628,7 @@ private void applyCargoCritical(Aero aero, int damageCaused, Vector repo destroyed--; } } + // TODO: handle critical hit on internal bomb bay (cargo bay when internal bombs are loaded) } else { r = new Report(9167); r.subject = aero.getId(); From 8a2e4d84bc907d171b06e349b445c06d6fcd14ac Mon Sep 17 00:00:00 2001 From: Sleet01 Date: Wed, 20 Dec 2023 16:50:44 -0800 Subject: [PATCH 17/22] Remove unneeded extra stack trace variable This was intended for a new option in the top-level Exception handler dialog, but extraneous here. --- megamek/src/megamek/MegaMek.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/megamek/src/megamek/MegaMek.java b/megamek/src/megamek/MegaMek.java index 1e2b766d550..76d7c1ea9a6 100644 --- a/megamek/src/megamek/MegaMek.java +++ b/megamek/src/megamek/MegaMek.java @@ -62,7 +62,6 @@ public static void main(String... args) { Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { LogManager.getLogger().error("Uncaught Exception Detected", t); final String name = t.getClass().getName(); - final StackTraceElement[] stack = t.getStackTrace(); JOptionPane.showMessageDialog(null, String.format("Uncaught %s detected. Please open up an issue containing all logs, the game save file, and customs at https://github.com/MegaMek/megamek/issues", name), "Uncaught " + name, JOptionPane.ERROR_MESSAGE); @@ -357,4 +356,4 @@ public static void initializeSuiteGraphicalSetups(final String currentProject) { // Setup Button Order Preferences ButtonOrderPreferences.getInstance().setButtonPriorities(); } -} \ No newline at end of file +} From 3be068e352d66aa0abb6d006732bb3056f39560e Mon Sep 17 00:00:00 2001 From: sleet01 Date: Thu, 21 Dec 2023 00:36:43 -0800 Subject: [PATCH 18/22] Start on implementing IBB + Cargo crit, plus more cleanup --- .../megamek/common/report-messages.properties | 4 ++ megamek/src/megamek/server/GameManager.java | 66 ++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/megamek/i18n/megamek/common/report-messages.properties b/megamek/i18n/megamek/common/report-messages.properties index cfb2d1a3c19..68f412919fd 100755 --- a/megamek/i18n/megamek/common/report-messages.properties +++ b/megamek/i18n/megamek/common/report-messages.properties @@ -731,6 +731,10 @@ 5602= () must roll under + to avoid bomb bay explosion, rolls . 5603=\ () takes damage when remaining bombs explode! 5604=\ () avoids bomb bay explosion; internal bombs remaining. +5605=\ () suffers a critical Cargo hit; +5606=player must choose bombs to be destroyed. +5607=bot loses bombs. +5608= () Internal Bomb Bay + Cargo critical: Damage exceeds remaining bombs; all bombs destroyed. #6000's -- Damage Related 6005=\ no effect. diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index 72df9929229..374c60a9646 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -20916,7 +20916,8 @@ private Vector resolveInternalBombHit(Entity e) { return vReport; } - if (e.isAero()) { + // + if (e.isAero() && !(e instanceof LandAirMech)) { // Only ground fire can hit internal bombs if (e.getGroundAttackedByThisTurn().isEmpty()) { return vReport; @@ -24890,7 +24891,9 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in js = (Jumpship) aero; } - switch (cs.getIndex()) { + // For testing purposes + // switch (cs.getIndex()) { + switch (Aero.CRIT_CARGO) { case Aero.CRIT_NONE: // no effect r = new Report(6005); @@ -25627,8 +25630,48 @@ private void applyCargoCritical(Aero aero, int damageCaused, Vector repo false, true)); destroyed--; } + } else { + // TODO: handle critical hit on internal bomb bay (cargo bay when internal bombs are loaded) + // Ruling: calculate % of cargo space destroyed; user chooses that many bombs to destroy. + if(aero.hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) { + // Prompt user, but just randomize bot's bombs to lose. + destroyed = (int) percentDestroyed * aero.getMaxIntBombPoints(); + r = new Report(5605); + r.choose(!aero.getOwner().isBot()); + r.subject = aero.getId(); + r.addDesc(aero); + r.add((int) destroyed); + reports.add(r); + int bombsDestroyed = (int) (aero.getInternalBombsDamageTotal() / destroyed); + if (destroyed >= aero.getBombPoints()) { + // Actually, no prompt or randomization if all bombs will be destroyed; just do it. + r = new Report(5608); + r.subject = aero.getId(); + r.addDesc(aero); + reports.add(r); + for (Mounted bomb: ((IBomber) aero).getBombs()) { + damageBomb(bomb); + } + aero.applyDamage(); + } else if (!aero.getOwner().isBot()) { + // handle person choosing bombs to remove. + // This will require firing an event to the End Phase to display a dialog; + // for now just randomly dump bombs just like bots'. + // TODO: fire event here to display dialog in end phase. + for (Mounted bomb:randomlySubSelectList(((IBomber) aero).getBombs(), bombsDestroyed)) { + damageBomb(bomb); + } + aero.applyDamage(); + } else { + // This should always use the random method. + for (Mounted bomb:randomlySubSelectList(((IBomber) aero).getBombs(), bombsDestroyed)) { + damageBomb(bomb); + } + aero.applyDamage(); + } + } } - // TODO: handle critical hit on internal bomb bay (cargo bay when internal bombs are loaded) + } else { r = new Report(9167); r.subject = aero.getId(); @@ -25637,6 +25680,23 @@ private void applyCargoCritical(Aero aero, int damageCaused, Vector repo } } + private void damageBomb(Mounted bomb) { + bomb.setHit(true); + if (bomb.getLinked() != null && (bomb.getLinked().getUsableShotsLeft() > 0)) { + bomb.getLinked().setHit(true); + } + } + + // Randomly select subset of Mounted items. + private ArrayList randomlySubSelectList(List list, int size) { + ArrayList subset = new ArrayList<>(); + Random random_method = new Random(); + for (int i = 0; i < size; i++) { + subset.add(list.get(random_method.nextInt(list.size()))); + } + return subset; + } + /** * Apply a single critical hit to a vehicle. * From b74edc9547274aef0e78f8547b2983884d1a786e Mon Sep 17 00:00:00 2001 From: sleet01 Date: Wed, 27 Dec 2023 17:54:36 -0800 Subject: [PATCH 19/22] Various fixes for auto check complaints --- megamek/src/megamek/common/Aero.java | 33 ++++----------------- megamek/src/megamek/common/LandAirMech.java | 3 ++ megamek/src/megamek/common/MULParser.java | 17 +++++++---- megamek/src/megamek/common/VTOL.java | 3 ++ megamek/src/megamek/server/GameManager.java | 26 ++++++++-------- 5 files changed, 35 insertions(+), 47 deletions(-) diff --git a/megamek/src/megamek/common/Aero.java b/megamek/src/megamek/common/Aero.java index d08b105b33e..d77c3cafcb4 100644 --- a/megamek/src/megamek/common/Aero.java +++ b/megamek/src/megamek/common/Aero.java @@ -484,10 +484,12 @@ public int getMaxBombPoints() { return maxExtBombPoints + maxIntBombPoints; } + @Override public int getMaxIntBombPoints() { return (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) ? maxIntBombPoints : 0; } + @Override public int getMaxExtBombPoints() { return maxExtBombPoints; } @@ -533,14 +535,17 @@ public int reduceMPByBombLoad(int t) { return t; } + @Override public void setUsedInternalBombs(int b){ usedInternalBombs = b; } + @Override public void increaseUsedInternalBombs(int b){ usedInternalBombs += b; } + @Override public int getUsedInternalBombs() { return usedInternalBombs; } @@ -2768,34 +2773,6 @@ public void doDisbandDamage() { } } - /** - * Damage a capital fighter's weapons. WeaponGroups are damaged by critical hits. - * This matches up the individual fighter's weapons and critical slots and damages those - * for MHQ resolution - * @param loc - Int corresponding to the location struck - */ - public void damageCapFighterWeapons(int loc) { - for (Mounted weapon : weaponList) { - if (weapon.getLocation() == loc) { - //Damage the weapon - weapon.setHit(true); - //Damage the critical slot - for (int i = 0; i < getNumberOfCriticals(loc); i++) { - CriticalSlot slot1 = getCritical(loc, i); - if ((slot1 == null) || - (slot1.getType() == CriticalSlot.TYPE_SYSTEM)) { - continue; - } - Mounted mounted = slot1.getMount(); - if (mounted.equals(weapon)) { - hitAllCriticals(loc, i); - break; - } - } - } - } - } - /** * @return The total number of crew available to supplement marines on boarding actions. * Includes officers, enlisted, and bay personnel, but not marines/ba or passengers. diff --git a/megamek/src/megamek/common/LandAirMech.java b/megamek/src/megamek/common/LandAirMech.java index 0695fb8fd63..5ce443a2755 100644 --- a/megamek/src/megamek/common/LandAirMech.java +++ b/megamek/src/megamek/common/LandAirMech.java @@ -1121,14 +1121,17 @@ public void setIntBombChoices(int[] bc) { } } + @Override public void setUsedInternalBombs(int b){ // Do nothing; LAMs don't take internal bomb bay hits like this } + @Override public void increaseUsedInternalBombs(int b){ // Do nothing } + @Override public int getUsedInternalBombs() { // Currently not possible return 0; diff --git a/megamek/src/megamek/common/MULParser.java b/megamek/src/megamek/common/MULParser.java index c38416f11f2..ac47142ca10 100644 --- a/megamek/src/megamek/common/MULParser.java +++ b/megamek/src/megamek/common/MULParser.java @@ -2261,12 +2261,17 @@ private void parseBombs(Element bombsTag, Entity entity) { continue; } - if (internal) { - intBombChoices[bombType] += Integer.parseInt(load); - ((IBomber) entity).setIntBombChoices(intBombChoices); - } else { - extBombChoices[bombType] += Integer.parseInt(load); - ((IBomber) entity).setExtBombChoices(extBombChoices); + try { + if (internal) { + intBombChoices[bombType] += Integer.parseInt(load); + ((IBomber) entity).setIntBombChoices(intBombChoices); + } else { + extBombChoices[bombType] += Integer.parseInt(load); + ((IBomber) entity).setExtBombChoices(extBombChoices); + } + } catch (NumberFormatException ignore) { + // If something wrote bad bomb data, don't even bother with it - user + // can fix it in configure menu } } } diff --git a/megamek/src/megamek/common/VTOL.java b/megamek/src/megamek/common/VTOL.java index 58694d779a8..f422afa1368 100644 --- a/megamek/src/megamek/common/VTOL.java +++ b/megamek/src/megamek/common/VTOL.java @@ -301,14 +301,17 @@ public int reduceMPByBombLoad(int t) { return Math.max(0, (t - (int) this.getBombs().stream().filter(m -> (m.getUsableShotsLeft() > 0)).count())); } + @Override public void setUsedInternalBombs(int b){ // Do nothing } + @Override public void increaseUsedInternalBombs(int b){ // Do nothing } + @Override public int getUsedInternalBombs() { // Currently not possible return 0; diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index 374c60a9646..f67d4a08a71 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -20896,8 +20896,8 @@ private Vector resolveControl(Entity e) { private Vector resolveInternalBombHits() { Vector vFullReport = new Vector<>(); vFullReport.add(new Report(5600, Report.PUBLIC)); - for (Iterator i = game.getEntities(); i.hasNext(); ) { - Vector interim = resolveInternalBombHit(i.next()); + for (Entity e : game.getEntitiesVector()) { + Vector interim = resolveInternalBombHit(e); if (!interim.isEmpty()) { vFullReport.addAll(interim); } @@ -20928,7 +20928,6 @@ private Vector resolveInternalBombHit(Entity e) { if (b.getUsedInternalBombs() > 0) { int id = e.getId(); - Vector rolls = new Vector<>(); // Header r = new Report(5601); @@ -25103,11 +25102,12 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in } case Aero.CRIT_WEAPON: if (aero.isCapitalFighter()) { + FighterSquadron cf = (FighterSquadron) aero; boolean destroyAll = false; // CRIT_WEAPON damages the capital fighter/squadron's weapon groups // Go ahead and map damage for the fighter's weapon criticals for MHQ // resolution. - aero.damageCapFighterWeapons(loc); + cf.damageCapFighterWeapons(loc); if ((loc == Aero.LOC_NOSE) || (loc == Aero.LOC_AFT)) { destroyAll = true; } @@ -25118,13 +25118,13 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in } if (loc == Aero.LOC_WINGS) { - if (aero.areWingsHit()) { + if (cf.areWingsHit()) { destroyAll = true; } else { - aero.setWingsHit(true); + cf.setWingsHit(true); } } - for (Mounted weapon : aero.getWeaponList()) { + for (Mounted weapon : cf.getWeaponList()) { if (weapon.getLocation() == loc) { if (destroyAll) { weapon.setHit(true); @@ -25134,7 +25134,7 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in } } // also destroy any ECM or BAP in the location hit - for (Mounted misc : aero.getMisc()) { + for (Mounted misc : cf.getMisc()) { if ((misc.getType().hasFlag(MiscType.F_ECM) || misc.getType().hasFlag(MiscType.F_ANGEL_ECM) || misc.getType().hasFlag(MiscType.F_BAP)) @@ -25143,23 +25143,23 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in //Taharqa: We should also damage the critical slot, or //MM and MHQ won't remember that this weapon is damaged on the MUL //file - for (int i = 0; i < aero.getNumberOfCriticals(loc); i++) { - CriticalSlot slot1 = aero.getCritical(loc, i); + for (int i = 0; i < cf.getNumberOfCriticals(loc); i++) { + CriticalSlot slot1 = cf.getCritical(loc, i); if ((slot1 == null) || (slot1.getType() == CriticalSlot.TYPE_SYSTEM)) { continue; } Mounted mounted = slot1.getMount(); if (mounted.equals(misc)) { - aero.hitAllCriticals(loc, i); + cf.hitAllCriticals(loc, i); break; } } } } r = new Report(9152); - r.subject = aero.getId(); - r.add(aero.getLocationName(loc)); + r.subject = cf.getId(); + r.add(cf.getLocationName(loc)); reports.add(r); break; } From d7d721c06b5ca3575342841d2c03a4a6897b1c47 Mon Sep 17 00:00:00 2001 From: sleet01 Date: Sat, 30 Dec 2023 02:24:31 -0800 Subject: [PATCH 20/22] Work on fixing search options, includes some extra stuff to remove --- .../unitSelector/TWAdvancedSearchPanel.java | 36 +++++++++++++------ .../src/megamek/common/MechSearchFilter.java | 17 +++++---- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/megamek/src/megamek/client/ui/swing/unitSelector/TWAdvancedSearchPanel.java b/megamek/src/megamek/client/ui/swing/unitSelector/TWAdvancedSearchPanel.java index 23ad3a33fb1..ebfe326504b 100644 --- a/megamek/src/megamek/client/ui/swing/unitSelector/TWAdvancedSearchPanel.java +++ b/megamek/src/megamek/client/ui/swing/unitSelector/TWAdvancedSearchPanel.java @@ -321,6 +321,8 @@ public class TWAdvancedSearchPanel extends JPanel implements ActionListener, Ite private JButton btnFilterVTOL = new JButton("\u2610"); private JLabel lblFilterSupportVTOL = new JLabel(Messages.getString("MechSelectorDialog.Search.SupportVTOL")); private JButton btnFilterSupportVTOL = new JButton("\u2610"); + private JLabel lblFilterWiGE = new JLabel(Messages.getString("MechSelectorDialog.Search.WiGE")); + private JButton btnFilterWiGE = new JButton("\u2610"); private JLabel lblFilterGunEmplacement = new JLabel(Messages.getString("MechSelectorDialog.Search.GunEmplacement")); private JButton btnFilterGunEmplacement = new JButton("\u2610"); private JLabel lblFilterSupportTank = new JLabel(Messages.getString("MechSelectorDialog.Search.SupportTank")); @@ -977,6 +979,8 @@ private JPanel createUnitTypePanel() { btnFilterVTOL.addActionListener(this); btnFilterSupportVTOL.setBorder(emptyBorder);; btnFilterSupportVTOL.addActionListener(this); + btnFilterWiGE.setBorder(emptyBorder); + btnFilterWiGE.addActionListener(this); btnFilterGunEmplacement.setBorder(emptyBorder); btnFilterGunEmplacement.addActionListener(this); btnFilterSupportTank.setBorder(emptyBorder); @@ -1071,13 +1075,13 @@ private JPanel createUnitTypePanel() { filterAerospaceFighterPanel.add(btnFilterAerospaceFighter); filterAerospaceFighterPanel.add(lblFilterAerospaceFighter); unitTypePanel.add(filterAerospaceFighterPanel, c); - - c.gridy++; - c.gridx = 1; + c.gridx = 2; JPanel filterConvFighterPanel = new JPanel(); filterConvFighterPanel.add(btnFilterConvFighter); filterConvFighterPanel.add(lblFilterConvFighter); unitTypePanel.add(filterConvFighterPanel, c); + + c.gridy++; c.gridx = 2; JPanel filterFixedWingSupportPanel = new JPanel(); filterFixedWingSupportPanel.add(btnFilterFixedWingSupport); @@ -1142,6 +1146,12 @@ private JPanel createUnitTypePanel() { filterrSupportVTOLPanel.add(lblFilterSupportVTOL); unitTypePanel.add(filterrSupportVTOLPanel, c); + c.gridx = 1; c.gridy++; + JPanel filterWiGEPanel = new JPanel(); + filterWiGEPanel.add(btnFilterWiGE); + filterWiGEPanel.add(lblFilterWiGE); + unitTypePanel.add(filterWiGEPanel, c); + c.gridy++; c.gridx = 1; JPanel filterGunEmplacementPanel = new JPanel(); @@ -1684,6 +1694,8 @@ public void actionPerformed(java.awt.event.ActionEvent ev) { toggleText(btnFilterVTOL); } else if (ev.getSource().equals(btnFilterSupportVTOL)) { toggleText(btnFilterSupportVTOL); + } else if (ev.getSource().equals(btnFilterWiGE)) { + toggleText(btnFilterWiGE); } else if (ev.getSource().equals(btnFilterGunEmplacement)) { toggleText(btnFilterGunEmplacement); } else if (ev.getSource().equals(btnFilterFixedWingSupport)) { @@ -2053,6 +2065,7 @@ private void clearUnitType() { btnFilterTank.setText("\u2610"); btnFilterVTOL.setText("\u2610"); btnFilterSupportVTOL.setText("\u2610"); + btnFilterWiGE.setText("\u2610"); btnFilterGunEmplacement.setText("\u2610"); btnFilterSupportTank.setText("\u2610"); btnFilterLargeSupportTank.setText("\u2610"); @@ -2321,6 +2334,7 @@ private void updateUnitTypes() { mechFilter.filterTank = getValue(btnFilterTank); mechFilter.filterVTOL = getValue(btnFilterVTOL); mechFilter.filterSupportVTOL = getValue(btnFilterSupportVTOL); + mechFilter.filterWiGE = getValue(btnFilterWiGE); mechFilter.filterGunEmplacement = getValue(btnFilterGunEmplacement); mechFilter.filterSupportTank = getValue(btnFilterSupportTank); mechFilter.filterLargeSupportTank = getValue(btnFilterLargeSupportTank); @@ -2450,7 +2464,7 @@ public Object getValueAt(int row, int col) { if (row >= weaponClasses.size()) { return null; } - + switch (col) { case COL_QTY: return qty[row] + ""; @@ -2976,7 +2990,7 @@ public boolean matches(String name) { if (this == PHYSICAL) { String lName = name.toLowerCase(); - if (lName.contains("backhoe") || + if (lName.contains("backhoe") || lName.contains("saw") || lName.contains("whip") || lName.contains("claw") || @@ -3013,12 +3027,12 @@ public boolean matches(String name) { } } else if (this == MISSILE) { if ((name.toLowerCase().contains("lrm") || - name.toLowerCase().contains("mrm") || - name.toLowerCase().contains("srm")) && + name.toLowerCase().contains("mrm") || + name.toLowerCase().contains("srm")) && !name.toLowerCase().contains("ammo")) { return true; } - } else if (this == RE_ENGINEERED) { + } else if (this == RE_ENGINEERED) { if (name.toLowerCase().contains("engineered")) { return true; } @@ -3031,9 +3045,9 @@ public boolean matches(String name) { return true; } } else if (this == BALLISTIC) { - return WeaponClass.AUTOCANNON.matches(name) || - WeaponClass.GAUSS.matches(name) || - WeaponClass.MISSILE.matches(name) || + return WeaponClass.AUTOCANNON.matches(name) || + WeaponClass.GAUSS.matches(name) || + WeaponClass.MISSILE.matches(name) || WeaponClass.MACHINE_GUN.matches(name); } else if (this == RAC) { if (name.toLowerCase().contains("rotary")) { diff --git a/megamek/src/megamek/common/MechSearchFilter.java b/megamek/src/megamek/common/MechSearchFilter.java index 3dfa3d79a0f..7678930fdf1 100644 --- a/megamek/src/megamek/common/MechSearchFilter.java +++ b/megamek/src/megamek/common/MechSearchFilter.java @@ -139,6 +139,8 @@ public enum BoolOp { AND, OR, NOP } public String sEndTons; public String sStartBV; public String sEndBV; + public String sStartCost; + public String sEndCost; public boolean isDisabled; public List engineType = new ArrayList<>(); public List engineTypeExclude = new ArrayList<>(); @@ -182,6 +184,7 @@ public enum BoolOp { AND, OR, NOP } public int filterInfantry; public int filterBattleArmor; public int filterTank; + public int filterWiGE; public int filterVTOL; public int filterSupportVTOL; public int filterGunEmplacement; @@ -787,10 +790,6 @@ public static boolean isMatch(MechSummary mech, MechSearchFilter f) { long entityType = mech.getEntityType(); - if (mech.isAerospaceFighter()) { - entityType = entityType | Entity.ETYPE_AEROSPACEFIGHTER; - } - long entityTypes = 0; if (f.filterMech == 1) { @@ -865,6 +864,9 @@ public static boolean isMatch(MechSummary mech, MechSearchFilter f) { if (f.filterSuperHeavyTank == 1) { entityTypes = entityTypes | Entity.ETYPE_SUPER_HEAVY_TANK; } + if (f.filterWiGE == 1) { + entityTypes = entityTypes | Entity.ETYPE_WIGE; + } if (f.iAerospaceFighter == 1) { entityTypes = entityTypes | Entity.ETYPE_AEROSPACEFIGHTER; } @@ -950,6 +952,9 @@ public static boolean isMatch(MechSummary mech, MechSearchFilter f) { if (f.iAerospaceFighter == 2) { entityTypes = entityTypes | Entity.ETYPE_AEROSPACEFIGHTER; } + if (f.filterWiGE == 2) { + entityTypes = entityTypes | Entity.ETYPE_WIGE; + } if (((entityType & entityTypes) > 0) && (entityTypes != 0)) { return false; @@ -1037,10 +1042,10 @@ private boolean evaluate(List eq, List qty, ExpNode n) { } else if (currEq.equals(n.name) && n.qty == 0) { return false; } - + } - // If we reach this point. It means that the MechSummary didn't have a weapon/equipment that matched the leaf node. + // If we reach this point. It means that the MechSummary didn't have a weapon/equipment that matched the leaf node. // If the leaf quantity is 0, that means that the mech is a match. If the leaf quantity is non-zero, that means the mech isn't // a match. if (n.qty == 0) { From 4ca0034e7aa3a8f4b26f9a4d4052134078880ef8 Mon Sep 17 00:00:00 2001 From: sleet01 Date: Tue, 2 Jan 2024 11:34:15 -0800 Subject: [PATCH 21/22] Reconstituting fixes from failed branch --- .../swing/unitSelector/TWAdvancedSearchPanel.java | 14 -------------- megamek/src/megamek/common/MechSearchFilter.java | 9 --------- megamek/src/megamek/server/GameManager.java | 7 +++---- 3 files changed, 3 insertions(+), 27 deletions(-) diff --git a/megamek/src/megamek/client/ui/swing/unitSelector/TWAdvancedSearchPanel.java b/megamek/src/megamek/client/ui/swing/unitSelector/TWAdvancedSearchPanel.java index ebfe326504b..34ed808bbe5 100644 --- a/megamek/src/megamek/client/ui/swing/unitSelector/TWAdvancedSearchPanel.java +++ b/megamek/src/megamek/client/ui/swing/unitSelector/TWAdvancedSearchPanel.java @@ -321,8 +321,6 @@ public class TWAdvancedSearchPanel extends JPanel implements ActionListener, Ite private JButton btnFilterVTOL = new JButton("\u2610"); private JLabel lblFilterSupportVTOL = new JLabel(Messages.getString("MechSelectorDialog.Search.SupportVTOL")); private JButton btnFilterSupportVTOL = new JButton("\u2610"); - private JLabel lblFilterWiGE = new JLabel(Messages.getString("MechSelectorDialog.Search.WiGE")); - private JButton btnFilterWiGE = new JButton("\u2610"); private JLabel lblFilterGunEmplacement = new JLabel(Messages.getString("MechSelectorDialog.Search.GunEmplacement")); private JButton btnFilterGunEmplacement = new JButton("\u2610"); private JLabel lblFilterSupportTank = new JLabel(Messages.getString("MechSelectorDialog.Search.SupportTank")); @@ -979,8 +977,6 @@ private JPanel createUnitTypePanel() { btnFilterVTOL.addActionListener(this); btnFilterSupportVTOL.setBorder(emptyBorder);; btnFilterSupportVTOL.addActionListener(this); - btnFilterWiGE.setBorder(emptyBorder); - btnFilterWiGE.addActionListener(this); btnFilterGunEmplacement.setBorder(emptyBorder); btnFilterGunEmplacement.addActionListener(this); btnFilterSupportTank.setBorder(emptyBorder); @@ -1146,12 +1142,6 @@ private JPanel createUnitTypePanel() { filterrSupportVTOLPanel.add(lblFilterSupportVTOL); unitTypePanel.add(filterrSupportVTOLPanel, c); - c.gridx = 1; c.gridy++; - JPanel filterWiGEPanel = new JPanel(); - filterWiGEPanel.add(btnFilterWiGE); - filterWiGEPanel.add(lblFilterWiGE); - unitTypePanel.add(filterWiGEPanel, c); - c.gridy++; c.gridx = 1; JPanel filterGunEmplacementPanel = new JPanel(); @@ -1694,8 +1684,6 @@ public void actionPerformed(java.awt.event.ActionEvent ev) { toggleText(btnFilterVTOL); } else if (ev.getSource().equals(btnFilterSupportVTOL)) { toggleText(btnFilterSupportVTOL); - } else if (ev.getSource().equals(btnFilterWiGE)) { - toggleText(btnFilterWiGE); } else if (ev.getSource().equals(btnFilterGunEmplacement)) { toggleText(btnFilterGunEmplacement); } else if (ev.getSource().equals(btnFilterFixedWingSupport)) { @@ -2065,7 +2053,6 @@ private void clearUnitType() { btnFilterTank.setText("\u2610"); btnFilterVTOL.setText("\u2610"); btnFilterSupportVTOL.setText("\u2610"); - btnFilterWiGE.setText("\u2610"); btnFilterGunEmplacement.setText("\u2610"); btnFilterSupportTank.setText("\u2610"); btnFilterLargeSupportTank.setText("\u2610"); @@ -2334,7 +2321,6 @@ private void updateUnitTypes() { mechFilter.filterTank = getValue(btnFilterTank); mechFilter.filterVTOL = getValue(btnFilterVTOL); mechFilter.filterSupportVTOL = getValue(btnFilterSupportVTOL); - mechFilter.filterWiGE = getValue(btnFilterWiGE); mechFilter.filterGunEmplacement = getValue(btnFilterGunEmplacement); mechFilter.filterSupportTank = getValue(btnFilterSupportTank); mechFilter.filterLargeSupportTank = getValue(btnFilterLargeSupportTank); diff --git a/megamek/src/megamek/common/MechSearchFilter.java b/megamek/src/megamek/common/MechSearchFilter.java index 7678930fdf1..8f9b8325578 100644 --- a/megamek/src/megamek/common/MechSearchFilter.java +++ b/megamek/src/megamek/common/MechSearchFilter.java @@ -139,8 +139,6 @@ public enum BoolOp { AND, OR, NOP } public String sEndTons; public String sStartBV; public String sEndBV; - public String sStartCost; - public String sEndCost; public boolean isDisabled; public List engineType = new ArrayList<>(); public List engineTypeExclude = new ArrayList<>(); @@ -184,7 +182,6 @@ public enum BoolOp { AND, OR, NOP } public int filterInfantry; public int filterBattleArmor; public int filterTank; - public int filterWiGE; public int filterVTOL; public int filterSupportVTOL; public int filterGunEmplacement; @@ -864,9 +861,6 @@ public static boolean isMatch(MechSummary mech, MechSearchFilter f) { if (f.filterSuperHeavyTank == 1) { entityTypes = entityTypes | Entity.ETYPE_SUPER_HEAVY_TANK; } - if (f.filterWiGE == 1) { - entityTypes = entityTypes | Entity.ETYPE_WIGE; - } if (f.iAerospaceFighter == 1) { entityTypes = entityTypes | Entity.ETYPE_AEROSPACEFIGHTER; } @@ -952,9 +946,6 @@ public static boolean isMatch(MechSummary mech, MechSearchFilter f) { if (f.iAerospaceFighter == 2) { entityTypes = entityTypes | Entity.ETYPE_AEROSPACEFIGHTER; } - if (f.filterWiGE == 2) { - entityTypes = entityTypes | Entity.ETYPE_WIGE; - } if (((entityType & entityTypes) > 0) && (entityTypes != 0)) { return false; diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index f67d4a08a71..984c86d35c0 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -24890,9 +24890,7 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in js = (Jumpship) aero; } - // For testing purposes - // switch (cs.getIndex()) { - switch (Aero.CRIT_CARGO) { + switch (cs.getIndex()) { case Aero.CRIT_NONE: // no effect r = new Report(6005); @@ -25637,9 +25635,9 @@ private void applyCargoCritical(Aero aero, int damageCaused, Vector repo // Prompt user, but just randomize bot's bombs to lose. destroyed = (int) percentDestroyed * aero.getMaxIntBombPoints(); r = new Report(5605); - r.choose(!aero.getOwner().isBot()); r.subject = aero.getId(); r.addDesc(aero); + r.choose(!aero.getOwner().isBot()); r.add((int) destroyed); reports.add(r); int bombsDestroyed = (int) (aero.getInternalBombsDamageTotal() / destroyed); @@ -25681,6 +25679,7 @@ private void applyCargoCritical(Aero aero, int damageCaused, Vector repo } private void damageBomb(Mounted bomb) { + bomb.setShotsLeft(0); bomb.setHit(true); if (bomb.getLinked() != null && (bomb.getLinked().getUsableShotsLeft() > 0)) { bomb.getLinked().setHit(true); From 94ee6c897176f4e3e15e06c956a69a02717755c7 Mon Sep 17 00:00:00 2001 From: sleet01 Date: Tue, 2 Jan 2024 15:44:15 -0800 Subject: [PATCH 22/22] Fixes and updates from review --- .../src/megamek/common/AeroSpaceFighter.java | 38 ++++++-------- megamek/src/megamek/common/Entity.java | 2 +- megamek/src/megamek/common/IBomber.java | 51 +++++++++++++++++-- .../common/battlevalue/BVCalculator.java | 2 +- 4 files changed, 65 insertions(+), 28 deletions(-) diff --git a/megamek/src/megamek/common/AeroSpaceFighter.java b/megamek/src/megamek/common/AeroSpaceFighter.java index abf1333440a..98be7c4f8d7 100644 --- a/megamek/src/megamek/common/AeroSpaceFighter.java +++ b/megamek/src/megamek/common/AeroSpaceFighter.java @@ -1,16 +1,22 @@ /* - * MegaMek - Copyright (C) 2000-2003 Ben Mazur (bmazur@sev.org) + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation; either version 2 of the License, or (at your option) any later - * version. + * This file is part of MegaMek. * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. + * MegaMek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MegaMek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MegaMek. If not, see . */ + package megamek.common; import megamek.client.ui.swing.calculationReport.CalculationReport; @@ -145,29 +151,17 @@ public boolean isAero() { return true; } - /** - * Fighters may carry external ordnance; - * Other Aerospace units with cargo bays and the Internal Bomb Bay quirk may carry bombs internally. - * @return boolean - */ @Override public boolean isBomber() { return true; } @Override - /** - * Returns true if this is an aerospace or conventional fighter - * but not a larger craft (i.e. "SmallCraft" or "Dropship" and bigger - */ public boolean isFighter() { return true; } @Override - /** - * Returns true if and only if this is an aerospace fighter. - */ public boolean isAerospaceFighter() { return true; } @@ -176,4 +170,4 @@ public boolean isAerospaceFighter() { public long getEntityType() { return super.getEntityType() | Entity.ETYPE_AEROSPACEFIGHTER; } -} \ No newline at end of file +} diff --git a/megamek/src/megamek/common/Entity.java b/megamek/src/megamek/common/Entity.java index f6cd5be95c2..68ccab5bf4f 100644 --- a/megamek/src/megamek/common/Entity.java +++ b/megamek/src/megamek/common/Entity.java @@ -4184,7 +4184,7 @@ protected void resetBombAttacks() { if (eq.getType().equals(spaceBomb) || eq.getType().equals(altBomb) || eq.getType().equals(diveBomb)) { bombAttacksToRemove.add(eq); - } else if (eq.getLinked() != null && eq.getLinked().isInternalBomb()){ // Does not do what's intended! + } else if (eq.getLinked() != null && eq.getLinked().isInternalBomb()){ // Remove any used internal bombs if (eq.getLinked().getUsableShotsLeft() <= 0) { bombAttacksToRemove.add(eq); diff --git a/megamek/src/megamek/common/IBomber.java b/megamek/src/megamek/common/IBomber.java index 7d50d1c42f5..202dbbcba44 100644 --- a/megamek/src/megamek/common/IBomber.java +++ b/megamek/src/megamek/common/IBomber.java @@ -36,8 +36,23 @@ public interface IBomber { String DIVE_BOMB_ATTACK = "DiveBombAttack"; String ALT_BOMB_ATTACK = "AltBombAttack"; + /** + * Set count of internal bombs used; this is used to reset, revert, or increase count + * of internal bombs a unit has dropped during a turn. + * @param b + */ void setUsedInternalBombs(int b); + + /** + * Increase count of internal bombs used this turn. + * @param b + */ void increaseUsedInternalBombs(int b); + + /** + * @return the number of internal bombs used by this bomber during a turn, for + * IBB internal hit calculations. + */ int getUsedInternalBombs(); @@ -65,9 +80,13 @@ default int getMaxExtBombSize() { } /** - * @return The number of each bomb type that was selected prior to deployment + * @return The number of each internally-mounted bomb type that was selected prior to deployment */ int[] getIntBombChoices(); + + /** + * @return The number of each externally-mounted bomb type that was selected prior to deployment + */ int[] getExtBombChoices(); /** @@ -76,6 +95,11 @@ default int getMaxExtBombSize() { * @param bc An array with the count of each bomb type as the value of the bomb type's index */ void setIntBombChoices(int[] bc); + + /** + * Sets the bomb type selections for external mounts. + * @param bc An array with the count of each bomb type as the value of the bomb type's index + */ void setExtBombChoices(int[] bc); /** @@ -89,6 +113,10 @@ default int[] getBombChoices(){ return stream3.toArray(); } + /** + * Backwards compatibility bomb choice setter that only affects external stores. + * @param ebc + */ default void setBombChoices(int[] ebc) { setExtBombChoices(ebc); } @@ -154,9 +182,6 @@ default int getInternalBombsDamageTotal() { } } - // int total = getBombs().stream().filter( - // b -> b.isInternalBomb() - // ).mapToInt(b -> b.getExplosionDamage()).sum(); return total; } @@ -242,6 +267,12 @@ default void applyBombs() { clearBombChoices(); } + /** + * Helper to apply equipment-type bombs, either externally or internally. + * @param type of bomb equipment. + * @param loc location where mounted. + * @param internal mounted internally or not. + */ private void applyBombEquipment(int type, int loc, boolean internal){ try { EquipmentType et = EquipmentType.get(BombType.getBombInternalName(type)); @@ -253,6 +284,12 @@ private void applyBombEquipment(int type, int loc, boolean internal){ } + /** + * Helper to apply weapon-type bombs, either externally or internally. + * @param type of bomb equipment. + * @param loc location where mounted. + * @param internal mounted internally or not. + */ private void applyBombWeapons(int type, int loc, boolean internal){ Mounted m; try { @@ -276,7 +313,13 @@ private void applyBombWeapons(int type, int loc, boolean internal){ void clearBombs(); + /** + * @return maximum number of bomb points this bomber can mount externally + */ int getMaxExtBombPoints(); + /** + * @return maximum number of bomb points this bomber can mount internally + */ int getMaxIntBombPoints(); } diff --git a/megamek/src/megamek/common/battlevalue/BVCalculator.java b/megamek/src/megamek/common/battlevalue/BVCalculator.java index 62ec6b1ddca..9c9f2332c3d 100644 --- a/megamek/src/megamek/common/battlevalue/BVCalculator.java +++ b/megamek/src/megamek/common/battlevalue/BVCalculator.java @@ -711,7 +711,7 @@ protected double arcFactor(Mounted equipment) { } /** - * Forwards to {@link #maintainUsedSearchlight(Mounted, boolean, boolean, int)} with a weaponCount + * Forwards to {@link #processWeapon(Mounted, boolean, boolean, int)} with a weaponCount * parameter of 1 (single weapon). */ protected double processWeapon(Mounted weapon, boolean showInReport,