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/data/mechfiles/infantry/3085/Comstar/Surveillance Specialist Com Guards Battlefield Data Miners.blk b/megamek/data/mechfiles/infantry/3085/Comstar/Surveillance Specialist Com Guards Battlefield Data Miners.blk new file mode 100644 index 00000000000..faf435f0c5d --- /dev/null +++ b/megamek/data/mechfiles/infantry/3085/Comstar/Surveillance Specialist Com Guards Battlefield Data Miners.blk @@ -0,0 +1,86 @@ +#building block data file + +1 + + +##Write the version number just in case... + +MAM0 + + + +Infantry + + + +Surveillance Specialist + + + +Com Guards Battlefield Data Miners + + + +3136 + + + +3064 + + + +3064 + + + +IS Level 3 + + + +Hover + + + + + + +

Equipped with advanced remote sensors and mechanized carriers, the Data Miners excel in capturing enemy transmissions and conducting signal measurements. While real-time decryption of encrypted communications is rare, their ability to identify enemy commanders and key positions through signal analysis greatly aids in targeting and strategic planning.

+

Their technical expertise is not just limited to signal interception; they also play a key role in the processing and analysis of the gathered data, making them indispensable in the Com Guards' information warfare strategies.

+
+ + +

The Surveillance Specialists of the Com Guards Battlefield Data Miners are a crucial component of ComStar's intelligence apparatus. With ComStar's foundational business in communications, these units specialize in intercepting and analyzing battlefield transmissions. Their role is pivotal in providing up-to-date intelligence, a vital element in the strategic planning and execution of ComStar's military operations.

+
+ + +

Deployed primarily in scenarios where intelligence gathering is crucial, the Surveillance Specialists operate behind the front lines. Their actions are often decisive in identifying enemy movements and command structures, thereby shaping the Com Guards' tactical responses and operations on the battlefield.

+
+ + +

The inception and evolution of the Battlefield Data Miners are deeply intertwined with ComStar's ethos of prioritizing communication and intelligence in warfare. Over the years, they have proved their worth in various conflicts, notably during the Jihad, where their intelligence gathering capabilities significantly contributed to the strategic successes of the Com Guards.

+
+ + +Technical Readout: 3085 + + + +5 + + + +2 + + + +InfantryFederatedBarrettM42B + + + +ComStar Infantry Kit + + + +16 + + diff --git a/megamek/docs/UserDirHelp.html b/megamek/docs/UserDirHelp.html new file mode 100644 index 00000000000..37d86079b08 --- /dev/null +++ b/megamek/docs/UserDirHelp.html @@ -0,0 +1,53 @@ +

How to Use the User Data Directory

+ +

