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/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/messages.properties b/megamek/i18n/megamek/common/messages.properties index 05eed7b3bfb..f335a550587 100644 --- a/megamek/i18n/megamek/common/messages.properties +++ b/megamek/i18n/megamek/common/messages.properties @@ -311,7 +311,8 @@ PlanetaryConditions.Indicator.Gravity.Normal=\u23AF PlanetaryConditions.Indicator.Gravity.High=\u2B73 PlanetaryConditions.Indicator.EMI.true=\u2301 PlanetaryConditions.Indicator.EMI.false=\u2312 -UnitType.Aero=Aerospace Fighter +UnitType.Aero=Aerospace +UnitType.AeroSpaceFighter=Aerospace Fighter UnitType.BattleArmor=Battle Armor UnitType.Conventional\ Fighter=Conventional Fighter UnitType.Dropship=Dropship 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/i18n/megamek/common/report-messages.properties b/megamek/i18n/megamek/common/report-messages.properties index 9b8446dc4d3..68f412919fd 100755 --- a/megamek/i18n/megamek/common/report-messages.properties +++ b/megamek/i18n/megamek/common/report-messages.properties @@ -726,6 +726,15 @@ 5543=success! 5550=External heat reduced due to intact heat-dissipating armor! 5560= takes over as of (). +5600=Bomb Bay Explosions------------------- +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. +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/MegaMek.java b/megamek/src/megamek/MegaMek.java index 1160d109f68..76d7c1ea9a6 100644 --- a/megamek/src/megamek/MegaMek.java +++ b/megamek/src/megamek/MegaMek.java @@ -356,4 +356,4 @@ public static void initializeSuiteGraphicalSetups(final String currentProject) { // Setup Button Order Preferences ButtonOrderPreferences.getInstance().setButtonPriorities(); } -} \ No newline at end of file +} 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/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/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/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/ratgenerator/AbstractUnitRecord.java b/megamek/src/megamek/client/ratgenerator/AbstractUnitRecord.java index 45be0fd459b..27f4cdff6e6 100644 --- a/megamek/src/megamek/client/ratgenerator/AbstractUnitRecord.java +++ b/megamek/src/megamek/client/ratgenerator/AbstractUnitRecord.java @@ -135,6 +135,8 @@ public static int parseUnitType(String typeName) { return UnitType.GUN_EMPLACEMENT; case "Conventional Fighter": return UnitType.CONV_FIGHTER; + case "AeroSpaceFighter": + return UnitType.AEROSPACEFIGHTER; case "Aero": return UnitType.AERO; case "Small Craft": diff --git a/megamek/src/megamek/client/ratgenerator/ForceDescriptor.java b/megamek/src/megamek/client/ratgenerator/ForceDescriptor.java index 3707bc3f104..b89c4315272 100644 --- a/megamek/src/megamek/client/ratgenerator/ForceDescriptor.java +++ b/megamek/src/megamek/client/ratgenerator/ForceDescriptor.java @@ -538,10 +538,10 @@ public void generateLance(List 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..e2b1a690253 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; @@ -35,18 +40,25 @@ 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") - 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,51 +66,112 @@ 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(); - maxSize = bomber.getMaxBombSize(); - int[] bombChoices = bomber.getBombChoices(); - - // 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); + 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(); + + int columns = (maxPoints.get(INTNAME) > 0 ? 1 : 0) + (maxPoints.get(EXTNAME) > 0 ? 1 : 0); + // Should not occur! + if (columns == 0){ + empty = true; + return; } - int availBombPoints = bomber.getMaxBombPoints() - curBombPoints; + + JPanel outer = new JPanel(); + 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); + EmptyBorder emptyBorder = new EmptyBorder(10, 10, 10, 10); + CompoundBorder compoundBorder = new CompoundBorder(titledBorder, emptyBorder); + outer.setBorder(compoundBorder); + + interiorPanel = initSubPanel(maxPoints.get(INTNAME) - compileBombPoints(intBombChoices), intBombChoices, INTNAME); + exteriorPanel = initSubPanel(maxPoints.get(EXTNAME) - compileBombPoints(extBombChoices), extBombChoices, EXTNAME); + + if (maxPoints.get(INTNAME) != 0) { + outer.add(interiorPanel); + } + if (maxPoints.get(EXTNAME) != 0) { + outer.add(exteriorPanel); + } + add(outer); + } + + private JPanel initSubPanel(int availBombPoints, int[] bombChoices, String title){ + + // 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) { if ((maxNumBombs > 0) && (maxNumBombs > typeMax[type])) { maxNumBombs = typeMax[type]; @@ -113,23 +186,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) { @@ -140,94 +213,114 @@ 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++; } + 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() { + // 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++) { - 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); + if (empty) { + return choices; + } + 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/client/ui/swing/BombPayloadDialog.java b/megamek/src/megamek/client/ui/swing/BombPayloadDialog.java index e71703357fd..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; @@ -47,6 +40,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(); @@ -86,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; @@ -183,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/client/ui/swing/ClientGUI.java b/megamek/src/megamek/client/ui/swing/ClientGUI.java index 91c2ff1f4a3..fa8b123886f 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 @@ -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(); } } diff --git a/megamek/src/megamek/client/ui/swing/FiringDisplay.java b/megamek/src/megamek/client/ui/swing/FiringDisplay.java index 5e9e943fd3e..786d2c10675 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; + } } } } @@ -1257,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); @@ -1287,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); @@ -1496,38 +1508,64 @@ 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; } - 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]; + + loadouts.put("internal", ce().getInternalBombLoadout()); + 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 relevant loadout + for (EntityAction o : attacks) { + if (o instanceof WeaponAttackAction) { + WeaponAttackAction waa = (WeaponAttackAction) o; + if (waa.getEntityId() == ce().getId()) { + int[] priorLoad = waa.getBombPayloads().get(title); + 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 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); + } + + 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++) { + payloads.get(title)[i] += choices[i]; + } + } } - return payload; + return payloads; } /** @@ -1621,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) @@ -1664,6 +1698,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 @@ -1807,6 +1844,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(); + } } /** @@ -1827,6 +1869,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); @@ -1966,6 +2009,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); @@ -2674,4 +2720,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/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/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/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/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/client/ui/swing/unitDisplay/SystemPanel.java b/megamek/src/megamek/client/ui/swing/unitDisplay/SystemPanel.java index c9c892c6114..28f01b7b1ea 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) { @@ -739,7 +739,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 +822,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/client/ui/swing/unitSelector/TWAdvancedSearchPanel.java b/megamek/src/megamek/client/ui/swing/unitSelector/TWAdvancedSearchPanel.java index 23ad3a33fb1..34ed808bbe5 100644 --- a/megamek/src/megamek/client/ui/swing/unitSelector/TWAdvancedSearchPanel.java +++ b/megamek/src/megamek/client/ui/swing/unitSelector/TWAdvancedSearchPanel.java @@ -1071,13 +1071,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); @@ -2450,7 +2450,7 @@ public Object getValueAt(int row, int col) { if (row >= weaponClasses.size()) { return null; } - + switch (col) { case COL_QTY: return qty[row] + ""; @@ -2976,7 +2976,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 +3013,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 +3031,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/Aero.java b/megamek/src/megamek/common/Aero.java index 6828e5c60eb..d77c3cafcb4 100644 --- a/megamek/src/megamek/common/Aero.java +++ b/megamek/src/megamek/common/Aero.java @@ -154,8 +154,12 @@ 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]; + + protected int usedInternalBombs = 0; // fuel - number of fuel points private int fuel = 0; @@ -477,33 +481,73 @@ public void setAccLast(boolean b) { @Override public int getMaxBombPoints() { - return maxBombPoints; + return maxExtBombPoints + maxIntBombPoints; + } + + @Override + public int getMaxIntBombPoints() { + return (hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) ? maxIntBombPoints : 0; + } + + @Override + public int getMaxExtBombPoints() { + return maxExtBombPoints; } public void autoSetMaxBombPoints() { - maxBombPoints = (int) Math.round(getWeight() / 5); + // Stock Aerospace units cannot carry bombs + maxExtBombPoints = maxIntBombPoints = 0; } @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.clone(); + } + } + + @Override + public int[] getExtBombChoices() { + return extBombChoices.clone(); + } + + @Override + public void setExtBombChoices(int[] bc) { + if (bc.length == extBombChoices.length) { + extBombChoices = bc.clone(); } } @Override public void clearBombChoices() { - Arrays.fill(bombChoices, 0); + Arrays.fill(intBombChoices, 0); + Arrays.fill(extBombChoices, 0); } @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 t; + } + + @Override + public void setUsedInternalBombs(int b){ + usedInternalBombs = b; + } + + @Override + public void increaseUsedInternalBombs(int b){ + usedInternalBombs += b; + } + + @Override + public int getUsedInternalBombs() { + return usedInternalBombs; } public void setWhoFirst() { @@ -985,6 +1029,9 @@ public void newRound(int roundNumber) { setWhoFirst(); resetAltLossThisRound(); + + // Reset usedInternalBombs + setUsedInternalBombs(0); } /** @@ -2726,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. @@ -2990,7 +3009,7 @@ public long getEntityType() { } public boolean isInASquadron() { - return game.getEntity(getTransportId()) instanceof FighterSquadron; + return false; } @Override @@ -2998,9 +3017,14 @@ 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 false; } @Override @@ -3009,7 +3033,7 @@ public boolean isBomber() { * but not a larger craft (i.e. "SmallCraft" or "Dropship" and bigger */ public boolean isFighter() { - return true; + return false; } @Override @@ -3017,7 +3041,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 new file mode 100644 index 00000000000..98be7c4f8d7 --- /dev/null +++ b/megamek/src/megamek/common/AeroSpaceFighter.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MegaMek. + * + * 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; +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.*; + +/** + * AeroSpaceFighter subclass of Aero that encapsulates Fighter functionality + */ +public class AeroSpaceFighter extends Aero { + public AeroSpaceFighter() { + super(); + } + + @Override + public int getUnitType() { + return UnitType.AEROSPACEFIGHTER; + } + + @Override + public void autoSetMaxBombPoints() { + // Aerospace fighters can carry both external and internal ordnances, if configured and quirked + // appropriately + 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 + public boolean isInASquadron() { + return game.getEntity(getTransportId()) instanceof FighterSquadron; + } + + @Override + public int reduceMPByBombLoad(int t) { + return Math.max(0, t - (int) Math.ceil(getExternalBombPoints() / 5.0)); + } + + @Override + public boolean isSpheroid() { + 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; + } + } + } + } + } + + @Override + public boolean isAero() { + return true; + } + + @Override + public boolean isBomber() { + return true; + } + + @Override + public boolean isFighter() { + return true; + } + + @Override + public boolean isAerospaceFighter() { + return true; + } + + @Override + public long getEntityType() { + return super.getEntityType() | Entity.ETYPE_AEROSPACEFIGHTER; + } +} 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/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/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 c04f709f116..62412655b36 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. diff --git a/megamek/src/megamek/common/Entity.java b/megamek/src/megamek/common/Entity.java index 829e75ecc2f..68ccab5bf4f 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. @@ -4183,6 +4184,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()){ + // Remove any used internal bombs + if (eq.getLinked().getUsableShotsLeft() <= 0) { + bombAttacksToRemove.add(eq); + } } } equipmentList.removeAll(bombAttacksToRemove); @@ -12976,17 +12982,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; @@ -14459,14 +14487,25 @@ public void addAttackedByThisTurn(int entityId) { attackedByThisTurn.add(entityId); } + public void addGroundAttackedByThisTurn(int entityId) { + groundAttackedByThisTurn.add(entityId); + } + public void clearAttackedByThisTurn() { attackedByThisTurn.clear(); + if (groundAttackedByThisTurn != null) { + 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/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 7a95e66ce43..c0dc1dbd8d0 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; - } + } } } } @@ -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()) { @@ -492,7 +498,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() { @@ -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) @@ -684,14 +690,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 +706,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 +720,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..2b28db6685d 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,14 +235,20 @@ 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++; } } - maxBombPoints = bombpoints; + maxExtBombPoints = bombpoints; + + // fixed-wing support craft may also use internal transport bays as bomb bays with Internal Bomb Bay quirk. + maxIntBombPoints = getTransportBays().stream().mapToInt( + tb -> (tb instanceof CargoBay) ? (int) Math.floor(tb.getUnused()) : 0 + ).sum(); } @Override 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 ea42e39496a..202dbbcba44 100644 --- a/megamek/src/megamek/common/IBomber.java +++ b/megamek/src/megamek/common/IBomber.java @@ -20,48 +20,107 @@ import java.util.Arrays; import java.util.List; +import java.util.stream.IntStream; import megamek.common.options.OptionsConstants; /** * 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"; + /** + * 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(); + + /** * @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 + * 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 getMaxBombSize() { - return getMaxBombPoints(); + default int getMaxIntBombSize() { + return getMaxIntBombPoints(); } - + /** - * @return The number of each bomb type that was selected prior to deployment + * + * @return The largest single bomb that can be carried externally. */ - int[] getBombChoices(); - + default int getMaxExtBombSize() { + return getMaxExtBombPoints(); + } + + /** + * @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(); + /** * 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 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 setBombChoices(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(); + } + + /** + * Backwards compatibility bomb choice setter that only affects external stores. + * @param ebc + */ + default void setBombChoices(int[] ebc) { + setExtBombChoices(ebc); + } + /** * Sets the count of each bomb to zero */ @@ -71,7 +130,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 +155,52 @@ 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 total damage from remaining bombs + */ + default int getInternalBombsDamageTotal() { + int total = 0; + for (Mounted bomb: getBombs()) { + if (bomb.isInternalBomb()) { + total += bomb.getExplosionDamage(); + } + } + + return total; + } + + /** + * @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. @@ -115,15 +208,20 @@ default int getBombPoints() { 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)) { @@ -137,34 +235,91 @@ 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 < getExtBombChoices()[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(); } + /** + * 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)); + Mounted m = ((Entity) this).addEquipment(et, loc, false); + m.setInternalBomb(internal); + } catch (LocationFullException ignored) { + + } + + } + + /** + * 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 { + EquipmentType et = EquipmentType.get(BombType.getBombWeaponName(type)); + 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); + + } + } catch (LocationFullException ignored) { + + } + } + 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/LandAirMech.java b/megamek/src/megamek/common/LandAirMech.java index 3954afcdb50..5ce443a2755 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,31 +1087,68 @@ 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); } + /** + * + * @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))); } @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.clone(); } } + @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; + } + + @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/MULParser.java b/megamek/src/megamek/common/MULParser.java index 37d5595d371..ac47142ca10 100644 --- a/megamek/src/megamek/common/MULParser.java +++ b/megamek/src/megamek/common/MULParser.java @@ -2250,17 +2250,29 @@ 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); + 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/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/MechSearchFilter.java b/megamek/src/megamek/common/MechSearchFilter.java index 3dfa3d79a0f..8f9b8325578 100644 --- a/megamek/src/megamek/common/MechSearchFilter.java +++ b/megamek/src/megamek/common/MechSearchFilter.java @@ -787,10 +787,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) { @@ -1037,10 +1033,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) { 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/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 fe9076d7c6b..c91a4a19b75 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; @@ -550,6 +551,10 @@ public String getDesc() { if (isArmored()) { desc.append(" (armored)"); } + + if (isInternalBomb()) { + desc.append(" (Int. Bay)"); + } return desc.toString(); } @@ -1631,6 +1636,19 @@ 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 isInternalBomb; + } + // 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 fb6be7bd94a..ef7a811b951 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,12 +891,23 @@ public int height() { public long getEntityType() { return Entity.ETYPE_AERO | Entity.ETYPE_SMALL_CRAFT; } - + @Override 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; @@ -919,4 +930,12 @@ public boolean isLargeAerospace() { public int getLandingLength() { return 8; } + + @Override + public void autoSetMaxBombPoints() { + // 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(); + } } \ No newline at end of file diff --git a/megamek/src/megamek/common/UnitType.java b/megamek/src/megamek/common/UnitType.java index 6eebef72d07..bfb064f7244 100644 --- a/megamek/src/megamek/common/UnitType.java +++ b/megamek/src/megamek/common/UnitType.java @@ -28,17 +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 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", "Small Craft", "Dropship", + "Jumpship", "Warship", "Space Station", "Aero"}; 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..f422afa1368 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[] getIntBombChoices() { + return intBombChoices.clone(); + } @Override - public int[] getBombChoices() { - return bombChoices.clone(); + public void setIntBombChoices(int[] bc) { } @Override - public void setBombChoices(int... bc) { - if (bc.length == bombChoices.length) { - bombChoices = bc; + 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 @@ -280,16 +301,32 @@ 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; + } + @Override public Targetable getVTOLBombTarget() { return bombTarget; } - + @Override public void setVTOLBombTarget(Targetable t) { bombTarget = t; } - + public List getStrafingCoords() { return strafingCoords; } @@ -565,7 +602,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/actions/WeaponAttackAction.java b/megamek/src/megamek/common/actions/WeaponAttackAction.java index d7e20f18d30..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; @@ -80,7 +81,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 +117,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() { @@ -1606,6 +1611,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)) { @@ -1615,7 +1623,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())) { @@ -2151,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"); } @@ -2742,16 +2752,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() { @@ -3593,7 +3612,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/loaders/BLKAeroFile.java b/megamek/src/megamek/common/loaders/BLKAeroSpaceFighterFile.java similarity index 90% rename from megamek/src/megamek/common/loaders/BLKAeroFile.java rename to megamek/src/megamek/common/loaders/BLKAeroSpaceFighterFile.java index 7593c43671d..d0a28609c65 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); @@ -164,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); } @@ -175,7 +175,9 @@ 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(); + a.setArmorTonnage(a.getArmorWeight()); loadQuirks(a); return a; @@ -237,10 +239,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 +269,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)); @@ -281,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/BLKConvFighterFile.java b/megamek/src/megamek/common/loaders/BLKConvFighterFile.java index 33abea1effc..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); @@ -116,10 +113,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(); @@ -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; @@ -197,7 +198,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 +215,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..36dc5aa6cc2 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(); @@ -188,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/loaders/BLKFile.java b/megamek/src/megamek/common/loaders/BLKFile.java index dbe02215953..85d7c5f6fd1 100644 --- a/megamek/src/megamek/common/loaders/BLKFile.java +++ b/megamek/src/megamek/common/loaders/BLKFile.java @@ -308,6 +308,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()); } @@ -585,6 +590,8 @@ public static BuildingBlock getBlock(Entity t) { blk.writeBlockData("UnitType", "Tank"); } else if (t instanceof Infantry) { blk.writeBlockData("UnitType", "Infantry"); + } else if (t instanceof AeroSpaceFighter) { + blk.writeBlockData("UnitType", "AeroSpaceFighter"); } else if (t instanceof Aero) { blk.writeBlockData("UnitType", "Aero"); } 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 b854a9257c7..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(); @@ -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)); 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() { 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..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; @@ -246,7 +247,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; @@ -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(); 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/common/weapons/AmmoWeaponHandler.java b/megamek/src/megamek/common/weapons/AmmoWeaponHandler.java index 8ee0e900e15..d391140f987 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).increaseUsedInternalBombs(1); + } + 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. */ @@ -99,19 +104,19 @@ 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); 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 a110981265b..3c426db4109 100644 --- a/megamek/src/megamek/common/weapons/BombAttackHandler.java +++ b/megamek/src/megamek/common/weapons/BombAttackHandler.java @@ -61,8 +61,12 @@ 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).increaseUsedInternalBombs(1); + } break; } } @@ -73,7 +77,7 @@ protected void useAmmo() { /* * (non-Javadoc) - * + * * @see megamek.common.weapons.AttackHandler#handle(int, java.util.Vector) */ @Override 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 23790c3ff16..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()); + } } } @@ -1824,6 +1827,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 83bdc075e12..984c86d35c0 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(); @@ -3695,7 +3696,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(); } @@ -20887,6 +20888,88 @@ private Vector resolveControl(Entity e) { return vReport; } + /** + * Check all aircraft that may have used internal bomb bays for incidental explosions + * caused by ground fire. + * @return + */ + private Vector resolveInternalBombHits() { + Vector vFullReport = new Vector<>(); + vFullReport.add(new Report(5600, Report.PUBLIC)); + for (Entity e : game.getEntitiesVector()) { + Vector interim = resolveInternalBombHit(e); + if (!interim.isEmpty()) { + vFullReport.addAll(interim); + } + } + // Return empty Vector if no reports (besides the header) are added. + return (vFullReport.size() == 1) ? new Vector<>() : 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<>(); + // 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; + } + + // + if (e.isAero() && !(e instanceof LandAirMech)) { + // 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(); + + // Header + r = new Report(5601); + r.subject = id; + r.addDesc(e); + vReport.add(r); + + // Roll + 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(rollTarget); + r.add(roll, false); + 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 hd = new HitData(b.getBodyLocation(), false, HitData.EFFECT_NONE); + vReport.addAll(damageEntity(e, hd, bombDamage, true, DamageType.NONE,true)); + e.applyDamage(); + } + } + } + return vReport; + } + /** * Inflict damage on a pilot * @@ -24965,10 +25048,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); } } @@ -25017,11 +25100,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; } @@ -25032,13 +25116,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); @@ -25048,7 +25132,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)) @@ -25057,40 +25141,42 @@ 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; } 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 + // add 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); } } @@ -25542,7 +25628,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.subject = aero.getId(); + r.addDesc(aero); + r.choose(!aero.getOwner().isBot()); + 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(); + } + } } + } else { r = new Report(9167); r.subject = aero.getId(); @@ -25551,6 +25678,24 @@ 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); + } + } + + // 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. * @@ -29635,7 +29780,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/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. 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()) {