Use this directory for resources you want to share between different installs or versions of MegaMek, MegaMekLab and MekHQ. The files listed below will also be loaded from this directory (in addition to what is loaded from MegaMek's own data). The directory should be an absolute path such as D:/MyBTStuff (in other words, not relative to your MegaMek directory).

+ +

Generally, all content from the user directory is added to the pre-defined content. In some cases, added content may replace pre-defined content when it has the same name or file path.

+ +

How to place files within the user data directory: +

+ +

This is an example of a suitable directory structure with a few example files: +

+D:/myBTStuff
+    Oxanium.ttf
+    Exo.ttf
+    campaign_units/
+        Atlas AS8-XT.mtf
+    data/
+        Jura.ttf
+ MyMMSkin.xml
+        images/
+            camo/
+                myForceCamo.png
+                oldcamo/
+                    camo1.png
+                    camo2.png
+            portraits/
+                minscandboo.png
+            fluff/
+                Mech/
+                    Atlas.png
+                DropShip/
+                    Colossus.png
+        universe/
+            ranks.xml
+            awards/
+                MyAwards.xml
+                AuriganAwards.xml
diff --git a/megamek/docs/history.txt b/megamek/docs/history.txt index b201b12e3b6..892a5395bcd 100644 --- a/megamek/docs/history.txt +++ b/megamek/docs/history.txt @@ -48,6 +48,12 @@ VERSION HISTORY: + PR #4956: Use foot/jump type modifier for beast-mounted infantry + PR #4963: In the heat report, heat effects are now shown after the affected unit (instead of before) + PR #4966: The mtf file format for Meks now uses chassis: and model: qualifiers and no longer includes the mtf version ++ Issue #4949: return impossible instead of raising an error ++ PR #4957: More tooltips colors (set FG color and BG color for more tooltips, add highlight, weapon, quirk client settings, consolidate some colors into existing client setting colors) ++ PR #4922: Add deployment options (There is a lot in this one see note from PR link for specifics) ++ PR #4981: Add unit readout button (Warbook) that displays the unit summary, TRO entry, and AS card in Game ++ PR #4961: User file directory ++ PR #4977: add a split pane lobby between player and unit panels 0.49.15 (2023-10-21 1530 UTC) + PR #4688: Round reports should no longer take increasingly long to load diff --git a/megamek/i18n/megamek/client/messages.properties b/megamek/i18n/megamek/client/messages.properties index c0fb5e2f665..dc833761a53 100644 --- a/megamek/i18n/megamek/client/messages.properties +++ b/megamek/i18n/megamek/client/messages.properties @@ -63,6 +63,8 @@ BotReadmeNagDialog.title=Please Read The Bot Documentation First BotReadmeNagDialog.text=Please read the Princess Notes.txt file in the docs/Bot Stuff folder before using the bot. \nNote: The bot does not support all game options. \nWould you like to read the AI documentation now? ### Unspecified Dialogs +UserDirHelpDialog.title=How to Use the User Data Directory + ## AlphaStrikeStatsDialog Class AlphaStrikeStatsDialog.title=Alpha Strike Conversion Results @@ -285,6 +287,7 @@ BoardSelectionDialog.UpdateSize=Update Size BoardSelectionDialog.Updating=Updating... BoardSelectionDialog.ViewGameBoard=Preview Game Map... * BoardSelectionDialog.ViewGameBoardTooltip=Shows a preview of the game's map.
* Note: For generated maps and surprise maps the preview is only a sample
and will not be the same as the actual map used. +BoardSelectionDialog.ViewGameBoardButton=Refresh Preview #Game board tooltips BoardView1.ACTIVATING=ACTIVATING @@ -772,6 +775,8 @@ ChatLounge.notDone=Not Done ChatLounge.PartialRepairs=Partial Repairs ChatLounge.Player=Player ChatLounge.Players=Players +ChatLounge.Player0=Player0 +ChatLounge.Blind=Blind ChatLounge.quickView=Unit Quick View ChatLounge.OverlapDeploy.title=Must choose exclusive deployment zone ChatLounge.OverlapDeploy.msg=When using double blind, each player needs to have an exclusive deployment zone. @@ -896,6 +901,7 @@ ClientGUI.targetMenuItem=Target ClientGUI.title=MegaMek Client ClientGUI.TransmittingData=Transmitting game data... ClientGUI.viewMenuItem=View +ClientGUI.viewReadoutMenuItem=View Readout ClientGUI.doneMenuItem=Done ClientGUI.waitingOnTheServer=Waiting on the server... ClientGUI.PointBlankShot.Message={0} has moved adjacent to the hidden {1}. Take pointblank shot? @@ -1188,6 +1194,9 @@ CommonSettingsDialog.colors.UnitTooltipBuildingBGColor=Building BG Color CommonSettingsDialog.colors.UnitTooltipAltBGColor=Alt BG Color CommonSettingsDialog.colors.UnitTooltipBlockBGColor=Block BG Color CommonSettingsDialog.colors.UnitTooltipTerrainBGColor=Terrain BG Color +CommonSettingsDialog.colors.UnitTooltipHighlightColor=Highlight Color +CommonSettingsDialog.colors.UnitTooltipWeaponColor=Weapon Color +CommonSettingsDialog.colors.UnitTooltipQuirkColor=Quirk Color CommonSettingsDialog.colors.VisualRangeColor=Visual Range Color CommonSettingsDialog.colors.warningColor=Warning Color CommonSettingsDialog.darkenMapAtNight=Darken Map At Night @@ -1226,6 +1235,9 @@ CommonSettingsDialog.locale.Russian=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 CommonSettingsDialog.locale.Spanish=EspaƱol CommonSettingsDialog.locale=Language/Sprache/\u042f\u0437\u044b\u043a: CommonSettingsDialog.logFileName=Game log filename: +CommonSettingsDialog.userDir=User Files Directory: +CommonSettingsDialog.userDir.tooltip=Use this directory for resources you want to share between different installs or versions of MegaMek, MegaMekLab and MekHQ. Fonts, units, camos, portraits and fluff images will also be loaded from this directory.
Note: Inside the user directory, use the directory structure of MM/MML/MHQ for camos, portraits and fluff images, i.e. data/images/camo, data/images/portraits and data/images/fluff/.
Fonts and units can be placed anywhere in the user directory. +CommonSettingsDialog.userDir.chooser.title=Choose User Data Folder CommonSettingsDialog.main=Main CommonSettingsDialog.audio=Audio CommonSettingsDialog.miniMap=Mini Map @@ -1444,6 +1456,12 @@ CustomMechDialog.labDeploymentOffset=Deployment Zone Offset: CustomMechDialog.labDeploymentOffsetTip=Deployment Zone Offset, in hexes from corresponding map edge CustomMechDialog.labDeploymentWidth=Deployment Zone Width: CustomMechDialog.labDeploymentWidthTip=Deployment Zone width, in hexes +CustomMechDialog.labDeploymentAnyNW=Deployment Any NW corner: +CustomMechDialog.labDeploymentAnySE=Deployment Any SE corner: +CustomMechDialog.BtnDeploymentUseRuler=Use ruler coords +CustomMechDialog.BtnDeploymentUseRulerTip=Set the Any NW and SE corners based on the corners of the NW and SE corners the square defined by the map preview ruler +CustomMechDialog.BtnDeploymentApply=Apply +CustomMechDialog.BtnDeploymentApplyTip=Apply player setting CustomMechDialog.labDeployShutdown=Shutdown CustomMechDialog.labDeployProne=Prone CustomMechDialog.labDeployHullDown=Hull Down @@ -1971,6 +1989,7 @@ MechDisplay.Target=Target: MechDisplay.ToHit=To Hit: MechDisplay.Turretlocked=Turret locked MechDisplay.Unit=Unit +MechDisplay.UnitReadout=Unit Readout MechDisplay.UnusedSpace=Unused Space: MechDisplay.Weapon=Weapon MechDisplay.Variable=Variable @@ -3929,6 +3948,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 34c9c5d8874..c7d9c1292f9 100644 --- a/megamek/i18n/megamek/common/options/messages.properties +++ b/megamek/i18n/megamek/common/options/messages.properties @@ -33,6 +33,10 @@ GameOptionsInfo.option.dumping_from_round.displayableName=first round for ammo d GameOptionsInfo.option.dumping_from_round.description=Number of the round that has to be at least reached before allowing ammo dumps. GameOptionsInfo.option.set_arty_player_homeedge.displayableName=Automatically set artillery home edge GameOptionsInfo.option.set_arty_player_homeedge.description=If checked, all of the players' artillery units will have their home edge set to the deployment edge of the player, NW and NE are North, SW and SE are South. \nUnchecked by default. +GameOptionsInfo.option.set_default_team_1.displayableName=Default non-bot players to team 1, useful in coop games +GameOptionsInfo.option.set_default_team_1.description=When this option is unchecked each player is assigned new team +GameOptionsInfo.option.set_player_deployment_to_player0.displayableName=Non-bot player entities with \"Use Owners*\" deployment set, use Player 0\'s settings, instead of current player\'s settings +GameOptionsInfo.option.set_player_deployment_to_player0.description=When this option is unchecked use the standard player deployments settings GameOptionsInfo.option.restrict_game_commands.displayableName=Restrict sensitive commands to non-Observers GameOptionsInfo.option.restrict_game_commands.description=If checked, commands such as /reset and /kick cannot be used by Observers while others are playing. \nUnchecked by default. GameOptionsInfo.option.disable_local_save.displayableName=Disable local saves when using double blind @@ -822,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/MMConstants.java b/megamek/src/megamek/MMConstants.java index 485858b30bc..d0145cd11bd 100644 --- a/megamek/src/megamek/MMConstants.java +++ b/megamek/src/megamek/MMConstants.java @@ -49,6 +49,7 @@ public final class MMConstants extends SuiteConstants { public static final String GIVEN_NAME_MALE_FILE = "data/names/maleGivenNames.csv"; public static final String SURNAME_FILE = "data/names/surnames.csv"; public static final String BOT_README_FILE_PATH = "docs/Bot Stuff/Princess Notes.txt"; + public static final String USER_DIR_README_FILE = "docs/UserDirHelp.html"; public static final String SERIALKILLER_CONFIG_FILE = "mmconf/serialkiller.xml"; public static final String USER_NAME_FACTIONS_DIRECTORY_PATH = "userdata/data/names/factions/"; public static final String USER_CALLSIGN_FILE_PATH = "userdata/data/names/callsigns.csv"; 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/preferences/SuitePreferences.java b/megamek/src/megamek/client/ui/preferences/SuitePreferences.java index dac1d9455b2..e472b6dc773 100644 --- a/megamek/src/megamek/client/ui/preferences/SuitePreferences.java +++ b/megamek/src/megamek/client/ui/preferences/SuitePreferences.java @@ -135,7 +135,6 @@ public void loadFromFile(final String filePath) { LogManager.getLogger().error("Error reading node. " + getParserInformation(parser), e); } } - LogManager.getLogger().info("Finished loading user preferences"); } catch (FileNotFoundException ignored) { } catch (Exception e) { 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/AimedShotDialog.java b/megamek/src/megamek/client/ui/swing/AimedShotDialog.java index adf6847bf0c..c252c614670 100644 --- a/megamek/src/megamek/client/ui/swing/AimedShotDialog.java +++ b/megamek/src/megamek/client/ui/swing/AimedShotDialog.java @@ -70,7 +70,7 @@ public AimedShotDialog(JFrame parent, String title, String message, getContentPane().add(labMessage); String div = "

" + UnitToolTip.getTargetTipDetail(target, clientGUI.getClient()) + "
"; - JLabel labTarget = new JLabel("" + div + "", SwingConstants.LEFT); + JLabel labTarget = new JLabel(UnitToolTip.wrapWithHTML(div), SwingConstants.LEFT); c.weightx = 1.0; c.weighty = 1.0; 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 6b3615e82b1..36ea9cfec1b 100644 --- a/megamek/src/megamek/client/ui/swing/ClientGUI.java +++ b/megamek/src/megamek/client/ui/swing/ClientGUI.java @@ -585,7 +585,7 @@ public void windowClosing(WindowEvent e) { Ruler.color1 = GUIP.getRulerColor1(); Ruler.color2 = GUIP.getRulerColor2(); - ruler = new Ruler(frame, client, bv); + ruler = new Ruler(frame, client, bv, client.getGame()); ruler.setLocation(GUIP.getRulerPosX(), GUIP.getRulerPosY()); ruler.setSize(GUIP.getRulerSizeHeight(), GUIP.getRulerSizeWidth()); UIUtil.updateWindowBounds(ruler); @@ -648,7 +648,7 @@ private void showSkinningHowTo() { URL helpUrl = new URL(helpPath.toString()); // Launch the help dialog. - HelpDialog helpDialog = new HelpDialog(Messages.getString("ClientGUI.skinningHelpPath.title"), helpUrl); + HelpDialog helpDialog = new HelpDialog(Messages.getString("ClientGUI.skinningHelpPath.title"), helpUrl, frame); helpDialog.setVisible(true); } catch (MalformedURLException ex) { LogManager.getLogger().error("", ex); @@ -680,7 +680,7 @@ private void showOptions() { } public void customizePlayer() { - PlayerSettingsDialog psd = new PlayerSettingsDialog(this, client); + PlayerSettingsDialog psd = new PlayerSettingsDialog(this, client, bv); psd.setVisible(true); } @@ -1876,7 +1876,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 @@ -1941,7 +1941,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/CommonSettingsDialog.java b/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java index c45b2ce3c05..4dc89834a01 100644 --- a/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java +++ b/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java @@ -47,8 +47,17 @@ import java.awt.*; import java.awt.event.*; import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; /** The Client Settings Dialog offering GUI options concerning tooltips, map display, keybinds etc. */ public class CommonSettingsDialog extends AbstractButtonDialog implements ItemListener, @@ -167,6 +176,7 @@ private void moveElement(DefaultListModel srcModel, int srcIndex, int trg private final JCheckBox soundMuteOthersTurn = new JCheckBox(Messages.getString("CommonSettingsDialog.soundMuteOthersTurn")); private JTextField tfSoundMuteOthersFileName; + private JTextField userDir; private final JCheckBox keepGameLog = new JCheckBox(Messages.getString("CommonSettingsDialog.keepGameLog")); private JTextField gameLogFilename; private final JCheckBox stampFilenames = new JCheckBox(Messages.getString("CommonSettingsDialog.stampFilenames")); @@ -298,6 +308,9 @@ private void moveElement(DefaultListModel srcModel, int srcIndex, int trg private ColourSelectorButton csbUnitTooltipAltBGColor; private ColourSelectorButton csbUnitTooltipBlockBGColor; private ColourSelectorButton csbUnitTooltipTerrainBGColor; + private ColourSelectorButton csbUnitTooltipHighlightColor; + private ColourSelectorButton csbUnitTooltipWeaponColor; + private ColourSelectorButton csbUnitTooltipQuirkColor; private ColourSelectorButton csbUnitDisplayHeatLevel1; private ColourSelectorButton csbUnitDisplayHeatLevel2; @@ -1010,6 +1023,18 @@ private JPanel getUnitDisplayPanel() { row.add(csbUnitTooltipTerrainBGColor); comps.add(row); + row = new ArrayList<>(); + csbUnitTooltipHighlightColor = new ColourSelectorButton(Messages.getString("CommonSettingsDialog.colors.UnitTooltipHighlightColor")); + csbUnitTooltipHighlightColor.setColour(GUIP.getUnitToolTipHighlightColor()); + row.add(csbUnitTooltipHighlightColor); + csbUnitTooltipWeaponColor = new ColourSelectorButton(Messages.getString("CommonSettingsDialog.colors.UnitTooltipWeaponColor")); + csbUnitTooltipWeaponColor.setColour(GUIP.getUnitToolTipWeaponColor()); + row.add(csbUnitTooltipWeaponColor); + csbUnitTooltipQuirkColor = new ColourSelectorButton(Messages.getString("CommonSettingsDialog.colors.UnitTooltipQuirkColor")); + csbUnitTooltipQuirkColor.setColour(GUIP.getUnitToolTipQuirkColor()); + row.add(csbUnitTooltipQuirkColor); + comps.add(row); + addLineSpacer(comps); comps.add(checkboxEntry(showArmorMiniVisTT, null)); @@ -1451,6 +1476,34 @@ private JPanel getSettingsPanel() { addLineSpacer(comps); + JLabel userDirLabel = new JLabel(Messages.getString("CommonSettingsDialog.userDir")); + userDirLabel.setToolTipText(Messages.getString("CommonSettingsDialog.userDir.tooltip")); + userDir = new JTextField(20); + userDir.setMaximumSize(new Dimension(250, 40)); + userDir.setToolTipText(Messages.getString("CommonSettingsDialog.userDir.tooltip")); + JButton userDirChooser = new JButton("..."); + userDirChooser.addActionListener(e -> fileChooseUserDir(userDir, getFrame())); + userDirChooser.setToolTipText(Messages.getString("CommonSettingsDialog.userDir.chooser.title")); + JButton userDirHelp = new JButton("Help"); + try { + String helpTitle = Messages.getString("UserDirHelpDialog.title"); + URL helpFile = new File(MMConstants.USER_DIR_README_FILE).toURI().toURL(); + userDirHelp.addActionListener(e -> new HelpDialog(helpTitle, helpFile, getFrame()).setVisible(true)); + } catch (MalformedURLException e) { + LogManager.getLogger().error("Could not find the user data directory readme file at " + + MMConstants.USER_DIR_README_FILE); + } + row = new ArrayList<>(); + row.add(userDirLabel); + row.add(userDir); + row.add(Box.createHorizontalStrut(10)); + row.add(userDirChooser); + row.add(Box.createHorizontalStrut(10)); + row.add(userDirHelp); + comps.add(row); + + addLineSpacer(comps); + // UI Theme uiThemes = new JComboBox<>(); uiThemes.setMaximumSize(new Dimension(400, uiThemes.getMaximumSize().height)); @@ -1464,6 +1517,12 @@ private JPanel getSettingsPanel() { // Skin skinFiles = new JComboBox<>(); + skinFiles.setRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + return super.getListCellRendererComponent(list, new File((String) value).getName(), index, isSelected, cellHasFocus); + } + }); skinFiles.setMaximumSize(new Dimension(400, skinFiles.getMaximumSize().height)); JLabel skinFileLabel = new JLabel(Messages.getString("CommonSettingsDialog.skinFile")); row = new ArrayList<>(); @@ -1633,6 +1692,7 @@ public void setVisible(boolean visible) { keepGameLog.setSelected(CP.keepGameLog()); gameLogFilename.setEnabled(keepGameLog.isSelected()); gameLogFilename.setText(CP.getGameLogFilename()); + userDir.setText(CP.getUserDir()); stampFilenames.setSelected(CP.stampFilenames()); stampFormat.setEnabled(stampFilenames.isSelected()); stampFormat.setText(CP.getStampFormat()); @@ -1685,15 +1745,20 @@ public void setVisible(boolean visible) { gameSummaryMM.setSelected(GUIP.getGameSummaryMinimap()); skinFiles.removeAllItems(); - List xmlFiles = new ArrayList<>(Arrays - .asList(Configuration.skinsDir().list((directory, fileName) -> fileName.endsWith(".xml")))); - xmlFiles.addAll(userDataFiles(Configuration.skinsDir(), ".xml")); - Collections.sort(xmlFiles); - for (String file : xmlFiles) { - if (SkinXMLHandler.validSkinSpecFile(file)) { - skinFiles.addItem(file); - } + ArrayList xmlFiles = new ArrayList<>(filteredFiles(Configuration.skinsDir(), ".xml")); + + String userDirName = PreferenceManager.getClientPreferences().getUserDir(); + File userDir = new File(userDirName); + if (!userDirName.isBlank() && userDir.isDirectory()) { + xmlFiles.addAll(filteredFilesWithSubDirs(userDir, ".xml")); } + + File internalUserDataDir = new File(Configuration.userdataDir(), Configuration.skinsDir().toString()); + xmlFiles.addAll(filteredFiles(internalUserDataDir, ".xml")); + xmlFiles.removeIf(file -> !SkinXMLHandler.validSkinSpecFile(file)); + Collections.sort(xmlFiles); + var model = new DefaultComboBoxModel<>(xmlFiles.toArray(new String[0])); + skinFiles.setModel(model); // Select the default file first skinFiles.setSelectedItem(SkinXMLHandler.defaultSkinXML); // If this select fails, the default skin will be selected @@ -1904,6 +1969,10 @@ protected void cancelAction() { csbUnitTooltipBlockBGColor.setColour(GUIP.getUnitToolTipBlockBGColor()); csbUnitTooltipTerrainBGColor.setColour(GUIP.getUnitToolTipTerrainBGColor()); + csbUnitTooltipHighlightColor.setColour(GUIP.getUnitToolTipHighlightColor()); + csbUnitTooltipWeaponColor.setColour(GUIP.getUnitToolTipWeaponColor()); + csbUnitTooltipQuirkColor.setColour(GUIP.getUnitToolTipQuirkColor()); + csbUnitTooltipArmorMiniIntact.setColour(GUIP.getUnitTooltipArmorMiniColorIntact()); csbUnitTooltipArmorMiniPartial.setColour(GUIP.getUnitTooltipArmorMiniColorPartialDamage()); csbUnitTooltipArmorMiniDamaged.setColour(GUIP.getUnitTooltipArmorMiniColorDamaged()); @@ -2067,6 +2136,7 @@ protected void okAction() { CP.setKeepGameLog(keepGameLog.isSelected()); CP.setGameLogFilename(gameLogFilename.getText()); + CP.setUserDir(userDir.getText()); CP.setStampFilenames(stampFilenames.isSelected()); CP.setStampFormat(stampFormat.getText()); CP.setReportKeywords(reportKeywordsTextPane.getText()); @@ -2353,6 +2423,10 @@ protected void okAction() { GUIP.setUnitTooltipBlockBGColor(csbUnitTooltipBlockBGColor.getColour()); GUIP.setUnitTooltipTerrainBGColor(csbUnitTooltipTerrainBGColor.getColour()); + GUIP.setUnitTooltipHightlightColor(csbUnitTooltipHighlightColor.getColour()); + GUIP.setUnitTooltipWeaponColor(csbUnitTooltipQuirkColor.getColour()); + GUIP.setUnitTooltipQuirkColor(csbUnitTooltipWeaponColor.getColour()); + GUIP.setUnitTooltipArmorminiColorIntact(csbUnitTooltipArmorMiniIntact.getColour()); GUIP.setUnitTooltipArmorminiColorPartialDamage(csbUnitTooltipArmorMiniPartial.getColour()); GUIP.setUnitTooltipArmorminiColorDamaged(csbUnitTooltipArmorMiniDamaged.getColour()); @@ -3012,7 +3086,43 @@ public static List userDataFiles(File relativePath, String fileEnding) { return result; } + public static List filteredFiles(File path, String fileEnding) { + List result = new ArrayList<>(); + String[] userDataFiles = path.list((direc, name) -> name.endsWith(fileEnding)); + if (userDataFiles != null) { + Arrays.stream(userDataFiles).map(file -> path + "/" + file).forEach(result::add); + } + return result; + } + + public static List filteredFilesWithSubDirs(File path, String fileEnding) { + try (Stream entries = Files.walk(path.toPath())) { + return entries.map(Objects::toString).filter(name -> name.endsWith(fileEnding)).collect(toList()); + } catch (IOException e) { + LogManager.getLogger().warn("Error while reading " + fileEnding + " files from " + path, e); + return new ArrayList<>(); + } + } + private void adaptToGUIScale() { UIUtil.adjustDialog(this, UIUtil.FONT_SCALE1); } + + /** + * Shows a file chooser for selecting a user directory and sets the given text field to the + * result if one was chosen. This is for use with settings dialogs (also used in MML and MHQ) + * + * @param userDirTextField The textfield showing the user dir for manual change + * @param parent The parent JFrame of the settings dialog + */ + public static void fileChooseUserDir(JTextField userDirTextField, JFrame parent) { + JFileChooser userDirChooser = new JFileChooser(userDirTextField.getText()); + userDirChooser.setDialogTitle(Messages.getString("CommonSettingsDialog.userDir.chooser.title")); + userDirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int returnVal = userDirChooser.showOpenDialog(parent); + if ((returnVal == JFileChooser.APPROVE_OPTION) && (userDirChooser.getSelectedFile() != null) + && userDirChooser.getSelectedFile().isDirectory()) { + userDirTextField.setText(userDirChooser.getSelectedFile().toString()); + } + } } diff --git a/megamek/src/megamek/client/ui/swing/CustomMechDialog.java b/megamek/src/megamek/client/ui/swing/CustomMechDialog.java index 030b086fbfc..0cb6df18afd 100644 --- a/megamek/src/megamek/client/ui/swing/CustomMechDialog.java +++ b/megamek/src/megamek/client/ui/swing/CustomMechDialog.java @@ -85,6 +85,11 @@ public class CustomMechDialog extends AbstractButtonDialog implements ActionList private final JFormattedTextField txtDeploymentOffset = new JFormattedTextField(formatterFactory); private final JFormattedTextField txtDeploymentWidth = new JFormattedTextField(formatterFactory); + private JSpinner spinStartingAnyNWx; + private JSpinner spinStartingAnyNWy; + private JSpinner spinStartingAnySEx; + private JSpinner spinStartingAnySEy; + private final JLabel labDeployShutdown = new JLabel( Messages.getString("CustomMechDialog.labDeployShutdown"), SwingConstants.RIGHT); private final JCheckBox chDeployShutdown = new JCheckBox(); @@ -473,6 +478,17 @@ private void refreshDeployment() { txtDeploymentOffset.setText(Integer.toString(entity.getStartingOffset(false))); txtDeploymentWidth.setText(Integer.toString(entity.getStartingWidth(false))); + int bh = clientgui.getClient().getMapSettings().getBoardHeight(); + int bw = clientgui.getClient().getMapSettings().getBoardWidth(); + int x = Math.min(entity.getStartingAnyNWx(false) + 1, bw); + spinStartingAnyNWx.setValue(x); + int y = Math.min(entity.getStartingAnyNWy(false) + 1, bh); + spinStartingAnyNWy.setValue(y); + x = Math.min(entity.getStartingAnySEx(false) + 1, bw); + spinStartingAnySEy.setValue(x); + y = Math.min(entity.getStartingAnySEy(false) + 1, bh); + spinStartingAnySEy.setValue(y); + boolean enableDeploymentZoneControls = choDeploymentZone.isEnabled() && (choDeploymentZone.getSelectedIndex() > 0); txtDeploymentOffset.setEnabled(enableDeploymentZoneControls); txtDeploymentWidth.setEnabled(enableDeploymentZoneControls); @@ -881,6 +897,15 @@ protected void okAction() { entity.setStartingOffset(Integer.parseInt(txtDeploymentOffset.getText())); entity.setStartingWidth(Integer.parseInt(txtDeploymentWidth.getText())); + int x = Math.min((Integer) spinStartingAnyNWx.getValue(), (Integer) spinStartingAnySEx.getValue()); + int y = Math.min((Integer) spinStartingAnyNWy.getValue(), (Integer) spinStartingAnySEy.getValue()); + entity.setStartingAnyNWx(x - 1); + entity.setStartingAnyNWy(y - 1); + x = Math.max((Integer) spinStartingAnyNWx.getValue(), (Integer) spinStartingAnySEx.getValue()); + y = Math.max((Integer) spinStartingAnyNWy.getValue(), (Integer) spinStartingAnySEy.getValue()); + entity.setStartingAnySEx(x - 1); + entity.setStartingAnySEy(y - 1); + // Should the entity begin the game shutdown? if (chDeployShutdown.isSelected() && gameOptions().booleanOption(OptionsConstants.RPG_BEGIN_SHUTDOWN)) { entity.performManualShutdown(); @@ -1200,6 +1225,32 @@ protected Container createCenterPane() { panDeploy.add(labDeploymentWidth, GBC.std()); panDeploy.add(txtDeploymentWidth, GBC.eol()); + int bh = clientgui.getClient().getMapSettings().getBoardHeight(); + int bw = clientgui.getClient().getMapSettings().getBoardWidth(); + + panDeploy.add(new JLabel(Messages.getString("CustomMechDialog.labDeploymentAnyNW")), GBC.std()); + int x = Math.min(entity.getStartingAnyNWx(false) + 1, bw); + SpinnerNumberModel mStartingAnyNWx = new SpinnerNumberModel(x, 0,bw, 1); + spinStartingAnyNWx = new JSpinner(mStartingAnyNWx); + spinStartingAnyNWx.setValue(x); + panDeploy.add(spinStartingAnyNWx, GBC.std()); + int y = Math.min(entity.getStartingAnyNWy(false) + 1, bh); + SpinnerNumberModel mStartingAnyNWy = new SpinnerNumberModel(y, 0, bh, 1); + spinStartingAnyNWy = new JSpinner(mStartingAnyNWy); + spinStartingAnyNWy.setValue(y); + panDeploy.add(spinStartingAnyNWy, GBC.eol()); + panDeploy.add(new JLabel(Messages.getString("CustomMechDialog.labDeploymentAnySE")), GBC.std()); + x = Math.min(entity.getStartingAnySEx(false) + 1, bw); + SpinnerNumberModel mStartingAnySEx = new SpinnerNumberModel(x, 0, bw, 1); + spinStartingAnySEx = new JSpinner(mStartingAnySEx); + spinStartingAnySEx.setValue(x); + panDeploy.add(spinStartingAnySEx, GBC.std()); + y = Math.min(entity.getStartingAnySEy(false) + 1, bh); + SpinnerNumberModel mStartingAnySEy = new SpinnerNumberModel(y, -0, bh, 1); + spinStartingAnySEy = new JSpinner(mStartingAnySEy); + spinStartingAnySEy.setValue(y); + panDeploy.add(spinStartingAnySEy, GBC.eol()); + numFormatter.setMinimum(0); numFormatter.setCommitsOnValidEdit(true); diff --git a/megamek/src/megamek/client/ui/swing/EntityChoiceDialog.java b/megamek/src/megamek/client/ui/swing/EntityChoiceDialog.java index 9758571e022..5426ab17b33 100644 --- a/megamek/src/megamek/client/ui/swing/EntityChoiceDialog.java +++ b/megamek/src/megamek/client/ui/swing/EntityChoiceDialog.java @@ -48,12 +48,13 @@ protected EntityChoiceDialog(JFrame frame, String title, String message, @Override protected void detailLabel(JToggleButton button, Entity target) { String div = "
" + UnitToolTip.getEntityTipAsTarget(target, null) + "
"; - button.setText("" + div + ""); + button.setText(UnitToolTip.wrapWithHTML(div)); } @Override protected void summaryLabel(JToggleButton button, Entity target) { - button.setText("" +UnitToolTip.getTargetTipSummaryEntity(target, null) + ""); + String txt = UnitToolTip.getTargetTipSummaryEntity(target, null); + button.setText(UnitToolTip.wrapWithHTML(txt)); } /** 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/GUIPreferences.java b/megamek/src/megamek/client/ui/swing/GUIPreferences.java index 3bd16c720bb..6b5a5c55b42 100644 --- a/megamek/src/megamek/client/ui/swing/GUIPreferences.java +++ b/megamek/src/megamek/client/ui/swing/GUIPreferences.java @@ -191,6 +191,9 @@ public class GUIPreferences extends PreferenceStoreProxy { public static final String UNIT_TOOLTIP_ALT_BGCOLOR = "UnitToolTipAltBGColor"; public static final String UNIT_TOOLTIP_BLOCK_BGCOLOR = "UnitToolTipBlockBGColor"; public static final String UNIT_TOOLTIP_TERRAIN_BGCOLOR = "UnitToolTipTerainBGColor"; + public static final String UNIT_TOOLTIP_HIGHTLIGHT_COLOR = "UnitToolTipHighlightColor"; + public static final String UNIT_TOOLTIP_WEAPON_COLOR = "UnitToolTipWeaponColor"; + public static final String UNIT_TOOLTIP_QUIRK_COLOR = "UnitToolTipQuirkColor"; public static final String SPLIT_PANE_A_DIVIDER_LOCATION = "SplitPaneADividerLocation"; public static final String GAME_SUMMARY_BOARD_VIEW = "GameSummaryBoardView"; @@ -344,7 +347,6 @@ public class GUIPreferences extends PreferenceStoreProxy { public static final String SBFSHEET_VALUEFONT = "SBFSheetValueFont"; public static final String SUMMARY_FONT = "SummaryCardFont"; - // RAT dialog preferences public static String RAT_TECH_LEVEL = "RATTechLevel"; public static String RAT_BV_MIN = "RATBVMin"; @@ -590,6 +592,9 @@ protected GUIPreferences() { setDefault(UNIT_TOOLTIP_ALT_BGCOLOR, new Color(0x003333)); setDefault(UNIT_TOOLTIP_BLOCK_BGCOLOR, new Color(0x000060)); setDefault(UNIT_TOOLTIP_TERRAIN_BGCOLOR, new Color(0x8DAF8D)); + setDefault(UNIT_TOOLTIP_HIGHTLIGHT_COLOR, new Color(0xB496DC)); + setDefault(UNIT_TOOLTIP_WEAPON_COLOR, new Color(0x9696D2)); + setDefault(UNIT_TOOLTIP_QUIRK_COLOR, new Color(0x64B4B4)); store.setDefault(GAME_SUMMARY_BOARD_VIEW, false); store.setDefault(ENTITY_OWNER_LABEL_COLOR, true); @@ -1524,6 +1529,8 @@ public boolean getBoardEdRndStart() { return store.getBoolean(BOARDEDIT_RNDDIALOG_START); } + + public void setShadowMap(boolean state) { store.setValue(SHADOWMAP, state); } @@ -2779,6 +2786,18 @@ public Color getUnitToolTipTerrainBGColor() { return getColor(UNIT_TOOLTIP_TERRAIN_BGCOLOR); } + public Color getUnitToolTipHighlightColor() { + return getColor(UNIT_TOOLTIP_HIGHTLIGHT_COLOR); + } + + public Color getUnitToolTipWeaponColor() { + return getColor(UNIT_TOOLTIP_WEAPON_COLOR); + } + + public Color getUnitToolTipQuirkColor() { + return getColor(UNIT_TOOLTIP_QUIRK_COLOR); + } + public boolean getDockOnLeft() { return getBoolean(DOCK_ON_LEFT); } @@ -2966,6 +2985,18 @@ public void setUnitTooltipTerrainBGColor(Color c) { store.setValue(UNIT_TOOLTIP_TERRAIN_BGCOLOR, getColorString(c)); } + public void setUnitTooltipHightlightColor(Color c) { + store.setValue(UNIT_TOOLTIP_HIGHTLIGHT_COLOR, getColorString(c)); + } + + public void setUnitTooltipWeaponColor(Color c) { + store.setValue(UNIT_TOOLTIP_WEAPON_COLOR, getColorString(c)); + } + + public void setUnitTooltipQuirkColor(Color c) { + store.setValue(UNIT_TOOLTIP_QUIRK_COLOR, getColorString(c)); + } + public void setDockOnLeft(Boolean state) { store.setValue(DOCK_ON_LEFT, state); } diff --git a/megamek/src/megamek/client/ui/swing/HelpDialog.java b/megamek/src/megamek/client/ui/swing/HelpDialog.java index 2f1689f2577..536a71ebbfe 100644 --- a/megamek/src/megamek/client/ui/swing/HelpDialog.java +++ b/megamek/src/megamek/client/ui/swing/HelpDialog.java @@ -1,48 +1,53 @@ -/* -* MegaMek - Copyright (C) 2013-2020 - The MegaMek Team -* -* This program is free software; you can redistribute it and/or modify it under -* the terms of the GNU General Public License as published by the Free Software -* Foundation; either version 2 of the License, or (at your option) any later -* version. -* -* This program is distributed in the hope that it will be useful, but WITHOUT -* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -* details. -*/ +/* + * Copyright (c) 2013-2020, 2023 - 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.client.ui.swing; import megamek.client.ui.swing.util.UIUtil; import org.apache.logging.log4j.LogManager; import javax.swing.*; +import javax.swing.border.EmptyBorder; import javax.swing.event.HyperlinkEvent; import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; import java.net.URL; /** + * This is a basic help dialog that can display HTML pages and also reacts to hyperlink clicks. * @author Deric "Netzilla" Page (deric dot page at usa dot net) - * @since 11/4/13 9:20 PM + * @author Simon (Juliez) */ public class HelpDialog extends JDialog { - private static final long serialVersionUID = 1442198850518387690L; - - private static final int WIDTH = 600; - private static final int HEIGHT = 400; + protected static final String CLOSE_ACTION = "closeAction"; - private URL helpUrl; + private static final int WIDTH = 800; + private static final int HEIGHT = 600; - public HelpDialog(String title, URL helpURL) { - setTitle(title); - getContentPane().setLayout(new BorderLayout()); - this.helpUrl = helpURL; + public HelpDialog(String title, URL helpURL, JFrame parent) { + super(parent, title, true); JEditorPane mainView = new JEditorPane(); mainView.setEditable(false); try { - mainView.setPage(helpUrl); + mainView.setPage(helpURL); } catch (Exception ex) { LogManager.getLogger().error("", ex); JOptionPane.showMessageDialog(this, ex.getMessage(), "ERROR", JOptionPane.ERROR_MESSAGE); @@ -60,16 +65,20 @@ public HelpDialog(String title, URL helpURL) { } }); - add(new JScrollPane(mainView)); - setModalExclusionType(ModalExclusionType.TOOLKIT_EXCLUDE); + JScrollPane scrollPane = new JScrollPane(mainView); + scrollPane.getVerticalScrollBar().setUnitIncrement(16); + scrollPane.setBorder(new EmptyBorder(10, 10, 10, 10)); + add(scrollPane); + UIUtil.adjustDialog(this, UIUtil.FONT_SCALE1); + + // Escape keypress + final KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, CLOSE_ACTION); + getRootPane().getInputMap(JComponent.WHEN_FOCUSED).put(escape, CLOSE_ACTION); + getRootPane().getActionMap().put(CLOSE_ACTION, new CloseAction(this)); - adaptToGUIScale(); setDefaultCloseOperation(DISPOSE_ON_CLOSE); setSize(WIDTH, HEIGHT); setLocationRelativeTo(null); } - - private void adaptToGUIScale() { - UIUtil.adjustDialog(this, UIUtil.FONT_SCALE1); - } -} +} \ No newline at end of file diff --git a/megamek/src/megamek/client/ui/swing/MapMenu.java b/megamek/src/megamek/client/ui/swing/MapMenu.java index 74b3f4658f4..0efb665fdb6 100644 --- a/megamek/src/megamek/client/ui/swing/MapMenu.java +++ b/megamek/src/megamek/client/ui/swing/MapMenu.java @@ -22,6 +22,7 @@ import megamek.client.Client; import megamek.client.event.BoardViewEvent; import megamek.client.ui.Messages; +import megamek.client.ui.swing.lobby.LobbyUtility; import megamek.common.*; import megamek.common.Building.DemolitionCharge; import megamek.common.actions.BAVibroClawAttackAction; @@ -330,6 +331,23 @@ private JMenuItem viewJMenuItem(Entity en) { return item; } + private JMenuItem viewReadoutJMenuItem(Entity en) { + JMenuItem item = new JMenuItem(Messages.getString("ClientGUI.viewReadoutMenuItem") + + en.getDisplayName()); + + item.setActionCommand(Integer.toString(en.getId())); + item.addActionListener(evt -> { + try { + selectedEntity = game.getEntity(Integer.parseInt(evt.getActionCommand())); + LobbyUtility.mechReadout(selectedEntity, 0, false, gui.getFrame()); + } catch (Exception ex) { + LogManager.getLogger().error("", ex); + } + }); + + return item; + } + private JMenu touchOffExplosivesMenu() { JMenu menu = new JMenu("Touch off explosives"); @@ -467,6 +485,7 @@ private JMenu createViewMenu() { // With double blind on, the game may unseen units if (!entity.isSensorReturn(localPlayer) && entity.hasSeenEntity(localPlayer)) { menu.add(viewJMenuItem(entity)); + menu.add(viewReadoutJMenuItem(entity)); } } return menu; diff --git a/megamek/src/megamek/client/ui/swing/MegaMekGUI.java b/megamek/src/megamek/client/ui/swing/MegaMekGUI.java index 4ea2e075992..64e5383db87 100644 --- a/megamek/src/megamek/client/ui/swing/MegaMekGUI.java +++ b/megamek/src/megamek/client/ui/swing/MegaMekGUI.java @@ -912,7 +912,7 @@ private void showSkinningHowTo() { // Launch the help dialog. HelpDialog helpDialog = new HelpDialog( Messages.getString("ClientGUI.skinningHelpPath.title"), - helpUrl); + helpUrl, frame); helpDialog.setVisible(true); } catch (MalformedURLException e) { LogManager.getLogger().error("", e); diff --git a/megamek/src/megamek/client/ui/swing/MovementDisplay.java b/megamek/src/megamek/client/ui/swing/MovementDisplay.java index 3fe87d3c7e7..20c88269c35 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, @@ -793,31 +794,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(); + } + + ; }); } @@ -903,7 +906,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); @@ -948,7 +951,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)) { @@ -996,7 +999,7 @@ private void addStepToMovePath(MoveStepType moveStep) { updateMove(); } - private void addStepsToMovePath(MoveStepType ... moveSteps ) { + private void addStepsToMovePath(MoveStepType... moveSteps) { for (MoveStepType moveStep : moveSteps) { cmd.addStep(moveStep); } @@ -1008,7 +1011,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(); } @@ -1018,12 +1021,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); } @@ -1045,7 +1048,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(); } @@ -1104,7 +1107,7 @@ protected void updateDonePanel() { } } - private List computeTurnDetails(){ + private List computeTurnDetails() { String validTextColor = AbstractBoardViewOverlay.colorToHex(AbstractBoardViewOverlay.getTextColor()); String invalidTextColor = AbstractBoardViewOverlay.colorToHex(AbstractBoardViewOverlay.getTextColor(), 0.7f); @@ -1115,7 +1118,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; @@ -1441,7 +1444,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"); @@ -1456,7 +1459,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"); @@ -1507,9 +1510,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); @@ -1639,7 +1642,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; @@ -1702,9 +1705,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); @@ -1795,7 +1798,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) { @@ -1865,7 +1868,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); } @@ -1873,7 +1876,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); } @@ -2084,9 +2087,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, @@ -2094,7 +2097,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); } @@ -2225,8 +2228,7 @@ private void updateTakeCoverButton() { private synchronized void updateChaffButton() { Entity ce = ce(); - if (ce == null ) - { + if (ce == null) { return; } @@ -2255,7 +2257,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()); } @@ -2264,7 +2266,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()); @@ -2273,11 +2275,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); @@ -2300,14 +2302,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() { @@ -2588,9 +2590,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; } @@ -2602,13 +2604,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); @@ -2664,7 +2666,7 @@ private void updateBootleggerButton() { } if (!clientgui.getClient().getGame().getOptions() - .booleanOption(OptionsConstants.ADVGRNDMOV_VEHICLE_ADVANCED_MANEUVERS)) { + .booleanOption(OptionsConstants.ADVGRNDMOV_VEHICLE_ADVANCED_MANEUVERS)) { return; } @@ -2708,7 +2710,7 @@ private void updateStartupButton() { } if (!clientgui.getClient().getGame().getOptions() - .booleanOption(OptionsConstants.RPG_MANUAL_SHUTDOWN)) { + .booleanOption(OptionsConstants.RPG_MANUAL_SHUTDOWN)) { return; } @@ -2727,7 +2729,7 @@ private void updateSelfDestructButton() { } if (!clientgui.getClient().getGame().getOptions() - .booleanOption(OptionsConstants.ADVANCED_TACOPS_SELF_DESTRUCT)) { + .booleanOption(OptionsConstants.ADVANCED_TACOPS_SELF_DESTRUCT)) { return; } @@ -2896,8 +2898,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); } @@ -3017,8 +3019,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); } @@ -3067,7 +3069,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 @@ -3080,7 +3082,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); @@ -3102,7 +3104,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); } } @@ -3117,12 +3119,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 { @@ -3162,7 +3164,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); } } @@ -3170,9 +3172,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( @@ -3232,11 +3234,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 { @@ -3312,7 +3314,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; @@ -3366,7 +3368,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); @@ -3464,12 +3466,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; @@ -3515,12 +3517,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; @@ -3628,7 +3630,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)) { @@ -3714,7 +3716,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(); } @@ -3726,7 +3728,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) { @@ -3874,42 +3876,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 @@ -4584,18 +4586,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(); @@ -4622,8 +4624,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()) { @@ -4701,7 +4703,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; } @@ -4829,9 +4831,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) { @@ -4842,20 +4844,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); @@ -4948,7 +4950,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); @@ -4956,7 +4958,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 @@ -5143,8 +5145,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( @@ -5221,15 +5223,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) { @@ -5239,18 +5241,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/Ruler.java b/megamek/src/megamek/client/ui/swing/Ruler.java index b9a81e7b02f..2fa7d3983ff 100644 --- a/megamek/src/megamek/client/ui/swing/Ruler.java +++ b/megamek/src/megamek/client/ui/swing/Ruler.java @@ -43,6 +43,7 @@ public class Ruler extends JDialog implements BoardViewListener, IPreferenceChan private int distance; private Client client; private BoardView bv; + private Game game; private boolean flip; private JPanel buttonPanel; @@ -69,7 +70,7 @@ public class Ruler extends JDialog implements BoardViewListener, IPreferenceChan private JCheckBox cboIsMech2 = new JCheckBox(Messages.getString("Ruler.isMech")); - public Ruler(JFrame f, Client c, BoardView b) { + public Ruler(JFrame f, Client c, BoardView b, Game g) { super(f, Messages.getString("Ruler.title"), false); enableEvents(AWTEvent.WINDOW_EVENT_MASK); @@ -81,6 +82,7 @@ public Ruler(JFrame f, Client c, BoardView b) { bv = b; client = c; + game = g; b.addBoardViewListener(this); try { @@ -273,7 +275,7 @@ private void addPoint(Coords c) { int absHeight = Integer.MIN_VALUE; boolean isMech = false; boolean entFound = false; - for (Entity ent : client.getGame().getEntitiesVector(c)) { + for (Entity ent : game.getEntitiesVector(c)) { int trAbsheight = ent.relHeight(); if (trAbsheight > absHeight) { absHeight = trAbsheight; @@ -316,20 +318,20 @@ private void setText() { // leave at default value } - if (!client.getGame().getBoard().contains(start) || !client.getGame().getBoard().contains(end)) { + if (!game.getBoard().contains(start) || !game.getBoard().contains(end)) { return; } String toHit1 = "", toHit2 = ""; ToHitData thd; if (flip) { - thd = LosEffects.calculateLos(client.getGame(), + thd = LosEffects.calculateLos(game, buildAttackInfo(start, end, h1, h2, cboIsMech1.isSelected(), - cboIsMech2.isSelected())).losModifiers(client.getGame()); + cboIsMech2.isSelected())).losModifiers(game); } else { - thd = LosEffects.calculateLos(client.getGame(), + thd = LosEffects.calculateLos(game, buildAttackInfo(end, start, h2, h1, cboIsMech2.isSelected(), - cboIsMech1.isSelected())).losModifiers(client.getGame()); + cboIsMech1.isSelected())).losModifiers(game); } if (thd.getValue() != TargetRoll.IMPOSSIBLE) { toHit1 = thd.getValue() + " = "; @@ -337,13 +339,13 @@ private void setText() { toHit1 += thd.getDesc(); if (flip) { - thd = LosEffects.calculateLos(client.getGame(), + thd = LosEffects.calculateLos(game, buildAttackInfo(end, start, h2, h1, cboIsMech2.isSelected(), - cboIsMech1.isSelected())).losModifiers(client.getGame()); + cboIsMech1.isSelected())).losModifiers(game); } else { - thd = LosEffects.calculateLos(client.getGame(), + thd = LosEffects.calculateLos(game, buildAttackInfo(start, end, h1, h2, cboIsMech1.isSelected(), - cboIsMech2.isSelected())).losModifiers(client.getGame()); + cboIsMech2.isSelected())).losModifiers(game); } if (thd.getValue() != TargetRoll.IMPOSSIBLE) { toHit2 = thd.getValue() + " = "; @@ -377,8 +379,8 @@ private LosEffects.AttackInfo buildAttackInfo(Coords c1, Coords c2, int h1, ai.targetHeight = h2; ai.attackerIsMech = attackerIsMech; ai.targetIsMech = targetIsMech; - ai.attackAbsHeight = client.getGame().getBoard().getHex(c1).floor() + h1; - ai.targetAbsHeight = client.getGame().getBoard().getHex(c2).floor() + h2; + ai.attackAbsHeight = game.getBoard().getHex(c1).floor() + h1; + ai.targetAbsHeight = game.getBoard().getHex(c2).floor() + h2; return ai; } diff --git a/megamek/src/megamek/client/ui/swing/TargetChoiceDialog.java b/megamek/src/megamek/client/ui/swing/TargetChoiceDialog.java index a9fcd1940d0..35be8bc9a87 100644 --- a/megamek/src/megamek/client/ui/swing/TargetChoiceDialog.java +++ b/megamek/src/megamek/client/ui/swing/TargetChoiceDialog.java @@ -75,15 +75,15 @@ protected TargetChoiceDialog(JFrame frame, String title, String message, @Override protected void detailLabel(JToggleButton button, Targetable target) { - String div = "
" + infoText(target) + " " + UnitToolTip.getTargetTipDetail(target, - clientGUI.getClient()) + "
"; - button.setText("" + div + ""); + String div = infoText(target) + " " + UnitToolTip.getTargetTipDetail(target, clientGUI.getClient()); + div = "
" + div + "
"; + button.setText(UnitToolTip.wrapWithHTML(div)); } @Override protected void summaryLabel(JToggleButton button, Targetable target) { - button.setText("" + infoText(target) + "
" + UnitToolTip.getTargetTipSummary(target, - clientGUI.getClient()) + ""); + String txt = infoText(target) + "
" + UnitToolTip.getTargetTipSummary(target, clientGUI.getClient()); + button.setText(UnitToolTip.wrapWithHTML(txt)); } protected String infoText(Targetable target) { diff --git a/megamek/src/megamek/client/ui/swing/boardview/BoardView.java b/megamek/src/megamek/client/ui/swing/boardview/BoardView.java index 16d71ac131b..854bf3a58a9 100644 --- a/megamek/src/megamek/client/ui/swing/boardview/BoardView.java +++ b/megamek/src/megamek/client/ui/swing/boardview/BoardView.java @@ -1136,7 +1136,8 @@ public synchronized void paintComponent(Graphics g) { drawDeployment(g); } - if (game.getPhase().isSetArtilleryAutohitHexes() && showAllDeployment) { + if ((game.getPhase().isSetArtilleryAutohitHexes() && showAllDeployment) + || (game.getPhase().isLounge())) { drawAllDeployment(g); } @@ -1848,20 +1849,30 @@ private void drawAllDeployment(Graphics g) { int drawWidth = (view.width / (int) (HEX_WC * scale)) + 3; int drawHeight = (view.height / (int) (HEX_H * scale)) + 3; + List players = game.getPlayersList(); + final GameOptions gOpts = game.getOptions(); + + if (gOpts.booleanOption(OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0)) { + players = players.stream().filter(p -> p.isBot() || p.getId() == 0).collect(Collectors.toList()); + } + + if (game.getPhase().isLounge() && !localPlayer.isGameMaster() + && (gOpts.booleanOption(OptionsConstants.BASE_BLIND_DROP) + || gOpts.booleanOption(OptionsConstants.BASE_REAL_BLIND_DROP)) ) { + players = players.stream().filter(p -> !p.isEnemyOf(localPlayer)).collect(Collectors.toList()); + } + Board board = game.getBoard(); // loop through the hexes for (int i = 0; i < drawHeight; i++) { for (int j = 0; j < drawWidth; j++) { Coords c = new Coords(j + drawX, i + drawY); - Enumeration allP = game.getPlayers(); - Player cp; int pCount = 0; int bThickness = 1 + 10 / game.getNoOfPlayers(); // loop through all players - while (allP.hasMoreElements()) { - cp = allP.nextElement(); - if (board.isLegalDeployment(c, cp)) { - Color bC = cp.getColour().getColour(); + for (Player player : players) { + if (board.isLegalDeployment(c, player)) { + Color bC = player.getColour().getColour(); drawHexBorder(g, getHexLocation(c), bC, (bThickness + 2) * pCount, bThickness); pCount++; } @@ -3145,6 +3156,14 @@ public void drawRuler(Coords s, Coords e, Color sc, Color ec) { repaint(); } + public Coords getRulerStart() { + return rulerStart; + } + + public Coords getRulerEnd() { + return rulerEnd; + } + /** * @return the coords at the specified point */ @@ -5724,14 +5743,12 @@ public String getHexTooltip(MouseEvent e) { result += sSpecialHex; } - String div = "
" + result + "
"; StringBuffer txt = new StringBuffer(); - String htmlStyle = "style=\"color:" + GUIP.hexColor(GUIP.getUnitToolTipFGColor()) + "; "; - htmlStyle += "background-color:" + GUIP.hexColor(GUIP.getUnitToolTipBGColor()) + ";\""; - txt.append("" + div + ""); + String div = "
" + result + "
"; + txt.append(UnitToolTip.wrapWithHTML(div)); // Check to see if the tool tip is completely empty - if (txt.toString().equals("" + "")) { + if (result.isEmpty()) { return ""; } diff --git a/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java b/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java index a988bd06549..30282279875 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java +++ b/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java @@ -44,6 +44,7 @@ import megamek.client.ui.swing.widget.SkinSpecification; import megamek.common.*; import megamek.common.annotations.Nullable; +import megamek.common.enums.GamePhase; import megamek.common.event.*; import megamek.common.force.Force; import megamek.common.force.Forces; @@ -613,8 +614,12 @@ private void setupUnitsPanel() { rightSide.add(scrMekTable); panUnits.setLayout(new BoxLayout(panUnits, BoxLayout.LINE_AXIS)); - panUnits.add(leftSide); - panUnits.add(rightSide); + JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + sp.setDividerLocation(400); + sp.setDividerSize(10); + sp.setLeftComponent(leftSide); + sp.setRightComponent(rightSide); + panUnits.add(sp); } private void setupMapPanel() { @@ -753,11 +758,27 @@ public void componentShown(ComponentEvent e) { boardPreviewW.setLocationRelativeTo(clientgui.frame); try { + boardPreviewGame.setPhase(GamePhase.LOUNGE); previewBV = new BoardView(boardPreviewGame, null, null); previewBV.setDisplayInvalidHexInfo(false); previewBV.setUseLOSTool(false); - boardPreviewW.add(previewBV.getComponent(true)); + JButton bpButton = new JButton(Messages.getString("BoardSelectionDialog.ViewGameBoardButton")); + bpButton.addActionListener(e -> previewGameBoard()); + JPanel bpPanel = new JPanel(); + bpPanel.setLayout(new BoxLayout(bpPanel, BoxLayout.PAGE_AXIS)); + bpPanel.add(bpButton); + bpPanel.add(previewBV.getComponent(true)); + boardPreviewW.add(bpPanel); boardPreviewW.setSize(clientgui.frame.getWidth() / 2, clientgui.frame.getHeight() / 2); + + Ruler.color1 = GUIP.getRulerColor1(); + Ruler.color2 = GUIP.getRulerColor2(); + Ruler ruler = new Ruler(clientgui.frame, client(), previewBV, boardPreviewGame); + ruler.setLocation(GUIP.getRulerPosX(), GUIP.getRulerPosY()); + ruler.setSize(GUIP.getRulerSizeHeight(), GUIP.getRulerSizeWidth()); + ruler.setAlwaysOnTop(true); + UIUtil.updateWindowBounds(ruler); + // Most boards will be far too large on the standard zoom previewBV.zoomOut(); previewBV.zoomOut(); @@ -1138,6 +1159,16 @@ private void markServerSideBoard(BufferedImage image) { public void previewGameBoard() { Board newBoard = getPossibleGameBoard(false); boardPreviewGame.setBoard(newBoard); + previewBV.setLocalPlayer(client().getLocalPlayer()); + final GameOptions gOpts = game().getOptions(); + boardPreviewGame.setOptions(gOpts); + + for (Player player : game().getPlayersList()) { + boardPreviewGame.removePlayer(player.getId()); + } + for (Player player : game().getPlayersList()) { + boardPreviewGame.setPlayer(player.getId(), player); + } boardPreviewW.setVisible(true); } @@ -1251,7 +1282,8 @@ private void refreshMekTable() { boolean localUnit = entity.getOwner().equals(localPlayer()); boolean teamUnit = !entity.getOwner().isEnemyOf(localPlayer()); boolean realBlindDrop = opts.booleanOption(OptionsConstants.BASE_REAL_BLIND_DROP); - if (localUnit || teamUnit || !realBlindDrop) { + boolean localGM = localPlayer().isGameMaster(); + if (localUnit || teamUnit || !realBlindDrop || localGM) { mekModel.addUnit(entity); } } @@ -1486,8 +1518,9 @@ void disembarkAll(Collection entities) { * own units (and bots) though. */ boolean isEditable(Entity entity) { - return clientgui.getLocalBots().containsKey(entity.getOwner().getName()) - || (entity.getOwnerId() == localPlayer().getId()); + boolean localGM = clientgui.getClient().getLocalPlayer().isGameMaster(); + return localGM || (clientgui.getLocalBots().containsKey(entity.getOwner().getName()) + || (entity.getOwnerId() == localPlayer().getId())); } /** @@ -1528,38 +1561,10 @@ public void configPlayer() { return; } - PlayerSettingsDialog psd = new PlayerSettingsDialog(clientgui, c); - if (psd.showDialog().isConfirmed()) { - Player player = c.getLocalPlayer(); - player.setConstantInitBonus(psd.getInit()); - player.setNbrMFConventional(psd.getCnvMines()); - player.setNbrMFVibra(psd.getVibMines()); - player.setNbrMFActive(psd.getActMines()); - player.setNbrMFInferno(psd.getInfMines()); - psd.getSkillGenerationOptionsPanel().updateClient(); - player.setEmail(psd.getEmail()); - - // The deployment position - int startPos = psd.getStartPos(); - final GameOptions gOpts = clientgui.getClient().getGame().getOptions(); - - player.setStartingPos(startPos); - player.setStartOffset(psd.getStartOffset()); - player.setStartWidth(psd.getStartWidth()); - c.sendPlayerInfo(); - - // If the gameoption set_arty_player_homeedge is set, adjust the player's offboard - // arty units to be behind the newly selected home edge. - OffBoardDirection direction = OffBoardDirection.translateStartPosition(startPos); - if (direction != OffBoardDirection.NONE && - gOpts.booleanOption(OptionsConstants.BASE_SET_ARTY_PLAYER_HOMEEDGE)) { - for (Entity entity: c.getGame().getPlayerEntities(c.getLocalPlayer(), false)) { - if (entity.getOffBoardDirection() != OffBoardDirection.NONE) { - entity.setOffBoard(entity.getOffBoardDistance(), direction); - } - } - } - } + PlayerSettingsDialog psd = new PlayerSettingsDialog(clientgui, c, previewBV); + psd.setModal(false); + psd.setAlwaysOnTop(true); + psd.showDialog(); } /** diff --git a/megamek/src/megamek/client/ui/swing/lobby/LobbyActions.java b/megamek/src/megamek/client/ui/swing/lobby/LobbyActions.java index d1771161181..f8b8b69ddf3 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/LobbyActions.java +++ b/megamek/src/megamek/client/ui/swing/lobby/LobbyActions.java @@ -354,9 +354,11 @@ public void customizeMech(Entity entity) { if (editable) { c = client().localBots.get(entity.getOwner().getName()); } else { - editable |= entity.getOwnerId() == localPlayer().getId(); + boolean localGM = localPlayer().isGameMaster(); + editable |= localGM || entity.getOwnerId() == localPlayer().getId(); c = client(); } + // When we customize a single entity's C3 network setting, // **ALL** members of the network may get changed. Entity c3master = entity.getC3Master(); @@ -1192,7 +1194,9 @@ private Client correctSender(Force force) { * player is allowed to change everything. */ boolean isEditable(Entity entity) { - return client().localBots.containsKey(entity.getOwner().getName()) + boolean localGM = client().getLocalPlayer().isGameMaster(); + return localGM + || client().localBots.containsKey(entity.getOwner().getName()) || (entity.getOwnerId() == localPlayer().getId()) || (entity.partOfForce() && isSelfOrLocalBot(game().getForces().getOwner(entity.getForceId()))) || (entity.partOfForce() && isEditable(game().getForces().getForce(entity))); @@ -1222,7 +1226,8 @@ boolean isEditable(Collection entities) { * of the entities are not on his team. */ boolean canSeeAll(Collection entities) { - if (!isBlindDrop(game()) && !isRealBlindDrop(game())) { + boolean localGM = client().getLocalPlayer().isGameMaster(); + if (localGM || (!isBlindDrop(game()) && !isRealBlindDrop(game()))) { return true; } return entities.stream().noneMatch(this::isLocalEnemy); diff --git a/megamek/src/megamek/client/ui/swing/lobby/LobbyMekCellFormatter.java b/megamek/src/megamek/client/ui/swing/lobby/LobbyMekCellFormatter.java index 13d901b7a08..ccb1729196c 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/LobbyMekCellFormatter.java +++ b/megamek/src/megamek/client/ui/swing/lobby/LobbyMekCellFormatter.java @@ -85,7 +85,8 @@ static String formatUnitFull(Entity entity, ChatLounge lobby, boolean forceView) GameOptions options = game.getOptions(); Player localPlayer = client.getLocalPlayer(); Player owner = entity.getOwner(); - boolean hideEntity = owner.isEnemyOf(localPlayer) + boolean localGM = localPlayer.isGameMaster(); + boolean hideEntity = !localGM && owner.isEnemyOf(localPlayer) && options.booleanOption(OptionsConstants.BASE_BLIND_DROP); if (hideEntity) { result.append(DOT_SPACER); @@ -262,6 +263,16 @@ static String formatUnitFull(Entity entity, ChatLounge lobby, boolean forceView) } String msg_start = Messages.getString("ChatLounge.Start"); result.append(" " + msg_start + ":" + IStartingPositions.START_LOCATION_NAMES[sp]); + if (sp == 0) { + int NWx = entity.getStartingAnyNWx() + 1; + int NWy = entity.getStartingAnyNWy() + 1; + int SEx = entity.getStartingAnySEx() + 1; + int SEy = entity.getStartingAnySEy() + 1; + int hexes = (1 + SEx - NWx) * (1 + SEy - NWy); + if ((NWx + NWy + SEx + SEy) > 0) { + result.append(" (" + NWx + ", " + NWy + ")-(" + SEx + ", " + SEy + ") (" + hexes + ")"); + } + } int so = entity.getStartingOffset(true); int sw = entity.getStartingWidth(true); if ((so != 0) || (sw != 3)) { @@ -483,7 +494,8 @@ static String formatUnitCompact(Entity entity, ChatLounge lobby, boolean forceVi GameOptions options = game.getOptions(); Player localPlayer = client.getLocalPlayer(); Player owner = entity.getOwner(); - boolean hideEntity = owner.isEnemyOf(localPlayer) + boolean localGM = localPlayer.isGameMaster(); + boolean hideEntity = !localGM && owner.isEnemyOf(localPlayer) && options.booleanOption(OptionsConstants.BASE_BLIND_DROP); if (hideEntity) { String value = "  "; diff --git a/megamek/src/megamek/client/ui/swing/lobby/LobbyUtility.java b/megamek/src/megamek/client/ui/swing/lobby/LobbyUtility.java index e56f521369a..a41c8223ff2 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/LobbyUtility.java +++ b/megamek/src/megamek/client/ui/swing/lobby/LobbyUtility.java @@ -73,11 +73,22 @@ static boolean isValidStartPos(Game game, Player player, int pos) { if (!isExclusiveDeployment(game)) { return true; } else { + final GameOptions gOpts = game.getOptions(); + List players = game.getPlayersList(); + + if (gOpts.booleanOption(OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0) && !player.isBot() && player.getId() != 0) { + return true; + } + + if (gOpts.booleanOption(OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0)) { + players = players.stream().filter(p -> p.isBot() || p.getId() == 0).collect(Collectors.toList()); + } + if (isTeamsShareVision(game)) { - return game.getPlayersVector().stream().filter(p -> p.isEnemyOf(player)) + return players.stream().filter(p -> p.isEnemyOf(player)) .noneMatch(p -> startPosOverlap(pos, p.getStartingPos())); } else { - return game.getPlayersVector().stream().filter(p -> !p.equals(player)) + return players.stream().filter(p -> !p.equals(player)) .noneMatch(p -> startPosOverlap(pos, p.getStartingPos())); } } diff --git a/megamek/src/megamek/client/ui/swing/lobby/MekTableModel.java b/megamek/src/megamek/client/ui/swing/lobby/MekTableModel.java index 90aa34b8464..12ff162d0c4 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/MekTableModel.java +++ b/megamek/src/megamek/client/ui/swing/lobby/MekTableModel.java @@ -105,7 +105,8 @@ public Object getValueAt(int row, int col) { if (col == COLS.BV.ordinal()) { boolean isEnemy = clientGui.getClient().getLocalPlayer().isEnemyOf(ownerOf(entity)); boolean isBlindDrop = clientGui.getClient().getGame().getOptions().booleanOption(OptionsConstants.BASE_BLIND_DROP); - boolean hideEntity = isEnemy && isBlindDrop; + boolean localGM = clientGui.getClient().getLocalPlayer().isGameMaster(); + boolean hideEntity = !localGM && isEnemy && isBlindDrop; float size = chatLounge.isCompact() ? 0 : 0.2f; return hideEntity ? "" : guiScaledFontHTML(size) + NumberFormat.getIntegerInstance().format(bv.get(row)); @@ -177,8 +178,10 @@ private void addCellData(InGameObject entity) { // Note that units of a player's bots are obscured because they could be added from // a MekHQ AtB campaign. Thus, the player can still configure them and so can identify // the obscured units but has to actively decide to do it. - boolean hideEntity = clientGui.getClient().getLocalPlayer().isEnemyOf(owner) + boolean localGM = clientGui.getClient().getLocalPlayer().isGameMaster(); + boolean hideEntity = !localGM && clientGui.getClient().getLocalPlayer().isEnemyOf(owner) && clientGui.getClient().getGame().getOptions().booleanOption(OptionsConstants.BASE_BLIND_DROP); + if (hideEntity) { unitTooltips.add(null); pilotTooltips.add(null); @@ -186,14 +189,12 @@ private void addCellData(InGameObject entity) { MapSettings mset = chatLounge.mapSettings; Player lPlayer = clientGui.getClient().getLocalPlayer(); String s = UnitToolTip.lobbyTip(entity, lPlayer, mset).toString(); - String htmlStyle = "style=\"color:" + GUIP.hexColor(GUIP.getUnitToolTipFGColor()) + "; "; - htmlStyle += "background-color:" + GUIP.hexColor(GUIP.getUnitToolTipBGColor()) + ";\""; - unitTooltips.add("" + s + ""); + unitTooltips.add(UnitToolTip.wrapWithHTML(s)); s = PilotToolTip.lobbyTip(entity).toString(); if (entity instanceof Entity) { s += PilotToolTip.getCrewAdvs((Entity) entity, true).toString(); } - pilotTooltips.add("" + s + ""); + pilotTooltips.add(UnitToolTip.wrapWithHTML(s)); } final boolean rpgSkills = clientGui.getClient().getGame().getOptions().booleanOption(OptionsConstants.RPG_RPG_GUNNERY); unitCells.add(LobbyMekCellFormatter.unitTableEntry(entity, chatLounge, false, chatLounge.isCompact())); @@ -298,7 +299,8 @@ public Component getTableCellRendererComponent(final JTable table, } Player owner = ownerOf(entity); - boolean showAsUnknown = clientGui.getClient().getLocalPlayer().isEnemyOf(owner) + boolean localGM = clientGui.getClient().getLocalPlayer().isGameMaster(); + boolean showAsUnknown = !localGM && clientGui.getClient().getLocalPlayer().isEnemyOf(owner) && clientGui.getClient().getGame().getOptions().booleanOption(OptionsConstants.BASE_BLIND_DROP); int size = UIUtil.scaleForGUI(MEKTABLE_IMGHEIGHT); diff --git a/megamek/src/megamek/client/ui/swing/lobby/PlayerSettingsDialog.java b/megamek/src/megamek/client/ui/swing/lobby/PlayerSettingsDialog.java index f7374d79502..cf13f5ba4f8 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/PlayerSettingsDialog.java +++ b/megamek/src/megamek/client/ui/swing/lobby/PlayerSettingsDialog.java @@ -31,9 +31,13 @@ import megamek.client.ui.panels.SkillGenerationOptionsPanel; import megamek.client.ui.swing.ClientGUI; import megamek.client.ui.swing.GUIPreferences; +import megamek.client.ui.swing.boardview.BoardView; import megamek.client.ui.swing.util.UIUtil; +import megamek.common.Entity; import megamek.common.IStartingPositions; +import megamek.common.OffBoardDirection; import megamek.common.Player; +import megamek.common.options.GameOptions; import megamek.common.options.OptionsConstants; import javax.swing.*; @@ -59,10 +63,11 @@ */ public class PlayerSettingsDialog extends AbstractButtonDialog { - public PlayerSettingsDialog(ClientGUI cg, Client cl) { + public PlayerSettingsDialog(ClientGUI cg, Client cl, BoardView bv) { super(cg.frame, "PlayerSettingsDialog", "PlayerSettingsDialog.title"); client = cl; clientgui = cg; + this.bv = bv; currentPlayerStartPos = cl.getLocalPlayer().getStartingPos(); if (currentPlayerStartPos > 10) { currentPlayerStartPos -= 10; @@ -80,6 +85,11 @@ protected void finalizeInitialization() throws Exception { super.finalizeInitialization(); } + @Override + protected void okAction() { + apply(); + } + /** Returns the chosen initiative modifier. */ public int getInit() { return parseField(fldInit); @@ -120,6 +130,22 @@ public int getStartPos() { return currentPlayerStartPos; } + public int getStartingAnyNWx() { + return Math.min((Integer) spinStartingAnyNWx.getValue(), (Integer) spinStartingAnySEx.getValue()) - 1; + } + + public int getStartingAnyNWy() { + return Math.min((Integer) spinStartingAnyNWy.getValue(), (Integer) spinStartingAnySEy.getValue()) - 1; + } + + public int getStartingAnySEx() { + return Math.max((Integer) spinStartingAnyNWx.getValue(), (Integer) spinStartingAnySEx.getValue()) - 1; + } + + public int getStartingAnySEy() { + return Math.max((Integer) spinStartingAnyNWy.getValue(), (Integer) spinStartingAnySEy.getValue()) - 1; + } + /** * @return the current {@link SkillGenerationOptionsPanel} */ @@ -136,6 +162,7 @@ public String getEmail() { private final Client client; private final ClientGUI clientgui; + private final BoardView bv; // Initiative Section private final JLabel labInit = new TipLabel(Messages.getString("PlayerSettingsDialog.initMod"), SwingConstants.RIGHT); @@ -167,6 +194,10 @@ public String getEmail() { private final DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory(numFormatter); private final JFormattedTextField txtOffset = new JFormattedTextField(formatterFactory, 0); private final JFormattedTextField txtWidth = new JFormattedTextField(formatterFactory, 3); + private JSpinner spinStartingAnyNWx; + private JSpinner spinStartingAnyNWy; + private JSpinner spinStartingAnySEx; + private JSpinner spinStartingAnySEy; // Bot Settings Section private final JButton butBotSettings = new JButton(Messages.getString("PlayerSettingsDialog.botSettings")); @@ -248,10 +279,76 @@ private JPanel deploymentParametersPanel() { result.add(txtOffset, GBC.eol()); result.add(lblWidth, GBC.std()); result.add(txtWidth, GBC.eol()); - + + result.add(new JLabel(Messages.getString("CustomMechDialog.labDeploymentAnyNW")), GBC.std()); + result.add(spinStartingAnyNWx, GBC.std()); + result.add(spinStartingAnyNWy, GBC.eol()); + result.add(new JLabel(Messages.getString("CustomMechDialog.labDeploymentAnySE")), GBC.std()); + result.add(spinStartingAnySEx, GBC.std()); + result.add(spinStartingAnySEy, GBC.eol()); + + JButton btnUseRuler = new JButton(Messages.getString("CustomMechDialog.BtnDeploymentUseRuler")); + btnUseRuler.setToolTipText(Messages.getString("CustomMechDialog.BtnDeploymentUseRulerTip")); + btnUseRuler.addActionListener(e -> useRuler()); + result.add(btnUseRuler, GBC.std()); + JButton btnApply = new JButton(Messages.getString("CustomMechDialog.BtnDeploymentApply")); + btnApply.setToolTipText(Messages.getString("CustomMechDialog.BtnDeploymentApplyTip")); + btnApply.addActionListener(e -> apply()); + result.add(btnApply, GBC.eol()); + return result; } - + + + private void useRuler() { + if (bv.getRulerStart() != null && bv.getRulerEnd() != null) { + int x = Math.min(bv.getRulerStart().getX(), bv.getRulerEnd().getX()); + spinStartingAnyNWx.setValue(x + 1); + int y = Math.min(bv.getRulerStart().getY(), bv.getRulerEnd().getY()); + spinStartingAnyNWy.setValue(y + 1); + x = Math.max(bv.getRulerStart().getX(), bv.getRulerEnd().getX()); + spinStartingAnySEx.setValue(x + 1); + y = Math.max(bv.getRulerStart().getY(), bv.getRulerEnd().getY()); + spinStartingAnySEy.setValue(y + 1); + } + } + + private void apply() { + Player player = client.getLocalPlayer(); + + player.setConstantInitBonus(getInit()); + player.setNbrMFConventional(getCnvMines()); + player.setNbrMFVibra(getVibMines()); + player.setNbrMFActive(getActMines()); + player.setNbrMFInferno(getInfMines()); + getSkillGenerationOptionsPanel().updateClient(); + player.setEmail(getEmail()); + + final GameOptions gOpts = clientgui.getClient().getGame().getOptions(); + + // If the gameoption set_arty_player_homeedge is set, adjust the player's offboard + // arty units to be behind the newly selected home edge. + OffBoardDirection direction = OffBoardDirection.translateStartPosition(getStartPos()); + if (direction != OffBoardDirection.NONE && + gOpts.booleanOption(OptionsConstants.BASE_SET_ARTY_PLAYER_HOMEEDGE)) { + for (Entity entity: client.getGame().getPlayerEntities(client.getLocalPlayer(), false)) { + if (entity.getOffBoardDirection() != OffBoardDirection.NONE) { + entity.setOffBoard(entity.getOffBoardDistance(), direction); + } + } + } + + // The deployment position + player.setStartingPos(getStartPos()); + player.setStartOffset(getStartOffset()); + player.setStartWidth(getStartWidth()); + player.setStartingAnyNWx(getStartingAnyNWx()); + player.setStartingAnyNWy(getStartingAnyNWy()); + player.setStartingAnySEx(getStartingAnySEx()); + player.setStartingAnySEy(getStartingAnySEy()); + client.sendPlayerInfo(); + } + private JPanel initiativeSection() { JPanel result = new OptionPanel("PlayerSettingsDialog.header.initMod"); Content panContent = new Content(new GridLayout(1, 2, 10, 5)); @@ -309,7 +406,27 @@ private void setupValues() { fldEmail.setText(player.getEmail()); txtWidth.setText(Integer.toString(player.getStartWidth())); txtOffset.setText(Integer.toString(player.getStartOffset())); - + + int bh = clientgui.getClient().getMapSettings().getBoardHeight(); + int bw = clientgui.getClient().getMapSettings().getBoardWidth(); + + SpinnerNumberModel mStartingAnyNWx = new SpinnerNumberModel(0, 0,bw, 1); + spinStartingAnyNWx = new JSpinner(mStartingAnyNWx); + SpinnerNumberModel mStartingAnyNWy = new SpinnerNumberModel(0, 0, bh, 1); + spinStartingAnyNWy = new JSpinner(mStartingAnyNWy); + SpinnerNumberModel mStartingAnySEx = new SpinnerNumberModel(0, 0, bw, 1); + spinStartingAnySEx = new JSpinner(mStartingAnySEx); + SpinnerNumberModel mStartingAnySEy = new SpinnerNumberModel(0, -0, bh, 1); + spinStartingAnySEy = new JSpinner(mStartingAnySEy); + + int x = Math.min(player.getStartingAnyNWx() + 1, bw); + spinStartingAnyNWx.setValue(x); + int y = Math.min(player.getStartingAnyNWy() + 1, bh); + spinStartingAnyNWy.setValue(y); + x = Math.min(player.getStartingAnySEx() + 1, bw); + spinStartingAnySEx.setValue(x); + y = Math.min(player.getStartingAnySEy() + 1, bh); + spinStartingAnySEy.setValue(y); } private void setupStartGrid() { diff --git a/megamek/src/megamek/client/ui/swing/lobby/PlayerTable.java b/megamek/src/megamek/client/ui/swing/lobby/PlayerTable.java index 0b320781159..78ad59fddac 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/PlayerTable.java +++ b/megamek/src/megamek/client/ui/swing/lobby/PlayerTable.java @@ -25,6 +25,7 @@ import megamek.client.ui.swing.util.UIUtil; import megamek.common.IStartingPositions; import megamek.common.Player; +import megamek.common.options.GameOptions; import megamek.common.options.OptionsConstants; import javax.swing.*; @@ -195,8 +196,28 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole result.append(guiScaledFontHTML()); String msg_start = Messages.getString("ChatLounge.Start"); - if ((player.getStartingPos() >= 0) && (player.getStartingPos() <= IStartingPositions.START_LOCATION_NAMES.length)) { + + final GameOptions gOpts = lobby.game().getOptions(); + if (gOpts.booleanOption(OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0) && !player.isBot() && player.getId() != 0) { + result.append(msg_start + ": " + Messages.getString("ChatLounge.Player0")); + } else if ((!lobby.client().getLocalPlayer().isGameMaster() + && (isEnemy) + && (gOpts.booleanOption(OptionsConstants.BASE_BLIND_DROP) + || gOpts.booleanOption(OptionsConstants.BASE_REAL_BLIND_DROP)))) { + result.append(msg_start + ": " + Messages.getString("ChatLounge.Blind")); + } else if ((player.getStartingPos() >= 0) + && (player.getStartingPos() <= IStartingPositions.START_LOCATION_NAMES.length)) { result.append(msg_start + ": " + IStartingPositions.START_LOCATION_NAMES[player.getStartingPos()]); + + if (player.getStartingPos() == 0) { + int NWx = player.getStartingAnyNWx() + 1; + int NWy = player.getStartingAnyNWy() + 1; + int SEx = player.getStartingAnySEx() + 1; + int SEy = player.getStartingAnySEy() + 1; + if ((NWx + NWy + SEx + SEy) > 0) { + result.append(" (" + NWx + ", " + NWy + ")-(" + SEx + ", " + SEy + ")"); + } + } int so = player.getStartOffset(); int sw = player.getStartWidth(); if ((so != 0) || (sw != 3)) { diff --git a/megamek/src/megamek/client/ui/swing/tileset/MMStaticDirectoryManager.java b/megamek/src/megamek/client/ui/swing/tileset/MMStaticDirectoryManager.java index eeade540649..316aba19116 100644 --- a/megamek/src/megamek/client/ui/swing/tileset/MMStaticDirectoryManager.java +++ b/megamek/src/megamek/client/ui/swing/tileset/MMStaticDirectoryManager.java @@ -20,17 +20,21 @@ import megamek.common.Configuration; import megamek.common.annotations.Nullable; +import megamek.common.preference.PreferenceManager; import megamek.common.util.fileUtils.AbstractDirectory; import megamek.common.util.fileUtils.DirectoryItems; import megamek.common.util.fileUtils.ImageFileFactory; import megamek.common.util.fileUtils.ScaledImageFileFactory; import org.apache.logging.log4j.LogManager; +import java.io.File; +import java.util.Map; + public class MMStaticDirectoryManager { //region Variable Declarations // Directories - private static AbstractDirectory portraitDirectory; - private static AbstractDirectory camouflageDirectory; + private static DirectoryItems portraitDirectory; + private static DirectoryItems camouflageDirectory; private static MechTileset mechTileset; // Re-parsing Prevention Variables: They are True at startup and when the specified directory @@ -70,6 +74,13 @@ private static void initializePortraits() { try { portraitDirectory = new DirectoryItems(Configuration.portraitImagesDir(), new ImageFileFactory()); + + String userDir = PreferenceManager.getClientPreferences().getUserDir(); + File portraitUserDir = new File(userDir + "/" + Configuration.portraitImagesDir()); + if (!userDir.isBlank() && portraitUserDir.isDirectory()) { + DirectoryItems userDirPortraits = new DirectoryItems(portraitUserDir, new ImageFileFactory()); + portraitDirectory.merge(userDirPortraits); + } } catch (Exception e) { LogManager.getLogger().error("Could not parse the portraits directory!", e); } @@ -89,6 +100,13 @@ private static void initializeCamouflage() { try { camouflageDirectory = new DirectoryItems(Configuration.camoDir(), new ScaledImageFileFactory()); + + String userDir = PreferenceManager.getClientPreferences().getUserDir(); + File camoUserDir = new File(userDir + "/" + Configuration.camoDir()); + if (!userDir.isBlank() && camoUserDir.isDirectory()) { + DirectoryItems userDirCamo = new DirectoryItems(camoUserDir, new ScaledImageFileFactory()); + camouflageDirectory.merge(userDirCamo); + } } catch (Exception e) { LogManager.getLogger().error("Could not parse the camo directory!", e); } diff --git a/megamek/src/megamek/client/ui/swing/tooltip/HexTooltip.java b/megamek/src/megamek/client/ui/swing/tooltip/HexTooltip.java index c20240a64d6..6797f2ef7a1 100644 --- a/megamek/src/megamek/client/ui/swing/tooltip/HexTooltip.java +++ b/megamek/src/megamek/client/ui/swing/tooltip/HexTooltip.java @@ -190,7 +190,7 @@ public static String getOneLineSummary(BuildingTarget target, Board board) { public static String getTerrainTip(Hex mhex, GUIPreferences GUIP, Game game) { Coords mcoords = mhex.getCoords(); - String illuminated = IlluminationLevel.getIlluminationLevelIndicator(game, mcoords); + String illuminated = IlluminationLevel.getIlluminationLevelIndicator(game, mcoords, GUIP); String result = ""; StringBuilder sTerrain = new StringBuilder(Messages.getString("BoardView1.Tooltip.Hex", mcoords.getBoardNum(), mhex.getLevel()) + illuminated + "
"); diff --git a/megamek/src/megamek/client/ui/swing/tooltip/UnitToolTip.java b/megamek/src/megamek/client/ui/swing/tooltip/UnitToolTip.java index efe8724c6e4..4e384603886 100644 --- a/megamek/src/megamek/client/ui/swing/tooltip/UnitToolTip.java +++ b/megamek/src/megamek/client/ui/swing/tooltip/UnitToolTip.java @@ -91,6 +91,13 @@ public static StringBuilder getEntityTipReport(Entity entity) { false, true, false, false, false, true); } + public static String wrapWithHTML(String text) { + String fgColor = GUIP.hexColor(GUIP.getUnitToolTipFGColor()); + String bgColor = GUIP.hexColor(GUIP.getUnitToolTipBGColor()); + String format = "%s"; + return String.format(format, fgColor, bgColor, text); + } + // PRIVATE /** Assembles the whole unit tooltip. */ @@ -211,7 +218,7 @@ private static String getDisplayNames(Entity entity, @Nullable Game game, boolea if (showName) { // Unit Chassis and Player Player owner = (game != null) ? game.getPlayer(entity.getOwnerId()) : null; - Color ownerColor = (owner != null) ? owner.getColour().getColour() : uiGray(); + Color ownerColor = (owner != null) ? owner.getColour().getColour() : GUIP.getUnitToolTipFGColor(); String ownerName = (owner != null) ? owner.getName() : ReportMessages.getString("BoardView1.Tooltip.unknownOwner"); String msg_clanbrackets = Messages.getString("BoardView1.Tooltip.ClanBrackets"); String clanStr = entity.isClan() && !entity.isMixedTech() ? " " + msg_clanbrackets + " " : ""; @@ -220,7 +227,7 @@ private static String getDisplayNames(Entity entity, @Nullable Game game, boolea result += "  " + entity.getEntityTypeName(entity.getEntityType()); result += "
" + ownerName; String msg_id = MessageFormat.format(" [ID: {0}]", entity.getId()); - result += UIUtil.guiScaledFontHTML(UIUtil.uiGray()) + msg_id + ""; + result += UIUtil.guiScaledFontHTML(GUIP.getUnitToolTipFGColor()) + msg_id + ""; result = guiScaledFontHTML(ownerColor) + result + ""; } @@ -251,7 +258,7 @@ private static String getQuirks(Entity entity, Game game, boolean details) { } } - return guiScaledFontHTML(uiQuirksColor(), TT_SMALLFONT_DELTA) + sQuirks + ""; + return guiScaledFontHTML(GUIP.getUnitToolTipQuirkColor(), TT_SMALLFONT_DELTA) + sQuirks + ""; } return ""; @@ -263,7 +270,7 @@ private static String getPartialRepairs(Entity entity, boolean details) { grp -> entity.countPartialRepairs(), details); if (!partialList.isEmpty()) { - result += guiScaledFontHTML(uiPartialRepairColor(), TT_SMALLFONT_DELTA) + partialList + ""; + result += guiScaledFontHTML(GUIP.getPrecautionColor(), TT_SMALLFONT_DELTA) + partialList + ""; } return result; @@ -947,9 +954,9 @@ private static StringBuilder weaponList(Entity entity) { } }; - col1 = guiScaledFontHTML(uiTTWeaponColor()) + col1 + ""; + col1 = guiScaledFontHTML(GUIP.getUnitToolTipWeaponColor()) + col1 + ""; col1 = "" + col1 + ""; - col2 = guiScaledFontHTML(uiTTWeaponColor()) + col2 + ""; + col2 = guiScaledFontHTML(GUIP.getUnitToolTipWeaponColor()) + col2 + ""; col2 = "" + col2 + ""; row = "" + col1 + col2 + ""; } @@ -989,11 +996,11 @@ private static StringBuilder bombList(Entity entity) { col2 = " x "; col3 = BombType.getBombName(i); - col1 = guiScaledFontHTML(uiTTWeaponColor()) + col1 + ""; + col1 = guiScaledFontHTML(GUIP.getUnitToolTipWeaponColor()) + col1 + ""; col1 = "" + col1 + ""; - col2 = guiScaledFontHTML(uiTTWeaponColor()) + col2 + ""; + col2 = guiScaledFontHTML(GUIP.getUnitToolTipWeaponColor()) + col2 + ""; col2 = "" + col2 + ""; - col3 = guiScaledFontHTML(uiTTWeaponColor()) + col3 + ""; + col3 = guiScaledFontHTML(GUIP.getUnitToolTipWeaponColor()) + col3 + ""; col3 = "" + col3 + ""; row = "" + col1 + col2 + col3 + ""; } else { @@ -1338,7 +1345,7 @@ private static StringBuilder inGameValues(Entity entity, Player localPlayer, boo sAeroInfo = addToTT("Elev", BR, entity.getElevation()).toString(); } sAeroInfo = "" + sAeroInfo + ""; - result += guiScaledFontHTML(uiLightViolet()) + sAeroInfo + ""; + result += guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor()) + sAeroInfo + ""; result += "
"; String msg_facing = Messages.getString("BoardView1.Tooltip.Facing"); @@ -1360,7 +1367,7 @@ private static StringBuilder inGameValues(Entity entity, Player localPlayer, boo } HeatDisplayHelper hdh = getHeatCapacityForDisplay(entity); sHeat += " / "+ hdh.heatCapacityStr; - result += guiScaledFontHTML(GUIP.getColorForHeat(heat)) + sHeat + ""; + result += guiScaledFontHTML(GUIP.getColorForHeat(heat, GUIP.getUnitToolTipFGColor())) + sHeat + ""; if (entity instanceof Mech && ((Mech) entity).hasActiveTSM()) { result += DOT_SPACER; @@ -1370,12 +1377,12 @@ private static StringBuilder inGameValues(Entity entity, Player localPlayer, boo } String illuminated = entity.isIlluminated() ? DOT_SPACER +"\uD83D\uDCA1" : ""; - result += guiScaledFontHTML(uiYellow()) + illuminated + ""; + result += guiScaledFontHTML(GUIP.getCautionColor()) + illuminated + ""; if (entity.hasSearchlight()) { String searchLight = entity.isUsingSearchlight() ? DOT_SPACER + "\uD83D\uDD26" : ""; searchLight += entity.usedSearchlight() ? " \u2580\u2580" : ""; - result += guiScaledFontHTML(uiYellow()) + searchLight + ""; + result += guiScaledFontHTML(GUIP.getCautionColor()) + searchLight + ""; } else { String searchLight = "\uD83D\uDD26"; result += guiScaledFontHTML(GUIP.getWarningColor()) + DOT_SPACER + searchLight + ""; @@ -1500,7 +1507,7 @@ private static StringBuilder inGameValues(Entity entity, Player localPlayer, boo if (entity.hasAnyTypeNarcPodsAttached()) { String sNarced = addToTT(entity.hasNarcPodsAttached() ? "Narced" : "INarced", BR).toString(); - result += guiScaledFontHTML(uiLightRed()) + sNarced + ""; + result += guiScaledFontHTML(GUIP.getPrecautionColor()) + sNarced + ""; } // Towing @@ -1514,7 +1521,7 @@ private static StringBuilder inGameValues(Entity entity, Player localPlayer, boo } // Coloring to make these transient entries stand out - result = guiScaledFontHTML(uiLightViolet()) + result + ""; + result = guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor()) + result + ""; return new StringBuilder().append(result); } @@ -1882,7 +1889,7 @@ private static StringBuilder c3Info(Entity entity, boolean details) { sC3Info += "
"; } - result = guiScaledFontHTML(uiC3Color(), -0.2f) + sC3Info + ""; + result = guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor(), -0.2f) + sC3Info + ""; } @@ -1904,13 +1911,13 @@ private static String c3UnitName(Entity c3member, Entity entity) { } sC3UnitName += "" + msg_c3 + ""; - result += guiScaledFontHTML(uiGray(), -0.2f) + sC3UnitName + ""; + result += guiScaledFontHTML(GUIP.getUnitToolTipFGColor(), -0.2f) + sC3UnitName + ""; result += c3member.getShortNameRaw(); String msg_thisunit = " (" + Messages.getString("BoardView1.Tooltip.ThisUnit") + ")"; tmp = "" + msg_thisunit + ""; String sC3Member = c3member.equals(entity) ? tmp : ""; - result += guiScaledFontHTML(uiGray(), -0.2f) + sC3Member + ""; + result += guiScaledFontHTML(GUIP.getUnitToolTipFGColor(), -0.2f) + sC3Member + ""; return result; } diff --git a/megamek/src/megamek/client/ui/swing/unitDisplay/ExtraPanel.java b/megamek/src/megamek/client/ui/swing/unitDisplay/ExtraPanel.java index 1e776a2efcd..3bb5c56588c 100644 --- a/megamek/src/megamek/client/ui/swing/unitDisplay/ExtraPanel.java +++ b/megamek/src/megamek/client/ui/swing/unitDisplay/ExtraPanel.java @@ -3,10 +3,8 @@ import megamek.MMConstants; import megamek.client.ui.Messages; import megamek.client.ui.baseComponents.MMComboBox; -import megamek.client.ui.swing.ClientGUI; -import megamek.client.ui.swing.GUIPreferences; -import megamek.client.ui.swing.HeatEffects; -import megamek.client.ui.swing.Slider; +import megamek.client.ui.swing.*; +import megamek.client.ui.swing.lobby.LobbyUtility; import megamek.client.ui.swing.util.UIUtil; import megamek.client.ui.swing.widget.*; import megamek.client.ui.swing.tooltip.UnitToolTip; @@ -24,7 +22,6 @@ import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; -import java.util.Enumeration; /** * This class shows information about a unit that doesn't belong elsewhere. @@ -48,6 +45,7 @@ class ExtraPanel extends PicMap implements ActionListener, ItemListener, IPrefer private JTextArea sinksR; private JButton sinks2B; private JButton dumpBombs; + private JButton unitReadout; private JList narcList; private int myMechId; @@ -155,6 +153,10 @@ public Component getListCellRendererComponent(JList list, Object value, int i } }); + unitReadout = new JButton(Messages.getString("MechDisplay.UnitReadout")); + unitReadout.setActionCommand("UnitReadout"); + unitReadout.addActionListener(this); + // layout choice panel GridBagLayout gridbag; GridBagConstraints c; @@ -163,77 +165,75 @@ public Component getListCellRendererComponent(JList list, Object value, int i c = new GridBagConstraints(); panelMain = new JPanel(gridbag); - c.fill = GridBagConstraints.BOTH; - c.insets = new Insets(15, 9, 1, 9); + c.fill = GridBagConstraints.HORIZONTAL; + c.insets = new Insets(5, 9, 1, 9); c.gridwidth = GridBagConstraints.REMAINDER; - c.anchor = GridBagConstraints.CENTER; - c.weighty = 1.0; - - gridbag.setConstraints(curSensorsL, c); - panelMain.add(curSensorsL); - - gridbag.setConstraints(chSensors, c); - panelMain.add(chSensors); - - gridbag.setConstraints(narcLabel, c); - panelMain.add(narcLabel); - + c.anchor = GridBagConstraints.NORTHWEST; + c.weighty = 0; + c.gridy = 0; + c.gridx = 0; + panelMain.add(curSensorsL, c); + c.gridy++; + panelMain.add(chSensors, c); + + c.gridy++; + panelMain.add(narcLabel, c); + c.gridy++; c.insets = new Insets(1, 9, 1, 9); scrollPane = new JScrollPane(narcList); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - gridbag.setConstraints(scrollPane, c); - panelMain.add(scrollPane); - - gridbag.setConstraints(unusedL, c); - panelMain.add(unusedL); - - gridbag.setConstraints(unusedR, c); - panelMain.add(unusedR); - - gridbag.setConstraints(carrysL, c); - panelMain.add(carrysL); - - gridbag.setConstraints(carrysR, c); - panelMain.add(carrysR); - - gridbag.setConstraints(dumpBombs, c); - panelMain.add(dumpBombs); - - gridbag.setConstraints(sinksL, c); - panelMain.add(sinksL); - - gridbag.setConstraints(sinksR, c); - panelMain.add(sinksR); - - gridbag.setConstraints(sinks2B, c); - panelMain.add(sinks2B); - - gridbag.setConstraints(heatL, c); - panelMain.add(heatL); - - c.insets = new Insets(1, 9, 18, 9); - gridbag.setConstraints(heatR, c); - panelMain.add(heatR); - + panelMain.add(scrollPane, c); + + c.gridy++; + panelMain.add(unusedL, c); + c.gridy++; + panelMain.add(unusedR, c); + + c.gridy++; + panelMain.add(carrysL, c); + c.gridy++; + panelMain.add(carrysR, c); + + c.gridy++; + panelMain.add(dumpBombs, c); + + c.gridy++; + panelMain.add(sinksL, c); + c.gridy++; + panelMain.add(sinksR, c); + c.gridy++; + panelMain.add(sinks2B, c); + + c.gridy++; + panelMain.add(heatL, c); + c.gridy++; + c.insets = new Insets(1, 9, 5, 9); + panelMain.add(heatR, c); + + c.gridy++; c.insets = new Insets(0, 0, 0, 0); - gridbag.setConstraints(lblLastTarget, c); - panelMain.add(lblLastTarget); - - c.insets = new Insets(1, 9, 18, 9); - gridbag.setConstraints(lastTargetR, c); - panelMain.add(lastTargetR); + panelMain.add(lblLastTarget, c); + c.gridy++; + c.insets = new Insets(1, 9, 5, 9); + panelMain.add(lastTargetR, c); - c.insets = new Insets(1, 9, 1, 9); - gridbag.setConstraints(activateHidden, c); + c.gridy++; c.insets = new Insets(1, 9, 6, 9); - gridbag.setConstraints(comboActivateHiddenPhase, c); - panelMain.add(activateHidden); - panelMain.add(comboActivateHiddenPhase); + panelMain.add(activateHidden, c); + c.gridy++; + panelMain.add(comboActivateHiddenPhase, c); + + c.gridy++; + panelMain.add(unitReadout, c); + + c.weightx = 1; + c.weighty = 1; + panelMain.add(new Label(" "), c); adaptToGUIScale(); GUIPreferences.getInstance().addPreferenceChangeListener(this); setLayout(new BorderLayout()); - add(panelMain); + add(panelMain, BorderLayout.NORTH); panelMain.setOpaque(false); setBackGround(); @@ -626,6 +626,9 @@ public void actionPerformed(ActionEvent ae) { } else if (activateHidden.equals(ae.getSource()) && !dontChange) { final GamePhase phase = comboActivateHiddenPhase.getSelectedItem(); clientgui.getClient().sendActivateHidden(myMechId, (phase == null) ? GamePhase.UNKNOWN : phase); + } else if (unitReadout.equals(ae.getSource())) { + Entity entity = clientgui.getClient().getGame().getEntity(myMechId); + LobbyUtility.mechReadout(entity, 0, false, clientgui.getFrame()); } } diff --git a/megamek/src/megamek/client/ui/swing/unitDisplay/SummaryPanel.java b/megamek/src/megamek/client/ui/swing/unitDisplay/SummaryPanel.java index 8e7ef8139cf..deba4dde339 100644 --- a/megamek/src/megamek/client/ui/swing/unitDisplay/SummaryPanel.java +++ b/megamek/src/megamek/client/ui/swing/unitDisplay/SummaryPanel.java @@ -124,28 +124,23 @@ private void setBackGround() { */ public void displayMech(Entity entity) { Player localPlayer = unitDisplay.getClientGUI().getClient().getLocalPlayer(); - String htmlStyle = "style=\"color:" + GUIP.hexColor(GUIP.getUnitToolTipFGColor()) + "; "; - htmlStyle += "background-color:" + GUIP.hexColor(GUIP.getUnitToolTipBGColor()) + ";\""; + String txt = ""; if (entity == null) { - unitInfo.setText("" + padLeft("No Unit") + ""); - return; - } - - if (EntityVisibilityUtils.onlyDetectedBySensors(localPlayer, entity)) { - unitInfo.setText("" + padLeft( Messages.getString("BoardView1.sensorReturn")) + ""); + txt = padLeft("No Unit"); + } else if (EntityVisibilityUtils.onlyDetectedBySensors(localPlayer, entity)) { + txt = padLeft(Messages.getString("BoardView1.sensorReturn")); } else { // This is html tables inside tables to maintain transparency to the bg image but // also allow cells do have bg colors StringBuffer hexTxt = new StringBuffer(""); hexTxt.append(PilotToolTip.getPilotTipDetailed(entity, true)); hexTxt.append(UnitToolTip.getEntityTipUnitDisplay(entity, localPlayer)); - String col = ""; String row = ""; - BoardView bv = unitDisplay.getClientGUI().getBoardView(); Hex mhex = entity.getGame().getBoard().getHex(entity.getPosition()); + if (bv != null && mhex != null) { StringBuffer sb = new StringBuffer(); bv.appendTerrainTooltip(sb, mhex, GUIP); @@ -159,9 +154,10 @@ public void displayMech(Entity entity) { col = "" + t + ""; row = "" + col + ""; hexTxt.append("" + row + "
"); - - unitInfo.setText("" + padLeft(hexTxt.toString()) + ""); + txt = padLeft(hexTxt.toString()); } + + unitInfo.setText(UnitToolTip.wrapWithHTML(txt)); unitInfo.setOpaque(false); } 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/unitDisplay/WeaponPanel.java b/megamek/src/megamek/client/ui/swing/unitDisplay/WeaponPanel.java index 82bf11938b2..8f58b3a5d6b 100644 --- a/megamek/src/megamek/client/ui/swing/unitDisplay/WeaponPanel.java +++ b/megamek/src/megamek/client/ui/swing/unitDisplay/WeaponPanel.java @@ -366,9 +366,6 @@ public void sort(Comparator comparator) { public static final int LINE_HEIGHT = 25; public static final Color COLOR_FG = Color.WHITE; public static final Color TEXT_BG = Color.DARK_GRAY; - public static final String BODY = ""; - public static final String LOW_CONTRAST_FONT = ""; - public static final String HTML_BODY = ""+BODY+"%s"; private static final GUIPreferences GUIP = GUIPreferences.getInstance(); @@ -871,44 +868,58 @@ public void setToHit(ToHitData toHit) { } public void setToHit(ToHitData toHit, boolean natAptGunnery) { + String txt = ""; + switch (toHit.getValue()) { case TargetRoll.IMPOSSIBLE: case TargetRoll.AUTOMATIC_FAIL: - toHitText.setText(String.format("%sTo Hit: (0%%) %s", BODY, toHit.getDesc())); + txt = String.format("To Hit: (0%%) %s", toHit.getDesc()); break; case TargetRoll.AUTOMATIC_SUCCESS: - toHitText.setText(String.format("%sTo Hit: (100%%) %s", BODY, toHit.getDesc())); + txt = String.format("To Hit: (100%%) %s", toHit.getDesc()); break; default: - toHitText.setText(String.format("%sTo Hit: %2d (%2.0f%%)%s = %s", BODY, - toHit.getValue(), Compute.oddsAbove(toHit.getValue(), natAptGunnery), LOW_CONTRAST_FONT, toHit.getDesc())); + txt = String.format("To Hit: %2d (%2.0f%%) = %s", + GUIP.hexColor(GUIP.getUnitToolTipHighlightColor()), + toHit.getValue(), + Compute.oddsAbove(toHit.getValue(), natAptGunnery), + toHit.getDesc()); break; } + + toHitText.setText(UnitToolTip.wrapWithHTML(txt)); toHitText.setCaretPosition(0); } public void setToHit(String message) { - toHitText.setText(String.format(HTML_BODY,message)); + toHitText.setText(UnitToolTip.wrapWithHTML(message)); } public void setTarget(@Nullable Targetable target, @Nullable String extraInfo) { this.target = target; updateTargetInfo(); + String txt = ""; + if (extraInfo == null || extraInfo.isEmpty()) { - wTargetExtraInfo.setText(""); wTargetExtraInfo.setOpaque(false); } else { - wTargetExtraInfo.setText(String.format(HTML_BODY, extraInfo)); + txt = extraInfo; wTargetExtraInfo.setOpaque(true); } + + wTargetExtraInfo.setText(UnitToolTip.wrapWithHTML(txt)); } private void updateTargetInfo() {; + String txt = ""; + if (target == null) { - wTargetInfo.setText(String.format(HTML_BODY, Messages.getString("MechDisplay.NoTarget"))); + txt = Messages.getString("MechDisplay.NoTarget"); } else { - wTargetInfo.setText(String.format(HTML_BODY, UnitToolTip.getTargetTipDetail(target, client))); + txt = UnitToolTip.getTargetTipDetail(target, client); } + + wTargetInfo.setText(UnitToolTip.wrapWithHTML(txt)); } @Override diff --git a/megamek/src/megamek/client/ui/swing/util/FluffImageHelper.java b/megamek/src/megamek/client/ui/swing/util/FluffImageHelper.java index 2020a58335c..3b0a35737d3 100644 --- a/megamek/src/megamek/client/ui/swing/util/FluffImageHelper.java +++ b/megamek/src/megamek/client/ui/swing/util/FluffImageHelper.java @@ -18,6 +18,7 @@ import megamek.common.alphaStrike.ASCardDisplayable; import megamek.common.alphaStrike.ASUnitType; import megamek.common.annotations.Nullable; +import megamek.common.preference.PreferenceManager; import megamek.common.util.fileUtils.MegaMekFile; import javax.swing.*; @@ -81,8 +82,17 @@ protected static Image loadFluffImage(final Entity unit) { * @return An image or {@code null}. */ public static @Nullable Image loadFluffImageHeuristic(final Entity entity) { + String userDir = PreferenceManager.getClientPreferences().getUserDir(); + if (!userDir.isBlank()) { + var userDirPath = new File(userDir + "/" + Configuration.fluffImagesDir(), getImagePath(entity)); + Image image = loadFluffImageHeuristic(userDirPath, entity.getModel(), entity.getChassis()); + if (image != null) { + return image; + } + } + var path = new MegaMekFile(Configuration.fluffImagesDir(), getImagePath(entity)); - return loadFluffImageHeuristic(path, entity.getModel(), entity.getChassis()); + return loadFluffImageHeuristic(path.getFile(), entity.getModel(), entity.getChassis()); } /** @@ -91,12 +101,21 @@ protected static Image loadFluffImage(final Entity unit) { * @return An image or null */ public static @Nullable Image loadFluffImageHeuristic(final ASCardDisplayable element) { + String userDir = PreferenceManager.getClientPreferences().getUserDir(); + if (!userDir.isBlank()) { + var userDirPath = new File(userDir + "/" + Configuration.fluffImagesDir(), getImagePath(element)); + Image image = loadFluffImageHeuristic(userDirPath, element.getModel(), element.getChassis()); + if (image != null) { + return image; + } + } + var path = new MegaMekFile(Configuration.fluffImagesDir(), getImagePath(element)); - return loadFluffImageHeuristic(path, element.getModel(), element.getChassis()); + return loadFluffImageHeuristic(path.getFile(), element.getModel(), element.getChassis()); } - private static @Nullable Image loadFluffImageHeuristic(MegaMekFile path, String model, String chassis) { - File fluff_image_file = findFluffImage(path.getFile(), model, chassis); + private static @Nullable Image loadFluffImageHeuristic(File path, String model, String chassis) { + File fluff_image_file = findFluffImage(path, model, chassis); if (fluff_image_file != null) { return new ImageIcon(fluff_image_file.toString()).getImage(); } else { diff --git a/megamek/src/megamek/client/ui/swing/util/FontHandler.java b/megamek/src/megamek/client/ui/swing/util/FontHandler.java index 97682d3d63e..e28cae1b81d 100644 --- a/megamek/src/megamek/client/ui/swing/util/FontHandler.java +++ b/megamek/src/megamek/client/ui/swing/util/FontHandler.java @@ -19,6 +19,8 @@ package megamek.client.ui.swing.util; import megamek.MMConstants; +import megamek.client.ui.swing.CommonSettingsDialog; +import megamek.common.preference.PreferenceManager; import org.apache.logging.log4j.LogManager; import java.awt.*; @@ -78,7 +80,16 @@ public static void initialize() { } private void initializeFonts() { + LogManager.getLogger().info("Loading fonts from " + MMConstants.FONT_DIRECTORY); parseFontsInDirectory(new File(MMConstants.FONT_DIRECTORY)); + + String userDir = PreferenceManager.getClientPreferences().getUserDir(); + if (!userDir.isBlank()) { + LogManager.getLogger().info("Loading fonts from " + userDir); + parseFontsInDirectory(userDir); + } + + LogManager.getLogger().info("Loading fonts from Java's GraphicsEnvironment"); for (String fontName : GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()) { allFontNames.add(fontName); Font font = Font.decode(fontName); @@ -95,25 +106,22 @@ private void initializeFonts() { * * @param directory the directory to parse */ - public static void parseFontsInDirectory(final File directory) { - final String[] filenames = directory.list(); - if (filenames == null) { - return; - } + public static void parseFontsInDirectory(String directory) { + parseFontsInDirectory(new File(directory)); + } - for (final String filename : filenames) { - if (filename.toLowerCase().endsWith(MMConstants.TRUETYPE_FONT)) { - try (InputStream fis = new FileInputStream(directory.getPath() + '/' + filename)) { - GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont( - Font.createFont(Font.TRUETYPE_FONT, fis)); - } catch (Exception ex) { - LogManager.getLogger().error("Failed to parse font", ex); - } - } else { - final File file = new File(directory, filename); - if (file.isDirectory()) { - parseFontsInDirectory(file); - } + /** + * Searches the provided directory and all subdirectories and registers any truetype + * fonts from .ttf files it finds. + * + * @param directory the directory to parse + */ + public static void parseFontsInDirectory(final File directory) { + for (String fontFile : CommonSettingsDialog.filteredFilesWithSubDirs(directory, MMConstants.TRUETYPE_FONT)) { + try (InputStream fis = new FileInputStream(fontFile)) { + GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(Font.createFont(Font.TRUETYPE_FONT, fis)); + } catch (Exception ex) { + LogManager.getLogger().error("Failed to parse font", ex); } } } diff --git a/megamek/src/megamek/client/ui/swing/widget/SkinXMLHandler.java b/megamek/src/megamek/client/ui/swing/widget/SkinXMLHandler.java index b3108e6d5dd..25c270a3498 100644 --- a/megamek/src/megamek/client/ui/swing/widget/SkinXMLHandler.java +++ b/megamek/src/megamek/client/ui/swing/widget/SkinXMLHandler.java @@ -141,7 +141,7 @@ public class SkinXMLHandler { * @return */ public static boolean validSkinSpecFile(String fileName) { - File file = new MegaMekFile(Configuration.skinsDir(), fileName).getFile(); + File file = new MegaMekFile(fileName).getFile(); if (!file.exists() || !file.isFile()) { return false; } @@ -181,10 +181,14 @@ public synchronized static boolean initSkinXMLHandler(final @Nullable String fil return false; } - File file = new MegaMekFile(Configuration.skinsDir(), filename).getFile(); + File file = new MegaMekFile(filename).getFile(); if (!file.exists() || !file.isFile()) { - LogManager.getLogger().error("Cannot initialize skin based on a non-existent file with filename " + filename); - return false; + // for backwards compatibility, also check the skins filename as if it is only a relative path + file = new MegaMekFile(Configuration.skinsDir(), filename).getFile(); + if (!file.exists() || !file.isFile()) { + LogManager.getLogger().error("Cannot initialize skin based on a non-existent file with filename " + filename); + return false; + } } // Build the XML document. 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..abf1333440a --- /dev/null +++ b/megamek/src/megamek/common/AeroSpaceFighter.java @@ -0,0 +1,179 @@ +/* + * MegaMek - Copyright (C) 2000-2003 Ben Mazur (bmazur@sev.org) + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ +package megamek.common; + +import megamek.client.ui.swing.calculationReport.CalculationReport; +import megamek.common.cost.AeroCostCalculator; +import megamek.common.enums.AimingMode; +import megamek.common.options.OptionsConstants; +import org.apache.logging.log4j.LogManager; + +import java.text.NumberFormat; +import java.util.*; + +/** + * 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; + } + + /** + * Fighters may carry external ordnance; + * Other Aerospace units with cargo bays and the Internal Bomb Bay quirk may carry bombs internally. + * @return boolean + */ + @Override + public boolean isBomber() { + return true; + } + + @Override + /** + * Returns true if this is an aerospace or conventional fighter + * but not a larger craft (i.e. "SmallCraft" or "Dropship" and bigger + */ + public boolean isFighter() { + return true; + } + + @Override + /** + * Returns true if and only if this is an aerospace fighter. + */ + public boolean isAerospaceFighter() { + return true; + } + + @Override + public long getEntityType() { + return super.getEntityType() | Entity.ETYPE_AEROSPACEFIGHTER; + } +} \ No newline at end of file diff --git a/megamek/src/megamek/common/AmmoType.java b/megamek/src/megamek/common/AmmoType.java index 04e18100301..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; @@ -1687,7 +1690,7 @@ public static void initializeTypes() { .setAvailability(RATING_X, RATING_X, RATING_F, RATING_E) .setClanAdvancement(DATE_NONE, 3061, 3085, DATE_NONE, DATE_NONE) .setClanApproximate(false, false, true, false, false).setPrototypeFactions(F_CGS) - .setProductionFactions(F_CSF, F_RD).setStaticTechLevel(SimpleTechLevel.ADVANCED), + .setProductionFactions(F_CSF, F_RD).setStaticTechLevel(SimpleTechLevel.STANDARD), "283, TO")); munitions.add(new MunitionMutator("(Clan) Dead-Fire", 1, Munitions.M_DEAD_FIRE, @@ -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(); @@ -7738,8 +7741,9 @@ private static AmmoType createISMML3LRMAmmo() { ammo.techAdvancement.setTechBase(TECH_BASE_IS).setIntroLevel(false).setUnofficial(false).setTechRating(RATING_D) .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) .setISAdvancement(DATE_NONE, 3067, 3073, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setProductionFactions(F_MERC,F_WB); + .setISApproximate(false, true, false, false, false) + .setProductionFactions(F_MERC,F_WB) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -7763,8 +7767,9 @@ private static AmmoType createISMML3SRMAmmo() { ammo.techAdvancement.setTechBase(TECH_BASE_IS).setIntroLevel(false).setUnofficial(false).setTechRating(RATING_D) .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) .setISAdvancement(DATE_NONE, 3067, 3073, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setProductionFactions(F_MERC,F_WB); + .setISApproximate(false, true, false, false, false) + .setProductionFactions(F_MERC,F_WB) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -7789,8 +7794,9 @@ private static AmmoType createISMML5LRMAmmo() { ammo.techAdvancement.setTechBase(TECH_BASE_IS).setIntroLevel(false).setUnofficial(false).setTechRating(RATING_D) .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) .setISAdvancement(DATE_NONE, 3067, 3073, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setProductionFactions(F_MERC,F_WB); + .setISApproximate(false, true, false, false, false) + .setProductionFactions(F_MERC,F_WB) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -7814,8 +7820,9 @@ private static AmmoType createISMML5SRMAmmo() { ammo.techAdvancement.setTechBase(TECH_BASE_IS).setIntroLevel(false).setUnofficial(false).setTechRating(RATING_D) .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) .setISAdvancement(DATE_NONE, 3067, 3073, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setProductionFactions(F_MERC,F_WB); + .setISApproximate(false, true, false, false, false) + .setProductionFactions(F_MERC,F_WB) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -7840,8 +7847,9 @@ private static AmmoType createISMML7LRMAmmo() { ammo.techAdvancement.setTechBase(TECH_BASE_IS).setIntroLevel(false).setUnofficial(false).setTechRating(RATING_D) .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) .setISAdvancement(DATE_NONE, 3067, 3073, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setProductionFactions(F_MERC,F_WB); + .setISApproximate(false, true, false, false, false) + .setProductionFactions(F_MERC,F_WB) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -7865,8 +7873,9 @@ private static AmmoType createISMML7SRMAmmo() { ammo.techAdvancement.setTechBase(TECH_BASE_IS).setIntroLevel(false).setUnofficial(false).setTechRating(RATING_D) .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) .setISAdvancement(DATE_NONE, 3067, 3073, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setProductionFactions(F_MERC,F_WB); + .setISApproximate(false, true, false, false, false) + .setProductionFactions(F_MERC,F_WB) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -7891,8 +7900,9 @@ private static AmmoType createISMML9LRMAmmo() { ammo.techAdvancement.setTechBase(TECH_BASE_IS).setIntroLevel(false).setUnofficial(false).setTechRating(RATING_D) .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) .setISAdvancement(DATE_NONE, 3067, 3073, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setProductionFactions(F_MERC,F_WB); + .setISApproximate(false, true, false, false, false) + .setProductionFactions(F_MERC,F_WB) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -7916,8 +7926,9 @@ private static AmmoType createISMML9SRMAmmo() { ammo.techAdvancement.setTechBase(TECH_BASE_IS).setIntroLevel(false).setUnofficial(false).setTechRating(RATING_D) .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) .setISAdvancement(DATE_NONE, 3067, 3073, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setProductionFactions(F_MERC,F_WB); + .setISApproximate(false, true, false, false, false) + .setProductionFactions(F_MERC,F_WB) + .setStaticTechLevel(SimpleTechLevel.STANDARD); return ammo; } @@ -12265,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"; @@ -12760,7 +12771,6 @@ private static AmmoType createISRailGunAmmo() { } - private static AmmoType createISAC10iAmmo() { AmmoType ammo = new AmmoType(); @@ -13029,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 @@ -13052,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) { @@ -13473,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; } @@ -13488,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; } @@ -13497,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/Board.java b/megamek/src/megamek/common/Board.java index ecd7e834b65..c5a7b194769 100644 --- a/megamek/src/megamek/common/Board.java +++ b/megamek/src/megamek/common/Board.java @@ -846,7 +846,7 @@ public static boolean isValid(String board) { * Can the given player deploy at these coordinates? */ public boolean isLegalDeployment(Coords c, Player p) { - return isLegalDeployment(c, p.getStartingPos(), p.getStartWidth(), p.getStartOffset()); + return isLegalDeployment(c, p.getStartingPos(), p.getStartWidth(), p.getStartOffset(), p.getStartingAnyNWx(), p.getStartingAnyNWy(), p.getStartingAnySEx(), p.getStartingAnySEy()); } /** @@ -857,13 +857,13 @@ public boolean isLegalDeployment(Coords c, Entity e) { return false; } - return isLegalDeployment(c, e.getStartingPos(), e.getStartingWidth(), e.getStartingOffset()); + return isLegalDeployment(c, e.getStartingPos(), e.getStartingWidth(), e.getStartingOffset(), e.getStartingAnyNWx(), e.getStartingAnyNWy(), e.getStartingAnySEx(), e.getStartingAnySEy()); } /** * Can an object be deployed at these coordinates, given a starting zone, width of starting zone and offset from edge of board? */ - public boolean isLegalDeployment(Coords c, int zoneType, int startingWidth, int startingOffset) { + public boolean isLegalDeployment(Coords c, int zoneType, int startingWidth, int startingOffset, int startingAnyNWx, int startingAnyNWy, int startingAnySEx, int startingAnySEy) { if ((c == null) || !contains(c)) { return false; } @@ -876,7 +876,10 @@ public boolean isLegalDeployment(Coords c, int zoneType, int startingWidth, int switch (zoneType) { case START_ANY: - return true; + return (((startingAnyNWx == Entity.STARTING_ANY_NONE) || (c.getX() >= startingAnyNWx)) + && ((startingAnySEx == Entity.STARTING_ANY_NONE) || (c.getX() <= startingAnySEx)) + && ((startingAnyNWy == Entity.STARTING_ANY_NONE) || (c.getY() >= startingAnyNWy)) + && ((startingAnySEy == Entity.STARTING_ANY_NONE) || (c.getY() <= startingAnySEy))); case START_NW: return ((c.getX() < (minx + nLimit)) && (c.getX() >= minx) && (c.getY() >= miny) && (c.getY() < (height / 2))) || ((c.getY() < (miny + nLimit)) && (c.getY() >= miny) && (c.getX() >= minx) && (c.getX() < (width / 2))); 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/CriticalSlot.java b/megamek/src/megamek/common/CriticalSlot.java index 0506b4c598a..f4890ed9ebd 100644 --- a/megamek/src/megamek/common/CriticalSlot.java +++ b/megamek/src/megamek/common/CriticalSlot.java @@ -14,6 +14,8 @@ package megamek.common; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class CriticalSlot implements Serializable { @@ -223,4 +225,22 @@ public void setRepairable(boolean repair) { public boolean isRepairable() { return repairable; } + + @Override + public String toString() { + String typeString = type == 0 ? "System Slot" : "Equipment Slot"; + List state = new ArrayList<>(); + if (type == 0) state.add("System No: " + index); + if (mount != null) state.add("[" + mount.equipmentIndex() + "] " + mount.getType().getInternalName()); + if (mount2 != null) state.add("Mount 2: [" + mount2.equipmentIndex() + "] " + mount2.getType().getInternalName()); + if (destroyed) state.add("Destroyed"); + if (hit) state.add("Hit"); + if (!hittable) state.add("Not hittable"); + if (breached) state.add("Breached"); + if (missing) state.add("Missing"); + if (armored) state.add("Armored"); + if (repairing) state.add("Repairing"); + if (!repairable) state.add("Not repairable"); + return typeString + " { " + String.join(", ", state) + " }"; + } } 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 3100fa83bdd..f6cd5be95c2 100644 --- a/megamek/src/megamek/common/Entity.java +++ b/megamek/src/megamek/common/Entity.java @@ -210,6 +210,12 @@ public abstract class Entity extends TurnOrdered implements Transporter, Targeta private int startingOffset = 0; private int startingWidth = 3; + public static final int STARTING_ANY_NONE = -1; + private int startingAnyNWx = STARTING_ANY_NONE; + private int startingAnyNWy = STARTING_ANY_NONE; + private int startingAnySEx = STARTING_ANY_NONE; + private int startingAnySEy = STARTING_ANY_NONE; + /** * The pilot of the entity. Even infantry has a 'pilot'. */ @@ -764,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. @@ -4177,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()){ // Does not do what's intended! + // Remove any used internal bombs + if (eq.getLinked().getUsableShotsLeft() <= 0) { + bombAttacksToRemove.add(eq); + } } } equipmentList.removeAll(bombAttacksToRemove); @@ -11073,6 +11085,11 @@ public Engine getEngine() { return engine; } + /** @return The type of engine if it has an engine, or Engine.NONE, if it has no engine. */ + public int getEngineType() { + return hasEngine() ? getEngine().getEngineType() : Engine.NONE; + } + public boolean hasEngine() { return (null != engine); } @@ -12893,7 +12910,12 @@ public int getStartingPos() { public int getStartingPos(boolean inheritFromOwner) { if (inheritFromOwner && startingPos == Board.START_NONE) { - return getOwner().getStartingPos(); + final GameOptions gOpts = getGame().getOptions(); + if (!getOwner().isBot() && gOpts.booleanOption(OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0)) { + return game.getPlayer(0).getStartingPos(); + } else { + return getOwner().getStartingPos(); + } } return startingPos; } @@ -12960,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; @@ -14443,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; } @@ -15281,7 +15336,12 @@ public int getStartingOffset(boolean inheritFromOwner) { // if we are given permission to use the owner's settings // and have specified entity-specific settings, use the owner's settings if (inheritFromOwner && (startingPos == Board.START_NONE)) { - return getOwner().getStartOffset(); + final GameOptions gOpts = getGame().getOptions(); + if (!getOwner().isBot() && gOpts.booleanOption(OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0)) { + return game.getPlayer(0).getStartOffset(); + } else { + return getOwner().getStartOffset(); + } } return startingOffset; @@ -15299,7 +15359,12 @@ public int getStartingWidth(boolean inheritFromOwner) { // if we are given permission to use the owner's settings // and have specified entity-specific settings, use the owner's settings if (inheritFromOwner && (startingPos == Board.START_NONE)) { - return getOwner().getStartWidth(); + final GameOptions gOpts = getGame().getOptions(); + if (!getOwner().isBot() && gOpts.booleanOption(OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0)) { + return game.getPlayer(0).getStartWidth(); + } else { + return getOwner().getStartWidth(); + } } return startingWidth; @@ -15309,6 +15374,98 @@ public void setStartingWidth(int startingWidth) { this.startingWidth = startingWidth; } + public int getStartingAnyNWx() { + return getStartingAnyNWx(true); + } + + public int getStartingAnyNWx(boolean inheritFromOwner) { + // if we are given permission to use the owner's settings + // and have specified entity-specific settings, use the owner's settings + if (inheritFromOwner && (startingPos == Board.START_NONE)) { + final GameOptions gOpts = getGame().getOptions(); + if (!getOwner().isBot() && gOpts.booleanOption(OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0)) { + return game.getPlayer(0).getStartingAnyNWx(); + } else { + return getOwner().getStartingAnyNWx(); + } + } + + return startingAnyNWx; + } + + public void setStartingAnyNWx(int i) { + this.startingAnyNWx = i; + } + + public int getStartingAnyNWy() { + return getStartingAnyNWy(true); + } + + public int getStartingAnyNWy(boolean inheritFromOwner) { + // if we are given permission to use the owner's settings + // and have specified entity-specific settings, use the owner's settings + if (inheritFromOwner && (startingPos == Board.START_NONE)) { + final GameOptions gOpts = getGame().getOptions(); + if (!getOwner().isBot() && gOpts.booleanOption(OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0)) { + return game.getPlayer(0).getStartingAnyNWy(); + } else { + return getOwner().getStartingAnyNWy(); + } + } + + return startingAnyNWy; + } + + public void setStartingAnyNWy(int i) { + this.startingAnyNWy = i; + } + + public int getStartingAnySEx() { + return getStartingAnySEx(true); + } + + public int getStartingAnySEx(boolean inheritFromOwner) { + // if we are given permission to use the owner's settings + // and have specified entity-specific settings, use the owner's settings + if (inheritFromOwner && (startingPos == Board.START_NONE)) { + final GameOptions gOpts = getGame().getOptions(); + if (!getOwner().isBot() && gOpts.booleanOption(OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0)) { + return game.getPlayer(0).getStartingAnySEx(); + } else { + return getOwner().getStartingAnySEx(); + } + } + + return startingAnySEx; + } + + public void setStartingAnySEx(int i) { + this.startingAnySEx = i; + } + + public int getStartingAnySEy() { + return getStartingAnySEy(true); + } + + public int getStartingAnySEy(boolean inheritFromOwner) { + // if we are given permission to use the owner's settings + // and have specified entity-specific settings, use the owner's settings + if (inheritFromOwner && (startingPos == Board.START_NONE)) { + final GameOptions gOpts = getGame().getOptions(); + if (!getOwner().isBot() && gOpts.booleanOption(OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0)) { + return game.getPlayer(0).getStartingAnySEy(); + } else { + return getOwner().getStartingAnySEy(); + } + } + + return startingAnySEy; + } + + public void setStartingAnySEy(int i) { + this.startingAnySEy = i; + } + public int getBloodStalkerTarget() { return bloodStalkerTarget; } diff --git a/megamek/src/megamek/common/EntityListFile.java b/megamek/src/megamek/common/EntityListFile.java index 84f5c11649e..6121971f525 100644 --- a/megamek/src/megamek/common/EntityListFile.java +++ b/megamek/src/megamek/common/EntityListFile.java @@ -719,6 +719,14 @@ private static void writeEntityList(Writer output, ArrayList list) throw output.write(String.valueOf(entity.getStartingWidth(false))); output.write("\" deploymentZoneOffset=\""); output.write(String.valueOf(entity.getStartingOffset(false))); + output.write("\" " + MULParser.DEPLOYMENT_ZONE_ANY_NWX + "=\""); + output.write(String.valueOf(entity.getStartingAnyNWx(false))); + output.write("\" " + MULParser.DEPLOYMENT_ZONE_ANY_NWY + "=\""); + output.write(String.valueOf(entity.getStartingAnyNWy(false))); + output.write("\" " + MULParser.DEPLOYMENT_ZONE_ANY_SEX + "=\""); + output.write(String.valueOf(entity.getStartingAnySEx(false))); + output.write("\" " + MULParser.DEPLOYMENT_ZONE_ANY_SEY + "=\""); + output.write(String.valueOf(entity.getStartingAnySEy(false))); output.write("\" neverDeployed=\""); output.write(String.valueOf(entity.wasNeverDeployed())); if (entity.isAero()) { @@ -850,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"); } } @@ -871,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 fa01afb6c80..b6ce3893715 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..7d50d1c42f5 100644 --- a/megamek/src/megamek/common/IBomber.java +++ b/megamek/src/megamek/common/IBomber.java @@ -20,48 +20,79 @@ 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"; + void setUsedInternalBombs(int b); + void increaseUsedInternalBombs(int b); + 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 largest single bomb that can be carried externally. + */ + default int getMaxExtBombSize() { + return getMaxExtBombPoints(); + } + /** * @return The number of each bomb type that was selected prior to deployment */ - int[] getBombChoices(); - + int[] getIntBombChoices(); + int[] getExtBombChoices(); + /** * Sets the bomb type selections prior to deployment. - * + * * @param bc An array with the count of each bomb type as the value of the bomb type's index */ - void setBombChoices(int[] bc); - + void setIntBombChoices(int[] bc); + void setExtBombChoices(int[] bc); + + /** + * @return summed combination of internal and external choices + */ + default int[] getBombChoices(){ + int[] intArr = getIntBombChoices(); + int[] extArr = getExtBombChoices(); + IntStream range = IntStream.range(0, Math.min(intArr.length, extArr.length)); + IntStream stream3 = range.map(i -> intArr[i] + extArr[i]); + return stream3.toArray(); + } + + default void setBombChoices(int[] ebc) { + setExtBombChoices(ebc); + } + /** * Sets the count of each bomb to zero */ @@ -71,7 +102,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 +127,55 @@ 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(); + } + } + + // int total = getBombs().stream().filter( + // b -> b.isInternalBomb() + // ).mapToInt(b -> b.getExplosionDamage()).sum(); + 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 +183,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 +210,73 @@ 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(); } + 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) { + + } + + } + + 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(); + + int getMaxExtBombPoints(); + + 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 a18781f04b0..ac47142ca10 100644 --- a/megamek/src/megamek/common/MULParser.java +++ b/megamek/src/megamek/common/MULParser.java @@ -144,6 +144,10 @@ public class MULParser { private static final String DEPLOYMENT_ZONE = "deploymentZone"; private static final String DEPLOYMENT_ZONE_WIDTH = "deploymentZoneWidth"; private static final String DEPLOYMENT_ZONE_OFFSET = "deploymentZoneOffset"; + public static final String DEPLOYMENT_ZONE_ANY_NWX = "deploymentZoneAnyNWx"; + public static final String DEPLOYMENT_ZONE_ANY_NWY = "deploymentZoneAnyNWy"; + public static final String DEPLOYMENT_ZONE_ANY_SEX = "deploymentZoneAnySEx"; + public static final String DEPLOYMENT_ZONE_ANY_SEY = "deploymentZoneAnySEy"; private static final String NEVER_DEPLOYED = "neverDeployed"; private static final String VELOCITY = "velocity"; public static final String ALTITUDE = "altitude"; @@ -710,6 +714,35 @@ private void parseEntityAttributes(Entity entity, Element entityTag) { entity.setStartingOffset(0); } + // deployment zone Any + try { + int deployZoneAnyNWx = Integer.parseInt(entityTag.getAttribute(DEPLOYMENT_ZONE_ANY_NWX)); + entity.setStartingAnyNWx(deployZoneAnyNWx); + } catch (Exception e) { + entity.setStartingAnyNWx(Entity.STARTING_ANY_NONE); + } + + try { + int deployZoneAnyNWy = Integer.parseInt(entityTag.getAttribute(DEPLOYMENT_ZONE_ANY_NWY)); + entity.setStartingAnyNWy(deployZoneAnyNWy); + } catch (Exception e) { + entity.setStartingAnyNWy(Entity.STARTING_ANY_NONE); + } + + try { + int deployZoneAnySEx = Integer.parseInt(entityTag.getAttribute(DEPLOYMENT_ZONE_ANY_SEX)); + entity.setStartingAnySEx(deployZoneAnySEx); + } catch (Exception e) { + entity.setStartingAnySEx(Entity.STARTING_ANY_NONE); + } + + try { + int deployZoneAnySEy = Integer.parseInt(entityTag.getAttribute(DEPLOYMENT_ZONE_ANY_SEY)); + entity.setStartingAnySEy(deployZoneAnySEy); + } catch (Exception e) { + entity.setStartingAnySEy(Entity.STARTING_ANY_NONE); + } + // Was never deployed try { String ndeploy = entityTag.getAttribute(NEVER_DEPLOYED); @@ -2217,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/Mech.java b/megamek/src/megamek/common/Mech.java index 0ca84789e1e..f452f307af2 100644 --- a/megamek/src/megamek/common/Mech.java +++ b/megamek/src/megamek/common/Mech.java @@ -4176,8 +4176,8 @@ public String getMtf() { StringBuilder sb = new StringBuilder(); String newLine = "\n"; - sb.append(MtfFile.COMMENT).append("Saved from version ").append(SuiteConstants.VERSION); - sb.append(" on ").append(LocalDate.now()).append(newLine); + sb.append(MtfFile.GENERATOR).append(SuiteConstants.PROJECT_NAME) + .append(" ").append(SuiteConstants.VERSION).append(" on ").append(LocalDate.now()).append(newLine); boolean standard = (getCockpitType() == Mech.COCKPIT_STANDARD) && (getGyroType() == Mech.GYRO_STANDARD); 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/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/MechSummaryCache.java b/megamek/src/megamek/common/MechSummaryCache.java index 6a9ef608985..93526fb770e 100644 --- a/megamek/src/megamek/common/MechSummaryCache.java +++ b/megamek/src/megamek/common/MechSummaryCache.java @@ -18,6 +18,7 @@ import megamek.common.alphaStrike.ASUnitType; import megamek.common.alphaStrike.AlphaStrikeElement; import megamek.common.alphaStrike.conversion.ASConverter; +import megamek.common.preference.PreferenceManager; import megamek.common.util.fileUtils.MegaMekFile; import megamek.common.verifier.*; import org.apache.logging.log4j.LogManager; @@ -253,11 +254,19 @@ private void checkForChanges(boolean ignoreUnofficial, Vector vMech boolean bNeedsUpdate = loadMechsFromDirectory(vMechs, sKnownFiles, lLastCheck, Configuration.unitsDir(), ignoreUnofficial); + // load units from the MM internal user data dir File userDataUnits = new File(Configuration.userdataDir(), Configuration.unitsDir().toString()); if (userDataUnits.isDirectory()) { bNeedsUpdate |= loadMechsFromDirectory(vMechs, sKnownFiles, lLastCheck, userDataUnits, ignoreUnofficial); } + // load units from the external user data dir + String userDir = PreferenceManager.getClientPreferences().getUserDir(); + File userDataUnits2 = new File(userDir, ""); + if (!userDir.isBlank() && userDataUnits2.isDirectory()) { + bNeedsUpdate |= loadMechsFromDirectory(vMechs, sKnownFiles, lLastCheck, userDataUnits2, ignoreUnofficial); + } + // save updated cache back to disk if (bNeedsUpdate) { saveCache(vMechs); @@ -759,6 +768,11 @@ private boolean loadMechsFromDirectory(Vector vMechs, lLastCheck, f, ignoreUnofficial); continue; } + if (!f.getName().toLowerCase().endsWith(".mtf") && !f.getName().toLowerCase().endsWith(".blk") + && !f.getName().toLowerCase().endsWith(".hmp") && !f.getName().toLowerCase().endsWith(".hmv") + && !f.getName().toLowerCase().endsWith(".mep") && !f.getName().toLowerCase().endsWith(".tdb")) { + continue; + } if (f.getName().indexOf('.') == -1) { continue; } 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 0e353aa7bbc..c91a4a19b75 100644 --- a/megamek/src/megamek/common/Mounted.java +++ b/megamek/src/megamek/common/Mounted.java @@ -28,6 +28,7 @@ import java.io.Serializable; import java.util.*; +import java.util.stream.Collectors; /** * This describes equipment mounted on a mech. @@ -109,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; @@ -549,6 +551,10 @@ public String getDesc() { if (isArmored()) { desc.append(" (armored)"); } + + if (isInternalBomb()) { + desc.append(" (Int. Bay)"); + } return desc.toString(); } @@ -1461,11 +1467,6 @@ public int getVibraSetting() { return vibraSetting; } - @Override - public String toString() { - return "megamek.common.Mounted (" + typeName + ")"; - } - public int getBaseDamageAbsorptionRate() { return baseDamageAbsorptionRate; } @@ -1635,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) { @@ -2128,4 +2142,61 @@ public boolean isHomingAmmoInHomingMode() { return ammoType.getMunitionType().contains(AmmoType.Munitions.M_HOMING) && curMode().equals("Homing"); } + + public int equipmentIndex() { + if (entity != null) { + return entity.getEquipmentNum(this); + } else { + return -1; + } + } + + @Override + public String toString() { + List locations = allLocations().stream().map(entity::getLocationAbbr).collect(Collectors.toList()); + String intro = getType().getInternalName() + + " (" + String.join("/", locations) + + (rearMounted ? "-R" : "") + + (mechTurretMounted ? "-MTu" : "") + + (sponsonTurretMounted ? "-STu" : "") + + (pintleTurretMounted ? "-PTu" : "") + + (isDWPMounted ? "-DWP" : "") + + (isAPMMounted ? "-APM" : "") + + (squadSupportWeapon ? "-SSW" : "") + + (baMountLoc != -1 ? "-" + BattleArmor.MOUNT_LOC_NAMES[baMountLoc] : "") + + (omniPodMounted ? "-Pod" : "") + + + ")"; + + List state = new ArrayList<>(); + if (linked != null) state.add("Linked: [" + entity.getEquipment().indexOf(linked) + "]"); + if (linkedBy != null) state.add("LinkedBy: [" + entity.getEquipment().indexOf(linkedBy) + "]"); + if (crossLinkedBy != null) state.add("CrossLinkedBy: [" + entity.getEquipment().indexOf(crossLinkedBy) + "]"); + if (linkedBayId != -1) state.add("LinkedBay: [" + linkedBayId + "]"); + if (!bayWeapons.isEmpty()) { + List bayWeaponIds = bayWeapons.stream().map(id -> "[" + id + "]").collect(Collectors.toList()); + state.add("Bay Weapons: " + String.join(", ", bayWeaponIds)); + } + if (!bayAmmo.isEmpty()) { + List bayAmmoIds = bayAmmo.stream().map(id -> "[" + id + "]").collect(Collectors.toList()); + state.add("Bay Ammo: " + String.join(", ", bayAmmoIds)); + } + if (type instanceof AmmoType) { + state.add("Shots: " + shotsLeft); + } + if (destroyed) state.add("Destroyed"); + if (hit) state.add("Hit"); + if (missing) state.add("Missing"); + if (fired) state.add("Fired"); + if (rapidfire) state.add("Rapidfire"); + if (jammed) state.add("Jammed"); + if (useless) state.add("Useless"); + if (armoredComponent) state.add("Armored"); + if (facing != -1) state.add("Facing: " + facing); + if (!quirks.activeQuirks().isEmpty()) state.add("Quirks: " + quirks.getOptionList("/")); + if (weaponGroup) state.add("Group"); + if (nweapons != 1) state.add("#Weapons: " + nweapons); + if (size != 1) state.add("Size: " + size); + return intro + " { " + String.join(", ", state) + " }"; + } } \ No newline at end of file diff --git a/megamek/src/megamek/common/Player.java b/megamek/src/megamek/common/Player.java index f88228a2c98..7a6a30f646e 100644 --- a/megamek/src/megamek/common/Player.java +++ b/megamek/src/megamek/common/Player.java @@ -56,6 +56,10 @@ public final class Player extends TurnOrdered { private int startingPos = Board.START_ANY; private int startOffset = 0; private int startWidth = 3; + private int startingAnyNWx = Entity.STARTING_ANY_NONE; + private int startingAnyNWy = Entity.STARTING_ANY_NONE; + private int startingAnySEx = Entity.STARTING_ANY_NONE; + private int startingAnySEy = Entity.STARTING_ANY_NONE; // number of minefields private int numMfConv = 0; @@ -384,6 +388,38 @@ public void setStartWidth(int startWidth) { this.startWidth = startWidth; } + public int getStartingAnyNWx() { + return startingAnyNWx; + } + + public void setStartingAnyNWx(int i) { + this.startingAnyNWx = i; + } + + public int getStartingAnyNWy() { + return startingAnyNWy; + } + + public void setStartingAnyNWy(int i) { + this.startingAnyNWy = i; + } + + public int getStartingAnySEx() { + return startingAnySEx; + } + + public void setStartingAnySEx(int i) { + this.startingAnySEx = i; + } + + public int getStartingAnySEy() { + return startingAnySEy; + } + + public void setStartingAnySEy(int i) { + this.startingAnySEy = i; + } + /** * Set deployment zone to edge of board for reinforcements */ @@ -748,6 +784,11 @@ public Player copy() { copy.startOffset = startOffset; copy.startWidth = startWidth; + copy.startingAnyNWx = startingAnyNWx; + copy.startingAnyNWy = startingAnyNWy; + copy.startingAnySEx = startingAnySEx; + copy.startingAnySEy = startingAnySEy; + copy.numMfConv = numMfConv; copy.numMfCmd = numMfCmd; copy.numMfVibra = numMfVibra; 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/BAVibroClawAttackAction.java b/megamek/src/megamek/common/actions/BAVibroClawAttackAction.java index c5bea04be1d..b2ce08daeb6 100644 --- a/megamek/src/megamek/common/actions/BAVibroClawAttackAction.java +++ b/megamek/src/megamek/common/actions/BAVibroClawAttackAction.java @@ -15,6 +15,7 @@ import megamek.common.*; import megamek.common.options.OptionsConstants; +import org.apache.logging.log4j.LogManager; /** * A BattleArmor uses its vibroclaws @@ -48,8 +49,13 @@ public static ToHitData toHit(Game game, int attackerId, Targetable target) { int targetId = Entity.NONE; Entity te = null; // arguments legal? - if ((ae == null) || (target == null)) { - throw new IllegalArgumentException("Attacker or target not valid"); + if (ae == null) { + LogManager.getLogger().error("Attacker not valid"); + return new ToHitData(TargetRoll.IMPOSSIBLE, "Attacker not valid"); + } + if (target == null) { + LogManager.getLogger().error("target not valid"); + return new ToHitData(TargetRoll.IMPOSSIBLE, "target not valid"); } if (target.getTargetType() == Targetable.TYPE_ENTITY) { diff --git a/megamek/src/megamek/common/actions/BrushOffAttackAction.java b/megamek/src/megamek/common/actions/BrushOffAttackAction.java index 5adc1ddfb9e..a8a0883d975 100644 --- a/megamek/src/megamek/common/actions/BrushOffAttackAction.java +++ b/megamek/src/megamek/common/actions/BrushOffAttackAction.java @@ -26,6 +26,7 @@ import megamek.common.Targetable; import megamek.common.ToHitData; import megamek.common.options.OptionsConstants; +import org.apache.logging.log4j.LogManager; /** * The attacker brushes the target off. @@ -97,9 +98,13 @@ public static ToHitData toHit(Game game, int attackerId, final Entity ae = game.getEntity(attackerId); int targetId = Entity.NONE; Entity te = null; - if ((ae == null) || (target == null)) { - return new ToHitData(TargetRoll.IMPOSSIBLE, - "Attacker or target not valid"); + if (ae == null) { + LogManager.getLogger().error("Attacker not valid"); + return new ToHitData(TargetRoll.IMPOSSIBLE, "Attacker not valid"); + } + if (target == null) { + LogManager.getLogger().error("target not valid"); + return new ToHitData(TargetRoll.IMPOSSIBLE, "target not valid"); } if (target.getTargetType() == Targetable.TYPE_ENTITY) { te = (Entity) target; diff --git a/megamek/src/megamek/common/actions/ClubAttackAction.java b/megamek/src/megamek/common/actions/ClubAttackAction.java index b076eabdb50..a913af0b80d 100644 --- a/megamek/src/megamek/common/actions/ClubAttackAction.java +++ b/megamek/src/megamek/common/actions/ClubAttackAction.java @@ -28,6 +28,7 @@ import megamek.common.Targetable; import megamek.common.ToHitData; import megamek.common.options.OptionsConstants; +import org.apache.logging.log4j.LogManager; /** * The attacker makes a club attack on the target. This also covers mech melee @@ -256,8 +257,13 @@ public static ToHitData toHit(Game game, int attackerId, final Entity ae = game.getEntity(attackerId); MiscType clubType; // arguments legal? - if ((ae == null) || (target == null)) { - throw new IllegalArgumentException("Attacker or target not valid"); + if (ae == null) { + LogManager.getLogger().error("Attacker not valid"); + return new ToHitData(TargetRoll.IMPOSSIBLE, "Attacker not valid"); + } + if (target == null) { + LogManager.getLogger().error("target not valid"); + return new ToHitData(TargetRoll.IMPOSSIBLE, "target not valid"); } if (club == null) { throw new IllegalArgumentException("Club is null"); diff --git a/megamek/src/megamek/common/actions/ProtomechPhysicalAttackAction.java b/megamek/src/megamek/common/actions/ProtomechPhysicalAttackAction.java index 6cef9cd6b5d..294a227a942 100644 --- a/megamek/src/megamek/common/actions/ProtomechPhysicalAttackAction.java +++ b/megamek/src/megamek/common/actions/ProtomechPhysicalAttackAction.java @@ -17,6 +17,7 @@ import megamek.client.ui.Messages; import megamek.common.*; import megamek.common.options.OptionsConstants; +import org.apache.logging.log4j.LogManager; /** * The attacking ProtoMech makes its combo physical attack action. @@ -80,8 +81,13 @@ public static ToHitData toHit(Game game, int attackerId, Targetable target) { int targetId = Entity.NONE; Entity te = null; // arguments legal? - if ((ae == null) || (target == null)) { - throw new IllegalArgumentException("Attacker or target not valid"); + if (ae == null) { + LogManager.getLogger().error("Attacker not valid"); + return new ToHitData(TargetRoll.IMPOSSIBLE, "Attacker not valid"); + } + if (target == null) { + LogManager.getLogger().error("target not valid"); + return new ToHitData(TargetRoll.IMPOSSIBLE, "target not valid"); } if (target.getTargetType() == Targetable.TYPE_ENTITY) { diff --git a/megamek/src/megamek/common/actions/PunchAttackAction.java b/megamek/src/megamek/common/actions/PunchAttackAction.java index 2afe09af3a5..90b2f0c31ff 100644 --- a/megamek/src/megamek/common/actions/PunchAttackAction.java +++ b/megamek/src/megamek/common/actions/PunchAttackAction.java @@ -27,6 +27,7 @@ import megamek.common.Targetable; import megamek.common.ToHitData; import megamek.common.options.OptionsConstants; +import org.apache.logging.log4j.LogManager; /** * The attacker punches the target. @@ -184,9 +185,13 @@ protected static String toHitIsImpossible(Game game, Entity ae, public static ToHitData toHit(Game game, int attackerId, Targetable target, int arm, boolean zweihandering) { final Entity ae = game.getEntity(attackerId); - - if ((ae == null) || (target == null)) { - throw new IllegalArgumentException("Attacker or target not valid"); + if (ae == null) { + LogManager.getLogger().error("Attacker not valid"); + return new ToHitData(TargetRoll.IMPOSSIBLE, "Attacker not valid"); + } + if (target == null) { + LogManager.getLogger().error("target not valid"); + return new ToHitData(TargetRoll.IMPOSSIBLE, "target not valid"); } String impossible = PunchAttackAction.toHitIsImpossible(game, ae, target, arm); if (impossible != null) { diff --git a/megamek/src/megamek/common/actions/ThrashAttackAction.java b/megamek/src/megamek/common/actions/ThrashAttackAction.java index b4a6dfb721e..cc224dfded8 100644 --- a/megamek/src/megamek/common/actions/ThrashAttackAction.java +++ b/megamek/src/megamek/common/actions/ThrashAttackAction.java @@ -15,6 +15,7 @@ import megamek.common.*; import megamek.common.options.OptionsConstants; +import org.apache.logging.log4j.LogManager; /** * The prone attacker thrashes at the target. @@ -47,8 +48,13 @@ public ToHitData toHit(Game game) { final Entity ae = getEntity(game); final Targetable target = getTarget(game); // arguments legal? - if (ae == null || target == null) { - throw new IllegalArgumentException("Attacker or target not valid"); + if (ae == null) { + LogManager.getLogger().error("Attacker not valid"); + return new ToHitData(TargetRoll.IMPOSSIBLE, "Attacker not valid"); + } + if (target == null) { + LogManager.getLogger().error("target not valid"); + return new ToHitData(TargetRoll.IMPOSSIBLE, "target not valid"); } Entity te = null; 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/battlevalue/BVCalculator.java b/megamek/src/megamek/common/battlevalue/BVCalculator.java index 9c9f2332c3d..62ec6b1ddca 100644 --- a/megamek/src/megamek/common/battlevalue/BVCalculator.java +++ b/megamek/src/megamek/common/battlevalue/BVCalculator.java @@ -711,7 +711,7 @@ protected double arcFactor(Mounted equipment) { } /** - * Forwards to {@link #processWeapon(Mounted, boolean, boolean, int)} with a weaponCount + * Forwards to {@link #maintainUsedSearchlight(Mounted, boolean, boolean, int)} with a weaponCount * parameter of 1 (single weapon). */ protected double processWeapon(Mounted weapon, boolean showInReport, diff --git a/megamek/src/megamek/common/enums/IlluminationLevel.java b/megamek/src/megamek/common/enums/IlluminationLevel.java index ad07a5e6d0b..02e71cf8369 100644 --- a/megamek/src/megamek/common/enums/IlluminationLevel.java +++ b/megamek/src/megamek/common/enums/IlluminationLevel.java @@ -18,6 +18,7 @@ */ package megamek.common.enums; +import megamek.client.ui.swing.GUIPreferences; import megamek.common.Coords; import megamek.common.Game; import megamek.common.Hex; @@ -91,14 +92,14 @@ public static IlluminationLevel determineIlluminationLevel(final Game game, fina return neighbouringFire ? IlluminationLevel.FIRE : IlluminationLevel.NONE; } - public static String getIlluminationLevelIndicator(final Game game, final Coords coords) { + public static String getIlluminationLevelIndicator(final Game game, final Coords coords, final GUIPreferences GUIP) { switch (IlluminationLevel.determineIlluminationLevel(game, coords)) { case FIRE: - return DOT_SPACER + guiScaledFontHTML(uiYellow()) + " \uD83D\uDD25" + ""; + return DOT_SPACER + guiScaledFontHTML(GUIP.getCautionColor()) + " \uD83D\uDD25" + ""; case FLARE: - return DOT_SPACER + guiScaledFontHTML(uiYellow()) + " \uD83C\uDF86" + ""; + return DOT_SPACER + guiScaledFontHTML(GUIP.getCautionColor()) + " \uD83C\uDF86" + ""; case SEARCHLIGHT: - return DOT_SPACER + guiScaledFontHTML(uiYellow()) + " \uD83D\uDD26" + ""; + return DOT_SPACER + guiScaledFontHTML(GUIP.getCautionColor()) + " \uD83D\uDD26" + ""; default: return ""; } 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 26ad00ea777..94eb8719c8a 100644 --- a/megamek/src/megamek/common/loaders/BLKFile.java +++ b/megamek/src/megamek/common/loaders/BLKFile.java @@ -310,6 +310,11 @@ protected void loadEquipment(Entity t, String sName, int nLoc) * ((InfantryWeapon) mount.getType()).getShots()); mount.getLinked().setShotsLeft(mount.getLinked().getOriginalShots()); } + if (etype.hasFlag(MiscType.F_CARGO)) { + // Treat F_CARGO equipment as cargo bays with 1 door, e.g. for ASF with IBB. + int idx = t.getTransportBays().size(); + t.addTransporter(new CargoBay(mount.getSize(), 1, idx), isOmniMounted); + } } catch (LocationFullException ex) { throw new EntityLoadingException(ex.getMessage()); } @@ -587,6 +592,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 f70f2afdf64..ea8a07b83f5 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(); @@ -226,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); @@ -243,7 +243,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/loaders/MtfFile.java b/megamek/src/megamek/common/loaders/MtfFile.java index 0f69c78b37a..21305995d1f 100644 --- a/megamek/src/megamek/common/loaders/MtfFile.java +++ b/megamek/src/megamek/common/loaders/MtfFile.java @@ -92,7 +92,8 @@ public class MtfFile implements IMechLoader { {Mech.LOC_LT, Mech.LOC_RT, Mech.LOC_CT}; public static final String COMMENT = "#"; - public static final String MTF_VERSION = "Version:"; + public static final String MTF_VERSION = "version:"; + public static final String GENERATOR = "generator:"; public static final String CHASSIS = "chassis:"; public static final String MODEL = "model:"; public static final String COCKPIT = "cockpit:"; @@ -507,11 +508,11 @@ private void readLines(BufferedReader r) throws IOException { while (r.ready()) { String line = r.readLine().trim(); - if (line.isBlank() || line.startsWith(COMMENT)) { + if (line.isBlank() || line.startsWith(COMMENT) || line.startsWith(GENERATOR)) { continue; } - if (line.startsWith(MTF_VERSION)) { + if (line.toLowerCase().startsWith(MTF_VERSION)) { // Reading the version, chassis and model as the first three lines without header is kept // for backward compatibility for user-generated units. However the version is no longer checked // for correct values as that makes no difference so long as the unit can be loaded @@ -519,7 +520,14 @@ private void readLines(BufferedReader r) throws IOException { // Version 1.1: Added level 3 cockpit and gyro options. // version 1.2: added full head ejection // Version 1.3: Added MUL ID - chassis = readLineIgnoringComments(r); + + String generatorOrChassis = readLineIgnoringComments(r); + if (generatorOrChassis.toLowerCase().startsWith(GENERATOR)) { + // Compatibility with SSW 0.7.6.1 - Generator: comes between Version and chassis + chassis = readLineIgnoringComments(r); + } else { + chassis = generatorOrChassis; + } model = readLineIgnoringComments(r); continue; } diff --git a/megamek/src/megamek/common/options/GameOptions.java b/megamek/src/megamek/common/options/GameOptions.java index 7c9d5e9d97b..63936a5a375 100755 --- a/megamek/src/megamek/common/options/GameOptions.java +++ b/megamek/src/megamek/common/options/GameOptions.java @@ -61,7 +61,9 @@ public synchronized void initialize() { addOption(base, OptionsConstants.BASE_REAL_BLIND_DROP, false); addOption(base, OptionsConstants.BASE_LOBBY_AMMO_DUMP, false); addOption(base, OptionsConstants.BASE_DUMPING_FROM_ROUND, 1); - addOption(base, OptionsConstants.BASE_SET_ARTY_PLAYER_HOMEEDGE, false); + addOption(base, OptionsConstants.BASE_SET_ARTY_PLAYER_HOMEEDGE, false); + addOption(base, OptionsConstants.BASE_SET_DEFAULT_TEAM_1, false); + addOption(base, OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0, false); addOption(base, OptionsConstants.BASE_RESTRICT_GAME_COMMANDS, false); addOption(base, OptionsConstants.BASE_DISABLE_LOCAL_SAVE, false); addOption(base, OptionsConstants.BASE_BRIDGECF, 0); diff --git a/megamek/src/megamek/common/options/OptionsConstants.java b/megamek/src/megamek/common/options/OptionsConstants.java index 02d11bd3fce..127433e1a93 100644 --- a/megamek/src/megamek/common/options/OptionsConstants.java +++ b/megamek/src/megamek/common/options/OptionsConstants.java @@ -281,6 +281,8 @@ public class OptionsConstants { public static final String BASE_LOBBY_AMMO_DUMP = "lobby_ammo_dump"; public static final String BASE_DUMPING_FROM_ROUND = "dumping_from_round"; public static final String BASE_SET_ARTY_PLAYER_HOMEEDGE = "set_arty_player_homeedge"; + public static final String BASE_SET_DEFAULT_TEAM_1 = "set_default_team_1"; + public static final String BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER0 = "set_player_deployment_to_player0"; public static final String BASE_RESTRICT_GAME_COMMANDS = "restrict_game_commands"; public static final String BASE_DISABLE_LOCAL_SAVE = "disable_local_save"; public static final String BASE_BRIDGECF = "bridgeCF"; diff --git a/megamek/src/megamek/common/options/Quirks.java b/megamek/src/megamek/common/options/Quirks.java index d7e83c24445..d6f7690f030 100644 --- a/megamek/src/megamek/common/options/Quirks.java +++ b/megamek/src/megamek/common/options/Quirks.java @@ -230,7 +230,7 @@ public static boolean isQuirkLegalFor(IOption quirk, Entity en) { QUIRK_NEG_EM_INTERFERENCE_WHOLE, QUIRK_NEG_POOR_PERFORMANCE, QUIRK_NEG_HARD_PILOT, QUIRK_NEG_POOR_TARG_S, QUIRK_NEG_POOR_TARG_M, QUIRK_NEG_POOR_TARG_L, QUIRK_NEG_POOR_WORK, QUIRK_NEG_PROTOTYPE, - QUIRK_NEG_SENSOR_GHOSTS); + QUIRK_NEG_SENSOR_GHOSTS, QUIRK_POS_UBIQUITOUS_IS, QUIRK_POS_UBIQUITOUS_CLAN); } } diff --git a/megamek/src/megamek/common/preference/ClientPreferences.java b/megamek/src/megamek/common/preference/ClientPreferences.java index 41acd2eab4b..c3e37d46c43 100644 --- a/megamek/src/megamek/common/preference/ClientPreferences.java +++ b/megamek/src/megamek/common/preference/ClientPreferences.java @@ -60,6 +60,10 @@ public class ClientPreferences extends PreferenceStoreProxy { private static final String REPORTKEYWORDSDEFAULTS = "Needs\nRolls\nTakes\nHit\nFalls\nSkill Roll\nPilot Skill\nPhase\nDestroyed\nDamage"; public static final String IP_ADDRESSES_IN_CHAT = "IPAddressesInChat"; public static final String START_SEARCHLIGHTS_ON = "StartSearchlightsOn"; + + /** A user-specified directory, typically outside the MM directory, where content may be loaded from. */ + public static final String USER_DIR = "UserDir"; + //endregion Variable Declarations //region Constructors @@ -91,6 +95,7 @@ public ClientPreferences(IPreferenceStore store) { store.setDefault(REPORT_KEYWORDS, REPORTKEYWORDSDEFAULTS); store.setDefault(IP_ADDRESSES_IN_CHAT, false); store.setDefault(START_SEARCHLIGHTS_ON, true); + store.setDefault(USER_DIR, ""); setLocale(store.getString(LOCALE)); setMekHitLocLog(); } @@ -370,4 +375,17 @@ public int getMapWidth() { public int getMapHeight() { return store.getInt(MAP_HEIGHT); } + + /** @return The absolute user directory path (usually outside of MM). Does not end in a slash or backslash. */ + public String getUserDir() { + return store.getString(USER_DIR); + } + + public void setUserDir(String userDir) { + // remove directory separators at the end + while (!userDir.isBlank() && (userDir.endsWith("/") || userDir.endsWith("\\"))) { + userDir = userDir.substring(0, userDir.length() - 1); + } + store.setValue(USER_DIR, userDir); + } } 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/util/fileUtils/AbstractDirectory.java b/megamek/src/megamek/common/util/fileUtils/AbstractDirectory.java index 33dc701d9a9..5e3bbaa0e1f 100644 --- a/megamek/src/megamek/common/util/fileUtils/AbstractDirectory.java +++ b/megamek/src/megamek/common/util/fileUtils/AbstractDirectory.java @@ -148,4 +148,22 @@ public Iterator getItemNames(final @Nullable String categoryName) { return (entry == null) ? null : entry.getItem(); } //endregion Getters/Setters + + /** + * Adds the given AbstractDirectory's contents to this one's. Note: equally named categories are integrated + * into one another, but for items in the exact same category (file path) and with the same name the + * item of other will replace the item in this AbstractDirectory. + * + * @param other The AbstractDirectory to merge into this one + */ + public void merge(AbstractDirectory other) { + getItems().putAll(other.getItems()); + for (Map.Entry categoryEntry : other.getCategories().entrySet()) { + if (categories.containsKey(categoryEntry.getKey())) { + categories.get(categoryEntry.getKey()).merge(categoryEntry.getValue()); + } else { + categories.put(categoryEntry.getKey(), categoryEntry.getValue()); + } + } + } } 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/TestAdvancedAerospace.java b/megamek/src/megamek/common/verifier/TestAdvancedAerospace.java index 56e7b25428e..1c51b9a4d0d 100644 --- a/megamek/src/megamek/common/verifier/TestAdvancedAerospace.java +++ b/megamek/src/megamek/common/verifier/TestAdvancedAerospace.java @@ -1071,7 +1071,7 @@ public StringBuffer printEntity() { } @Override - public double calculateWeight() { + public double calculateWeightExact() { double weight = 0; weight += getWeightStructure(); weight += getWeightEngine(); diff --git a/megamek/src/megamek/common/verifier/TestAero.java b/megamek/src/megamek/common/verifier/TestAero.java index dccede956ba..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; @@ -52,7 +53,7 @@ public class TestAero extends TestEntity { */ public enum AeroArmor { STANDARD(EquipmentType.T_ARMOR_STANDARD, 0, 0, false), - CLAN_FERRO_ALUM(EquipmentType.T_ARMOR_ALUM, 1, 1, true), + CLAN_FERRO_ALUM(EquipmentType.T_ARMOR_ALUM, 2, 1, true), FERRO_LAMELLOR(EquipmentType.T_ARMOR_FERRO_LAMELLOR, 2, 1, true), CLAN_REACTIVE(EquipmentType.T_ARMOR_REACTIVE, 1, 1, true), CLAN_REFLECTIVE(EquipmentType.T_ARMOR_REFLECTIVE, 1, 1, true), @@ -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; @@ -1070,7 +1071,7 @@ public StringBuffer printEntity() { } @Override - public double calculateWeight() { + public double calculateWeightExact() { double weight = 0; weight += getWeightEngine(); weight += getWeightControls(); @@ -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/TestBattleArmor.java b/megamek/src/megamek/common/verifier/TestBattleArmor.java index b1add802f8f..96c097a72e6 100644 --- a/megamek/src/megamek/common/verifier/TestBattleArmor.java +++ b/megamek/src/megamek/common/verifier/TestBattleArmor.java @@ -1348,7 +1348,7 @@ public double calculateWeight(int trooper) { } @Override - public double calculateWeight() { + public double calculateWeightExact() { double totalWeight = 0.0; for (int i = 0; i < ba.getTroopers(); i++) { totalWeight += calculateWeight(i); diff --git a/megamek/src/megamek/common/verifier/TestEntity.java b/megamek/src/megamek/common/verifier/TestEntity.java index e81ecdd6628..a74aba999e3 100755 --- a/megamek/src/megamek/common/verifier/TestEntity.java +++ b/megamek/src/megamek/common/verifier/TestEntity.java @@ -860,7 +860,32 @@ public static int calcHeatNeutralHSRequirement(Entity entity) { return heat; } + /** + * According to TM, unit weights are to be rounded up to the nearest half ton or kilo. This method + * returns the rounded weight. + * + * @return The weight of the unit, rounded up according to TM, p.22. + */ public double calculateWeight() { + double weight = calculateWeightExact(); + // If the unit used kg standard, we just need to get rid of floating-point math anomalies. + // Otherwise accumulated kg-scale equipment needs to be rounded up to the nearest half-ton. + weight = round(weight, Ceil.KILO); + if (usesKgStandard()) { + return weight; + } else { + return ceil(weight, Ceil.HALFTON); + } + } + + /** + * According to TM p.22, unit weights are to be rounded up to the nearest half ton or kilo, but in MML + * for construction at least we should be able to show the exact weight. This method returns the unrounded + * weight. + * + * @return The unrounded weight of the unit. + */ + public double calculateWeightExact() { double weight = 0; weight += getWeightEngine(); weight += getWeightStructure(); @@ -877,14 +902,7 @@ public double calculateWeight() { weight += getWeightCarryingSpace(); weight += getArmoredComponentWeight(); - // If the unit used kg standard, we just need to get rid of floating-point math anomalies. - // Otherwise accumulated kg-scale equipment needs to be rounded up to the nearest half-ton. - weight = round(weight, Ceil.KILO); - if (usesKgStandard()) { - return weight; - } else { - return ceil(weight, Ceil.HALFTON); - } + return weight; } public String printWeightCalculation() { @@ -1671,6 +1689,14 @@ boolean usesKgStandard() { return usesKgStandard(getEntity()); } + + public int totalCritSlotCount() { + int slotCount = 0; + for (int i = 0; i < getEntity().locations(); i++) { + slotCount += getEntity().getNumberOfCriticals(i); + } + return slotCount; + } } // End class TestEntity class Armor { diff --git a/megamek/src/megamek/common/verifier/TestInfantry.java b/megamek/src/megamek/common/verifier/TestInfantry.java index 5fbe9625110..2f589b8170d 100644 --- a/megamek/src/megamek/common/verifier/TestInfantry.java +++ b/megamek/src/megamek/common/verifier/TestInfantry.java @@ -287,10 +287,9 @@ public String getName() { public double getWeightPowerAmp() { return 0; } - + @Override - public double calculateWeight() { + public double calculateWeightExact() { return infantry.getWeight(); } - } \ No newline at end of file diff --git a/megamek/src/megamek/common/verifier/TestProtomech.java b/megamek/src/megamek/common/verifier/TestProtomech.java index be487fe2920..c86e1e9995d 100644 --- a/megamek/src/megamek/common/verifier/TestProtomech.java +++ b/megamek/src/megamek/common/verifier/TestProtomech.java @@ -244,9 +244,9 @@ public int getCountHeatSinks() { @Override public double calculateWeight() { // Deal with some floating point precision issues - return round(super.calculateWeight(), Ceil.KILO); + return round(super.calculateWeightExact(), Ceil.KILO); } - + @Override public double getWeightAllocatedArmor() { ProtomechArmor armor = ProtomechArmor.getArmor(proto); @@ -780,7 +780,7 @@ public static int maxArmorFactor(Protomech proto, int location) { || (location == Protomech.LOC_RARM)) { if (proto.isQuad()) { return 0; - } else if (proto.getWeight() < 3) { + } else if (proto.getWeight() < 6) { return 2; } else if (proto.getWeight() < 10) { return 4; @@ -789,15 +789,6 @@ public static int maxArmorFactor(Protomech proto, int location) { } } else if (location == Protomech.LOC_BODY) { return 0; - } else if (proto.isQuad()) { - switch ((int) proto.getWeight()) { - case 3: - return 12; - case 4: - case 5: - return 14; - // else drop through - } } return proto.getOInternal(location) * 2; } diff --git a/megamek/src/megamek/common/verifier/TestSmallCraft.java b/megamek/src/megamek/common/verifier/TestSmallCraft.java index f36ec9ad712..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,9 +806,9 @@ public StringBuffer printEntity() { printFailedEquipment(buff); return buff; } - + @Override - public double calculateWeight() { + public double calculateWeightExact() { double weight = 0; weight += getWeightStructure(); weight += getWeightEngine(); @@ -831,7 +831,7 @@ public double calculateWeight() { @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/verifier/TestSupportVehicle.java b/megamek/src/megamek/common/verifier/TestSupportVehicle.java index 3bda4d48c5d..909cb351593 100644 --- a/megamek/src/megamek/common/verifier/TestSupportVehicle.java +++ b/megamek/src/megamek/common/verifier/TestSupportVehicle.java @@ -715,7 +715,12 @@ private double ceilWeight(double val) { @Override public double calculateWeight() { - return ceilWeight(super.calculateWeight() + getFuelTonnage()); + return ceilWeight(calculateWeightExact()); + } + + @Override + public double calculateWeightExact() { + return super.calculateWeightExact() + getFuelTonnage(); } @Override 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/common/weapons/missiles/ISMML3.java b/megamek/src/megamek/common/weapons/missiles/ISMML3.java index 4a1f512b2a2..38dc1aabb9e 100644 --- a/megamek/src/megamek/common/weapons/missiles/ISMML3.java +++ b/megamek/src/megamek/common/weapons/missiles/ISMML3.java @@ -21,6 +21,7 @@ import megamek.common.alphaStrike.AlphaStrikeElement; import megamek.common.Mounted; +import megamek.common.SimpleTechLevel; /** * @author Sebastian Brocks @@ -58,8 +59,9 @@ public ISMML3() { .setTechRating(RATING_D) .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) .setISAdvancement(DATE_NONE, 3067, 3073, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true,false, false) - .setProductionFactions(F_MERC,F_WB); + .setISApproximate(false, true, false, false, false) + .setProductionFactions(F_MERC,F_WB) + .setStaticTechLevel(SimpleTechLevel.STANDARD); } @Override diff --git a/megamek/src/megamek/common/weapons/missiles/ISMML5.java b/megamek/src/megamek/common/weapons/missiles/ISMML5.java index 79f06800111..4ab5ac39a44 100644 --- a/megamek/src/megamek/common/weapons/missiles/ISMML5.java +++ b/megamek/src/megamek/common/weapons/missiles/ISMML5.java @@ -21,6 +21,7 @@ import megamek.common.alphaStrike.AlphaStrikeElement; import megamek.common.Mounted; +import megamek.common.SimpleTechLevel; import static megamek.common.MountedHelper.*; @@ -60,8 +61,9 @@ public ISMML5() { .setTechRating(RATING_D) .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) .setISAdvancement(DATE_NONE, 3067, 3073, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true,false, false) - .setProductionFactions(F_MERC,F_WB); + .setISApproximate(false, true, false, false, false) + .setProductionFactions(F_MERC,F_WB) + .setStaticTechLevel(SimpleTechLevel.STANDARD); } @Override diff --git a/megamek/src/megamek/common/weapons/missiles/ISMML7.java b/megamek/src/megamek/common/weapons/missiles/ISMML7.java index 65e9a3d5cab..5043d2ce845 100644 --- a/megamek/src/megamek/common/weapons/missiles/ISMML7.java +++ b/megamek/src/megamek/common/weapons/missiles/ISMML7.java @@ -21,6 +21,7 @@ import megamek.common.alphaStrike.AlphaStrikeElement; import megamek.common.Mounted; +import megamek.common.SimpleTechLevel; import static megamek.common.MountedHelper.isArtemisIV; import static megamek.common.MountedHelper.isArtemisProto; @@ -61,8 +62,9 @@ public ISMML7() { .setTechRating(RATING_D) .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) .setISAdvancement(DATE_NONE, 3067, 3073, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setProductionFactions(F_MERC,F_WB); + .setISApproximate(false, true, false, false, false) + .setProductionFactions(F_MERC,F_WB) + .setStaticTechLevel(SimpleTechLevel.STANDARD); } @Override diff --git a/megamek/src/megamek/common/weapons/missiles/ISMML9.java b/megamek/src/megamek/common/weapons/missiles/ISMML9.java index 829ca8258c6..c5916ab532a 100644 --- a/megamek/src/megamek/common/weapons/missiles/ISMML9.java +++ b/megamek/src/megamek/common/weapons/missiles/ISMML9.java @@ -21,6 +21,7 @@ import megamek.common.alphaStrike.AlphaStrikeElement; import megamek.common.Mounted; +import megamek.common.SimpleTechLevel; import static megamek.common.MountedHelper.isArtemisIV; import static megamek.common.MountedHelper.isArtemisProto; @@ -54,14 +55,16 @@ public ISMML9() { longAV = 5; maxRange = RANGE_LONG; rulesRefs = "229, TM"; + //March 2022 - CGL (Greekfire) requested MML adjustments to Tech Progression. techAdvancement.setTechBase(TECH_BASE_IS) .setIntroLevel(false) .setUnofficial(false) .setTechRating(RATING_D) .setAvailability(RATING_X, RATING_X, RATING_E, RATING_D) .setISAdvancement(DATE_NONE, 3067, 3073, DATE_NONE, DATE_NONE) - .setISApproximate(false, false, true, false, false) - .setProductionFactions(F_MERC,F_WB); + .setISApproximate(false, true, false, false, false) + .setProductionFactions(F_MERC,F_WB) + .setStaticTechLevel(SimpleTechLevel.STANDARD); } @Override diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index 6913bb0d3f7..271e72ade86 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -482,7 +482,13 @@ public void saveGame(String sFile) { public void disconnect(Player player) { // in the lounge, just remove all entities for that player if (getGame().getPhase().isLounge()) { - removeAllEntitiesOwnedBy(player); + List gms = game.getPlayersList().stream().filter(p -> p.isGameMaster()).collect(Collectors.toList()); + + if (gms.size() > 0) { + transferAllEnititiesOwnedBy(player, gms.get(0)); + } else { + removeAllEntitiesOwnedBy(player); + } } // if a player has active entities, he becomes a ghost @@ -540,6 +546,16 @@ public void checkForObservers() { } } + private void transferAllEnititiesOwnedBy(Player pFrom, Player pTo) { + for (Entity entity : game.getEntitiesVector().stream().filter(e -> e.getOwner().equals(pFrom)).collect(Collectors.toList())) { + entity.setOwner(pTo); + } + game.getForces().correct(); + ServerLobbyHelper.correctLoading(game); + ServerLobbyHelper.correctC3Connections(game); + send(createFullEntitiesPacket()); + } + @Override public void removeAllEntitiesOwnedBy(Player player) { int pid = player.getId(); @@ -2011,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(); @@ -3679,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(); } @@ -20871,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 * @@ -24791,7 +24890,9 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in js = (Jumpship) aero; } - switch (cs.getIndex()) { + // For testing purposes + // switch (cs.getIndex()) { + switch (Aero.CRIT_CARGO) { case Aero.CRIT_NONE: // no effect r = new Report(6005); @@ -24949,10 +25050,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); } } @@ -25001,11 +25102,12 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in } case Aero.CRIT_WEAPON: if (aero.isCapitalFighter()) { + FighterSquadron cf = (FighterSquadron) aero; boolean destroyAll = false; // CRIT_WEAPON damages the capital fighter/squadron's weapon groups // Go ahead and map damage for the fighter's weapon criticals for MHQ // resolution. - aero.damageCapFighterWeapons(loc); + cf.damageCapFighterWeapons(loc); if ((loc == Aero.LOC_NOSE) || (loc == Aero.LOC_AFT)) { destroyAll = true; } @@ -25016,13 +25118,13 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in } if (loc == Aero.LOC_WINGS) { - if (aero.areWingsHit()) { + if (cf.areWingsHit()) { destroyAll = true; } else { - aero.setWingsHit(true); + cf.setWingsHit(true); } } - for (Mounted weapon : aero.getWeaponList()) { + for (Mounted weapon : cf.getWeaponList()) { if (weapon.getLocation() == loc) { if (destroyAll) { weapon.setHit(true); @@ -25032,7 +25134,7 @@ private Vector applyAeroCritical(Aero aero, int loc, CriticalSlot cs, in } } // also destroy any ECM or BAP in the location hit - for (Mounted misc : aero.getMisc()) { + for (Mounted misc : cf.getMisc()) { if ((misc.getType().hasFlag(MiscType.F_ECM) || misc.getType().hasFlag(MiscType.F_ANGEL_ECM) || misc.getType().hasFlag(MiscType.F_BAP)) @@ -25041,40 +25143,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); } } @@ -25526,7 +25630,48 @@ private void applyCargoCritical(Aero aero, int damageCaused, Vector repo false, true)); destroyed--; } + } else { + // TODO: handle critical hit on internal bomb bay (cargo bay when internal bombs are loaded) + // Ruling: calculate % of cargo space destroyed; user chooses that many bombs to destroy. + if(aero.hasQuirk(OptionsConstants.QUIRK_POS_INTERNAL_BOMB)) { + // Prompt user, but just randomize bot's bombs to lose. + destroyed = (int) percentDestroyed * aero.getMaxIntBombPoints(); + r = new Report(5605); + r.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(); @@ -25535,6 +25680,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. * @@ -29619,7 +29782,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/Server.java b/megamek/src/megamek/server/Server.java index 0fd46b61af3..23d452d2ff0 100644 --- a/megamek/src/megamek/server/Server.java +++ b/megamek/src/megamek/server/Server.java @@ -32,6 +32,8 @@ import megamek.common.net.factories.ConnectionFactory; import megamek.common.net.listeners.ConnectionListener; import megamek.common.net.packets.Packet; +import megamek.common.options.GameOptions; +import megamek.common.options.OptionsConstants; import megamek.common.preference.PreferenceManager; import megamek.common.util.EmailService; import megamek.common.util.SerializationHelper; @@ -542,6 +544,10 @@ private void receivePlayerInfo(Packet packet, int connId) { gamePlayer.setStartingPos(player.getStartingPos()); gamePlayer.setStartWidth(player.getStartWidth()); gamePlayer.setStartOffset(player.getStartOffset()); + gamePlayer.setStartingAnyNWx(player.getStartingAnyNWx()); + gamePlayer.setStartingAnyNWy(player.getStartingAnyNWy()); + gamePlayer.setStartingAnySEx(player.getStartingAnySEx()); + gamePlayer.setStartingAnySEy(player.getStartingAnySEy()); gamePlayer.setTeam(player.getTeam()); gamePlayer.setCamouflage(player.getCamouflage().clone()); gamePlayer.setNbrMFConventional(player.getNbrMFConventional()); @@ -751,12 +757,18 @@ private Player addNewPlayer(int connId, String name, boolean isBot) { int team = Player.TEAM_UNASSIGNED; if (getGame().getPhase().isLounge()) { team = Player.TEAM_NONE; - for (Player p : getGame().getPlayersVector()) { - if (p.getTeam() > team) { - team = p.getTeam(); + final GameOptions gOpts = getGame().getOptions(); + if (isBot || !gOpts.booleanOption(OptionsConstants.BASE_SET_DEFAULT_TEAM_1)) { + for (Player p : getGame().getPlayersList()) { + if (p.getTeam() > team) { + team = p.getTeam(); + } } + team++; + } else { + team = 1; } - team++; + } Player newPlayer = new Player(connId, name); newPlayer.setBot(isBot); 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/DebugEntity.java b/megamek/src/megamek/utilities/DebugEntity.java new file mode 100644 index 00000000000..85047fdd664 --- /dev/null +++ b/megamek/src/megamek/utilities/DebugEntity.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2023 - 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.utilities; + +import megamek.common.CriticalSlot; +import megamek.common.Entity; +import megamek.common.Mech; +import megamek.common.Protomech; + +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; + +/** + * This class is for debugging Entity with respect to the internal state of equipment. + */ +@SuppressWarnings("unused") +public class DebugEntity { + + /** + * Gets a full listing of the internal representation of the unit's equipment and crit slots with most + * of the internal state of each ({@link #getEquipmentState(Entity)}) and copies it to the clipboard. + * + * @param entity The entity to debug + */ + public static void copyEquipmentState(Entity entity) { + copyToClipboard(getEquipmentState(entity)); + } + + /** Copies the given text to the Clipboard. */ + public static void copyToClipboard(String text) { + StringSelection stringSelection = new StringSelection(text); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(stringSelection, null); + } + + /** + * Returns a full listing of the internal representation of the unit's equipment and crit slots with most + * of the internal state of each. + * + * @param entity The entity to debug + * @return A String describing the internal state of the Entity's equipment + */ + public static String getEquipmentState(Entity entity) { + StringBuilder result = new StringBuilder(); + try { + result.append("Chassis: >").append(entity.getChassis()).append("<\n"); + result.append("Model: >").append(entity.getModel()).append("<\n"); + + result.append("Equipment:\n"); + for (int i = 0; i < entity.getEquipment().size(); i++) { + result.append("[" + i + "] ").append(entity.getEquipment(i)).append("\n"); + if (entity != entity.getEquipment(i).getEntity()) { + result.append("Different Entity!"); + } + } + result.append("\n"); + + result.append("Locations:\n"); + for (int location = 0; location < entity.locations(); location++) { + result.append(entity.getLocationAbbr(location)).append(":\n"); + for (int slot = 0; slot < entity.getNumberOfCriticals(location); slot++) { + CriticalSlot criticalSlot = entity.getCritical(location, slot); + if (criticalSlot != null) { + result.append("[" + slot + "] ").append(criticalSlot); + if (criticalSlot.getType() == 0) { + result.append(" ("); + if (entity instanceof Mech) { + result.append(((Mech) entity).getSystemName(criticalSlot.getIndex())); + } else if (entity instanceof Protomech) { + result.append(Protomech.systemNames[criticalSlot.getIndex()]); + } + result.append(")"); + } + result.append("\n"); + } + } + } + } catch (Exception e) { + result.append("\nAn exception was encountered here. " + e.getMessage()); + } + + return result.toString(); + } + + private DebugEntity() { } +} 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()) {