diff --git a/megamek/resources/megamek/client/messages.properties b/megamek/resources/megamek/client/messages.properties index 3fa1d0688ea..ae1703ddc52 100644 --- a/megamek/resources/megamek/client/messages.properties +++ b/megamek/resources/megamek/client/messages.properties @@ -847,6 +847,7 @@ ChatLounge.CustomMapSize=Custom board size ChatLounge.CustomizeUnits=Customize Units ChatLounge.deploysAfterRound=Round {0} ChatLounge.deploysAfterZone=in zone {0} +ChatLounge.deploysPreGame=Pre-game ChatLounge.deploysOffBoard=Off board ChatLounge.ExclusiveDeploy.title=Starting Position not allowed ChatLounge.ExclusiveDeploy.msg=In Double Blind play, you cannot choose 'Any' as starting position. @@ -2171,6 +2172,7 @@ CustomMekDialog.labTough=Toughness: CustomMekDialog.labFatigue=Fatigue: CustomMekDialog.labFatigueToolTip=CamOps fatigue (CamOps p219) CustomMekDialog.labUnitNum=Swap Units With: +CustomMekDialog.PreGame=Pre-game CustomMekDialog.tooltipBackupGunner=The crew member that will take over if the gunner is incapacitated CustomMekDialog.tooltipBackupPilot=The crew member that will take over if the pilot is incapacitated CustomMekDialog.MEAPanelTitle=Manipulators and Armored Glove Weapon Selection @@ -2468,6 +2470,8 @@ DeploymentDisplay.deployFacing=Facing DeploymentDisplay.deployLoad=Load DeploymentDisplay.deployUnload=Unload DeploymentDisplay.deployDock=Dock +DeploymentDisplay.deployClear=Clear Deployment +DeploymentDisplay.deployClear.tooltip=Clear this unit's deployment settings # --- DeploymentDisplay.removeTitle=Remove Unit DeploymentDisplay.removeUnit=Are you sure you want to remove {0}? @@ -3688,6 +3692,8 @@ MovementDisplay.butAcc=Accelerate MovementDisplay.butAccN=Acc Next MovementDisplay.butBackup=Back Up MovementDisplay.moveCancel=Cancel +MovementDisplay.moveClearDeploy=Reset deployment +MovementDisplay.moveClearDeployTip=Clear the deployment of this unit. Clears Elevation Selection MovementDisplay.moveCarefulStand=Careful Stand MovementDisplay.butCharge=Charge MovementDisplay.butClear=Clear mines @@ -3972,6 +3978,7 @@ MovementDisplay.butVLand=V Land MovementDisplay.butVTakeOff=V Lift Off MovementDisplay.moveShakeOff=Shake Off MovementDisplay.MoveTakeOff=Lift Off +MovementDisplay.moveDeploy=Deploy MovementDisplay.moveTakeCover=Take Cover MovementDisplay.moveTow=Tow MovementDisplay.moveTurn=Turn @@ -4007,7 +4014,6 @@ MovementDisplay.ClearMinefieldDialog.message=The unit successfully clears the\nm MovementDisplay.ClearMinefieldDialog.title=Clear the minefield? MovementDisplay.ConfirmCrushingBuildings=The selected path will crash into a building crushing the building, damaging the moving unit and causing the moving unit to get stuck. \nDo you wish to proceed? MovementDisplay.ConfirmDoomedMove={0} has a location with no armour left. Going under water breaches it there with no roll to survive, and that destroys the unit.\n\nMove into the water anyway? - Move into the water anyway? MovementDisplay.ConfirmChaff.title=Deploy Chaff MovementDisplay.ConfirmChaff.message=The unit will try to deploy chaff at the end of its movement \ndo you wish to proceed ? @@ -4390,6 +4396,8 @@ PlayerSettingsDialog.labConventional=Conventional: PlayerSettingsDialog.labInferno=Inferno: PlayerSettingsDialog.labVibrabomb=Vibrabomb: PlayerSettingsDialog.labEMP=EMP: +PlayerSettingsDialog.labDeploymentWidthWalkOn=Deployment width restricted to 1 +PlayerSettingsDialog.labWalkOnDeployment=Walk-on Deployment enabled. PlayerSettingsDialog.labTripwire=Tripwires: PlayerSettingsDialog.tripwireTT=Tripwires have a chance to cause a Mek to fall when entering a hex and are one-use. PlayerSettingsDialog.labPitfall=Pitfalls: @@ -5110,8 +5118,8 @@ C3NetworkManagerDialog.btnDisconnect.tooltip=Disconnect the units selected in th C3NetworkManagerDialog.status={0} unassigned units, {1} networks. Drag units onto a network or empty slot to connect; drag members to the left list to disconnect. C3NetworkManagerDialog.slotSlaves=Empty slave slot - {0} free (drop C3 Slave units here) C3NetworkManagerDialog.slotMasters=Empty lance master slot - {0} free (drop C3 Master units here) -C3NetworkManagerDialog.configProgress= | Configuration {0}: {1} of {2} units - add {3} -C3NetworkManagerDialog.configComplete= | Configuration {0} complete ({1} units) +C3NetworkManagerDialog.configProgress=| Configuration {0}: {1} of {2} units - add {3} +C3NetworkManagerDialog.configComplete=| Configuration {0} complete ({1} units) C3NetworkManagerDialog.configNeedsMasters={0} lance master(s) C3NetworkManagerDialog.configNeedsSlaves={0} slave(s) C3NetworkManagerDialog.seedSummary=New network: {0} #{1} - drop units here @@ -5127,8 +5135,8 @@ C3NetworkManagerDialog.rolePeer=Peer C3NetworkManagerDialog.roleCompanyMaster=Company M C3NetworkManagerDialog.roleLanceMaster=Master C3NetworkManagerDialog.roleSlave=Slave -C3NetworkManagerDialog.capacity= - free: {0} slaves, {1} masters -C3NetworkManagerDialog.capacitySlaves= - free: {0} slaves +C3NetworkManagerDialog.capacity=- free: {0} slaves, {1} masters +C3NetworkManagerDialog.capacitySlaves=- free: {0} slaves C3NetworkManagerDialog.equipMasters=Master x{0} C3NetworkManagerDialog.equipMaster=Master C3NetworkManagerDialog.equipSlave=Slave @@ -5338,6 +5346,11 @@ VictorySetupDisplay.waitingVictorySetup=Waiting to begin Victory Setup phase... #Select Starting Position Dialog #Objective control points Objective.toast.zoneEntered={0} has entered the control zone of {1}. +VictoryHex.word.standard=Standard +VictoryHex.word.raid=Raid +VictoryHex.word.hold=Hold +VictoryHex.word.defend=Defend +VictoryHex.word.capture=Capture StartingPositionDialog.title=Select a Starting Position... #Targeting Phase Display TargetingPhaseDisplay.alreadyFired=Already fired @@ -6803,7 +6816,7 @@ Gamemaster.cmd.building.armor=The building's armor in this hex, or -1 to leave i Gamemaster.cmd.building.height=How many levels the building stands in this hex, or -1 to leave it as it is. Gamemaster.cmd.building.basement=The basement under this hex. Leave blank to keep the one it has. Gamemaster.cmd.building.refused=The building was not changed: {0}. -Gamemaster.cmd.building.damageReason= is brought down on the gamemaster's order for +Gamemaster.cmd.building.damageReason=is brought down on the gamemaster's order for Gamemaster.cmd.playerID=The ID of the player that is going to be held as responsible. # Rescue cmd Gamemaster.cmd.rescue.longName=Rescue Unit @@ -7403,7 +7416,6 @@ ForceGeneratorDialog.formationMix.requirement.none=Requirements: none. ForceGeneratorDialog.formationMix.idealRole=Ideal role: {0} ForceGeneratorDialog.formationDropped.title=Formation could not be built ForceGeneratorDialog.formationDropped=A {0} formation could not be built here.\n\n{1} forces in {2} have too few units meeting its requirements, so an ordinary formation was generated instead. Its hover text in the Formation Mix panel lists what it needs.\n\nTrying a different era, faction or unit type may make it available. - # SearchableComboBox SearchableComboBox.tooltip=Type to search the list, Enter picks the highlighted or first match, Escape clears the search diff --git a/megamek/resources/megamek/common/options/messages.properties b/megamek/resources/megamek/common/options/messages.properties index c96624a0a78..10d48ef62a6 100644 --- a/megamek/resources/megamek/common/options/messages.properties +++ b/megamek/resources/megamek/common/options/messages.properties @@ -115,6 +115,8 @@ GameOptionsInfo.option.hide_unofficial.displayableName=Don't show unofficial gam GameOptionsInfo.option.hide_unofficial.description=If checked, unofficial game options will be deactivated and hidden GameOptionsInfo.option.hide_legacy.displayableName=Don't show legacy game options GameOptionsInfo.option.hide_legacy.description=If checked, legacy game options, i.e. official rules that are superseded by more modern rules, will be deactivated and hidden +GameOptionsInfo.option.walk_on_deployment.displayableName=Walk-on Deployment +GameOptionsInfo.option.walk_on_deployment.description=If checked, use walk-on deployment for any units not pre-deployed or any or center. Always on in Core Rules. GameOptionsInfo.option.new_damage_manager.displayableName=Use new damage handling system GameOptionsInfo.option.new_damage_manager.description=Use new code to handle damage application, may be buggy GameOptionsInfo.group.gameMaster.displayableName=Game Master @@ -1431,7 +1433,6 @@ WeaponQuirksInfo.option.misreplaced_weapon.description=+1 to hit. WeaponQuirksInfo.option.misreplaced_weapon.rulesBook=CO WeaponQuirksInfo.option.misreplaced_weapon.rulesPage=208 WeaponQuirksInfo.option.misreplaced_weapon.working=1 - # SPA implementation-status catalog (SpaCatalog.java), generated from # docs/Pilot SPAs - CampOps vs MegaMek.xlsx (CamOps 2024 Rev 5th Printing pp. 71-82) SpaImplementationStatus.full=Implemented @@ -1537,7 +1538,6 @@ SpaCatalog.tm_nightwalker.missing=At faster rates the book grants -1 MP cost; MM SpaCatalog.tm_swamp_beast.missing=Defensive +1 granted automatically at run - book requires spending +1 MP/hex as a player choice SpaCatalog.urban_guerrilla.missing=Protection applies only in urban-terrain hexes, not literally 'even in the open' SpaCatalog.wind_walker.missing=Atmosphere-interface crossing PSR bonus not implemented; 'ignores atmospheric combat modifiers' not implemented - # Quirk implementation-status catalog (QuirkCatalog.java). A quirk's status is the value of its own # QuirksInfo/WeaponQuirksInfo ".working" key above: 1 = implemented, partial = has some effect but not the # printed rule, 0 = the option exists but the MegaMek engine ignores it. Reconciled against the engine on diff --git a/megamek/src/megamek/client/commands/MoveCommand.java b/megamek/src/megamek/client/commands/MoveCommand.java index 830a2893f79..0d203eef4af 100644 --- a/megamek/src/megamek/client/commands/MoveCommand.java +++ b/megamek/src/megamek/client/commands/MoveCommand.java @@ -77,19 +77,19 @@ public String run(String[] args) { return "Move aborted, all movement data cleared."; } else if (args[1].equalsIgnoreCase("HELP")) { return """ - Available commands: - #move ABORT = aborts planed move and deselect unit. - #move SELECT unitID = Selects the unit named unit ID for movement. This is a prerequisite for all commands listed after this. Also changed current hex. - #move COMMIT = commits the planed movement. - #move JUMP = clears all movement and starts jump movement. Either the entire move is a jump or the entire move is a walk. switching gears will cancel all planned movement (but leave the unit selected). - #move BACK [x y] = Start walking backwards, can be followed by a coordinate. - #move WALK [x y] = Start walking/running forwards, this is the default. Can be followed by a coordinate. - #move TURN [x y] = Starts turning towards target coordinate. Can be followed by a coordinate. - #move CLIP = Clips to path to what is actually possible, and reports on what will happen if committed. - #move GETUP = Attempt to stand up. Will require a piloting roll. - #move CAREFUL = Attempt to stand up. Will require a piloting roll. - #move x y = move towards coordinate in the current gear. It will do pathfinding for least cost path. Note that the entity will try to move to each coordinate supplied in order. - """; + Available commands: + #move ABORT = aborts planed move and deselect unit. + #move SELECT unitID = Selects the unit named unit ID for movement. This is a prerequisite for all commands listed after this. Also changed current hex. + #move COMMIT = commits the planed movement. + #move JUMP = clears all movement and starts jump movement. Either the entire move is a jump or the entire move is a walk. switching gears will cancel all planned movement (but leave the unit selected). + #move BACK [x y] = Start walking backwards, can be followed by a coordinate. + #move WALK [x y] = Start walking/running forwards, this is the default. Can be followed by a coordinate. + #move TURN [x y] = Starts turning towards target coordinate. Can be followed by a coordinate. + #move CLIP = Clips to path to what is actually possible, and reports on what will happen if committed. + #move GETUP = Attempt to stand up. Will require a piloting roll. + #move CAREFUL = Attempt to stand up. Will require a piloting roll. + #move x y = move towards coordinate in the current gear. It will do pathfinding for least cost path. Note that the entity will try to move to each coordinate supplied in order. + """; } else if (args[1].equalsIgnoreCase("SELECT")) { try { clearAllMoves(); @@ -102,7 +102,7 @@ public String run(String[] args) { getClientGUI().setCurrentHex(currentEntity().getPosition()); return "Entity " + currentEntity().toString() - + " selected for movement."; + + " selected for movement."; } catch (Exception e) { return "Not an entity ID or valid number." + e; } @@ -134,13 +134,13 @@ public String run(String[] args) { } else if (args[1].equalsIgnoreCase("CLIP")) { cmd.clipToPossible(); return "Path clipped to whats actually possible. " - + currentEntity().toString() + " is now in gear " - + gearName(gear) + " heading towards " - + cmd.getFinalCoords().toFriendlyString() - + " with a final facing of " - + getDirection(cmd.getFinalFacing()) - + ". Total mp used: " + cmd.getMpUsed() - + " for a movement of: " + cmd.getHexesMoved(); + + currentEntity().toString() + " is now in gear " + + gearName(gear) + " heading towards " + + cmd.getFinalCoords().toFriendlyString() + + " with a final facing of " + + getDirection(cmd.getFinalFacing()) + + ". Total mp used: " + cmd.getMpUsed() + + " for a movement of: " + cmd.getHexesMoved(); } else if (args[1].equalsIgnoreCase("GETUP")) { if (cmd.getFinalProne() || cmd.getFinalHullDown()) { cmd.addStep(MoveStepType.GET_UP); @@ -150,7 +150,7 @@ public String run(String[] args) { return "Trying to get up but the Mek is not prone."; } else if (args[1].equalsIgnoreCase("CAREFULSTAND")) { if (cmd.getFinalProne() || cmd.getFinalHullDown() && getClient().getGame().getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CAREFUL_STAND)) { + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CAREFUL_STAND)) { cmd.addStep(MoveStepType.CAREFUL_STAND); return "Mek will try to stand up. this requires a piloting roll."; } @@ -167,11 +167,11 @@ public String run(String[] args) { currentMove(target); return "Commands accepted " + currentEntity().toString() - + " is now in gear " + gearName(gear) - + " heading towards " - + cmd.getFinalCoords().toFriendlyString() - + ". Total mp used: " + cmd.getMpUsed() - + " for a movement of: " + cmd.getHexesMoved(); + + " is now in gear " + gearName(gear) + + " heading towards " + + cmd.getFinalCoords().toFriendlyString() + + ". Total mp used: " + cmd.getMpUsed() + + " for a movement of: " + cmd.getHexesMoved(); } } clearAllMoves(); @@ -230,7 +230,6 @@ private void clearAllMoves() { } else if (currentEntity().getMovementMode().isQuadSwim()) { currentEntity().setMovementMode(EntityMovementMode.QUAD); } - cmd = new MovePath(getClient().getGame(), currentEntity()); } else { cmd = null; diff --git a/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/EntitySprite.java b/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/EntitySprite.java index 30813bcc37e..138930fbdd5 100644 --- a/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/EntitySprite.java +++ b/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/EntitySprite.java @@ -286,6 +286,7 @@ public Rectangle getBounds() { // Move to the board position, save this origin for correct drawing hexOrigin = bounds.getLocation(); Point ePos; + if (secondaryPos == -1) { ePos = bv.getHexLocation(entity.getPosition()); } else { diff --git a/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/GhostEntitySprite.java b/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/GhostEntitySprite.java index e940243e485..246b2ec010d 100644 --- a/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/GhostEntitySprite.java +++ b/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/GhostEntitySprite.java @@ -32,9 +32,7 @@ */ package megamek.client.ui.clientGUI.boardview.sprite; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Rectangle; +import java.awt.*; import java.awt.image.ImageObserver; import megamek.MMConstants; @@ -57,7 +55,9 @@ public GhostEntitySprite(BoardView boardView1, final Entity entity) { modelRect = new Rectangle(47, 55, bv.getPanel().getFontMetrics(font).stringWidth( shortName) + 1, bv.getPanel().getFontMetrics(font).getAscent()); Rectangle tempBounds = new Rectangle(bv.getHexSize()).union(modelRect); - tempBounds.setLocation(bv.getHexLocation(entity.getPosition())); + if (entity.getPosition() != null) { + tempBounds.setLocation(bv.getHexLocation(entity.getPosition())); + } bounds = tempBounds; image = null; @@ -78,7 +78,9 @@ public void prepare() { @Override public Rectangle getBounds() { Rectangle tempBounds = new Rectangle(bv.getHexSize()).union(modelRect); - tempBounds.setLocation(bv.getHexLocation(entity.getPosition())); + if (entity.getPosition() != null) { + tempBounds.setLocation(bv.getHexLocation(entity.getPosition())); + } bounds = tempBounds; return bounds; diff --git a/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/StepSprite.java b/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/StepSprite.java index 99e2b96c9f4..22c4078fd5d 100644 --- a/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/StepSprite.java +++ b/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/StepSprite.java @@ -242,6 +242,10 @@ public void prepare() { String load = Messages.getString("BoardView1.Load"); drawAnnouncement(g2D, load, step, col); break; + case DEPLOY: + String deploy = Messages.getString("MovementDisplay.moveDeploy"); + drawAnnouncement(g2D, deploy, step, col); + break; case PICKUP_CARGO: String pickup = Messages.getString("MovementDisplay.movePickupCargo"); drawAnnouncement(g2D, pickup, step, col); diff --git a/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/isometric/IsometricSprite.java b/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/isometric/IsometricSprite.java index ae527d3303f..073a04f64c6 100644 --- a/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/isometric/IsometricSprite.java +++ b/megamek/src/megamek/client/ui/clientGUI/boardview/sprite/isometric/IsometricSprite.java @@ -117,6 +117,10 @@ public void drawOnto(Graphics g, int x, int y, ImageObserver observer, boolean m } else { p = bv.getHexLocation(entity.getSecondaryPositions().get(secondaryPos)); } + if (p == null) { + // The entity has no position, there is nothing to draw + return; + } Graphics2D g2 = (Graphics2D) g; if (onlyDetectedBySensors()) { diff --git a/megamek/src/megamek/client/ui/dialogs/customMek/CustomMekDialog.java b/megamek/src/megamek/client/ui/dialogs/customMek/CustomMekDialog.java index b5dc00affde..d8f7d182a77 100644 --- a/megamek/src/megamek/client/ui/dialogs/customMek/CustomMekDialog.java +++ b/megamek/src/megamek/client/ui/dialogs/customMek/CustomMekDialog.java @@ -33,13 +33,7 @@ */ package megamek.client.ui.dialogs.customMek; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.LayoutManager; -import java.awt.Rectangle; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; @@ -110,7 +104,9 @@ * @since March 18, 2002, 2:56 PM */ public class CustomMekDialog extends AbstractButtonDialog - implements ActionListener, DialogOptionListener, ItemListener { + implements ActionListener, + DialogOptionListener, + ItemListener { public static final int DONE = 0; public static final int NEXT = 1; @@ -129,13 +125,13 @@ public class CustomMekDialog extends AbstractButtonDialog private final JCheckBox chCommander = new JCheckBox(); private final JLabel labDeploymentRound = new JLabel(Messages.getString("CustomMekDialog.labDeployment"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JLabel labDeploymentZone = new JLabel(Messages.getString("CustomMekDialog.labDeploymentZone"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JLabel labDeploymentOffset = new JLabel(Messages.getString("CustomMekDialog.labDeploymentOffset"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JLabel labDeploymentWidth = new JLabel(Messages.getString("CustomMekDialog.labDeploymentWidth"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JComboBox choDeploymentRound = new JComboBox<>(); private final JComboBox choDeploymentZone = new JComboBox<>(); @@ -153,48 +149,48 @@ public class CustomMekDialog extends AbstractButtonDialog private JSpinner spinStartingAnySEy; private final JLabel labDeployShutdown = new JLabel(Messages.getString("CustomMekDialog.labDeployShutdown"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JCheckBox chDeployShutdown = new JCheckBox(); private final JLabel labDeployProne = new JLabel(Messages.getString("CustomMekDialog.labDeployProne"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JCheckBox chDeployProne = new JCheckBox(); private final JLabel labDeployHullDown = new JLabel(Messages.getString("CustomMekDialog.labDeployHullDown"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JCheckBox chDeployHullDown = new JCheckBox(); private final JLabel labDeployDugIn = new JLabel(Messages.getString("CustomMekDialog.labDeployDugIn"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JCheckBox chDeployDugIn = new JCheckBox(); private final JLabel labHidden = new JLabel(Messages.getString("CustomMekDialog.labHidden"), SwingConstants.RIGHT); private final JCheckBox chHidden = new JCheckBox(); private final JLabel labDeployStealth = new JLabel(Messages.getString("CustomMekDialog.labDeployStealth"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JCheckBox chDeployStealth = new JCheckBox(); private final JLabel labOffBoard = new JLabel(Messages.getString("CustomMekDialog.labOffBoard"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JCheckBox chOffBoard = new JCheckBox(); private final JLabel labOffBoardDirection = new JLabel(Messages.getString("CustomMekDialog.labOffBoardDirection"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JComboBox choOffBoardDirection = new JComboBox<>(); private final JLabel labOffBoardDistance = new JLabel(Messages.getString("CustomMekDialog.labOffBoardDistance"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JTextField fldOffBoardDistance = new JTextField(4); private final JButton butOffBoardDistance = new JButton("0"); private final JLabel labStartingMode = new JLabel(Messages.getString("CustomMekDialog.labStartingMode"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JComboBox choStartingMode = new JComboBox<>(); private final JLabel labCurrentFuel = new JLabel(Messages.getString("CustomMekDialog.labCurrentFuel"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JTextField fldCurrentFuel = new JTextField(7); private final JLabel labStartVelocity = new JLabel(Messages.getString("CustomMekDialog.labStartVelocity"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JTextField fldStartVelocity = new JTextField(3); private final JLabel labStartAltitude = new JLabel(Messages.getString("CustomMekDialog.labStartAltitude"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JTextField fldStartAltitude = new JTextField(3); private final JLabel labStartHeight = new JLabel(Messages.getString("CustomMekDialog.labStartHeight"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JTextField fldStartHeight = new JTextField(3); private final JCheckBox chDeployAirborne = new JCheckBox(); private final JPanel panButtons = new JPanel(); @@ -240,15 +236,21 @@ public class CustomMekDialog extends AbstractButtonDialog /** * Creates new CustomMekDialog */ - public CustomMekDialog(ClientGUI clientgui, Client client, List entities, boolean editable) { + public CustomMekDialog(ClientGUI clientgui, + Client client, + List entities, + boolean editable) { this(clientgui, client, entities, editable, true); } /** * Creates new CustomMekDialog */ - public CustomMekDialog(ClientGUI clientgui, Client client, List entities, boolean editable, - boolean editableDeployment) { + public CustomMekDialog(ClientGUI clientgui, + Client client, + List entities, + boolean editable, + boolean editableDeployment) { super(clientgui.getFrame(), "CustomizeMekDialog", "CustomMekDialog.title"); this.entities = entities; @@ -267,8 +269,11 @@ public CustomMekDialog(ClientGUI clientgui, Client client, List entities initialize(); } - public CustomMekDialog(JFrame frame, Client client, List entities, boolean editable, - boolean editableDeployment) { + public CustomMekDialog(JFrame frame, + Client client, + List entities, + boolean editable, + boolean editableDeployment) { super(frame, "CustomizeMekDialog", "CustomMekDialog.title"); this.entities = entities; this.clientGUI = null; @@ -407,7 +412,8 @@ public void refreshQuirks() { * Finishes one pilot option row built by {@link PilotOptionsPanel}: populates choice values and attaches the inline * prosthetic controls. Passed to the panel as its {@link PilotOptionsPanel.OptionRowConfigurator}. */ - private void configureOptionRow(IOption option, DialogOptionComponentYPanel optionComp) { + private void configureOptionRow(IOption option, + DialogOptionComponentYPanel optionComp) { Entity entity = entities.getFirst(); if ((OptionsConstants.GUNNERY_WEAPON_SPECIALIST).equals(option.getName())) { @@ -472,7 +478,8 @@ private void configureOptionRow(IOption option, DialogOptionComponentYPanel opti } } - private void addPartRep(IOption option, boolean editable) { + private void addPartRep(IOption option, + boolean editable) { DialogOptionComponentYPanel optionComp = new DialogOptionComponentYPanel(this, option, editable); panPartReps.add(optionComp, GBC.eol()); partRepsComps.add(optionComp); @@ -486,8 +493,9 @@ private void addPartRep(IOption option, boolean editable) { * @param entity The entity being configured * @param isStandardEnhanced True for MD_PL_ENHANCED (slot 1 only), false for MD_PL_I_ENHANCED (both slots) */ - private void addInlineProstheticControls(DialogOptionComponentYPanel optionComp, Entity entity, - boolean isStandardEnhanced) { + private void addInlineProstheticControls(DialogOptionComponentYPanel optionComp, + Entity entity, + boolean isStandardEnhanced) { ConvInfantry infantry = (entity instanceof ConvInfantry) ? (ConvInfantry) entity : null; String typeTooltip = Messages.getString("CustomMekDialog.ProstheticTypeTooltip"); String countTooltip = Messages.getString("CustomMekDialog.ProstheticCountTooltip"); @@ -634,7 +642,8 @@ private void populateProstheticDropdown(JComboBox dropdown) { * @param optionComp The DialogOptionComponentYPanel to add controls to * @param entity The entity being configured */ - private void addInlineExtraneousControls(DialogOptionComponentYPanel optionComp, Entity entity) { + private void addInlineExtraneousControls(DialogOptionComponentYPanel optionComp, + Entity entity) { ConvInfantry infantry = (entity instanceof ConvInfantry) ? (ConvInfantry) entity : null; String pair1Tooltip = Messages.getString("CustomMekDialog.ExtraneousPair1Tooltip"); String pair2Tooltip = Messages.getString("CustomMekDialog.ExtraneousPair2Tooltip"); @@ -694,7 +703,7 @@ private void addInlineExtraneousControls(DialogOptionComponentYPanel optionComp, // Glider or powered flight wings limit extraneous limbs to one pair (IO p.85) // Disable pair 2 if any wing type is already enabled boolean hasWings = entity.hasAbility(OptionsConstants.MD_PL_GLIDER) - || entity.hasAbility(OptionsConstants.MD_PL_FLIGHT); + || entity.hasAbility(OptionsConstants.MD_PL_FLIGHT); if (hasWings && editable) { choExtraneousPair2.setEnabled(false); lblExtraneousPair2.setEnabled(false); @@ -707,15 +716,15 @@ private void addInlineExtraneousControls(DialogOptionComponentYPanel optionComp, // Add listener to prevent pair 2 selection when any wing type is enabled choExtraneousPair2.addItemListener(event -> { if (event.getStateChange() == ItemEvent.SELECTED - && choExtraneousPair2.getSelectedIndex() > 0 - && (isOptionSelected(OptionsConstants.MD_PL_GLIDER) - || isOptionSelected(OptionsConstants.MD_PL_FLIGHT))) { + && choExtraneousPair2.getSelectedIndex() > 0 + && (isOptionSelected(OptionsConstants.MD_PL_GLIDER) + || isOptionSelected(OptionsConstants.MD_PL_FLIGHT))) { // Revert to None choExtraneousPair2.setSelectedIndex(0); JOptionPane.showMessageDialog(this, - Messages.getString("CustomMekDialog.GliderWingsLimitExtraneousLimbs"), - Messages.getString("CustomMekDialog.GliderWingsLimitExtraneousLimbsTitle"), - JOptionPane.WARNING_MESSAGE); + Messages.getString("CustomMekDialog.GliderWingsLimitExtraneousLimbs"), + Messages.getString("CustomMekDialog.GliderWingsLimitExtraneousLimbsTitle"), + JOptionPane.WARNING_MESSAGE); } }); } @@ -726,7 +735,8 @@ private void addInlineExtraneousControls(DialogOptionComponentYPanel optionComp, * @param optionName The option that was toggled (MD_PL_ENHANCED, MD_PL_I_ENHANCED, or MD_PL_EXTRA_LIMBS) * @param isChecked Whether the checkbox is now checked */ - private void updateInlineProstheticVisibility(String optionName, boolean isChecked) { + private void updateInlineProstheticVisibility(String optionName, + boolean isChecked) { if (OptionsConstants.MD_PL_ENHANCED.equals(optionName)) { // Standard Enhanced uses its own slot 1 controls if (choProstheticTypeStd != null) { @@ -797,8 +807,10 @@ private void applyProstheticEnhancement(Entity entity) { /** * Applies a single prosthetic enhancement slot from UI to entity. */ - private void applyProstheticSlot(ConvInfantry infantry, JComboBox typeDropdown, - JSpinner countSpinner, boolean isSlot1) { + private void applyProstheticSlot(ConvInfantry infantry, + JComboBox typeDropdown, + JSpinner countSpinner, + boolean isSlot1) { int selectedIndex = typeDropdown.getSelectedIndex(); if (selectedIndex <= 0) { // "None" selected - clear this slot @@ -862,7 +874,9 @@ private void applyExtraneousLimbs(Entity entity) { /** * Applies a single extraneous limb pair from UI to entity. */ - private void applyExtraneousPair(ConvInfantry infantry, JComboBox typeDropdown, boolean isPair1) { + private void applyExtraneousPair(ConvInfantry infantry, + JComboBox typeDropdown, + boolean isPair1) { int selectedIndex = typeDropdown.getSelectedIndex(); if (selectedIndex <= 0) { // "None" selected - clear this pair @@ -895,7 +909,9 @@ private void applyExtraneousPair(ConvInfantry infantry, JComboBox typeDr } @Override - public void optionClicked(DialogOptionComponentYPanel comp, IOption option, boolean state) { + public void optionClicked(DialogOptionComponentYPanel comp, + IOption option, + boolean state) { // Enforce max 2 sensory implants rule for infantry // Defensive check for isConventionalInfantry in case options are set through other means Entity entity = entities.getFirst(); @@ -905,9 +921,9 @@ public void optionClicked(DialogOptionComponentYPanel comp, IOption option, bool // Revert the selection comp.setSelected(false); JOptionPane.showMessageDialog(this, - Messages.getString("CustomMekDialog.MaxSensoryImplants"), - Messages.getString("CustomMekDialog.MaxSensoryImplantsTitle"), - JOptionPane.WARNING_MESSAGE); + Messages.getString("CustomMekDialog.MaxSensoryImplants"), + Messages.getString("CustomMekDialog.MaxSensoryImplantsTitle"), + JOptionPane.WARNING_MESSAGE); } } @@ -931,47 +947,47 @@ public void optionClicked(DialogOptionComponentYPanel comp, IOption option, bool // When wings are deselected, re-enable extraneous pair 2 if no other wing type is active if (!state && (option.getName().equals(OptionsConstants.MD_PL_GLIDER) - || option.getName().equals(OptionsConstants.MD_PL_FLIGHT))) { + || option.getName().equals(OptionsConstants.MD_PL_FLIGHT))) { boolean anyWingsActive = isOptionSelected(OptionsConstants.MD_PL_GLIDER) - || isOptionSelected(OptionsConstants.MD_PL_FLIGHT); + || isOptionSelected(OptionsConstants.MD_PL_FLIGHT); updateExtraneousPair2ForWings(anyWingsActive); } // Update prosthetic enhancement inline control visibility when Enhanced/Improved Enhanced/Extraneous is toggled if (option.getName().equals(OptionsConstants.MD_PL_ENHANCED) - || option.getName().equals(OptionsConstants.MD_PL_I_ENHANCED) - || option.getName().equals(OptionsConstants.MD_PL_EXTRA_LIMBS)) { + || option.getName().equals(OptionsConstants.MD_PL_I_ENHANCED) + || option.getName().equals(OptionsConstants.MD_PL_EXTRA_LIMBS)) { updateInlineProstheticVisibility(option.getName(), state); } // Gas Effuser (Pheromone/Toxin) is only for Conventional Infantry (IO pg 79) if (state && !entity.isConventionalInfantry() - && (option.getName().equals(OptionsConstants.MD_GAS_EFFUSER_PHEROMONE) - || option.getName().equals(OptionsConstants.MD_GAS_EFFUSER_TOXIN))) { + && (option.getName().equals(OptionsConstants.MD_GAS_EFFUSER_PHEROMONE) + || option.getName().equals(OptionsConstants.MD_GAS_EFFUSER_TOXIN))) { comp.setSelected(false); JOptionPane.showMessageDialog(this, - Messages.getString("CustomMekDialog.GasEffuserInfantryOnly"), - Messages.getString("CustomMekDialog.GasEffuserInfantryOnlyTitle"), - JOptionPane.WARNING_MESSAGE); + Messages.getString("CustomMekDialog.GasEffuserInfantryOnly"), + Messages.getString("CustomMekDialog.GasEffuserInfantryOnlyTitle"), + JOptionPane.WARNING_MESSAGE); } // Can only have one Gas Effuser type at a time (IO pg 79) if (state && option.getName().equals(OptionsConstants.MD_GAS_EFFUSER_PHEROMONE) - && hasOtherGasEffuserSelected(comp, OptionsConstants.MD_GAS_EFFUSER_TOXIN)) { + && hasOtherGasEffuserSelected(comp, OptionsConstants.MD_GAS_EFFUSER_TOXIN)) { comp.setSelected(false); JOptionPane.showMessageDialog(this, - Messages.getString("CustomMekDialog.GasEffuserOnlyOne"), - Messages.getString("CustomMekDialog.GasEffuserOnlyOneTitle"), - JOptionPane.WARNING_MESSAGE); + Messages.getString("CustomMekDialog.GasEffuserOnlyOne"), + Messages.getString("CustomMekDialog.GasEffuserOnlyOneTitle"), + JOptionPane.WARNING_MESSAGE); } if (state && option.getName().equals(OptionsConstants.MD_GAS_EFFUSER_TOXIN) - && hasOtherGasEffuserSelected(comp, OptionsConstants.MD_GAS_EFFUSER_PHEROMONE)) { + && hasOtherGasEffuserSelected(comp, OptionsConstants.MD_GAS_EFFUSER_PHEROMONE)) { comp.setSelected(false); JOptionPane.showMessageDialog(this, - Messages.getString("CustomMekDialog.GasEffuserOnlyOne"), - Messages.getString("CustomMekDialog.GasEffuserOnlyOneTitle"), - JOptionPane.WARNING_MESSAGE); + Messages.getString("CustomMekDialog.GasEffuserOnlyOne"), + Messages.getString("CustomMekDialog.GasEffuserOnlyOneTitle"), + JOptionPane.WARNING_MESSAGE); } // DNI types are mutually exclusive - can only have one of VDNI, BVDNI, or Proto DNI @@ -981,32 +997,32 @@ && hasOtherGasEffuserSelected(comp, OptionsConstants.MD_GAS_EFFUSER_PHEROMONE)) // Glider wings limit extraneous limbs to one pair (IO p.85) if (state && option.getName().equals(OptionsConstants.MD_PL_GLIDER) - && entity.isConventionalInfantry()) { + && entity.isConventionalInfantry()) { updateExtraneousPair2ForGliderWings(true); } else if (!state && option.getName().equals(OptionsConstants.MD_PL_GLIDER) - && entity.isConventionalInfantry()) { + && entity.isConventionalInfantry()) { updateExtraneousPair2ForGliderWings(false); } // Proto DNI is BattleMek only (IO pg 83) if (state && option.getName().equals(OptionsConstants.MD_PROTO_DNI) - && !isValidForProtoDni(entity)) { + && !isValidForProtoDni(entity)) { comp.setSelected(false); JOptionPane.showMessageDialog(this, - Messages.getString("CustomMekDialog.ProtoDniBattleMekOnly"), - Messages.getString("CustomMekDialog.ProtoDniBattleMekOnlyTitle"), - JOptionPane.WARNING_MESSAGE); + Messages.getString("CustomMekDialog.ProtoDniBattleMekOnly"), + Messages.getString("CustomMekDialog.ProtoDniBattleMekOnlyTitle"), + JOptionPane.WARNING_MESSAGE); } // VDNI/BVDNI valid for BM, IM, BA, CV, SV, AF, CF (IO pg 71) if (state && (option.getName().equals(OptionsConstants.MD_VDNI) - || option.getName().equals(OptionsConstants.MD_BVDNI)) - && !isValidForVdni(entity)) { + || option.getName().equals(OptionsConstants.MD_BVDNI)) + && !isValidForVdni(entity)) { comp.setSelected(false); JOptionPane.showMessageDialog(this, - Messages.getString("CustomMekDialog.VdniInvalidUnitType"), - Messages.getString("CustomMekDialog.VdniInvalidUnitTypeTitle"), - JOptionPane.WARNING_MESSAGE); + Messages.getString("CustomMekDialog.VdniInvalidUnitType"), + Messages.getString("CustomMekDialog.VdniInvalidUnitTypeTitle"), + JOptionPane.WARNING_MESSAGE); } // EI Implant pilot option automatically adds/removes EI Interface equipment (IO p.69) @@ -1028,8 +1044,8 @@ && hasOtherGasEffuserSelected(comp, OptionsConstants.MD_GAS_EFFUSER_PHEROMONE)) // This ensures Equipment tab sees correct implant state when checking hasDNIImplant() // The equipment is not auto-added - user must confirm via the checkbox (IO p.83) if (option.getName().equals(OptionsConstants.MD_VDNI) - || option.getName().equals(OptionsConstants.MD_BVDNI) - || option.getName().equals(OptionsConstants.MD_PROTO_DNI)) { + || option.getName().equals(OptionsConstants.MD_BVDNI) + || option.getName().equals(OptionsConstants.MD_PROTO_DNI)) { // Update entity crew options directly (like EI does via setEIInterface) for (Entity e : entities) { e.getCrew().getOptions().getOption(option.getName()).setValue(state); @@ -1037,8 +1053,8 @@ && hasOtherGasEffuserSelected(comp, OptionsConstants.MD_GAS_EFFUSER_PHEROMONE)) // Also update Equipment tab checkbox if it exists if (equipChoicePanel != null) { boolean anyDniSelected = isOptionSelected(OptionsConstants.MD_VDNI) - || isOptionSelected(OptionsConstants.MD_BVDNI) - || isOptionSelected(OptionsConstants.MD_PROTO_DNI); + || isOptionSelected(OptionsConstants.MD_BVDNI) + || isOptionSelected(OptionsConstants.MD_PROTO_DNI); if (state) { anyDniSelected = true; } @@ -1052,9 +1068,9 @@ && hasOtherGasEffuserSelected(comp, OptionsConstants.MD_GAS_EFFUSER_PHEROMONE)) */ private boolean isSensoryImplant(String optionName) { return optionName.equals(OptionsConstants.MD_CYBER_IMP_AUDIO) - || optionName.equals(OptionsConstants.MD_CYBER_IMP_VISUAL) - || optionName.equals(OptionsConstants.MD_CYBER_IMP_LASER) - || optionName.equals(OptionsConstants.MD_CYBER_IMP_TELE); + || optionName.equals(OptionsConstants.MD_CYBER_IMP_VISUAL) + || optionName.equals(OptionsConstants.MD_CYBER_IMP_LASER) + || optionName.equals(OptionsConstants.MD_CYBER_IMP_TELE); } /** @@ -1067,7 +1083,7 @@ private int countSelectedSensoryImplants(DialogOptionComponentYPanel excludeComp continue; } if (isSensoryImplant(optComp.getOption().getName()) - && Boolean.TRUE.equals(optComp.getValue())) { + && Boolean.TRUE.equals(optComp.getValue())) { count++; } } @@ -1077,13 +1093,14 @@ private int countSelectedSensoryImplants(DialogOptionComponentYPanel excludeComp /** * Checks if another gas effuser of the specified type is already selected. */ - private boolean hasOtherGasEffuserSelected(DialogOptionComponentYPanel excludeComp, String otherEffuserName) { + private boolean hasOtherGasEffuserSelected(DialogOptionComponentYPanel excludeComp, + String otherEffuserName) { for (DialogOptionComponentYPanel optComp : panOptions.getOptionComponents()) { if (optComp == excludeComp) { continue; } if (optComp.getOption().getName().equals(otherEffuserName) - && Boolean.TRUE.equals(optComp.getValue())) { + && Boolean.TRUE.equals(optComp.getValue())) { return true; } } @@ -1131,9 +1148,9 @@ private void updateExtraneousPair2ForGliderWings(boolean gliderWingsEnabled) { if (choExtraneousPair2.getSelectedIndex() > 0) { choExtraneousPair2.setSelectedIndex(0); JOptionPane.showMessageDialog(this, - Messages.getString("CustomMekDialog.GliderWingsLimitExtraneousLimbs"), - Messages.getString("CustomMekDialog.GliderWingsLimitExtraneousLimbsTitle"), - JOptionPane.INFORMATION_MESSAGE); + Messages.getString("CustomMekDialog.GliderWingsLimitExtraneousLimbs"), + Messages.getString("CustomMekDialog.GliderWingsLimitExtraneousLimbsTitle"), + JOptionPane.INFORMATION_MESSAGE); } choExtraneousPair2.setEnabled(false); if (lblExtraneousPair2 != null) { @@ -1154,8 +1171,8 @@ private void updateExtraneousPair2ForGliderWings(boolean gliderWingsEnabled) { */ private boolean isDniOption(String optionName) { return optionName.equals(OptionsConstants.MD_VDNI) - || optionName.equals(OptionsConstants.MD_BVDNI) - || optionName.equals(OptionsConstants.MD_PROTO_DNI); + || optionName.equals(OptionsConstants.MD_BVDNI) + || optionName.equals(OptionsConstants.MD_PROTO_DNI); } /** @@ -1167,7 +1184,7 @@ private void deselectOtherDniOptions(DialogOptionComponentYPanel selectedComp) { continue; } if (isDniOption(optComp.getOption().getName()) - && Boolean.TRUE.equals(optComp.getValue())) { + && Boolean.TRUE.equals(optComp.getValue())) { optComp.setSelected(false); } } @@ -1197,7 +1214,9 @@ private boolean trainCarriesArtillery(Entity entity) { return false; } - /** Returns true when the unit mounts artillery or a capital missile bay, the weapons that can fire off board. */ + /** + * Returns true when the unit mounts artillery or a capital missile bay, the weapons that can fire off board. + */ private boolean carriesOffBoardWeapon(Entity entity) { for (WeaponMounted weapon : entity.getWeaponList()) { WeaponType weaponType = weapon.getType(); @@ -1222,11 +1241,11 @@ private boolean isValidForProtoDni(Entity entity) { */ private boolean isValidForVdni(Entity entity) { return entity.isMek() - || entity.isBattleArmor() - || entity.isCombatVehicle() - || entity.isSupportVehicle() - || entity.isAerospaceFighter() - || entity.isConventionalFighter(); + || entity.isBattleArmor() + || entity.isCombatVehicle() + || entity.isSupportVehicle() + || entity.isAerospaceFighter() + || entity.isConventionalFighter(); } /** @@ -1248,7 +1267,9 @@ private void updateExtraneousPair2ForWings(boolean hasWings) { } @Override - public void optionSwitched(DialogOptionComponentYPanel clickedComp, IOption option, int i) { + public void optionSwitched(DialogOptionComponentYPanel clickedComp, + IOption option, + int i) { // nothing implemented yet } @@ -1298,16 +1319,21 @@ private void refreshDeployment() { txtDeploymentWidth.setEnabled(false); choDeploymentRound.removeAllItems(); + int walkOnOffset = 0; + if (Game.rulesManager.getRulesGame().isWalkOnDeployment()) { + choDeploymentRound.addItem(Messages.getString("CustomMekDialog.PreGame")); + walkOnOffset = 1; + } choDeploymentRound.addItem(Messages.getString("CustomMekDialog.StartOfGame")); - if (entity.getDeployRound() < 1) { - choDeploymentRound.setSelectedIndex(0); + if (entity.getDeployRound() < 1 && entity.getDeployRound() >= 0) { + choDeploymentRound.setSelectedIndex(0 + walkOnOffset); } for (int i = 1; i <= 40; i++) { choDeploymentRound.addItem(Messages.getString("CustomMekDialog.AfterRound") + i); if (entity.getDeployRound() == i) { - choDeploymentRound.setSelectedIndex(i); + choDeploymentRound.setSelectedIndex(i + walkOnOffset); } } @@ -1331,7 +1357,7 @@ private void refreshDeployment() { if (client.getGame().getPhase().isLounge()) { for (int zoneID : ServerBoardHelper.getPossibleGameBoard(clientGUI.getClient().getMapSettings(), true) - .getCustomDeploymentZones()) { + .getCustomDeploymentZones()) { choDeploymentZone.addItem("Zone " + zoneID); } } @@ -1363,8 +1389,8 @@ private void refreshDeployment() { spinStartingAnySEy.setValue(y); boolean enableDeploymentZoneControls = choDeploymentZone.isEnabled() && - (choDeploymentZone.getSelectedIndex() > 0) && - (choDeploymentZone.getSelectedIndex() < Board.NUM_ZONES); + (choDeploymentZone.getSelectedIndex() > 0) && + (choDeploymentZone.getSelectedIndex() < Board.NUM_ZONES); txtDeploymentOffset.setEnabled(enableDeploymentZoneControls); txtDeploymentWidth.setEnabled(enableDeploymentZoneControls); @@ -1375,6 +1401,14 @@ private void refreshDeployment() { txtDeploymentWidth.setEnabled(editableDeployment); choDeploymentRound.setEnabled(editableDeployment); + if (Game.rulesManager.getRulesGame().restrictDeploymentWidth(null, choDeploymentZone.getSelectedIndex() - 1)) { + txtDeploymentWidth.setEnabled(false); + } else { + if (editableDeployment) { + txtDeploymentWidth.setEnabled(true); + } + } + chHidden.removeActionListener(this); boolean enableHidden = !(entity instanceof Dropship) && !entity.isAirborne() && !entity.isAirborneVTOLorWIGE(); labHidden.setEnabled(enableHidden); @@ -1403,8 +1437,8 @@ public void actionPerformed(ActionEvent actionEvent) { // Artillery bays can mix and match, so limit the bay // to the shortest range of the weapons in it int ratedRangeInMapSheets = (weaponType instanceof ArtilleryBayWeapon) - ? getBayShortestRange(weaponMount) - : weaponType.getLongRange(); + ? getBayShortestRange(weaponMount) + : weaponType.getLongRange(); distanceInHexes = offBoardArtilleryDistance(entity, weaponType, ratedRangeInMapSheets); } else if (weaponType.isCapital() || weaponType.isSubCapital()) { // Capital weapons use their maximum space hex range as the map sheet range. The range @@ -1427,11 +1461,11 @@ public void actionPerformed(ActionEvent actionEvent) { } SliderDialog sl = new SliderDialog(clientGUI.getFrame(), - Messages.getString("CustomMekDialog.offboardDistanceTitle"), - Messages.getString("CustomMekDialog.offboardDistanceQuestion"), - Math.clamp(entities.getFirst().getOffBoardDistance(), 17, maxDistance), - 17, - maxDistance); + Messages.getString("CustomMekDialog.offboardDistanceTitle"), + Messages.getString("CustomMekDialog.offboardDistanceQuestion"), + Math.clamp(entities.getFirst().getOffBoardDistance(), 17, maxDistance), + 17, + maxDistance); if (!sl.showDialog()) { return; } @@ -1463,9 +1497,8 @@ public void actionPerformed(ActionEvent actionEvent) { /** * @param weaponMount the artillery bay to measure - * * @return the rated range in map sheets of the shortest-ranged weapon in the bay, since that is as far as the whole - * bay can reach + * bay can reach */ private static int getBayShortestRange(WeaponMounted weaponMount) { int bayShortestRange = 150; // Cruise missile/120 @@ -1484,12 +1517,14 @@ private static int getBayShortestRange(WeaponMounted weaponMount) { * @param entity the unit being deployed off board * @param weaponType the type of the artillery weapon being measured * @param ratedRangeInMapSheets the artillery weapon's rated range, in map sheets - * * @return the greatest distance off board the unit may deploy, in hexes */ - private static int offBoardArtilleryDistance(Entity entity, WeaponType weaponType, int ratedRangeInMapSheets) { + private static int offBoardArtilleryDistance(Entity entity, + WeaponType weaponType, + int ratedRangeInMapSheets) { int rangeInHexes = ArtilleryRange.extendedRangeInHexes(ratedRangeInMapSheets, - ArtilleryRange.isExtendedByObliqueArtilleryman(entity, weaponType)); + ArtilleryRange.isExtendedByObliqueArtilleryman(entity, + weaponType)); // The play area itself takes up one map sheet of the weapon's reach return Math.max(rangeInHexes - Board.DEFAULT_BOARD_HEIGHT, 0); } @@ -1508,18 +1543,18 @@ protected void okAction() { boolean isGlider = true; for (Entity e : entities) { isAero &= ((e instanceof Aero) && !((e instanceof SmallCraft) || (e instanceof Jumpship))) || - ((e instanceof LandAirMek) && - (choStartingMode.getSelectedIndex() == 2 || - ((LandAirMek) e).getLAMType() == LandAirMek.LAM_BIMODAL && - choStartingMode.getSelectedIndex() == 1)); + ((e instanceof LandAirMek) && + (choStartingMode.getSelectedIndex() == 2 || + ((LandAirMek) e).getLAMType() == LandAirMek.LAM_BIMODAL && + choStartingMode.getSelectedIndex() == 1)); isShip &= (e instanceof SmallCraft) || (e instanceof Jumpship); isVTOL &= (e.getMovementMode() == EntityMovementMode.VTOL); isWiGE &= (e instanceof Tank) && (e.getMovementMode() == EntityMovementMode.WIGE); isQuadVee &= (e instanceof QuadVee); isLAM &= (e instanceof LandAirMek); isAirMek &= (e instanceof LandAirMek) && - (((LandAirMek) e).getLAMType() == LandAirMek.LAM_STANDARD) && - (choStartingMode.getSelectedIndex() == 1); + (((LandAirMek) e).getLAMType() == LandAirMek.LAM_STANDARD) && + (choStartingMode.getSelectedIndex() == 1); isGlider &= (e instanceof ProtoMek) && (e.getMovementMode() == EntityMovementMode.WIGE); } @@ -1550,25 +1585,25 @@ protected void okAction() { msg = Messages.getString("CustomMekDialog.EnterCorrectVelocity"); title = Messages.getString("CustomMekDialog.NumberFormatError"); JOptionPane.showMessageDialog(clientGUI == null ? this : clientGUI.getFrame(), - msg, - title, - JOptionPane.ERROR_MESSAGE); + msg, + title, + JOptionPane.ERROR_MESSAGE); return; } else if ((altitude < 0) || (altitude > 10)) { msg = Messages.getString("CustomMekDialog.EnterCorrectAltitude"); title = Messages.getString("CustomMekDialog.NumberFormatError"); JOptionPane.showMessageDialog(clientGUI == null ? this : clientGUI.getFrame(), - msg, - title, - JOptionPane.ERROR_MESSAGE); + msg, + title, + JOptionPane.ERROR_MESSAGE); return; } else if ((currentFuel < 0) || (currentFuel > fuel)) { msg = (Messages.getString("CustomMekDialog.EnterCorrectFuel") + fuel + "."); title = Messages.getString("CustomMekDialog.NumberFormatError"); JOptionPane.showMessageDialog(clientGUI == null ? this : clientGUI.getFrame(), - msg, - title, - JOptionPane.ERROR_MESSAGE); + msg, + title, + JOptionPane.ERROR_MESSAGE); return; } } @@ -1577,9 +1612,9 @@ protected void okAction() { msg = Messages.getString("CustomMekDialog.EnterCorrectHeight"); title = Messages.getString("CustomMekDialog.NumberFormatError"); JOptionPane.showMessageDialog(clientGUI == null ? this : clientGUI.getFrame(), - msg, - title, - JOptionPane.ERROR_MESSAGE); + msg, + title, + JOptionPane.ERROR_MESSAGE); return; } // Apply single-entity settings @@ -1625,33 +1660,33 @@ protected void okAction() { // keep these reasonable, please if ((gunnery < 0) || - (gunnery > 8) || - (piloting < 0) || - (piloting > 8) || - (gunneryL < 0) || - (gunneryL > 8) || - (gunneryM < 0) || - (gunneryM > 8) || - (gunneryB < 0) || - (gunneryB > 8) || - (gunneryAero < 0) || - (gunneryAero > 8) || - (pilotingAero < 0) || - (pilotingAero > 8) || - (gunneryAeroL < 0) || - (gunneryAeroL > 8) || - (gunneryAeroM < 0) || - (gunneryAeroM > 8) || - (gunneryAeroB < 0) || - (gunneryAeroB > 8) || - (artillery < 0) || - (artillery > 8)) { + (gunnery > 8) || + (piloting < 0) || + (piloting > 8) || + (gunneryL < 0) || + (gunneryL > 8) || + (gunneryM < 0) || + (gunneryM > 8) || + (gunneryB < 0) || + (gunneryB > 8) || + (gunneryAero < 0) || + (gunneryAero > 8) || + (pilotingAero < 0) || + (pilotingAero > 8) || + (gunneryAeroL < 0) || + (gunneryAeroL > 8) || + (gunneryAeroM < 0) || + (gunneryAeroM > 8) || + (gunneryAeroB < 0) || + (gunneryAeroB > 8) || + (artillery < 0) || + (artillery > 8)) { msg = Messages.getString("CustomMekDialog.EnterSkillsBetween0_8"); title = Messages.getString("CustomMekDialog.NumberFormatError"); JOptionPane.showMessageDialog(clientGUI == null ? this : clientGUI.getFrame(), - msg, - title, - JOptionPane.ERROR_MESSAGE); + msg, + title, + JOptionPane.ERROR_MESSAGE); return; } @@ -1776,13 +1811,13 @@ protected void okAction() { msg = Messages.getString("CustomMekDialog.OffboardDistance"); title = Messages.getString("CustomMekDialog.NumberFormatError"); JOptionPane.showMessageDialog(clientGUI == null ? this : clientGUI.getFrame(), - msg, - title, - JOptionPane.ERROR_MESSAGE); + msg, + title, + JOptionPane.ERROR_MESSAGE); return; } entity.setOffBoard(offBoardDistance, - OffBoardDirection.getDirection(choOffBoardDirection.getSelectedIndex())); + OffBoardDirection.getDirection(choOffBoardDirection.getSelectedIndex())); } else { entity.setOffBoard(0, OffBoardDirection.NONE); } @@ -1817,8 +1852,8 @@ protected void okAction() { } else if (choStartingMode.getSelectedIndex() == 1) { entity.setConversionMode(LandAirMek.CONV_MODE_FIGHTER); entity.setConversionMode(((LandAirMek) entity).getLAMType() == LandAirMek.LAM_BIMODAL ? - LandAirMek.CONV_MODE_FIGHTER : - LandAirMek.CONV_MODE_AIR_MEK); + LandAirMek.CONV_MODE_FIGHTER : + LandAirMek.CONV_MODE_AIR_MEK); } else { entity.setConversionMode(LandAirMek.CONV_MODE_MEK); } @@ -1829,13 +1864,27 @@ protected void okAction() { int zoneID = choDeploymentZone.getSelectedIndex() - 1; if (zoneID >= Board.NUM_ZONES) { zoneID = MathUtility.parseInt(Objects.requireNonNull(choDeploymentZone.getSelectedItem()) - .toString() - .substring(5), 0); + .toString() + .substring(5), 0); zoneID = Board.encodeCustomDeploymentZoneID(zoneID); } entity.setStartingPos(zoneID); - entity.setDeployRound(choDeploymentRound.getSelectedIndex()); + + // Handle if there is a pre-game item in the list for deployment when setting it. + if (choDeploymentRound.getSelectedItem().equals(Messages.getString("CustomMekDialog.PreGame"))) { + // They chose pre-game + entity.setDeployRound(Entity.DEPLOY_ROUND_PRE_GAME); + } else if (choDeploymentRound.getSelectedItem().equals(Messages.getString("CustomMekDialog.StartOfGame"))) { + // They chose start of game + entity.setDeployRound(0); + } else if (choDeploymentRound.getItemAt(0).equals(Messages.getString("CustomMekDialog.PreGame"))) { + // They chose a specific round and pre-game was an option + entity.setDeployRound(choDeploymentRound.getSelectedIndex() - 1); + } else { + // They chose a specific round and pre-game was not an option. + entity.setDeployRound(choDeploymentRound.getSelectedIndex()); + } entity.setStartingOffset(MathUtility.parseInt(txtDeploymentOffset.getText(), 0)); entity.setStartingWidth(MathUtility.parseInt(txtDeploymentWidth.getText(), 0)); @@ -1868,10 +1917,10 @@ protected void okAction() { // Should the infantry begin the game dug in? (TO:AR p.106; mechanized infantry cannot dig in.) if ((entity instanceof Infantry deployingInfantry) && !deployingInfantry.isMechanized() - && gameOptions().booleanOption(OptionsConstants.ADVANCED_TAC_OPS_DIG_IN)) { + && gameOptions().booleanOption(OptionsConstants.ADVANCED_TAC_OPS_DIG_IN)) { deployingInfantry.setDugIn(chDeployDugIn.isSelected() - ? Infantry.DUG_IN_COMPLETE - : Infantry.DUG_IN_NONE); + ? Infantry.DUG_IN_COMPLETE + : Infantry.DUG_IN_NONE); } } @@ -1906,10 +1955,15 @@ public void itemStateChanged(ItemEvent itemEvent) { if (itemEvent.getSource().equals(choDeploymentZone)) { boolean enableDeploymentZoneControls = choDeploymentZone.isEnabled() && - (choDeploymentZone.getSelectedIndex() > 0) && - choDeploymentZone.getSelectedIndex() < Board.NUM_ZONES; + (choDeploymentZone.getSelectedIndex() > 0) && + choDeploymentZone.getSelectedIndex() < Board.NUM_ZONES; txtDeploymentOffset.setEnabled(enableDeploymentZoneControls); txtDeploymentWidth.setEnabled(enableDeploymentZoneControls); + int deploymentZone = choDeploymentZone.getSelectedIndex() - 1; + if (enableDeploymentZoneControls && Game.rulesManager.getRulesGame() + .restrictDeploymentWidth(null, deploymentZone)) { + txtDeploymentWidth.setEnabled(false); + } } } @@ -1921,7 +1975,7 @@ private void updateStartingModeOptions() { } else if (entities.getFirst() instanceof LandAirMek) { int mode = index; if (((LandAirMek) entities.getFirst()).getLAMType() == LandAirMek.LAM_BIMODAL && - mode == LandAirMek.CONV_MODE_AIR_MEK) { + mode == LandAirMek.CONV_MODE_AIR_MEK) { mode = LandAirMek.CONV_MODE_FIGHTER; } labDeployProne.setEnabled(mode < LandAirMek.CONV_MODE_FIGHTER); @@ -1971,7 +2025,8 @@ private void setupEquip() { // No checkbox needed - the pilot option drives it (IO p.69) } - private void setStealth(Entity e, boolean stealthEnabled) { + private void setStealth(Entity e, + boolean stealthEnabled) { int newStealth = (stealthEnabled) ? 1 : 0; EquipmentMode newMode = (stealthEnabled) ? EquipmentMode.getMode("On") : EquipmentMode.getMode("Off"); for (MiscMounted m : e.getMiscEquipment(MiscType.F_STEALTH)) { @@ -1990,7 +2045,6 @@ private void setStealth(Entity e, boolean stealthEnabled) { * Availability is determined by the equipment's introduction date. * * @param entity the entity to check - * * @return true if the entity can have an EI Interface toggled */ private boolean canHaveEIInterface(Entity entity) { @@ -2019,7 +2073,8 @@ private boolean canHaveEIInterface(Entity entity) { * @param entity the entity to modify * @param enabled true to add EI Interface, false to remove it */ - private void setEIInterface(Entity entity, boolean enabled) { + private void setEIInterface(Entity entity, + boolean enabled) { boolean hasEI = entity.hasEiCockpit(); if (enabled && !hasEI) { @@ -2042,7 +2097,7 @@ private void setEIInterface(Entity entity, boolean enabled) { List> toRemove = new ArrayList<>(); for (Mounted mounted : entity.getEquipment()) { if ((mounted.getType() instanceof MiscType) && - mounted.getType().hasFlag(MiscType.F_EI_INTERFACE)) { + mounted.getType().hasFlag(MiscType.F_EI_INTERFACE)) { toRemove.add(mounted); } } @@ -2065,7 +2120,7 @@ protected Container createCenterPane() { final boolean isQuadVee = entities.stream().allMatch(e -> e instanceof QuadVee); final boolean isLAM = entities.stream().allMatch(e -> e instanceof LandAirMek); final boolean isGlider = entities.stream() - .allMatch(e -> (e instanceof ProtoMek) && e.getMovementMode().isWiGE()); + .allMatch(e -> (e instanceof ProtoMek) && e.getMovementMode().isWiGE()); final boolean hasStealth = entities.stream().allMatch(Entity::hasStealth); boolean eligibleForOffBoard = true; @@ -2109,10 +2164,10 @@ protected Container createCenterPane() { final boolean deploysWithItsTractor = e.getTractor() != Entity.NONE; final boolean entityEligibleForOffBoard = !space && - (e.getAltitude() == 0) && - !(e.isBuildingEntityOrGunEmplacement()) && - !deploysWithItsTractor && - trainCarriesArtillery(e); + (e.getAltitude() == 0) && + !(e.isBuildingEntityOrGunEmplacement()) && + !deploysWithItsTractor && + trainCarriesArtillery(e); eligibleForOffBoard &= entityEligibleForOffBoard; } // set up the panels @@ -2166,8 +2221,8 @@ protected Container createCenterPane() { if (this.clientGUI != null) { tabAll.addTab(Messages.getString(editableDeployment ? - "CustomMekDialog.tabDeployment" : - "CustomMekDialog.tabState"), new JScrollPane(panDeploy)); + "CustomMekDialog.tabDeployment" : + "CustomMekDialog.tabState"), new JScrollPane(panDeploy)); if (quirksEnabled && !multipleEntities) { JScrollPane scrQuirks = new JScrollPane(panQuirks); scrQuirks.getVerticalScrollBar().setUnitIncrement(16); @@ -2214,7 +2269,7 @@ protected Container createCenterPane() { } if (commandInitiative) { panCrewMember[0].addAdvancedRow(Messages.getString("CustomMekDialog.labCommandInit"), - fldCommandInit); + fldCommandInit); } panCrewMember[0].addClanPilotAdvancedRow(); panCrewMember[0].addAdvancedRow(Messages.getString("CustomMekDialog.labCommander"), chCommander); @@ -2224,22 +2279,22 @@ protected Container createCenterPane() { commandSection.setBorder(CustomPilotViewPanel.sectionBorder("CustomMekDialog.sectionCommand")); if (individualInitiative) { commandSection.add(new JLabel(Messages.getString("CustomMekDialog.labInit"), - SwingConstants.RIGHT), GBC.std()); + SwingConstants.RIGHT), GBC.std()); commandSection.add(fldInit, GBC.eop()); } if (commandInitiative) { commandSection.add(new JLabel(Messages.getString("CustomMekDialog.labCommandInit"), - SwingConstants.RIGHT), GBC.std()); + SwingConstants.RIGHT), GBC.std()); commandSection.add(fldCommandInit, GBC.eop()); } commandSection.add(new JLabel(Messages.getString("CustomMekDialog.labCommander"), - SwingConstants.RIGHT), GBC.std()); + SwingConstants.RIGHT), GBC.std()); commandSection.add(chCommander, GBC.eol()); panCrew.add(commandSection, GBC.eop().anchor(GridBagConstraints.NORTHWEST).insets(0, 0, 0, 10)); } panOptions = new PilotOptionsPanel(entities.getFirst(), editable, gameOptions(), this, - this::configureOptionRow); + this::configureOptionRow); panCrew.add(panOptions, GBC.eop().fill(GridBagConstraints.HORIZONTAL).weightX(1.0)); // **DEPLOYMENT TAB**// @@ -2261,7 +2316,7 @@ protected Container createCenterPane() { } if (isWiGE) { panDeploy.add(new JLabel(Messages.getString("CustomMekDialog.labDeployAirborne"), SwingConstants.RIGHT), - GBC.std()); + GBC.std()); panDeploy.add(chDeployAirborne, GBC.eol()); } if (isAero || isLAM || isShip) { @@ -2304,8 +2359,8 @@ protected Container createCenterPane() { txtDeploymentWidth.setColumns(4); if (gameOptions().booleanOption(OptionsConstants.RPG_BEGIN_SHUTDOWN) && - !(entity instanceof Infantry) && - !(entity.isBuildingEntityOrGunEmplacement())) { + !(entity instanceof Infantry) && + !(entity.isBuildingEntityOrGunEmplacement())) { panDeploy.add(labDeployShutdown, GBC.std()); panDeploy.add(chDeployShutdown, GBC.eol()); chDeployShutdown.setSelected(entity.isManualShutdown()); @@ -2327,8 +2382,8 @@ protected Container createCenterPane() { // offered only to hull-down-capable vehicles (not Large Vehicles, and not naval/hydrofoil/submarine units); // the deploy hex must still be fortified, which is validated at deployment time. boolean isHullDownCapableVehicle = (entity instanceof Tank deployingVehicle) - && deployingVehicle.isHullDownCapable() - && gameOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_HULL_DOWN); + && deployingVehicle.isHullDownCapable() + && gameOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_HULL_DOWN); if (isHullDownCapableVehicle) { panDeploy.add(labDeployHullDown, GBC.std()); panDeploy.add(chDeployHullDown, GBC.eol()); @@ -2339,7 +2394,7 @@ protected Container createCenterPane() { // Infantry may deploy already dug in (TO:AR p.106). Mechanized infantry cannot dig in, so this is // offered only to non-mechanized infantry and only when the dig-in option is enabled. if ((entity instanceof Infantry deployingInfantry) && !deployingInfantry.isMechanized() - && gameOptions().booleanOption(OptionsConstants.ADVANCED_TAC_OPS_DIG_IN)) { + && gameOptions().booleanOption(OptionsConstants.ADVANCED_TAC_OPS_DIG_IN)) { panDeploy.add(labDeployDugIn, GBC.std()); panDeploy.add(chDeployDugIn, GBC.eol()); chDeployDugIn.setSelected(deployingInfantry.getDugIn() == Infantry.DUG_IN_COMPLETE); @@ -2463,6 +2518,7 @@ private GameOptions gameOptions() { * scrolling works as usual. */ private static class WidthTrackingPanel extends JPanel implements Scrollable { + @Serial private static final long serialVersionUID = 3364552402022733440L; @@ -2478,12 +2534,16 @@ public Dimension getPreferredScrollableViewportSize() { } @Override - public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, + int direction) { return SCROLL_UNIT_INCREMENT; } @Override - public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + public int getScrollableBlockIncrement(Rectangle visibleRect, + int orientation, + int direction) { return (orientation == SwingConstants.VERTICAL) ? visibleRect.height : visibleRect.width; } diff --git a/megamek/src/megamek/client/ui/panels/GameOptionsPresentation.java b/megamek/src/megamek/client/ui/panels/GameOptionsPresentation.java index 4344f1dc056..7e05b35443b 100644 --- a/megamek/src/megamek/client/ui/panels/GameOptionsPresentation.java +++ b/megamek/src/megamek/client/ui/panels/GameOptionsPresentation.java @@ -160,7 +160,8 @@ private static void registerBasicOptions() { OptionsConstants.BASE_BLIND_DROP, OptionsConstants.BASE_REAL_BLIND_DROP, OptionsConstants.BASE_SET_ARTY_PLAYER_HOME_EDGE, - OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER_0); + OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER_0, + OptionsConstants.BASE_WALK_ON_DEPLOYMENT); register(BASIC, GENERAL_MATCH_SETUP, "general.matchSetup.interface", OptionsConstants.BASE_LOBBY_AMMO_DUMP, OptionsConstants.BASE_DUMPING_FROM_ROUND, @@ -547,6 +548,7 @@ private static PageDefinition nestedPage(String id, String categoryId, String ic int order) { return new PageDefinition(id, categoryId, iconGroupId, "", advanced, order); } + private static PageDefinition directPage(String id, String iconGroupId, boolean advanced, int order) { return new PageDefinition(id, "", iconGroupId, "", advanced, order); } diff --git a/megamek/src/megamek/client/ui/panels/phaseDisplay/DeploymentDisplay.java b/megamek/src/megamek/client/ui/panels/phaseDisplay/DeploymentDisplay.java index 78faa5d2ce8..f8abe86b718 100644 --- a/megamek/src/megamek/client/ui/panels/phaseDisplay/DeploymentDisplay.java +++ b/megamek/src/megamek/client/ui/panels/phaseDisplay/DeploymentDisplay.java @@ -60,8 +60,6 @@ import megamek.client.ui.clientGUI.boardview.overlay.ToastLevel; import megamek.client.ui.dialogs.phaseDisplay.AutomaticEjectionDialog; import megamek.client.ui.dialogs.phaseDisplay.BuildingFacingDialog; -import megamek.client.ui.dialogs.phaseDisplay.DeployElevationChoiceDialog; -import megamek.client.ui.dialogs.phaseDisplay.DeployFacingChoiceDialog; import megamek.client.ui.dialogs.phaseDisplay.EntityChoiceDialog; import megamek.client.ui.enums.DialogResult; import megamek.client.ui.util.CommandAction; @@ -69,7 +67,6 @@ import megamek.client.ui.util.MegaMekController; import megamek.client.ui.widget.MegaMekButton; import megamek.client.ui.widget.MekPanelTabStrip; -import megamek.common.Hex; import megamek.common.annotations.Nullable; import megamek.common.battleArmor.ProtoMekClampMount; import megamek.common.bays.Bay; @@ -77,7 +74,6 @@ import megamek.common.board.Board; import megamek.common.board.BoardLocation; import megamek.common.board.Coords; -import megamek.common.board.DeploymentElevationType; import megamek.common.board.ElevationOption; import megamek.common.board.FacingOption; import megamek.common.equipment.Transporter; @@ -94,11 +90,10 @@ import megamek.common.units.IAero; import megamek.common.units.Infantry; import megamek.common.units.Tank; -import megamek.common.units.Terrains; -import megamek.common.units.TrainLayout; import megamek.logging.MMLogger; public class DeploymentDisplay extends StatusBarPhaseDisplay { + private final static MMLogger logger = MMLogger.create(DeploymentDisplay.class); /** @@ -115,7 +110,8 @@ public enum DeployCommand implements PhaseCommand { DEPLOY_REMOVE("deployRemove"), DEPLOY_ASSAULT_DROP("assaultDrop"), DEPLOY_HULL_DOWN("deployHullDown"), - DEPLOY_DOCK("deployDock"); + DEPLOY_DOCK("deployDock"), + DEPLOY_CLEAR_DEPLOY("deployClear"); public final String cmd; @@ -172,8 +168,11 @@ public String getHotKeyDesc() { private int cen = Entity.NONE; // current entity number // is the shift key held? private boolean turnMode = false; + private int originalFacing = -1; private boolean assaultDropPreference = false; - /** Whether the crews-will-die-if-they-eject warning has already been given this deployment phase. */ + /** + * Whether the crews-will-die-if-they-eject warning has already been given this deployment phase. + */ private boolean hasWarnedAboutAutoEjection = false; private final Set lastHexDeploymentOptions = new HashSet<>(); private ElevationOption lastDeploymentOption = null; @@ -185,25 +184,8 @@ public String getHotKeyDesc() { * Represents the result of determining a deployment position. Contains the final elevation and facing for * deployment, or null if deployment was cancelled. */ - record DeploymentPosition(int elevation, int facing) { - } + record DeploymentPosition(int elevation, int facing, ElevationOption lastDeploymentOption) { - /** - * Represents the result of validating deployment on a board. - */ - enum BoardValidationResult { - /** Deployment is valid and can proceed */ - VALID, - /** Entity cannot deploy on this board type (e.g., space unit on ground board) */ - WRONG_BOARD_TYPE, - /** Coordinates are outside the allowed deployment area */ - OUTSIDE_DEPLOYMENT_AREA, - /** A hidden unit cannot deploy onto a fortified hex (the fortification would reveal the position) */ - HIDDEN_IN_FORTIFIED, - /** A vehicle set to deploy hull-down must start in a fortified hex and must not be a Large Vehicle */ - HULL_DOWN_NEEDS_FORTIFIED, - /** A tractor's trailers would land outside the deployment area; the whole train has to fit */ - TRAIN_DOES_NOT_FIT } /** @@ -276,10 +258,13 @@ private void turnUnit() { buttons.get(DeployCommand.DEPLOY_TURN).doClick(); } - /** Selects an entity for deployment. */ + /** + * Selects an entity for deployment. + */ public void selectEntity(int en) { lastHexDeploymentOptions.clear(); lastDeploymentOption = null; + originalFacing = -1; // hmm, sometimes this gets called when there's no ready entities? Entity entity = game.getEntity(en); @@ -308,49 +293,24 @@ public void selectEntity(int en) { labelTurnButtonFor(entity); butDone.setEnabled(false); markDeploymentHexes(entity); - // set facing according to starting position - switch (entity.getStartingPos()) { - case Board.START_W: - case Board.START_SW: - entity.setFacing(1); - entity.setSecondaryFacing(1); - break; - case Board.START_SE: - case Board.START_E: - entity.setFacing(5); - entity.setSecondaryFacing(5); - break; - case Board.START_NE: - entity.setFacing(4); - entity.setSecondaryFacing(4); - break; - case Board.START_N: - entity.setFacing(3); - entity.setSecondaryFacing(3); - break; - case Board.START_NW: - entity.setFacing(2); - entity.setSecondaryFacing(2); - break; - default: - entity.setFacing(0); - entity.setSecondaryFacing(0); - break; + DeploymentHelper facingHelper = new DeploymentHelper(clientgui); + if (game != null) { + facingHelper.setStartingFacing(entity, game.getPlayersList(), null); } boolean assaultDropOption = game.getOptions().booleanOption(OptionsConstants.ADVANCED_ASSAULT_DROP); setAssaultDropEnabled(entity.canAssaultDrop() && assaultDropOption); if (!entity.canAssaultDrop() && assaultDropOption) { buttons.get(DeployCommand.DEPLOY_ASSAULT_DROP) - .setText(Messages.getString("DeploymentDisplay.AssaultDrop")); + .setText(Messages.getString("DeploymentDisplay.AssaultDrop")); assaultDropPreference = false; } // Vehicles may deploy hull down (TO:AR p.19) onto a fortified hex. Offer a toggle so a player who set a // vehicle to deploy hull-down can turn it off when there is no fortified hex to deploy into. boolean hullDownOption = game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_HULL_DOWN); + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_HULL_DOWN); boolean canToggleHullDown = (entity instanceof Tank deployingVehicle) - && deployingVehicle.isHullDownCapable() && hullDownOption; + && deployingVehicle.isHullDownCapable() && hullDownOption; setHullDownEnabled(canToggleHullDown); updateHullDownButtonText(entity); @@ -388,21 +348,35 @@ private void computeWarningHexes(Entity ce) { clientgui.showCollapseWarning(warnList); } - /** Enables relevant buttons and sets up for the local player's turn. */ + /** + * Enables relevant buttons and sets up for the local player's turn. + */ private void beginMyTurn() { clientgui.maybeShowUnitDisplay(); selectEntity(clientgui.getClient().getFirstDeployableEntityNum()); setNextEnabled(true); setRemoveEnabled(true); + setClearEnabled(false); } - /** Clears out old deployment data and disables relevant buttons. */ + /** + * Enabled or disables the clear deployment button. + * + * @param enabled + */ + private void setClearEnabled(boolean enabled) { + buttons.get(DeployCommand.DEPLOY_CLEAR_DEPLOY).setEnabled(enabled); + } + + /** + * Clears out old deployment data and disables relevant buttons. + */ private void endMyTurn() { Entity next = game.getNextEntity(game.getTurnIndex()); if (game.getPhase().isDeployment() && - (null != next) && - (null != currentEntity()) && - (next.getOwnerId() != currentEntity().getOwnerId())) { + (null != next) && + (null != currentEntity()) && + (next.getOwnerId() != currentEntity().getOwnerId())) { clientgui.maybeShowUnitDisplay(); } cen = Entity.NONE; @@ -413,7 +387,9 @@ private void endMyTurn() { disableButtons(); } - /** Disables all buttons in the interface. */ + /** + * Disables all buttons in the interface. + */ private void disableButtons() { for (DeployCommand cmd : DeployCommand.values()) { setButtonEnabled(cmd, false); @@ -425,7 +401,8 @@ private void disableButtons() { setHullDownEnabled(false); } - private void setButtonEnabled(DeployCommand cmd, boolean enabled) { + private void setButtonEnabled(DeployCommand cmd, + boolean enabled) { MegaMekButton button = buttons.get(cmd); if (button != null) { button.setEnabled(enabled); @@ -448,10 +425,11 @@ private boolean checkNags() { allPositions.add(entity.getPosition()); allPositions.addAll(entity.getPosition().allAdjacent()); boolean crushesBuildingHex = allPositions.stream() - .anyMatch(c -> game.getBoard(entity.getBoardId()).getBuildingAt(c) != null); + .anyMatch(c -> game.getBoard(entity.getBoardId()) + .getBuildingAt(c) != null); if (crushesBuildingHex) { clientgui.addToast(ToastLevel.ERROR, - Messages.getString("DeploymentDisplay.dropshipBuildingDeploy"), entity); + Messages.getString("DeploymentDisplay.dropshipBuildingDeploy"), entity); return true; } } @@ -480,14 +458,15 @@ private boolean checkNags() { List unitsWithEjectionSystems = unitsWithAnEjectionSystem(); hasWarnedAboutAutoEjection = true; logger.debug("[EnvironmentalSealing] warning about auto-ejection - listing {} unit(s) with an ejection " - + "system in conditions that would kill an ejected crew", unitsWithEjectionSystems.size()); + + "system in conditions that would kill an ejected crew", unitsWithEjectionSystems.size()); AutomaticEjectionDialog ejectionDialog = new AutomaticEjectionDialog(clientgui.getFrame(), - clientgui, unitsWithEjectionSystems, - game.getPlanetaryConditions().whyLethalToEjectedCrew()); + clientgui, unitsWithEjectionSystems, + game.getPlanetaryConditions() + .whyLethalToEjectedCrew()); ejectionDialog.setVisible(true); if (ejectionDialog.isDeploymentCancelled()) { takeBackDeployment(entity, - Messages.getString("DeploymentDisplay.ConfirmAutoEject.cancelReason")); + Messages.getString("DeploymentDisplay.ConfirmAutoEject.cancelReason")); return true; } } @@ -505,7 +484,7 @@ public void ready() { int elevationOrAltitude = entity.isAero() ? entity.getAltitude() : entity.getElevation(); clientgui.getClient().deploy(entity.getId(), entity.getPosition(), entity.getBoardId(), entity.getFacing(), - elevationOrAltitude, entity.getLoadedUnits(), assaultDropPreference); + elevationOrAltitude, entity.getLoadedUnits(), assaultDropPreference); entity.setDeployed(true); if (entity.isWeaponOrderChanged()) { @@ -514,13 +493,21 @@ public void ready() { endMyTurn(); } - /** What the player chose to do about a unit the planetary conditions would destroy. */ + /** + * What the player chose to do about a unit the planetary conditions would destroy. + */ private enum DoomedDeploymentChoice { - /** Put it on the board anyway and accept the consequences. */ + /** + * Put it on the board anyway and accept the consequences. + */ DEPLOY_ANYWAY, - /** Take it out of the game, so it stops being offered for deployment. */ + /** + * Take it out of the game, so it stops being offered for deployment. + */ REMOVE_FROM_GAME, - /** Neither - lift it back off the board and let the player think again. */ + /** + * Neither - lift it back off the board and let the player think again. + */ CANCEL } @@ -535,26 +522,30 @@ private enum DoomedDeploymentChoice { * * @param entity the unit being deployed * @param reason the reason the conditions would destroy it, as {@code whyDoomed} gave it - * * @return what the player chose */ - private DoomedDeploymentChoice askAboutDoomedDeployment(Entity entity, String reason) { + private DoomedDeploymentChoice askAboutDoomedDeployment(Entity entity, + String reason) { JCheckBox dontAskAgain = new JCheckBox( - Messages.getString("DeploymentDisplay.ConfirmDoomed.dontAskAgain")); - Object[] message = { Messages.getString("DeploymentDisplay.ConfirmDoomed.message", - entity.getShortName(), reason), dontAskAgain }; - Object[] choices = { Messages.getString("DeploymentDisplay.ConfirmDoomed.deployAnyway"), - Messages.getString("DeploymentDisplay.ConfirmDoomed.removeFromGame"), - Messages.getString("DeploymentDisplay.ConfirmDoomed.cancel") }; + Messages.getString("DeploymentDisplay.ConfirmDoomed.dontAskAgain")); + Object[] message = { + Messages.getString("DeploymentDisplay.ConfirmDoomed.message", + entity.getShortName(), reason), dontAskAgain + }; + Object[] choices = { + Messages.getString("DeploymentDisplay.ConfirmDoomed.deployAnyway"), + Messages.getString("DeploymentDisplay.ConfirmDoomed.removeFromGame"), + Messages.getString("DeploymentDisplay.ConfirmDoomed.cancel") + }; int chosenIndex = JOptionPane.showOptionDialog(clientgui.getFrame(), - message, - Messages.getString("DeploymentDisplay.ConfirmDoomed.title"), - JOptionPane.DEFAULT_OPTION, - JOptionPane.WARNING_MESSAGE, - null, - choices, - choices[2]); + message, + Messages.getString("DeploymentDisplay.ConfirmDoomed.title"), + JOptionPane.DEFAULT_OPTION, + JOptionPane.WARNING_MESSAGE, + null, + choices, + choices[2]); if (dontAskAgain.isSelected()) { GUIP.setNagForDoomed(false); @@ -615,9 +606,9 @@ private boolean anyCrewWouldDieEjecting() { */ private List unitsWithAnEjectionSystem() { return game.getPlayerEntities(clientgui.getClient().getLocalPlayer(), false) - .stream() - .filter(AutomaticEjectionRules::hasEjectionSystem) - .toList(); + .stream() + .filter(AutomaticEjectionRules::hasEjectionSystem) + .toList(); } /** @@ -627,14 +618,15 @@ private List unitsWithAnEjectionSystem() { * @param entity the unit being taken back off the board * @param reason the reason the conditions would destroy it, as {@code whyDoomed} gave it */ - private void takeBackDeployment(Entity entity, String reason) { + private void takeBackDeployment(Entity entity, + String reason) { entity.setPosition(null); clientgui.boardViews().forEach(boardView -> ((BoardView) boardView).redrawEntity(entity)); clientgui.boardViews().forEach(IBoardView::repaint); butDone.setEnabled(false); clientgui.addToast(ToastLevel.INFO, - Messages.getString("DeploymentDisplay.doomedDeploymentCancelled", reason), - entity); + Messages.getString("DeploymentDisplay.doomedDeploymentCancelled", reason), + entity); } /** @@ -660,7 +652,9 @@ private void moveOnToNextDeployableUnit() { selectEntity(client.getNextDeployableEntityNum(cen)); } - /** Sends an entity removal to the server. */ + /** + * Sends an entity removal to the server. + */ private void remove() { disableButtons(); clientgui.getClient().sendDeleteEntity(cen); @@ -677,7 +671,9 @@ private void remove() { cen = Entity.NONE; } - /** Returns the current entity. */ + /** + * Returns the current entity. + */ private Entity currentEntity() { return game.getEntity(cen); } @@ -702,8 +698,8 @@ public void gameTurnChange(GameTurnChangeEvent e) { // We want to ignore turns from other players and only listen to events we generated // Except on the first turn if (game.getPhase().isSimultaneous(game) && - (e.getPreviousPlayerId() != clientgui.getClient().getLocalPlayerNumber()) && - (game.getTurnIndex() != 0)) { + (e.getPreviousPlayerId() != clientgui.getClient().getLocalPlayerNumber()) && + (game.getTurnIndex() != 0)) { return; } @@ -765,61 +761,19 @@ private void markDeploymentHexes(@Nullable Entity entity) { * @param event The board view event * @param coords The coordinates from the event * @param entity The current entity being deployed (may be null) - * * @return true if the event should be processed, false if it should be ignored */ - boolean shouldProcessDeployment(BoardViewEvent event, Coords coords, @Nullable Entity entity) { + boolean shouldProcessDeployment(BoardViewEvent event, + Coords coords, + @Nullable Entity entity) { return !isIgnoringEvents() && - game.hasBoardLocation(coords, event.getBoardId()) && - (entity != null) && - clientgui.getClient().isMyTurn() && - (event.getType() == BoardViewEvent.BOARD_HEX_DRAGGED) && - (event.getButton() == MouseEvent.BUTTON1) && - ((event.getModifiers() & InputEvent.CTRL_DOWN_MASK) == 0) && - ((event.getModifiers() & InputEvent.ALT_DOWN_MASK) == 0); - } - - /** - * Validates whether an entity can deploy on the given board at the specified coordinates. - * - * @param entity The entity to deploy - * @param board The board to deploy on - * @param coords The coordinates for deployment - * - * @return VALID if deployment can proceed, WRONG_BOARD_TYPE or OUTSIDE_DEPLOYMENT_AREA otherwise - */ - BoardValidationResult validateDeploymentBoard(Entity entity, Board board, Coords coords) { - if (entity.isBoardProhibited(board)) { - return BoardValidationResult.WRONG_BOARD_TYPE; - } - if (!(board.isLegalDeployment(coords, entity) || assaultDropPreference)) { - return BoardValidationResult.OUTSIDE_DEPLOYMENT_AREA; - } - // A train deploys as one piece, so every hex it would occupy has to be legal, not just the tractor's. - if (!entity.getAllTowedUnits().isEmpty() && !assaultDropPreference) { - for (Coords trainHex : TrainLayout.deploymentFootprint(game, entity, coords, entity.getFacing())) { - if (!board.isLegalDeployment(trainHex, entity)) { - logger.info("[Train] {} cannot deploy at {} facing {}: trailer hex {} is outside the " - + "deployment area", entity.getShortName(), coords, entity.getFacing(), trainHex); - return BoardValidationResult.TRAIN_DOES_NOT_FIT; - } - } - } - // A hidden unit cannot start in a fortified hex - the fortification is visible terrain that would give - // the position away. (A unit may, however, deploy both dug in and hidden in concealing terrain.) - Hex deployHex = board.getHex(coords); - if (entity.isHidden() && (deployHex != null) && deployHex.containsTerrain(Terrains.FORTIFIED)) { - return BoardValidationResult.HIDDEN_IN_FORTIFIED; - } - // A vehicle set to deploy hull-down must start in a fortified ("infantry-built") hex; only that terrain lets - // a vehicle take cover, and Large Vehicles cannot use it at all (TO:AR p.19). - if ((entity instanceof Tank deployingVehicle) && entity.isHullDown()) { - boolean fortifiedHex = (deployHex != null) && deployHex.containsTerrain(Terrains.FORTIFIED); - if (deployingVehicle.isLargeVehicleForHullDown() || !fortifiedHex) { - return BoardValidationResult.HULL_DOWN_NEEDS_FORTIFIED; - } - } - return BoardValidationResult.VALID; + game.hasBoardLocation(coords, event.getBoardId()) && + (entity != null) && + clientgui.getClient().isMyTurn() && + (event.getType() == BoardViewEvent.BOARD_HEX_DRAGGED) && + (event.getButton() == MouseEvent.BUTTON1) && + ((event.getModifiers() & InputEvent.CTRL_DOWN_MASK) == 0) && + ((event.getModifiers() & InputEvent.ALT_DOWN_MASK) == 0); } /** @@ -829,7 +783,8 @@ BoardValidationResult validateDeploymentBoard(Entity entity, Board board, Coords * @param entity The entity to apply settings to * @param deploymentPosition The deployment position containing elevation and facing */ - void applyDeploymentToEntity(Entity entity, DeploymentPosition deploymentPosition) { + void applyDeploymentToEntity(Entity entity, + DeploymentPosition deploymentPosition) { // entity.isAero will check if a unit is a LAM in Fighter mode if ((entity instanceof IAero aero) && (entity.isAero())) { entity.setAltitude(deploymentPosition.elevation()); @@ -842,6 +797,7 @@ void applyDeploymentToEntity(Entity entity, DeploymentPosition deploymentPositio entity.setElevation(deploymentPosition.elevation()); } entity.setFacing(deploymentPosition.facing()); + lastDeploymentOption = deploymentPosition.lastDeploymentOption(); } /** @@ -853,7 +809,10 @@ void applyDeploymentToEntity(Entity entity, DeploymentPosition deploymentPositio * @param boardId The board ID where the entity was placed * @param shiftHeld Whether the shift key was held during placement */ - private void updateDeploymentUI(Entity entity, Coords coords, int boardId, boolean shiftHeld) { + private void updateDeploymentUI(Entity entity, + Coords coords, + int boardId, + boolean shiftHeld) { entity.setPosition(coords); entity.setBoardId(boardId); clientgui.boardViews().forEach(bv -> ((BoardView) bv).redrawAllEntities()); @@ -868,50 +827,18 @@ private void updateDeploymentUI(Entity entity, Coords coords, int boardId, boole } /** - * Determines the deployment position (elevation and facing) for an entity at the given coordinates. Handles user - * interaction for elevation and facing choices when multiple options are available. - * - * @param entity The entity being deployed - * @param coords The coordinates where deployment is attempted - * @param board The board on which deployment is occurring + * Once a multi-hex building is placed, a plain click on a hex next to it means "face that way" rather than + * "move there" (#7858): the player places the building, then clicks the direction it should face. A click + * further away still moves it. * - * @return DeploymentPosition with elevation and facing, or null if deployment was cancelled or invalid + * @param entity the unit being deployed + * @param coords the clicked hex + * @return {@code true} when the click sets the facing of an already placed building */ - private @Nullable DeploymentPosition determineDeploymentPosition(Entity entity, Coords coords, Board board) { - int finalElevation; - int finalFacing = entity.getFacing(); - var deploymentHelper = new AllowedDeploymentHelper(entity, coords, board, - board.getHex(coords), game); - List elevationOptions = deploymentHelper.findAllowedElevations(); - int FACING_ELEVATION = 0; // If we care about facing at other altitudes or elevations ever... - FacingOption facingOptions = deploymentHelper.findAllowedFacings(FACING_ELEVATION); - boolean validFacings = facingOptions != null && facingOptions.hasValidFacings(); - - if (elevationOptions.isEmpty() && !validFacings) { - showCannotDeployHereMessage(coords); - return null; - } else if (elevationOptions.size() == 1) { - finalElevation = elevationOptions.getFirst().elevation(); - updateDeploymentCache(elevationOptions, elevationOptions.getFirst()); - finalFacing = promptForFacingIfNeeded(facingOptions, finalFacing); - } else if (useLastDeployElevation(elevationOptions) && !coords.equals(entity.getPosition())) { - // When the player clicks the same hex again, always ask for the elevation - finalElevation = entity.isAero() ? entity.getAltitude() : entity.getElevation(); - } else if (elevationOptions.isEmpty() && validFacings) { - finalElevation = FACING_ELEVATION; // Only option in current implementation - finalFacing = promptForFacingIfNeeded(facingOptions, finalFacing); - } else { - ElevationOption elevationOption = showElevationChoiceDialog(elevationOptions); - if (elevationOption != null) { - updateDeploymentCache(elevationOptions, elevationOption); - finalElevation = elevationOption.elevation(); - finalFacing = promptForFacingIfNeeded(facingOptions, finalFacing); - } else { - return null; - } - } - - return new DeploymentPosition(finalElevation, finalFacing); + private static boolean isFacingClickOnPlacedBuilding(Entity entity, + Coords coords) { + return AllowedDeploymentHelper.hasFacingDependentFootprint(entity) + && (entity.getPosition().distance(coords) == 1); } // @@ -941,26 +868,21 @@ public void hexMoused(BoardViewEvent b) { return; } - BoardValidationResult validationResult = validateDeploymentBoard(entity, board, coords); - if (validationResult == BoardValidationResult.WRONG_BOARD_TYPE) { - showWrongBoardTypeMessage(board); - return; - } else if (validationResult == BoardValidationResult.OUTSIDE_DEPLOYMENT_AREA) { - showOutsideDeployAreaMessage(); - return; - } else if (validationResult == BoardValidationResult.HIDDEN_IN_FORTIFIED) { - showHiddenInFortifiedMessage(); - return; - } else if (validationResult == BoardValidationResult.HULL_DOWN_NEEDS_FORTIFIED) { - showHullDownNeedsFortifiedMessage(); - return; - } else if (validationResult == BoardValidationResult.TRAIN_DOES_NOT_FIT) { - showTrainDoesNotFitMessage(); + DeploymentHelper deploymentHelper = new DeploymentHelper(clientgui); + if (originalFacing == -1) { + deploymentHelper.setStartingFacing(entity, game.getPlayersList(), coords); + originalFacing = entity.getFacing(); + } + if (!deploymentHelper.checkDeployment(board, entity, coords, assaultDropPreference)) { return; } if (!board.isSpace()) { - DeploymentPosition deploymentPosition = determineDeploymentPosition(entity, coords, board); + DeploymentPosition deploymentPosition = deploymentHelper.determineDeploymentPosition(entity, + coords, + board, + lastHexDeploymentOptions, + lastDeploymentOption); if (deploymentPosition == null) { return; } @@ -968,134 +890,12 @@ public void hexMoused(BoardViewEvent b) { } updateDeploymentUI(entity, coords, b.getBoardId(), shiftHeld); + setClearEnabled(true); } finally { ToolTipManager.sharedInstance().setEnabled(true); } } - private @Nullable ElevationOption showElevationChoiceDialog(List elevationOptions) { - var dlg = new DeployElevationChoiceDialog(clientgui.getFrame(), elevationOptions); - DialogResult result = dlg.showDialog(); - if ((result == DialogResult.CONFIRMED) && (dlg.getFirstChoice() != null)) { - if (dlg.getFirstChoice().type() == DeploymentElevationType.ELEVATIONS_ABOVE) { - int elevation = showHighElevationChoiceDialog(); - return (elevation == -1) ? - null : - new ElevationOption(elevation, DeploymentElevationType.ELEVATIONS_ABOVE); - } else { - return dlg.getFirstChoice(); - } - } else { - return null; - } - } - - /** - * Shows a dialog allowing the user to choose a facing from the valid facings. For facing-dependent entities (like - * non-symmetrical multi-hex buildings), this allows the user to select which facing to deploy with. - * - * @param facingOption The FacingOption containing valid facings for the position - * - * @return The chosen facing (0-5), or -1 if cancelled or no valid facings - */ - private int showFacingChoiceDialog(FacingOption facingOption) { - if (facingOption == null || !facingOption.hasValidFacings()) { - return -1; - } - - var dlg = new DeployFacingChoiceDialog(clientgui.getFrame(), facingOption); - DialogResult result = dlg.showDialog(); - if ((result == DialogResult.CONFIRMED) && (dlg.getChosenFacing() != -1)) { - return dlg.getChosenFacing(); - } else { - return -1; - } - } - - private int showHighElevationChoiceDialog() { - String msg = Messages.getString("DeploymentDisplay.elevationChoice"); - String input = JOptionPane.showInputDialog(clientgui.getFrame(), msg); - try { - return Integer.parseInt(input); - } catch (Exception ex) { - return -1; - } - } - - /** - * @return True when the last chosen elevation can be re-used without asking again. This is true when the options - * for the current hex have no option that the previous hex didn't and the previous deployment option is - * available in the new hex. - */ - private boolean useLastDeployElevation(List currentOptions) { - return ((lastDeploymentOption != null) && - (lastDeploymentOption.type() == DeploymentElevationType.ELEVATIONS_ABOVE) && - isHighElevationAvailable(currentOptions, lastDeploymentOption.elevation())) || - ((currentOptions.size() <= lastHexDeploymentOptions.size()) && - lastHexDeploymentOptions.containsAll(currentOptions) && - currentOptions.contains(lastDeploymentOption)); - } - - private boolean isHighElevationAvailable(List currentOptions, int elevation) { - return currentOptions.stream() - .filter(o -> o.type() == DeploymentElevationType.ELEVATIONS_ABOVE) - .anyMatch(o -> o.elevation() <= elevation); - } - - private void showWrongBoardTypeMessage(Board board) { - String title = Messages.getString("DeploymentDisplay.alertDialog.title"); - String boardType = switch (board.getBoardType()) { - case CAPITAL_RADAR, RADAR -> "Radar"; - case SKY, SKY_WITH_TERRAIN -> "Atmospheric"; - case FAR_SPACE, NEAR_SPACE -> "Space"; - case GROUND -> "Ground"; - }; - String msg = Messages.getString("DeploymentDisplay.wrongMapType", currentEntity().getShortName(), boardType); - clientgui.addToast(ToastLevel.ERROR, msg, currentEntity()); - } - - private void showOutsideDeployAreaMessage() { - String msg = Messages.getString("DeploymentDisplay.outsideDeployArea"); - clientgui.addToast(ToastLevel.ERROR, msg); - } - - private void showHiddenInFortifiedMessage() { - String msg = Messages.getString("DeploymentDisplay.hiddenInFortified"); - clientgui.addToast(ToastLevel.WARNING, msg); - } - - private void showTrainDoesNotFitMessage() { - String msg = Messages.getString("DeploymentDisplay.trainDoesNotFit"); - clientgui.addToast(ToastLevel.WARNING, msg); - } - - private void showHullDownNeedsFortifiedMessage() { - String msg = Messages.getString("DeploymentDisplay.hullDownNeedsFortified"); - clientgui.addToast(ToastLevel.WARNING, msg); - } - - private void showCannotDeployHereMessage(Coords coords) { - String msg = Messages.getString("DeploymentDisplay.cantDeployInto", - currentEntity().getShortName(), - coords.getBoardNum()); - clientgui.addToast(ToastLevel.ERROR, msg, currentEntity()); - } - - /** - * Once a multi-hex building is placed, a plain click on a hex next to it means "face that way" rather than - * "move there" (#7858): the player places the building, then clicks the direction it should face. A click - * further away still moves it. - * - * @param entity the unit being deployed - * @param coords the clicked hex - * - * @return {@code true} when the click sets the facing of an already placed building - */ - private static boolean isFacingClickOnPlacedBuilding(Entity entity, Coords coords) { - return AllowedDeploymentHelper.hasFacingDependentFootprint(entity) - && (entity.getPosition().distance(coords) == 1); - } - /** * Turns the unit being deployed. A shift-click, or for a placed multi-hex building a click on a neighbouring * hex, turns it toward the clicked hex; the Turn button, for a multi-hex building, opens the chooser of facings @@ -1105,7 +905,9 @@ private static boolean isFacingClickOnPlacedBuilding(Entity entity, Coords coord * @param coords the clicked hex * @param viaTurnButton {@code true} when the Turn button was pressed rather than shift held */ - private void processTurn(Entity entity, Coords coords, boolean viaTurnButton) { + private void processTurn(Entity entity, + Coords coords, + boolean viaTurnButton) { if (AllowedDeploymentHelper.hasFacingDependentFootprint(entity)) { if (viaTurnButton) { turnBuildingToValidFacing(entity); @@ -1130,7 +932,8 @@ private void processTurn(Entity entity, Coords coords, boolean viaTurnButton) { * @param building the building being deployed * @param clicked the hex the player shift-clicked */ - private void turnBuildingToward(Entity building, Coords clicked) { + private void turnBuildingToward(Entity building, + Coords clicked) { turnMode = false; Coords position = building.getPosition(); int facing = position.direction(clicked); @@ -1143,13 +946,15 @@ private void turnBuildingToward(Entity building, Coords clicked) { boolean fits = (facingOptions != null) && facingOptions.getValidFacings().contains(facing); if (!fits) { logger.debug("[DeployBuilding] {} at {}: facing {} refused, part of the footprint would be off the map", - building.getShortName(), position.getBoardNum(), facing); - clientgui.addToast(ToastLevel.WARNING, Messages.getString("DeploymentDisplay.buildingCannotFace", - building.getShortName(), position.getBoardNum()), building); + building.getShortName(), position.getBoardNum(), facing); + clientgui.addToast(ToastLevel.WARNING, + Messages.getString("DeploymentDisplay.buildingCannotFace", + building.getShortName(), position.getBoardNum()), + building); return; } logger.debug("[DeployBuilding] {} at {}: turned to facing {}", building.getShortName(), - position.getBoardNum(), facing); + position.getBoardNum(), facing); applyBuildingFacing(building, facing); } @@ -1165,24 +970,27 @@ private void turnBuildingToValidFacing(Entity building) { FacingOption facingOptions = deploymentHelper.findAllowedFacings(building.getElevation()); if (facingOptions == null) { logger.debug("[DeployBuilding] {} fits in no facing at {}; turn refused", building.getShortName(), - position.getBoardNum()); - clientgui.addToast(ToastLevel.WARNING, Messages.getString("DeploymentDisplay.buildingCannotTurn", - building.getShortName(), position.getBoardNum()), building); + position.getBoardNum()); + clientgui.addToast(ToastLevel.WARNING, + Messages.getString("DeploymentDisplay.buildingCannotTurn", + building.getShortName(), position.getBoardNum()), + building); return; } logger.debug("[DeployBuilding] {} at {}: offering facings {}", building.getShortName(), - position.getBoardNum(), facingOptions.getValidFacings()); + position.getBoardNum(), facingOptions.getValidFacings()); // The same six-direction picker a tank turret uses, with the facings that do not fit greyed out var dialog = new BuildingFacingDialog(clientgui.getFrame(), clientgui, building, - facingOptions.getValidFacings()); + facingOptions.getValidFacings()); boolean confirmed = (dialog.showDialog() == DialogResult.CONFIRMED) - && (dialog.getChosenFacing() != BuildingFacingDialog.NO_FACING); + && (dialog.getChosenFacing() != BuildingFacingDialog.NO_FACING); if (confirmed) { applyBuildingFacing(building, dialog.getChosenFacing()); } } - private void applyBuildingFacing(Entity building, int facing) { + private void applyBuildingFacing(Entity building, + int facing) { building.setFacing(facing); building.setSecondaryFacing(facing); clientgui.boardViews().forEach(boardView -> ((BoardView) boardView).redrawEntity(building)); @@ -1206,7 +1014,7 @@ public void actionPerformed(ActionEvent evt) { } else if (actionCmd.equals(DeployCommand.DEPLOY_TURN.getCmd())) { Entity entity = currentEntity(); boolean placedBuilding = (entity != null) && (entity.getPosition() != null) - && AllowedDeploymentHelper.hasFacingDependentFootprint(entity); + && AllowedDeploymentHelper.hasFacingDependentFootprint(entity); if (placedBuilding) { // A placed multi-hex building offers its fitting facings straight away; no hex click is needed turnBuildingToValidFacing(entity); @@ -1221,11 +1029,12 @@ public void actionPerformed(ActionEvent evt) { if (!choices.isEmpty()) { // If we have multiple choices, display a selection dialog. Entity other = EntityChoiceDialog.showSingleChoiceDialog(clientgui.getFrame(), - "DeploymentDisplay.loadUnitDialog.title", - Messages.getString("DeploymentDisplay.loadUnitDialog.message", - currentEntity().getShortName(), - currentEntity().getUnusedString()), - choices); + "DeploymentDisplay.loadUnitDialog.title", + Messages.getString( + "DeploymentDisplay.loadUnitDialog.message", + currentEntity().getShortName(), + currentEntity().getUnusedString()), + choices); // Abort here if no Entity was generated if (other == null) { @@ -1246,20 +1055,20 @@ public void actionPerformed(ActionEvent evt) { int i = 0; for (Integer bn : bayChoices) { retVal[i++] = bn.toString() + - " (Free Slots: " + - (int) currentEntity().getBayById(bn).getUnused() + - ")"; + " (Free Slots: " + + (int) currentEntity().getBayById(bn).getUnused() + + ")"; } String title = Messages.getString("DeploymentDisplay.loadUnitBayNumberDialog.title"); String msg = Messages.getString("DeploymentDisplay.loadUnitBayNumberDialog.message", - currentEntity().getShortName()); + currentEntity().getShortName()); String bayString = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - msg, - title, - JOptionPane.QUESTION_MESSAGE, - null, - retVal, - null); + msg, + title, + JOptionPane.QUESTION_MESSAGE, + null, + retVal, + null); // No choice made? Bug out. if (bayString == null) { @@ -1283,17 +1092,19 @@ public void actionPerformed(ActionEvent evt) { int i = 0; for (Integer bn : bayChoices) { retVal[i++] = bn > 0 ? - Messages.getString("MovementDisplay.loadProtoClampMountDialog.rear") : - Messages.getString("MovementDisplay.loadProtoClampMountDialog.front"); + Messages.getString("MovementDisplay.loadProtoClampMountDialog.rear") : + Messages.getString("MovementDisplay.loadProtoClampMountDialog.front"); } String bayString = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.loadProtoClampMountDialog.message", - currentEntity().getShortName()), - Messages.getString("MovementDisplay.loadProtoClampMountDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - retVal, - null); + Messages.getString( + "MovementDisplay.loadProtoClampMountDialog.message", + currentEntity().getShortName()), + Messages.getString( + "MovementDisplay.loadProtoClampMountDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + retVal, + null); // No choice made? Bug out. if (bayString == null) { @@ -1301,7 +1112,7 @@ public void actionPerformed(ActionEvent evt) { } other.setTargetBay(bayString.equals(Messages.getString( - "MovementDisplay.loadProtoClampMountDialog.front")) ? 0 : 1); + "MovementDisplay.loadProtoClampMountDialog.front")) ? 0 : 1); // We need to update the entity here so that the server knows about our target // bay clientgui.getClient().sendUpdateEntity(other); @@ -1322,8 +1133,8 @@ public void actionPerformed(ActionEvent evt) { setUnloadEnabled(true); } else { clientgui.addToast(ToastLevel.ERROR, - Messages.getString("DeploymentDisplay.alertDialog1.message", - currentEntity().getShortName()), currentEntity()); + Messages.getString("DeploymentDisplay.alertDialog1.message", + currentEntity().getShortName()), currentEntity()); } } else if (actionCmd.equals(DeployCommand.DEPLOY_UNLOAD.getCmd())) { // Do we have anyone to unload? @@ -1332,11 +1143,12 @@ public void actionPerformed(ActionEvent evt) { if (!choices.isEmpty()) { Entity loaded = EntityChoiceDialog.showSingleChoiceDialog(clientgui.getFrame(), - "DeploymentDisplay.unloadUnitDialog.title", - Messages.getString("DeploymentDisplay.unloadUnitDialog.message", - currentEntity().getShortName(), - currentEntity().getUnusedString()), - choices); + "DeploymentDisplay.unloadUnitDialog.title", + Messages.getString( + "DeploymentDisplay.unloadUnitDialog.message", + currentEntity().getShortName(), + currentEntity().getUnusedString()), + choices); if (loaded != null) { if (loader.unload(loaded)) { @@ -1355,30 +1167,31 @@ public void actionPerformed(ActionEvent evt) { setLoadEnabled(!getLoadableEntities().isEmpty()); } else { logger.error("Could not unload {} from {}", - loaded.getShortName(), - currentEntity().getShortName()); + loaded.getShortName(), + currentEntity().getShortName()); } } } else { clientgui.addToast(ToastLevel.WARNING, - Messages.getString("DeploymentDisplay.alertDialog2.message", - currentEntity().getShortName()), currentEntity()); + Messages.getString("DeploymentDisplay.alertDialog2.message", + currentEntity().getShortName()), currentEntity()); } } else if (actionCmd.equals(DeployCommand.DEPLOY_REMOVE.getCmd())) { if (JOptionPane.showConfirmDialog(clientgui.getFrame(), - Messages.getString("DeploymentDisplay.removeUnit", currentEntity().getShortName()), - Messages.getString("DeploymentDisplay.removeTitle"), - JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { + Messages.getString("DeploymentDisplay.removeUnit", + currentEntity().getShortName()), + Messages.getString("DeploymentDisplay.removeTitle"), + JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { remove(); } } else if (actionCmd.equals(DeployCommand.DEPLOY_ASSAULT_DROP.getCmd())) { assaultDropPreference = !assaultDropPreference; if (assaultDropPreference) { buttons.get(DeployCommand.DEPLOY_ASSAULT_DROP) - .setText(Messages.getString("DeploymentDisplay.assaultDropOff")); + .setText(Messages.getString("DeploymentDisplay.assaultDropOff")); } else { buttons.get(DeployCommand.DEPLOY_ASSAULT_DROP) - .setText(Messages.getString("DeploymentDisplay.assaultDrop")); + .setText(Messages.getString("DeploymentDisplay.assaultDrop")); } } else if (actionCmd.equals(DeployCommand.DEPLOY_HULL_DOWN.getCmd())) { Entity entity = currentEntity(); @@ -1389,19 +1202,38 @@ public void actionPerformed(ActionEvent evt) { clientgui.getClient().sendUpdateEntity(entity); clientgui.getUnitDisplay().displayEntity(entity); } + } else if (actionCmd.equals(DeployCommand.DEPLOY_CLEAR_DEPLOY.getCmd())) { + Entity entity = currentEntity(); + if (entity != null) { + lastDeploymentOption = null; + lastHexDeploymentOptions.clear(); + clear(); + originalFacing = -1; + } } } - /** Sets the Hull Down deploy button label to reflect the entity's current hull-down state. */ + /** + * Sets the Hull Down deploy button label to reflect the entity's current hull-down state. + */ private void updateHullDownButtonText(Entity entity) { buttons.get(DeployCommand.DEPLOY_HULL_DOWN).setText(Messages.getString(entity.isHullDown() - ? "DeploymentDisplay.deployHullDownOff" - : "DeploymentDisplay.deployHullDown")); + ? "DeploymentDisplay.deployHullDownOff" + : "DeploymentDisplay.deployHullDown")); } @Override public void clear() { - beginMyTurn(); + clientgui.maybeShowUnitDisplay(); + if (currentEntity() != null) { + selectEntity(currentEntity().getId()); + currentEntity().setPosition(null); + clientgui.boardViews().forEach(bv -> ((BoardView) bv).redrawEntity(currentEntity())); + } + clientgui.boardViews().forEach(IBoardView::repaint); + setNextEnabled(true); + setRemoveEnabled(true); + setClearEnabled(false); } // @@ -1479,7 +1311,7 @@ private void setNextEnabled(boolean enabled) { */ private void labelTurnButtonFor(Entity entity) { String key = AllowedDeploymentHelper.hasFacingDependentFootprint(entity) - ? "DeploymentDisplay.deployFacing" : "DeploymentDisplay.deployTurn"; + ? "DeploymentDisplay.deployFacing" : "DeploymentDisplay.deployTurn"; buttons.get(DeployCommand.DEPLOY_TURN).setText(Messages.getString(key)); } @@ -1532,60 +1364,17 @@ private List getLoadableEntities() { List entities = game.getEntitiesVector(); for (Entity other : entities) { if (other.isSelectableThisTurn() && currentEntity().canLoad(other, false) - // We can't depend on the transport id to be set because we sent a server update - // before loading on the client side, and the loaded unit may have been reset - // by the resulting update from the server. - && !currentEntity().getLoadedUnits().contains(other) - // If you want to load a trailer into a DropShip or large support vehicle, do it - // in the lobby - // The 'load' button should not allow trailers - that's what 'tow' is for. - && !other.isTrailer()) { + // We can't depend on the transport id to be set because we sent a server update + // before loading on the client side, and the loaded unit may have been reset + // by the resulting update from the server. + && !currentEntity().getLoadedUnits().contains(other) + // If you want to load a trailer into a DropShip or large support vehicle, do it + // in the lobby + // The 'load' button should not allow trailers - that's what 'tow' is for. + && !other.isTrailer()) { choices.add(other); } } return choices; } - - /** - * Updates the deployment cache with the given elevation options and chosen option. This tracks the last deployment - * state to enable automatic re-use when clicking adjacent hexes with identical deployment options. - * - * @param elevationOptions All available elevation options for the hex - * @param chosenOption The elevation option that was chosen - */ - private void updateDeploymentCache(List elevationOptions, ElevationOption chosenOption) { - lastHexDeploymentOptions.clear(); - lastHexDeploymentOptions.addAll(elevationOptions); - lastDeploymentOption = chosenOption; - } - - /** - * Prompts the user to select a facing if needed, based on the available facing options. If all 6 facings are valid, - * no prompt is shown and the current facing is returned. If some facings are restricted, shows a dialog to let the - * user choose. - * - * @param facingOption The FacingOption containing valid facings, or null if not applicable - * @param currentFacing The entity's current facing - * - * @return The chosen facing (0-5), or currentFacing if no selection was made - */ - private int promptForFacingIfNeeded(FacingOption facingOption, int currentFacing) { - if (facingOption == null || !facingOption.hasValidFacings()) { - return currentFacing; - } - - // All 6 facings valid? Skip the dialog - if (facingOption.getValidFacingCount() == 6) { - return currentFacing; - } - - // Only one choice? Pick it. - if (facingOption.getValidFacingCount() == 1) { - return (int) facingOption.getValidFacings().toArray()[0]; - } - - // Show facing choice dialog - int chosenFacing = showFacingChoiceDialog(facingOption); - return (chosenFacing != -1) ? chosenFacing : currentFacing; - } } diff --git a/megamek/src/megamek/client/ui/panels/phaseDisplay/DeploymentHelper.java b/megamek/src/megamek/client/ui/panels/phaseDisplay/DeploymentHelper.java new file mode 100644 index 00000000000..a04a973b5d3 --- /dev/null +++ b/megamek/src/megamek/client/ui/panels/phaseDisplay/DeploymentHelper.java @@ -0,0 +1,441 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + * + * NOTICE: The MegaMek organization is a non-profit group of volunteers + * creating free software for the BattleTech community. + * + * MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks + * of The Topps Company, Inc. All Rights Reserved. + * + * Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of + * InMediaRes Productions, LLC. + * + * MechWarrior Copyright Microsoft Corporation. MegaMek was created under + * Microsoft's "Game Content Usage Rules" + * and it is not endorsed by or + * affiliated with Microsoft. + */ + +package megamek.client.ui.panels.phaseDisplay; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import megamek.client.ui.Messages; +import megamek.client.ui.clientGUI.ClientGUI; +import megamek.client.ui.clientGUI.boardview.overlay.ToastLevel; +import megamek.client.ui.dialogs.phaseDisplay.DeployElevationChoiceDialog; +import megamek.client.ui.dialogs.phaseDisplay.DeployFacingChoiceDialog; +import megamek.client.ui.enums.DialogResult; +import megamek.client.ui.panels.phaseDisplay.DeploymentDisplay.DeploymentPosition; +import megamek.common.Hex; +import megamek.common.Player; +import megamek.common.annotations.Nullable; +import megamek.common.board.AllowedDeploymentHelper; +import megamek.common.board.Board; +import megamek.common.board.Coords; +import megamek.common.board.DeploymentElevationType; +import megamek.common.board.ElevationOption; +import megamek.common.board.FacingOption; +import megamek.common.units.Entity; +import megamek.common.units.Tank; +import megamek.common.units.Terrains; +import megamek.common.units.TrainLayout; +import megamek.logging.MMLogger; + +public class DeploymentHelper { + + private final ClientGUI clientgui; + private Entity currentEntity; + + private static final MMLogger logger = MMLogger.create(DeploymentHelper.class); + + public DeploymentHelper(ClientGUI clientGUI) { + this.clientgui = clientGUI; + } + + public boolean checkDeployment(Board board, + Entity entity, + Coords coords, + boolean assaultDropPreference) { + BoardValidationResult validationResult = validateDeploymentBoard(entity, board, coords, assaultDropPreference); + if (validationResult == BoardValidationResult.WRONG_BOARD_TYPE) { + showWrongBoardTypeMessage(board, entity); + return false; + } else if (validationResult == BoardValidationResult.OUTSIDE_DEPLOYMENT_AREA) { + showOutsideDeployAreaMessage(); + return false; + } else if (validationResult == BoardValidationResult.HIDDEN_IN_FORTIFIED) { + showHiddenInFortifiedMessage(); + return false; + } else if (validationResult == BoardValidationResult.HULL_DOWN_NEEDS_FORTIFIED) { + showHullDownNeedsFortifiedMessage(); + return false; + } else if (validationResult == BoardValidationResult.TRAIN_DOES_NOT_FIT) { + showTrainDoesNotFitMessage(); + return false; + } + return true; + } + + /** + * Validates whether an entity can deploy on the given board at the specified coordinates. + * + * @param entity The entity to deploy + * @param board The board to deploy on + * @param coords The coordinates for deployment + * @return VALID if deployment can proceed, WRONG_BOARD_TYPE or OUTSIDE_DEPLOYMENT_AREA otherwise + */ + BoardValidationResult validateDeploymentBoard(Entity entity, + Board board, + Coords coords, + boolean assaultDropPreference) { + if (entity.isBoardProhibited(board)) { + return BoardValidationResult.WRONG_BOARD_TYPE; + } + if (!(board.isLegalDeployment(coords, entity) || assaultDropPreference)) { + return BoardValidationResult.OUTSIDE_DEPLOYMENT_AREA; + } + // A train deploys as one piece, so every hex it would occupy has to be legal, not just the tractor's. + if (!entity.getAllTowedUnits().isEmpty() && !assaultDropPreference) { + for (Coords trainHex : TrainLayout.deploymentFootprint(entity.getGame(), + entity, + coords, + entity.getFacing())) { + if (!board.isLegalDeployment(trainHex, entity)) { + logger.info("[Train] {} cannot deploy at {} facing {}: trailer hex {} is outside the " + + "deployment area", entity.getShortName(), coords, entity.getFacing(), trainHex); + return BoardValidationResult.TRAIN_DOES_NOT_FIT; + } + } + } + // A hidden unit cannot start in a fortified hex - the fortification is visible terrain that would give + // the position away. (A unit may, however, deploy both dug in and hidden in concealing terrain.) + Hex deployHex = board.getHex(coords); + if (entity.isHidden() && (deployHex != null) && deployHex.containsTerrain(Terrains.FORTIFIED)) { + return BoardValidationResult.HIDDEN_IN_FORTIFIED; + } + // A vehicle set to deploy hull-down must start in a fortified ("infantry-built") hex; only that terrain lets + // a vehicle take cover, and Large Vehicles cannot use it at all (TO:AR p.19). + if ((entity instanceof Tank deployingVehicle) && entity.isHullDown()) { + boolean fortifiedHex = (deployHex != null) && deployHex.containsTerrain(Terrains.FORTIFIED); + if (deployingVehicle.isLargeVehicleForHullDown() || !fortifiedHex) { + return BoardValidationResult.HULL_DOWN_NEEDS_FORTIFIED; + } + } + return BoardValidationResult.VALID; + } + + private void showWrongBoardTypeMessage(Board board, + Entity entity) { + String boardType = switch (board.getBoardType()) { + case CAPITAL_RADAR, RADAR -> "Radar"; + case SKY, SKY_WITH_TERRAIN -> "Atmospheric"; + case FAR_SPACE, NEAR_SPACE -> "Space"; + case GROUND -> "Ground"; + }; + String msg = Messages.getString("DeploymentDisplay.wrongMapType", + entity.getShortName(), + boardType); + clientgui.addToast(ToastLevel.ERROR, msg, entity); + } + + private void showOutsideDeployAreaMessage() { + String msg = Messages.getString("DeploymentDisplay.outsideDeployArea"); + clientgui.addToast(ToastLevel.ERROR, msg); + } + + private void showHiddenInFortifiedMessage() { + String msg = Messages.getString("DeploymentDisplay.hiddenInFortified"); + clientgui.addToast(ToastLevel.WARNING, msg); + } + + private void showTrainDoesNotFitMessage() { + String msg = Messages.getString("DeploymentDisplay.trainDoesNotFit"); + clientgui.addToast(ToastLevel.WARNING, msg); + } + + private void showHullDownNeedsFortifiedMessage() { + String msg = Messages.getString("DeploymentDisplay.hullDownNeedsFortified"); + clientgui.addToast(ToastLevel.WARNING, msg); + } + + /** + * Represents the result of validating deployment on a board. + */ + enum BoardValidationResult { + /** + * Deployment is valid and can proceed + */ + VALID, + /** + * Entity cannot deploy on this board type (e.g., space unit on ground board) + */ + WRONG_BOARD_TYPE, + /** + * Coordinates are outside the allowed deployment area + */ + OUTSIDE_DEPLOYMENT_AREA, + /** + * A hidden unit cannot deploy onto a fortified hex (the fortification would reveal the position) + */ + HIDDEN_IN_FORTIFIED, + /** + * A vehicle set to deploy hull-down must start in a fortified hex and must not be a Large Vehicle + */ + HULL_DOWN_NEEDS_FORTIFIED, + /** + * A tractor's trailers would land outside the deployment area; the whole train has to fit + */ + TRAIN_DOES_NOT_FIT + } + + /** + * Determines the deployment position (elevation and facing) for an entity at the given coordinates. Handles user + * interaction for elevation and facing choices when multiple options are available. + * + * @param entity The entity being deployed + * @param coords The coordinates where deployment is attempted + * @param board The board on which deployment is occurring + * @return DeploymentPosition with elevation and facing, or null if deployment was cancelled or invalid + */ + public @Nullable DeploymentPosition determineDeploymentPosition(Entity entity, + Coords coords, + Board board, + Set lastHexDeploymentOptions, + ElevationOption lastDeploymentOption) { + currentEntity = entity; + int finalElevation; + int finalFacing = entity.getFacing(); + var deploymentHelper = new AllowedDeploymentHelper(entity, coords, board, + board.getHex(coords), entity.getGame()); + List elevationOptions = deploymentHelper.findAllowedElevations(); + int FACING_ELEVATION = 0; // If we care about facing at other altitudes or elevations ever... + FacingOption facingOptions = deploymentHelper.findAllowedFacings(FACING_ELEVATION); + boolean validFacings = facingOptions != null && facingOptions.hasValidFacings(); + + if (elevationOptions.isEmpty() && !validFacings) { + showCannotDeployHereMessage(coords); + return null; + } else if (elevationOptions.size() == 1) { + finalElevation = elevationOptions.getFirst().elevation(); + lastHexDeploymentOptions.clear(); + lastHexDeploymentOptions.addAll(elevationOptions); + lastDeploymentOption = elevationOptions.getFirst(); + finalFacing = promptForFacingIfNeeded(facingOptions, finalFacing); + } else if (useLastDeployElevation(elevationOptions, + lastDeploymentOption, + lastHexDeploymentOptions) && !coords.equals(entity.getPosition())) { + // When the player clicks the same hex again, always ask for the elevation + finalElevation = entity.isAero() ? entity.getAltitude() : entity.getElevation(); + } else if (elevationOptions.isEmpty() && validFacings) { + finalElevation = FACING_ELEVATION; // Only option in current implementation + finalFacing = promptForFacingIfNeeded(facingOptions, finalFacing); + } else { + ElevationOption elevationOption = showElevationChoiceDialog(elevationOptions); + if (elevationOption != null) { + lastHexDeploymentOptions.clear(); + lastHexDeploymentOptions.addAll(elevationOptions); + lastDeploymentOption = elevationOption; + finalElevation = elevationOption.elevation(); + finalFacing = promptForFacingIfNeeded(facingOptions, finalFacing); + } else { + return null; + } + } + + return new DeploymentPosition(finalElevation, finalFacing, lastDeploymentOption); + } + + private @Nullable ElevationOption showElevationChoiceDialog(List elevationOptions) { + var dlg = new DeployElevationChoiceDialog(clientgui.getFrame(), + elevationOptions); + DialogResult result = dlg.showDialog(); + if ((result == DialogResult.CONFIRMED) && (dlg.getFirstChoice() != null)) { + if (dlg.getFirstChoice().type() == DeploymentElevationType.ELEVATIONS_ABOVE) { + int elevation = showHighElevationChoiceDialog(); + return (elevation == -1) ? + null : + new ElevationOption(elevation, DeploymentElevationType.ELEVATIONS_ABOVE); + } else { + return dlg.getFirstChoice(); + } + } else { + return null; + } + } + + /** + * Shows a dialog allowing the user to choose a facing from the valid facings. For facing-dependent entities (like + * non-symmetrical multi-hex buildings), this allows the user to select which facing to deploy with. + * + * @param facingOption The FacingOption containing valid facings for the position + * @return The chosen facing (0-5), or -1 if cancelled or no valid facings + */ + private int showFacingChoiceDialog(FacingOption facingOption) { + if (facingOption == null || !facingOption.hasValidFacings()) { + return -1; + } + + var dlg = new DeployFacingChoiceDialog(clientgui.getFrame(), + facingOption); + DialogResult result = dlg.showDialog(); + if ((result == DialogResult.CONFIRMED) && (dlg.getChosenFacing() != -1)) { + return dlg.getChosenFacing(); + } else { + return -1; + } + } + + private int showHighElevationChoiceDialog() { + String msg = Messages.getString("DeploymentDisplay.elevationChoice"); + String input = javax.swing.JOptionPane.showInputDialog(clientgui.getFrame(), msg); + try { + return Integer.parseInt(input); + } catch (Exception ex) { + return -1; + } + } + + /** + * @return True when the last chosen elevation can be re-used without asking again. This is true when the options + * for the current hex have no option that the previous hex didn't and the previous deployment option is + * available in the new hex. + */ + private boolean useLastDeployElevation(List currentOptions, + ElevationOption lastDeploymentOption, + Set lastHexDeploymentOptions) { + return ((lastDeploymentOption != null) && + (lastDeploymentOption.type() == DeploymentElevationType.ELEVATIONS_ABOVE) && + isHighElevationAvailable(currentOptions, lastDeploymentOption.elevation())) || + ((currentOptions.size() <= lastHexDeploymentOptions.size()) && + lastHexDeploymentOptions.containsAll(currentOptions) && + currentOptions.contains(lastDeploymentOption)); + } + + private boolean isHighElevationAvailable(List currentOptions, + int elevation) { + return currentOptions.stream() + .filter(o -> o.type() == DeploymentElevationType.ELEVATIONS_ABOVE) + .anyMatch(o -> o.elevation() <= elevation); + } + + private void showCannotDeployHereMessage(Coords coords) { + String msg = Messages.getString("DeploymentDisplay.cantDeployInto", + currentEntity.getShortName(), + coords.getBoardNum()); + clientgui.addToast(ToastLevel.ERROR, msg, currentEntity); + } + + /** + * Prompts the user to select a facing if needed, based on the available facing options. If all 6 facings are valid, + * no prompt is shown and the current facing is returned. If some facings are restricted, shows a dialog to let the + * user choose. + * + * @param facingOption The FacingOption containing valid facings, or null if not applicable + * @param currentFacing The entity's current facing + * @return The chosen facing (0-5), or currentFacing if no selection was made + */ + private int promptForFacingIfNeeded(FacingOption facingOption, + int currentFacing) { + if (facingOption == null || !facingOption.hasValidFacings()) { + return currentFacing; + } + + // All 6 facings valid? Skip the dialog + if (facingOption.getValidFacingCount() == 6) { + return currentFacing; + } + + // Only one choice? Pick it. + if (facingOption.getValidFacingCount() == 1) { + return (int) facingOption.getValidFacings().toArray()[0]; + } + + // Show facing choice dialog + int chosenFacing = showFacingChoiceDialog(facingOption); + return (chosenFacing != -1) ? chosenFacing : currentFacing; + } + + public void setStartingFacing(Entity entity, + List players, @Nullable Coords sourceCoords) { + int playerStartingPos = entity.getStartingPos(); + int enemyStartingPos = -1; + int enemyTeamNumber = -1; + int teamCount = 0; + List enemiesList = new ArrayList(); + for (Player player : players) { + if (player.getId() == entity.getOwnerId()) { + continue; + } + if (entity.getOwner().isEnemyOf(player)) { + enemiesList.add(player); + if (enemyTeamNumber != player.getTeam()) { + enemyTeamNumber = player.getTeam(); + teamCount++; + } + } + } + int facing = -1; + if (teamCount == 1) { + // All the enemies share the same starting team, and assuming starting position + enemyStartingPos = enemiesList.get(0).getStartingPos(); + Coords entityStartingPos = + (sourceCoords != null) ? sourceCoords : + entity.getGame().getBoard().getDeploymentCenter(entity.getStartingPos()); + Coords enemyDeploymentCenter = entity.getGame().getBoard().getDeploymentCenter(enemyStartingPos); + facing = entityStartingPos.direction(enemyDeploymentCenter); + entity.setFacing(facing); + entity.setSecondaryFacing(facing); + } else { + // With multiple opposing teams, it is likely a free for all, so go with default facing. + // set facing according to starting position + switch (playerStartingPos) { + case Board.START_W: + case Board.START_SW: + facing = 1; + break; + case Board.START_SE: + case Board.START_E: + facing = 5; + break; + case Board.START_NE: + facing = 4; + break; + case Board.START_N: + facing = 3; + break; + case Board.START_NW: + facing = 2; + break; + default: + facing = 0; + break; + } + } + if (facing != -1) { + entity.setFacing(facing); + entity.setSecondaryFacing(facing); + } else { + entity.setFacing(0); + entity.setSecondaryFacing(0); + } + + } +} diff --git a/megamek/src/megamek/client/ui/panels/phaseDisplay/MovementDisplay.java b/megamek/src/megamek/client/ui/panels/phaseDisplay/MovementDisplay.java index 11e54a66a76..9d308c51d25 100644 --- a/megamek/src/megamek/client/ui/panels/phaseDisplay/MovementDisplay.java +++ b/megamek/src/megamek/client/ui/panels/phaseDisplay/MovementDisplay.java @@ -63,6 +63,7 @@ import megamek.client.ui.dialogs.ChoiceDialog; import megamek.client.ui.dialogs.ConfirmDialog; import megamek.client.ui.dialogs.phaseDisplay.*; +import megamek.client.ui.panels.phaseDisplay.DeploymentDisplay.DeploymentPosition; import megamek.client.ui.panels.phaseDisplay.commands.MoveCommand; import megamek.client.ui.util.CommandAction; import megamek.client.ui.util.KeyCommandBind; @@ -95,6 +96,7 @@ import megamek.common.board.BoardLocation; import megamek.common.board.BridgeConstruction; import megamek.common.board.Coords; +import megamek.common.board.ElevationOption; import megamek.common.compute.Compute; import megamek.common.compute.ComputeArc; import megamek.common.enums.MoveStepType; @@ -135,6 +137,11 @@ import megamek.logging.MMLogger; public class MovementDisplay extends ActionPhaseDisplay { + + private record DeploymentAnchor(Coords coords, int boardId, int elevation, int facing) { + + } + private static final MMLogger LOGGER = MMLogger.create(MovementDisplay.class); @Serial @@ -160,22 +167,31 @@ public class MovementDisplay extends ActionPhaseDisplay { public static final int CMD_GROUND = CMD_MEK | CMD_TANK | CMD_VTOL | CMD_INF | CMD_PROTOMEK; public static final int CMD_NON_VECTORED = CMD_MEK | CMD_TANK | CMD_VTOL | CMD_INF | CMD_AERO | CMD_PROTOMEK; public static final int CMD_ALL = CMD_MEK | - CMD_TANK | - CMD_VTOL | - CMD_INF | - CMD_AERO | - CMD_AERO_VECTORED | - CMD_PROTOMEK; + CMD_TANK | + CMD_VTOL | + CMD_INF | + CMD_AERO | + CMD_AERO_VECTORED | + CMD_PROTOMEK; public static final int CMD_NON_INF = CMD_MEK | CMD_TANK | CMD_VTOL | CMD_AERO | CMD_AERO_VECTORED | CMD_PROTOMEK; public static int NO_UNIT_SELECTED = -1; private boolean isUnJammingRAC; private boolean isUsingChaff; + private int originalFacing = -1; - /** True when selecting a hex for Combat Vehicle Escape Pod landing (TO:AUE p.121) */ + // Deployment settings. + private final Set lastHexDeploymentOptions = new HashSet<>(); + private ElevationOption lastDeploymentOption = null; + + /** + * True when selecting a hex for Combat Vehicle Escape Pod landing (TO:AUE p.121) + */ private boolean isSelectingEscapePodLanding; - /** Valid hexes for escape pod landing (rear arc, 0-4 hexes) */ + /** + * Valid hexes for escape pod landing (rear arc, 0-4 hexes) + */ private Set validEscapePodHexes = new HashSet<>(); /** @@ -183,24 +199,56 @@ public class MovementDisplay extends ActionPhaseDisplay { * engineer's hex: the player first clicks the hex the bridge will occupy (SECTION), then clicks the far end the * bridge reaches to (DIRECTION). Two clicks, with the near bank fixed at the engineer. */ - private enum BridgeSelectionStage {NONE, SECTION, DIRECTION} + private enum BridgeSelectionStage { + NONE, + SECTION, + DIRECTION + } + + private @Nullable DeploymentAnchor deploymentAnchor(@Nullable MovePath path) { + if (path == null) { + return null; + } + for (MoveStep step : path.getStepVector()) { + if (step.getType() == MoveStepType.DEPLOY) { + return new DeploymentAnchor(step.getPosition(), + step.getBoardId(), + step.getElevation(), + step.getFacing()); + } + } + return null; + } + /** * One legal bridge a platoon could raise: the bridge occupies {@code middle} (adjacent to the engineer), with its * near bank {@code start} fixed at the engineer's hex and reaching the far bank {@code end}; {@code exits} is the * resulting hexside bitmask. */ - private record BridgeBuildPlan(Coords start, Coords middle, Coords end, int exits) {} + private record BridgeBuildPlan(Coords start, Coords middle, Coords end, int exits) { + + } - /** The current stage of bridge build hex selection, or NONE when not declaring a build. */ + /** + * The current stage of bridge build hex selection, or NONE when not declaring a build. + */ private BridgeSelectionStage bridgeSelectionStage = BridgeSelectionStage.NONE; - /** The bridge type chosen for the bridge build being declared. */ + /** + * The bridge type chosen for the bridge build being declared. + */ private int selectedBridgeType; - /** The hex the bridge will occupy (the "section"), chosen as the first click of the build being declared. */ + /** + * The hex the bridge will occupy (the "section"), chosen as the first click of the build being declared. + */ private Coords selectedBridgeMiddle; - /** Every legal bridge the selected platoon could raise this turn, computed when the build is declared. */ + /** + * Every legal bridge the selected platoon could raise this turn, computed when the build is declared. + */ private final List bridgeBuildPlans = new ArrayList<>(); - /** Valid hexes for the current bridge build selection stage. */ + /** + * Valid hexes for the current bridge build selection stage. + */ private final Set validBridgeSelectionHexes = new HashSet<>(); // buttons @@ -259,11 +307,17 @@ private record BridgeBuildPlan(Coords start, Coords middle, Coords end, int exit public static final int GEAR_LONGEST_RUN = 10; public static final int GEAR_LONGEST_WALK = 11; public static final int GEAR_STRAFE = 12; - /** Used to designate a ground map flight path for an aero on an atmospheric map. */ + /** + * Used to designate a ground map flight path for an aero on an atmospheric map. + */ public static final int GEAR_FLIGHTPATH = 13; - /** Used to choose a ground map hex for landing an aero from an atmospheric map (aero-on-ground move off). */ + /** + * Used to choose a ground map hex for landing an aero from an atmospheric map (aero-on-ground move off). + */ public static final int GEAR_LANDING_AERO = 14; - /** Used to designate the rubble hex a bulldozer vehicle drives into and clears (TacOps). */ + /** + * Used to designate the rubble hex a bulldozer vehicle drives into and clears (TacOps). + */ public static final int GEAR_CLEAR_RUBBLE = 15; public static final int GEAR_SUB_STANDARD = 0; public static final int GEAR_SUB_MEK_BOOSTERS = 2; @@ -338,7 +392,8 @@ private void turnRight() { * @param target target coordinate to which this movement is being executed * @param finalBoardId id of the final board where the movement ends */ - private void currentMoveHoldingShift(Coords target, int finalBoardId) { + private void currentMoveHoldingShift(Coords target, + int finalBoardId) { shiftHeld = true; currentMove(target, finalBoardId); shiftHeld = false; @@ -365,10 +420,18 @@ private void computeEnvelope() { } private void cancel() { - clear(); + boolean keepDeployment = true; + if (cmd != null) { + boolean jumpDeploy = ((cmd.length() == 2 && + cmd.getLastStep().getType() == MoveStepType.START_JUMP) || + (cmd.length() == 3 && cmd.getLastStep() + .getType() == MoveStepType.JUMP_MEK_MECHANICAL_BOOSTER)); + keepDeployment = ((cmd.length() > 1) && !jumpDeploy && (deploymentAnchor(cmd) != null)); + } + clear(keepDeployment); Entity currentEntity = currentEntity(); - if (currentEntity != null) { + if ((currentEntity != null) && currentEntity.isDeployed()) { computeMovementEnvelope(currentEntity); clientgui.updateFiringArc(currentEntity); } @@ -448,11 +511,11 @@ private void registerKeyCommands() { controller.registerCommandAction(KeyCommandBind.UNDO_SINGLE_STEP, this, this::undoLastStep); controller.registerCommandAction(KeyCommandBind.NEXT_UNIT, - this, - () -> selectEntity(clientgui.getClient().getNextEntityNum(currentEntity))); + this, + () -> selectEntity(clientgui.getClient().getNextEntityNum(currentEntity))); controller.registerCommandAction(KeyCommandBind.PREV_UNIT, - this, - () -> selectEntity(clientgui.getClient().getPrevEntityNum(currentEntity))); + this, + () -> selectEntity(clientgui.getClient().getPrevEntityNum(currentEntity))); controller.registerCommandAction(KeyCommandBind.CANCEL, this::shouldPerformClearKeyCommand, this::cancel); controller.registerCommandAction(KeyCommandBind.TOGGLE_MOVE_MODE, this, this::performToggleMovementMode); @@ -462,13 +525,13 @@ private void registerKeyCommands() { controller.registerCommandAction(KeyCommandBind.MOVE_STEP_FORWARD, this, this::moveStepForward); controller.registerCommandAction(KeyCommandBind.MOVE_STEP_BACKWARD, this, this::moveStepBackward); controller.registerCommandAction(KeyCommandBind.TOGGLE_CONVERSION_MODE, - this, - this::performToggleConversionMode); + this, + this::performToggleConversionMode); } /** * @return the button list: we need to determine what unit type is selected and then get a button list appropriate - * for that unit. + * for that unit. */ @Override protected ArrayList getButtonList() { @@ -507,7 +570,7 @@ protected ArrayList getButtonList() { } else if ((currentlySelectedEntity instanceof Mek) && ((Mek) currentlySelectedEntity).hasTracks()) { flag = CMD_MEK | CMD_CONVERTER; } else if ((currentlySelectedEntity instanceof ProtoMek) && - currentlySelectedEntity.getMovementMode().isWiGE()) { + currentlySelectedEntity.getMovementMode().isWiGE()) { flag = CMD_PROTOMEK | CMD_MEK | CMD_AIR_MEK; } else if (currentlySelectedEntity instanceof ProtoMek) { flag = CMD_PROTOMEK; @@ -524,7 +587,7 @@ private ArrayList getButtonList(int flag) { if (clientgui != null) { Player localPlayer = clientgui.getClient().getLocalPlayer(); forwardIni = (game.getTeamForPlayer(localPlayer) != null) && - (game.getTeamForPlayer(localPlayer).size() > 1); + (game.getTeamForPlayer(localPlayer).size() > 1); gameOptions = game.getOptions(); } @@ -572,6 +635,10 @@ public synchronized void selectNextPlayer() { */ public void selectEntity(int entityID) { final Entity selectedEntity = game.getEntity(entityID); + lastHexDeploymentOptions.clear(); + lastDeploymentOption = null; + originalFacing = -1; + if (selectedEntity == null) { LOGGER.error("Tried to select non-existent entity with id {}", entityID); return; @@ -592,13 +659,22 @@ public void selectEntity(int entityID) { updateButtonsLater(); LOGGER.debug("[CLIMB-TRACE] selectEntity: {} isClimbing={}, elevation={}, position={}, facing={}", - selectedEntity.getDisplayName(), selectedEntity.isClimbing(), - selectedEntity.getElevation(), selectedEntity.getPosition(), selectedEntity.getFacing()); + selectedEntity.getDisplayName(), selectedEntity.isClimbing(), + selectedEntity.getElevation(), selectedEntity.getPosition(), selectedEntity.getFacing()); clientgui.boardViews().forEach(IBoardView::clearMarkedHexes); - clientgui.getBoardView(selectedEntity).highlight(selectedEntity.getPosition()); + if (selectedEntity.getPosition() != null) { + clientgui.getBoardView(selectedEntity).highlight(selectedEntity.getPosition()); + } if (!clientgui.isCurrentBoardViewShowingAnimation()) { - clientgui.centerOnUnit(selectedEntity); + if (selectedEntity.getPosition() != null) { + clientgui.centerOnUnit(selectedEntity); + } else { + Coords coords = game.getBoard().getDeploymentCenter(selectedEntity.getStartingPos()); + BoardLocation location = new BoardLocation(coords, game.getBoard().getBoardId(), false); + clientgui.centerOnHex(location); + } + } initializeStatusBarText(selectedEntity); @@ -616,6 +692,27 @@ public void selectEntity(int entityID) { if ((selectedEntity instanceof Mek climbingMek) && climbingMek.isClimbing()) { SwingUtilities.invokeLater(() -> promptContinueClimbing(climbingMek)); } + + // If it has not deployed yet, draw the deployment + if (Game.rulesManager.getRulesGame().isWalkOnDeployment() && !selectedEntity.isDeployed()) { + markDeploymentHexes(selectedEntity); + DeploymentHelper facingHelper = new DeploymentHelper(clientgui); + if (game != null && Game.rulesManager.getRulesGame().canWalkOnThisRound(selectedEntity)) { + facingHelper.setStartingFacing(selectedEntity, game.getPlayersList(), null); + } + } else if (Game.rulesManager.getRulesGame().isWalkOnDeployment() && selectedEntity.isDeployed()) { + markDeploymentHexes(null); + } + + } + + /** + * Used to mark deployment area + * + * @param entity the entity to mark the area. Null clears it + */ + private void markDeploymentHexes(@Nullable Entity entity) { + clientgui.boardViews().forEach(bv -> ((BoardView) bv).markDeploymentHexesFor(entity)); } /** @@ -626,11 +723,11 @@ private void promptContinueClimbing(Mek climbingMek) { // Check if the Mek can still climb after taking damage if (!ClimbingHelper.canClimb(climbingMek)) { String fallMessage = climbingMek.getDisplayName() - + " can no longer hold on and will fall!"; + + " can no longer hold on and will fall!"; clientgui.addToast(ToastLevel.ERROR, fallMessage, climbingMek); JOptionPane.showMessageDialog(clientgui.getFrame(), fallMessage, - Messages.getString("MovementDisplay.ClimbingDialog.title"), - JOptionPane.WARNING_MESSAGE); + Messages.getString("MovementDisplay.ClimbingDialog.title"), + JOptionPane.WARNING_MESSAGE); // Submit empty movement - server will handle the auto-fall ready(); return; @@ -644,29 +741,29 @@ private void promptContinueClimbing(Mek climbingMek) { if (chosen.type() == ClimbingChoiceDialog.ClimbingActionType.CLING) { // Explicit choice to cling in place — forfeits movement this turn. clientgui.addToast(ToastLevel.INFO, - Messages.getString("MovementDisplay.ClimbingDialog.clingToast", - climbingMek.getDisplayName()), - climbingMek); + Messages.getString("MovementDisplay.ClimbingDialog.clingToast", + climbingMek.getDisplayName()), + climbingMek); ready(); return; } if (chosen.type() == ClimbingChoiceDialog.ClimbingActionType.DANGLE_DOWN) { LOGGER.debug("[DANGLE-TRACE] Dangle down chosen: entity={}, currentElevation={}, dangleLevels={}", - climbingMek.getDisplayName(), climbingMek.getElevation(), chosen.levels()); + climbingMek.getDisplayName(), climbingMek.getElevation(), chosen.levels()); clientgui.addToast(ToastLevel.INFO, - Messages.getString("MovementDisplay.ClimbingDialog.dangleToast", - climbingMek.getDisplayName(), chosen.levels()), - climbingMek); + Messages.getString("MovementDisplay.ClimbingDialog.dangleToast", + climbingMek.getDisplayName(), chosen.levels()), + climbingMek); cmd.addStep(MoveStepType.DOWN); ready(); return; } if (chosen.type() == ClimbingChoiceDialog.ClimbingActionType.DROP) { LOGGER.debug("[DANGLE-TRACE] Drop chosen: entity={}, currentElevation={}", - climbingMek.getDisplayName(), climbingMek.getElevation()); + climbingMek.getDisplayName(), climbingMek.getElevation()); clientgui.addToast(ToastLevel.WARNING, - buildDropToastText(climbingMek), - climbingMek); + buildDropToastText(climbingMek), + climbingMek); cmd.addStep(MoveStepType.DOWN); cmd.addStep(MoveStepType.DOWN); ready(); @@ -678,11 +775,11 @@ private void promptContinueClimbing(Mek climbingMek) { // from a dangle (1 bare DOWN) or drop (2 bare DOWN) on the server side — // entity.climbingLevelsChosen isn't transmitted, so we encode intent in the path. LOGGER.debug("[CLIMB-TRACE] Climb down chosen: entity={}, currentElevation={}, levels={}", - climbingMek.getDisplayName(), climbingMek.getElevation(), chosen.levels()); + climbingMek.getDisplayName(), climbingMek.getElevation(), chosen.levels()); clientgui.addToast(ToastLevel.INFO, - Messages.getString("MovementDisplay.ClimbingDialog.climbDownToast", - climbingMek.getDisplayName(), chosen.levels()), - climbingMek); + Messages.getString("MovementDisplay.ClimbingDialog.climbDownToast", + climbingMek.getDisplayName(), chosen.levels()), + climbingMek); climbingMek.setClimbingLevelsChosen(chosen.levels()); // Push the chosen-level count to the server — the entity field isn't carried // by the path, and the server uses 0 (= full climb) without this update. @@ -698,9 +795,9 @@ private void promptContinueClimbing(Mek climbingMek) { // Previously this called updateMove() which left the path editable, but a stray // Climb Mode toggle would invalidate and silently strip the climbing FORWARDS step. clientgui.addToast(ToastLevel.INFO, - Messages.getString("MovementDisplay.ClimbingDialog.continueClimbToast", - climbingMek.getDisplayName(), chosen.levels()), - climbingMek); + Messages.getString("MovementDisplay.ClimbingDialog.continueClimbToast", + climbingMek.getDisplayName(), chosen.levels()), + climbingMek); climbingMek.setClimbingLevelsChosen(chosen.levels()); // Push to server — without this, the server reads chosenLevels=0 and climbs the // maximum affordable instead of the player's chosen count. @@ -719,18 +816,18 @@ private void promptContinueClimbing(Mek climbingMek) { private String buildDropToastText(Mek mek) { if (mek.isDangling()) { return Messages.getString("MovementDisplay.ClimbingDialog.dropDangleToast", - mek.getDisplayName()); + mek.getDisplayName()); } Hex hex = game.getBoard(mek).getHex(mek.getPosition()); boolean atWaterSurface = (hex != null) - && (mek.getElevation() == 0) - && (hex.floor() < hex.getLevel()); + && (mek.getElevation() == 0) + && (hex.floor() < hex.getLevel()); if (atWaterSurface) { return Messages.getString("MovementDisplay.ClimbingDialog.sinkToast", - mek.getDisplayName()); + mek.getDisplayName()); } return Messages.getString("MovementDisplay.ClimbingDialog.dropClimbToast", - mek.getDisplayName()); + mek.getDisplayName()); } private void initializeStatusBarText(Entity selectedEntity) { @@ -784,7 +881,7 @@ private void updateButtonsLater() { /** * @return True when the active unit has jump MP available, either standard jump jet MP or, in the case of Meks, MP - * of Mechanical Jump Boosters. + * of Mechanical Jump Boosters. */ private boolean hasJumpMP() { return (currentEntity() != null) && (currentEntity().getAnyTypeMaxJumpMP() > 0); @@ -796,15 +893,15 @@ private boolean hasJumpMP() { * * @param unit the currently selected unit * @param gameOptions the active game options - * * @return true if the unit is infantry on a ground map at ground level with the dig in / hit the deck option on */ - private boolean infantryGroundPostureAvailable(Entity unit, GameOptions gameOptions) { + private boolean infantryGroundPostureAvailable(Entity unit, + GameOptions gameOptions) { return (unit instanceof Infantry) - && gameOptions.booleanOption(OptionsConstants.ADVANCED_TAC_OPS_DIG_IN) - && game.isOnGroundMap(unit) - && (unit.getAltitude() == 0) - && (unit.getElevation() == 0); + && gameOptions.booleanOption(OptionsConstants.ADVANCED_TAC_OPS_DIG_IN) + && game.isOnGroundMap(unit) + && (unit.getAltitude() == 0) + && (unit.getElevation() == 0); } /** @@ -823,22 +920,22 @@ private void updateButtons() { boolean isAero = selectedUnit.isAero(); setWalkEnabled(!selectedUnit.isImmobile() && - ((selectedUnit.getWalkMP() > 0) || (selectedUnit.getRunMP() > 0)) && - !selectedUnit.isStuck()); + ((selectedUnit.getWalkMP() > 0) || (selectedUnit.getRunMP() > 0)) && + !selectedUnit.isStuck()); // Conventional infantry also uses jump MP for VTOL and UMU MP setJumpEnabled(!isAero && - !selectedUnit.isImmobileForJump() && - !selectedUnit.isProne() && - (hasJumpMP() && - (!selectedUnit.isConventionalInfantry() || - selectedUnit.getMovementMode().isJumpInfantry())) && - !(selectedUnit.isStuck() && !selectedUnit.canUnstickByJumping())); + !selectedUnit.isImmobileForJump() && + !selectedUnit.isProne() && + (hasJumpMP() && + (!selectedUnit.isConventionalInfantry() || + selectedUnit.getMovementMode().isJumpInfantry())) && + !(selectedUnit.isStuck() && !selectedUnit.canUnstickByJumping())); setSwimEnabled(!isAero && - !selectedUnit.isImmobile() && - (selectedUnit.getActiveUMUCount() > 0) && - selectedUnit.isUnderwater()); + !selectedUnit.isImmobile() && + (selectedUnit.getActiveUMUCount() > 0) && + selectedUnit.isUnderwater()); setBackUpEnabled(!isAero && isEnabled(MoveCommand.MOVE_WALK)); setChargeEnabled(selectedUnit.canCharge()); @@ -852,24 +949,29 @@ private void updateButtons() { } boolean entityNotAbleToClimb = Stream.of(EntityMovementMode.HYDROFOIL, - EntityMovementMode.NAVAL, - EntityMovementMode.SUBMARINE, - EntityMovementMode.INF_UMU, - EntityMovementMode.VTOL, - EntityMovementMode.BIPED_SWIM, - EntityMovementMode.QUAD_SWIM) - .noneMatch(entityMovementMode -> (selectedUnit.getMovementMode() == - entityMovementMode)); + EntityMovementMode.NAVAL, + EntityMovementMode.SUBMARINE, + EntityMovementMode.INF_UMU, + EntityMovementMode.VTOL, + EntityMovementMode.BIPED_SWIM, + EntityMovementMode.QUAD_SWIM) + .noneMatch(entityMovementMode -> (selectedUnit.getMovementMode() == + entityMovementMode)); getBtn(MoveCommand.MOVE_CLIMB_MODE).setEnabled(entityNotAbleToClimb); // Descend button: enabled only when the unit is mid-climb / dangling and has the // arms to keep climbing. Replaces the old auto-prompt with on-demand access. boolean canDescend = (selectedUnit instanceof Mek) - && selectedUnit.isClimbing() - && ClimbingHelper.canClimb(selectedUnit); + && selectedUnit.isClimbing() + && ClimbingHelper.canClimb(selectedUnit); getBtn(MoveCommand.MOVE_DESCEND).setEnabled(canDescend); getBtn(MoveCommand.MOVE_DESCEND).setToolTipText( - Messages.getString("MovementDisplay.moveDescendTip")); + Messages.getString("MovementDisplay.moveDescendTip")); + boolean clearDeployEnabled = ((selectedUnit.isDeployed() && (cmd != null) && cmd.contains(MoveStepType.DEPLOY) || + !selectedUnit.isDeployed() && Game.rulesManager.getRulesGame() + .canWalkOnThisRound(selectedUnit))); + getBtn(MoveCommand.MOVE_CLEAR_DEPLOY).setEnabled(clearDeployEnabled); + getBtn(MoveCommand.MOVE_CLEAR_DEPLOY).setToolTipText(Messages.getString("MovementDisplay.moveClearDeployTip")); updateTurnButton(); updateProneButtons(); @@ -914,14 +1016,14 @@ private void updateButtons() { if (isInfantry && selectedUnit.hasWorkingMisc(MiscType.F_TRENCH_CAPABLE)) { // Crews adrift in space or atmosphere can't do this if (selectedUnit instanceof EjectedCrew && - (selectedUnit.isSpaceborne() || selectedUnit.isAirborne())) { + (selectedUnit.isSpaceborne() || selectedUnit.isAirborne())) { getBtn(MoveCommand.MOVE_DIG_IN).setEnabled(false); } else { getBtn(MoveCommand.MOVE_FORTIFY).setEnabled(true); } } else { boolean canBuildFieldworks = (isTank || (selectedUnit instanceof Mek)) - && selectedUnit.hasWorkingMisc(MiscType.F_TRENCH_CAPABLE); + && selectedUnit.hasWorkingMisc(MiscType.F_TRENCH_CAPABLE); getBtn(MoveCommand.MOVE_FORTIFY).setEnabled(canBuildFieldworks); } @@ -930,34 +1032,34 @@ private void updateButtons() { // any vehicle with clearing equipment so playtests can see from megamek.log why the button is or is not // available. boolean hasClearingEquipment = (selectedUnit instanceof RubbleClearer) - && (selectedUnit.hasWorkingBulldozer() || selectedUnit.hasWorkingBackhoe()); + && (selectedUnit.hasWorkingBulldozer() || selectedUnit.hasWorkingBackhoe()); boolean rubbleInReach = hasClearingEquipment - && BulldozerRules.isInOrAdjacentToClearableRubble(selectedUnit, game); + && BulldozerRules.isInOrAdjacentToClearableRubble(selectedUnit, game); boolean clearingRulesOn = BulldozerRules.canClearRubble(selectedUnit, game); getBtn(MoveCommand.MOVE_CLEAR_RUBBLE).setEnabled(rubbleInReach && clearingRulesOn); // Only log when a clearing-capable vehicle is next to rubble (the moment the player expects the button), to // avoid flooding the log. Tells the playtest why the button is or is not available. if (rubbleInReach) { LOGGER.debug("[Bulldozer] {}: rubble in reach - Clear Rubble button {} (optional rule {})", - selectedUnit.getDisplayName(), (rubbleInReach && clearingRulesOn) ? "ENABLED" : "disabled", - clearingRulesOn ? "on" : "OFF - enable it in Game Options > Advanced Combat"); + selectedUnit.getDisplayName(), (rubbleInReach && clearingRulesOn) ? "ENABLED" : "disabled", + clearingRulesOn ? "on" : "OFF - enable it in Game Options > Advanced Combat"); } // Infantry - Digging in, TO:AR p.106; could add terrain checking and restrict to first action here. // A unit that has hit the deck and stayed idle long enough may also convert directly to dug in. boolean canDigIn = infantryGroundPostureAvailable(selectedUnit, gameOptions) - && (selectedUnit instanceof Infantry infantry) - && !infantry.isMechanized() - && ((infantry.getDugIn() == Infantry.DUG_IN_NONE) || infantry.canDigInFromDeck()); + && (selectedUnit instanceof Infantry infantry) + && !infantry.isMechanized() + && ((infantry.getDugIn() == Infantry.DUG_IN_NONE) || infantry.canDigInFromDeck()); getBtn(MoveCommand.MOVE_DIG_IN).setEnabled(canDigIn); // Infantry - Hitting the deck, TO:AR p.106. Allowed for any infantry (including mechanized) that is not // already in a ground posture (dug in or on the deck) and has not yet moved this turn (the first/only action // restriction is enforced in MoveStep). boolean canHitDeck = infantryGroundPostureAvailable(selectedUnit, gameOptions) - && (selectedUnit instanceof Infantry deckInfantry) - && !deckInfantry.isHitTheDeck() - && (deckInfantry.getDugIn() == Infantry.DUG_IN_NONE); + && (selectedUnit instanceof Infantry deckInfantry) + && !deckInfantry.isHitTheDeck() + && (deckInfantry.getDugIn() == Infantry.DUG_IN_NONE); getBtn(MoveCommand.MOVE_HIT_DECK).setEnabled(canHitDeck); // Infantry - Bridge building, TO:AUE. Bridge-Building Engineers with their kit, remaining budget and at @@ -968,7 +1070,7 @@ private void updateButtons() { // Infantry - Take Cover // Crews adrift in space or atmosphere can't do this if (selectedUnit instanceof EjectedCrew && - (selectedUnit.isSpaceborne() || selectedUnit.isAirborne())) { + (selectedUnit.isSpaceborne() || selectedUnit.isAirborne())) { getBtn(MoveCommand.MOVE_TAKE_COVER).setEnabled(false); } else { updateTakeCoverButton(); @@ -976,17 +1078,17 @@ private void updateButtons() { // Infantry - Urban Guerrilla calling for support getBtn(MoveCommand.MOVE_CALL_SUPPORT).setEnabled(isInfantry && - selectedUnit.hasAbility(OptionsConstants.INFANTRY_URBAN_GUERRILLA) && - ((Infantry) selectedUnit).canCallSupport()); + selectedUnit.hasAbility(OptionsConstants.INFANTRY_URBAN_GUERRILLA) && + ((Infantry) selectedUnit).canCallSupport()); getBtn(MoveCommand.MOVE_SHAKE_OFF).setEnabled((selectedUnit instanceof Tank) && - (selectedUnit.getSwarmAttackerId() != - Entity.NONE)); + (selectedUnit.getSwarmAttackerId() != + Entity.NONE)); updateFleeButton(); if (gameOptions.booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLES_CAN_EJECT) && - (selectedUnit instanceof Tank)) { + (selectedUnit instanceof Tank)) { // Vehicles don't have ejection systems, so crews abandon, and must enter a valid hex. If they cannot, // they can't abandon as per TO pg 197. Coords position = currentEntity().getPosition(); @@ -1000,10 +1102,10 @@ private void updateButtons() { setEjectEnabled(hasLegalHex); } else { setEjectEnabled(((isMEK && - (((Mek) selectedUnit).getCockpitType() != Mek.COCKPIT_TORSO_MOUNTED)) || - isAero) && - selectedUnit.isActive() && - !selectedUnit.hasQuirk(OptionsConstants.QUIRK_NEG_NO_EJECT)); + (((Mek) selectedUnit).getCockpitType() != Mek.COCKPIT_TORSO_MOUNTED)) || + isAero) && + selectedUnit.isActive() && + !selectedUnit.hasQuirk(OptionsConstants.QUIRK_NEG_NO_EJECT)); } // Mek abandonment - only available for prone+shutdown Meks per TacOps:AR p.165 @@ -1026,7 +1128,7 @@ private void updateButtons() { if (selectedUnit instanceof LandAirMek) { updateConvertModeButton(); if (selectedUnit.getMovementMode().isWiGE() && - (selectedUnit.getAltitude() <= 3)) { + (selectedUnit.getAltitude() <= 3)) { updateHoverButton(); } getBtn(MoveCommand.MOVE_MORE).setEnabled(numButtonGroups > 1); @@ -1070,54 +1172,70 @@ private void addStepsToMovePath(MoveStepType... moveSteps) { updateMove(); } - private void addStepToMovePath(MoveStepType moveStep, Entity entity) { + private void addStepToMovePath(MoveStepType moveStep, + Entity entity) { cmd.addStep(moveStep, entity); updateMove(); } - private void addStepToMovePath(MoveStepType moveStep, TreeMap> targets) { + private void addStepToMovePath(MoveStepType moveStep, + TreeMap> targets) { cmd.addStep(moveStep, targets); updateMove(); } - private void addStepToMovePath(MoveStepType moveStep, boolean noCost) { + private void addStepToMovePath(MoveStepType moveStep, + boolean noCost) { cmd.addStep(moveStep, 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); } updateMove(); } - private void addStepToMovePath(MoveStepType moveStep, Entity entity, Coords coords) { + private void addStepToMovePath(MoveStepType moveStep, + Entity entity, + Coords coords) { cmd.addStep(moveStep, entity, coords); updateMove(); } - private void addStepToMovePath(MoveStepType moveStep, Minefield minefield) { + private void addStepToMovePath(MoveStepType moveStep, + Minefield minefield) { cmd.addStep(moveStep, minefield); updateMove(); } - private void addStepToMovePath(MoveStepType moveStep, int additionalIntData) { + private void addStepToMovePath(MoveStepType moveStep, + int additionalIntData) { cmd.addStep(moveStep, 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(); } - private void addStepToMovePath(MoveStepType moveStep, Map additionalIntData) { + private void addStepToMovePath(MoveStepType moveStep, + Map additionalIntData) { cmd.addStep(moveStep, additionalIntData); updateMove(); } @@ -1128,28 +1246,30 @@ private void updateMove() { private void updateMove(boolean redrawMovement) { Entity currentEntity = currentEntity(); - if (redrawMovement && (currentEntity != null)) { + if (redrawMovement && + (currentEntity != null) && + currentEntity.isDeployed()) { clientgui.getBoardView(currentEntity).drawMovementData(currentEntity, cmd); } // Check if the path ends with an illegal FORWARDS step that could have been a climb // Show feedback to the player about why climbing is not possible if ((currentEntity instanceof Mek) - && (cmd != null) - && (cmd.getLastStep() != null) - && (cmd.getLastStep().getType() == MoveStepType.FORWARDS) - && (cmd.getLastStep().getMovementType(true) == EntityMovementType.MOVE_ILLEGAL) - && cmd.getLastStep().climbMode() - && !ClimbingHelper.canClimb(currentEntity) - && game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING)) { + && (cmd != null) + && (cmd.getLastStep() != null) + && (cmd.getLastStep().getType() == MoveStepType.FORWARDS) + && (cmd.getLastStep().getMovementType(true) == EntityMovementType.MOVE_ILLEGAL) + && cmd.getLastStep().climbMode() + && !ClimbingHelper.canClimb(currentEntity) + && game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING)) { String reason = ClimbingHelper.getClimbingImpossibleReason(currentEntity); // updateMove() runs on every step add/redraw while plotting, so a modal // JOptionPane here would re-fire and block UI on every keypress. Use a // non-modal toast and gate by entity+reason so the same notice doesn't // scroll past repeatedly during a single plotting session. if ((reason != null) - && ((lastClimbImpossibleEntityId != currentEntity.getId()) - || !reason.equals(lastClimbImpossibleReason))) { + && ((lastClimbImpossibleEntityId != currentEntity.getId()) + || !reason.equals(lastClimbImpossibleReason))) { clientgui.addToast(ToastLevel.WARNING, reason, currentEntity); lastClimbImpossibleEntityId = currentEntity.getId(); lastClimbImpossibleReason = reason; @@ -1172,35 +1292,35 @@ private void updateMove(boolean redrawMovement) { if (hasLastStep && (currentEntity != null) && currentEntity.isProne()) { MoveStep lastStep = cmd.getLastStep(); LOGGER.debug("[CLIMB-TRACE] updateMove prone entity: type={}, movementType={}, " + - "isClimbing={}, climbMode={}, isProne={}, entity.isClimbing={}, elevation={}", - lastStep.getType(), lastStep.getMovementType(true), - lastStep.isClimbing(), lastStep.climbMode(), - currentEntity.isProne(), currentEntity.isClimbing(), lastStep.getElevation()); + "isClimbing={}, climbMode={}, isProne={}, entity.isClimbing={}, elevation={}", + lastStep.getType(), lastStep.getMovementType(true), + lastStep.isClimbing(), lastStep.climbMode(), + currentEntity.isProne(), currentEntity.isClimbing(), lastStep.getElevation()); } if (lastStepClimbing) { LOGGER.debug("[CLIMB-TRACE] updateMove dialog check: lastStepClimbing={}, entityNotYetClimbing={}, " + - "noChoiceMade={}, isMek={}, climbingLevelsChosen={}", - lastStepClimbing, entityNotYetClimbing, noChoiceMade, - currentEntity instanceof Mek, - currentEntity != null ? currentEntity.getClimbingLevelsChosen() : "null"); + "noChoiceMade={}, isMek={}, climbingLevelsChosen={}", + lastStepClimbing, entityNotYetClimbing, noChoiceMade, + currentEntity instanceof Mek, + currentEntity != null ? currentEntity.getClimbingLevelsChosen() : "null"); } if ((currentEntity instanceof Mek climbingMek) - && lastStepClimbing - && entityNotYetClimbing - && noChoiceMade) { + && lastStepClimbing + && entityNotYetClimbing + && noChoiceMade) { LOGGER.debug("[CLIMB-TRACE] updateMove: showing START climbing dialog for {}", - currentEntity.getDisplayName()); + currentEntity.getDisplayName()); // Pass the climbing step so the dialog uses the path's source/target hexes, // not the entity's pre-path facing (which is wrong if the path walked or // turned before reaching the cliff). ClimbingChoiceDialog.ClimbingOption chosen = showStartClimbingDialog(climbingMek, cmd.getLastStep()); if ((chosen != null) && (chosen.type() == ClimbingChoiceDialog.ClimbingActionType.CLIMB_UP) - && (chosen.levels() > 0)) { + && (chosen.levels() > 0)) { clientgui.addToast(ToastLevel.INFO, - Messages.getString("MovementDisplay.ClimbingDialog.startClimbToast", - climbingMek.getDisplayName(), chosen.levels()), - climbingMek); + Messages.getString("MovementDisplay.ClimbingDialog.startClimbToast", + climbingMek.getDisplayName(), chosen.levels()), + climbingMek); currentEntity.setClimbingLevelsChosen(chosen.levels()); // Push to server — the path doesn't carry chosenLevels, and without this // the server reads 0 (= full climb) and climbs to the maximum affordable @@ -1243,15 +1363,15 @@ private void updateMove(boolean redrawMovement) { // distanceToFinalIsOne catches both: a single direct step has the same final-coord // distance as a long routed loop that ends on an adjacent hex. boolean distanceToFinalIsOne = (currentEntity != null) && edgeHasLastStep - && (currentEntity.getPosition().distance(edgeLastStep.getPosition()) == 1); + && (currentEntity.getPosition().distance(edgeLastStep.getPosition()) == 1); long forwardStepCount = (cmd != null) ? cmd.getStepVector().stream() - .filter(s -> s.getType() == MoveStepType.FORWARDS).count() : 0; + .filter(s -> s.getType() == MoveStepType.FORWARDS).count() : 0; // Edge dangle: walking movement only — jumping uses jump jets to clear the drop // safely (no dangle needed). Climb mode is NOT required: per TO:AR p.20, dangle is // initiated by stepping off a 3+ level edge with two functional arms, and players // shouldn't have to know to toggle climb mode first to get the descent prompt. boolean isWalkingNotJumping = edgeHasLastStep - && (edgeLastStep.getMovementType(true) != EntityMovementType.MOVE_JUMP); + && (edgeLastStep.getMovementType(true) != EntityMovementType.MOVE_JUMP); // Routed-around case: planner used 2+ FORWARDS steps to reach an adjacent hex — // a clear sign the direct step was illegal (no leap, etc.). On Cancel we keep the // existing routed path; on a descent option we rebuild the path with proper facing. @@ -1260,10 +1380,10 @@ private void updateMove(boolean redrawMovement) { // Mek can still climb-down (with +2 PSR). Dangle/Drop options inside the dialog // are still gated on canDangle separately. if (distanceToFinalIsOne && isWalkingNotJumping - && (currentEntity instanceof Mek edgeMek) - && !currentEntity.isClimbing() - && ClimbingHelper.canClimb(currentEntity) - && game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING)) { + && (currentEntity instanceof Mek edgeMek) + && !currentEntity.isClimbing() + && ClimbingHelper.canClimb(currentEntity) + && game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING)) { // Use the locally-fetched step (edgeLastStep is non-null inside this block since // distanceToFinalIsOne required edgeHasLastStep). Don't refetch from cmd because // a concurrent path-rebuild could race with the modal dialog. @@ -1278,8 +1398,10 @@ private void updateMove(boolean redrawMovement) { // Omitted when one arm is damaged — climb-down is still available below. if (ClimbingHelper.canDangle(edgeMek)) { descentOptions.add(new ClimbingChoiceDialog.ClimbingOption(dangleLevels, 0, - Messages.getString("MovementDisplay.ClimbingDialog.dangleOption", dangleLevels), - ClimbingChoiceDialog.ClimbingActionType.DANGLE_DOWN)); + Messages.getString( + "MovementDisplay.ClimbingDialog.dangleOption", + dangleLevels), + ClimbingChoiceDialog.ClimbingActionType.DANGLE_DOWN)); } // Climb Down options (TO:AR p.20): controlled descent at climbing rate with PSR per level. @@ -1296,25 +1418,26 @@ private void updateMove(boolean redrawMovement) { int edgeBasePiloting = edgeMek.getCrew().getPiloting(); int edgeArms = ClimbingHelper.countClimbableArms(edgeMek); int edgePsrTarget = edgeBasePiloting + ClimbingHelper.CLIMBING_PSR_MODIFIER - + ((edgeArms == 1) ? ClimbingHelper.ONE_ARM_PSR_MODIFIER : 0); + + ((edgeArms == 1) ? ClimbingHelper.ONE_ARM_PSR_MODIFIER : 0); for (int i = 1; i <= edgeMaxDescend; i++) { int cost = i * edgeClimbCostPerLevel; String baseLabel = (i == 1) - ? Messages.getString("MovementDisplay.ClimbingDialog.descendOption", i, cost) - : Messages.getString("MovementDisplay.ClimbingDialog.descendOptions", i, cost); + ? Messages.getString("MovementDisplay.ClimbingDialog.descendOption", i, cost) + : Messages.getString("MovementDisplay.ClimbingDialog.descendOptions", i, cost); String label = baseLabel + " - PSR " + edgePsrTarget + "+ per level"; descentOptions.add(new ClimbingChoiceDialog.ClimbingOption(i, cost, label, - ClimbingChoiceDialog.ClimbingActionType.CLIMB_DOWN)); + ClimbingChoiceDialog.ClimbingActionType.CLIMB_DOWN)); } // Leap/drop option if (game.getOptions().booleanOption( - OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_LEAPING)) { + OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_LEAPING)) { descentOptions.add(new ClimbingChoiceDialog.ClimbingOption(levelDiff, - ClimbingHelper.DROP_MP_COST, - Messages.getString("MovementDisplay.ClimbingDialog.dropOption", - ClimbingHelper.DROP_MP_COST), - ClimbingChoiceDialog.ClimbingActionType.DROP)); + ClimbingHelper.DROP_MP_COST, + Messages.getString( + "MovementDisplay.ClimbingDialog.dropOption", + ClimbingHelper.DROP_MP_COST), + ClimbingChoiceDialog.ClimbingActionType.DROP)); } // Move as Calculated — commit whatever the path planner already drew (a @@ -1323,20 +1446,22 @@ private void updateMove(boolean redrawMovement) { // it as a climb. Distinct from Cancel: this option auto-commits via ready(); // Cancel leaves the dialog dismissed so the player can keep editing. descentOptions.add(new ClimbingChoiceDialog.ClimbingOption(0, 0, - Messages.getString("MovementDisplay.ClimbingDialog.walkAsCalculatedOption"), - ClimbingChoiceDialog.ClimbingActionType.WALK_AS_CALCULATED)); + Messages.getString( + "MovementDisplay.ClimbingDialog.walkAsCalculatedOption"), + ClimbingChoiceDialog.ClimbingActionType.WALK_AS_CALCULATED)); // Cancel - the unit is at the cliff top, not yet on the face, // so "Cling" is the wrong mental model. This option just aborts the descent. descentOptions.add(new ClimbingChoiceDialog.ClimbingOption(0, 0, - Messages.getString("MovementDisplay.ClimbingDialog.cancelEdgeOption"), - ClimbingChoiceDialog.ClimbingActionType.CLING)); + Messages.getString( + "MovementDisplay.ClimbingDialog.cancelEdgeOption"), + ClimbingChoiceDialog.ClimbingActionType.CLING)); String headerMessage = edgeMek.getDisplayName() - + " is at the edge of a " + levelDiff + " level drop.\n" - + "How would you like to descend?"; + + " is at the edge of a " + levelDiff + " level drop.\n" + + "How would you like to descend?"; ClimbingChoiceDialog dialog = new ClimbingChoiceDialog( - clientgui.getFrame(), headerMessage, descentOptions); + clientgui.getFrame(), headerMessage, descentOptions); dialog.setVisible(true); ClimbingChoiceDialog.ClimbingOption chosen = dialog.getFirstChoice(); @@ -1345,13 +1470,13 @@ private void updateMove(boolean redrawMovement) { // For routed-around paths the existing cmd is a long walk-around — we // need a fresh cmd containing only the turn-to-cliff + direct step. clientgui.addToast(ToastLevel.INFO, - Messages.getString("MovementDisplay.ClimbingDialog.edgeDangleToast", - edgeMek.getDisplayName()), - edgeMek); + Messages.getString("MovementDisplay.ClimbingDialog.edgeDangleToast", + edgeMek.getDisplayName()), + edgeMek); rebuildPathForEdgeDescent(edgeMek, stepPos, isRoutedAround); LOGGER.debug("[DANGLE-TRACE] Edge dangle: CLIMB_MODE_ON + FORWARDS to {}, " - + "cmd.length={}, routed={}", - stepPos, cmd.length(), isRoutedAround); + + "cmd.length={}, routed={}", + stepPos, cmd.length(), isRoutedAround); ready(); return; } else if (chosen != null && chosen.type() == ClimbingChoiceDialog.ClimbingActionType.CLIMB_DOWN) { @@ -1359,11 +1484,11 @@ private void updateMove(boolean redrawMovement) { // but server reads entity.climbingLevelsChosen to switch from dangle (no PSR, // 2 levels) to climb-down (PSR per level, chosen count). LOGGER.debug("[CLIMB-TRACE] Edge climb-down chosen: levels={}, routed={}", - chosen.levels(), isRoutedAround); + chosen.levels(), isRoutedAround); clientgui.addToast(ToastLevel.INFO, - Messages.getString("MovementDisplay.ClimbingDialog.edgeClimbDownToast", - edgeMek.getDisplayName(), chosen.levels()), - edgeMek); + Messages.getString("MovementDisplay.ClimbingDialog.edgeClimbDownToast", + edgeMek.getDisplayName(), chosen.levels()), + edgeMek); edgeMek.setClimbingLevelsChosen(chosen.levels()); clientgui.getClient().sendUpdateEntity(edgeMek); rebuildPathForEdgeDescent(edgeMek, stepPos, isRoutedAround); @@ -1374,29 +1499,29 @@ private void updateMove(boolean redrawMovement) { // Only valid for the direct-path case; the routed-around path was a walk // and shouldn't be reinterpreted as a leap, so guard against that. clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.ClimbingDialog.edgeLeapToast", - edgeMek.getDisplayName(), levelDiff), - edgeMek); + Messages.getString("MovementDisplay.ClimbingDialog.edgeLeapToast", + edgeMek.getDisplayName(), levelDiff), + edgeMek); if (isRoutedAround) { LOGGER.debug("[CLIMB-TRACE] Drop chosen on routed-around path; " - + "rebuilding cmd as direct leap"); + + "rebuilding cmd as direct leap"); cmd = new MovePath(game, edgeMek); cmd.rotatePathfinder(currentEntity.getPosition().direction(stepPos), - false, ManeuverType.MAN_NONE); + false, ManeuverType.MAN_NONE); cmd.addStep(MoveStepType.FORWARDS); } } else if (chosen != null - && chosen.type() == ClimbingChoiceDialog.ClimbingActionType.WALK_AS_CALCULATED) { + && chosen.type() == ClimbingChoiceDialog.ClimbingActionType.WALK_AS_CALCULATED) { // Commit the existing plotted path as-is — the routed walk-around for // the routed case, the direct leaping step for the direct case. The // path is already attached to cmd; just commit it the same way the // other branches do. LOGGER.debug("[CLIMB-TRACE] Move as Calculated chosen: routed={}, " - + "cmd.length={}", isRoutedAround, cmd.length()); + + "cmd.length={}", isRoutedAround, cmd.length()); clientgui.addToast(ToastLevel.INFO, - Messages.getString("MovementDisplay.ClimbingDialog.walkAsCalculatedToast", - edgeMek.getDisplayName()), - edgeMek); + Messages.getString("MovementDisplay.ClimbingDialog.walkAsCalculatedToast", + edgeMek.getDisplayName()), + edgeMek); ready(); return; } else { @@ -1423,10 +1548,10 @@ private void updateMove(boolean redrawMovement) { // so they still hit the warning (drop IS a leap). MoveStep currentLastStep = (cmd != null) ? cmd.getLastStep() : null; boolean isJumpStep = (currentLastStep != null) - && (currentLastStep.getMovementType(true) == EntityMovementType.MOVE_JUMP); + && (currentLastStep.getMovementType(true) == EntityMovementType.MOVE_JUMP); boolean isEdgeDescentPath = false; if ((cmd != null) && (currentLastStep != null) - && currentLastStep.getType() == MoveStepType.FORWARDS) { + && currentLastStep.getType() == MoveStepType.FORWARDS) { List stepVector = cmd.getStepVector(); if (stepVector.size() >= 2) { MoveStep stepBeforeLast = stepVector.get(stepVector.size() - 2); @@ -1434,15 +1559,15 @@ private void updateMove(boolean redrawMovement) { } } if ((currentLastStep != null) && (currentEntity instanceof Mek) - && currentLastStep.getType() == MoveStepType.FORWARDS - && !currentLastStep.isClimbing() - && !isJumpStep - && !isEdgeDescentPath - && game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_LEAPING)) { + && currentLastStep.getType() == MoveStepType.FORWARDS + && !currentLastStep.isClimbing() + && !isJumpStep + && !isEdgeDescentPath + && game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_LEAPING)) { Coords lastStepPos = currentLastStep.getPosition(); // Check the step BEFORE the last one to get the origin hex MoveStep prevStep = (cmd.getStepVector().size() >= 2) - ? cmd.getStepVector().get(cmd.getStepVector().size() - 2) : null; + ? cmd.getStepVector().get(cmd.getStepVector().size() - 2) : null; Coords originPos = (prevStep != null) ? prevStep.getPosition() : currentEntity.getPosition(); int originElev = (prevStep != null) ? prevStep.getElevation() : currentEntity.getElevation(); Hex originHex = game.getBoard(currentEntity).getHex(originPos); @@ -1461,16 +1586,16 @@ private void updateMove(boolean redrawMovement) { int legDamagePerLeg = dropHeight; String warning = currentEntity.getDisplayName() - + " is about to leap down " + dropHeight + " levels!\n\n" - + "Leg Damage PSR: needs " + legDamageTarget - + " [" + basePiloting + " + " + legDamageMod + "]" - + " - failure deals " + legDamagePerLeg + " damage per leg\n" - + "Fall PSR: needs " + fallTarget - + " [" + basePiloting + " + " + fallMod + "]" - + " - failure causes full fall damage\n\n" - + "Continue?"; + + " is about to leap down " + dropHeight + " levels!\n\n" + + "Leg Damage PSR: needs " + legDamageTarget + + " [" + basePiloting + " + " + legDamageMod + "]" + + " - failure deals " + legDamagePerLeg + " damage per leg\n" + + "Fall PSR: needs " + fallTarget + + " [" + basePiloting + " + " + fallMod + "]" + + " - failure causes full fall damage\n\n" + + "Continue?"; if (!clientgui.doYesNoDialog( - Messages.getString("MovementDisplay.ClimbingDialog.title"), warning)) { + Messages.getString("MovementDisplay.ClimbingDialog.title"), warning)) { cmd.removeLastStep(); if (redrawMovement) { clientgui.getBoardView(currentEntity).drawMovementData(currentEntity, cmd); @@ -1493,9 +1618,9 @@ private void updateFleeButton() { boolean hasLastStep = (cmd != null) && (cmd.getLastStep() != null); boolean fleeStart = !hasLastStep && movingEntity.canFlee(); boolean fleeEnd = hasLastStep - && (cmd.getMpUsed() < cmd.getMaxMP()) - && (cmd.getLastStepMovementType() != EntityMovementType.MOVE_ILLEGAL) - && game.canFleeFrom(movingEntity, cmd.getLastStep().getPosition()); + && (cmd.getMpUsed() < cmd.getMaxMP()) + && (cmd.getLastStepMovementType() != EntityMovementType.MOVE_ILLEGAL) + && game.canFleeFrom(movingEntity, cmd.getLastStep().getPosition()); setFleeEnabled(fleeStart || fleeEnd); } @@ -1513,7 +1638,7 @@ private void updateAeroButtons() { // no turning for spheroids in atmosphere PlanetaryConditions conditions = game.getPlanetaryConditions(); boolean spheroidOrLessThanThin = aero.isSpheroid() - || conditions.getAtmosphere().isLighterThan(Atmosphere.THIN); + || conditions.getAtmosphere().isLighterThan(Atmosphere.THIN); if (spheroidOrLessThanThin && !movingEntity.isSpaceborne()) { setTurnEnabled(false); } @@ -1532,9 +1657,9 @@ protected void updateDonePanel() { if (cmd == null || cmd.length() == 0) { updateDonePanelButtons(Messages.getString("MovementDisplay.Move"), - Messages.getString("MovementDisplay.Skip"), - false, - null); + Messages.getString("MovementDisplay.Skip"), + false, + null); return; } @@ -1543,14 +1668,14 @@ protected void updateDonePanel() { Entity currentEntity = currentEntity(); if ((possible.length() == 0) || (currentEntity == null)) { updateDonePanelButtons(Messages.getString("MovementDisplay.Move"), - Messages.getString("MovementDisplay.Skip"), - false, - null); + Messages.getString("MovementDisplay.Skip"), + false, + null); } else if (!possible.isMoveLegal()) { updateDonePanelButtons(Messages.getString("MovementDisplay.IllegalMove"), - Messages.getString("MovementDisplay.Skip"), - false, - null); + Messages.getString("MovementDisplay.Skip"), + false, + null); } else { int mp = possible.countMp(possible.isJumping()); // For climbing moves, cap displayed MP to walk MP (server handles multi-turn) @@ -1561,11 +1686,11 @@ protected void updateDonePanel() { } } boolean psrCheck = (!SharedUtility.doPSRCheck(cmd.clone()).isBlank()) - || (!SharedUtility.doThrustCheck(cmd.clone(), clientgui.getClient()).isBlank()); + || (!SharedUtility.doThrustCheck(cmd.clone(), clientgui.getClient()).isBlank()); boolean damageCheck = cmd.shouldMechanicalJumpCauseFallDamage() || cmd.hasActiveMASC() - || (!(currentEntity instanceof VTOL) && cmd.hasActiveSupercharger()) || cmd.willCrushBuildings(); + || (!(currentEntity instanceof VTOL) && cmd.hasActiveSupercharger()) || cmd.willCrushBuildings(); String moveMsg = Messages.getString("MovementDisplay.Move") + " (" + mp + "MP)" + (psrCheck ? "*" : "") - + (damageCheck ? "!" : ""); + + (damageCheck ? "!" : ""); updateDonePanelButtons(moveMsg, Messages.getString("MovementDisplay.Skip"), true, computeTurnDetails()); } } @@ -1598,12 +1723,12 @@ private List computeTurnDetails() { // switching to a new move type, so write a line if (accumTypeCount != 0) { turnDetails.add(String.format(turnDetailsFormat, - accumLegal ? validTextColor : invalidTextColor, - accumTypeCount == 1 ? "" : "x" + accumTypeCount, - accumType, - unicodeIcon, - accumMP, - "*".repeat(accumDanger))); + accumLegal ? validTextColor : invalidTextColor, + accumTypeCount == 1 ? "" : "x" + accumTypeCount, + accumType, + unicodeIcon, + accumMP, + "*".repeat(accumDanger))); } // switching to a new move type, reset @@ -1624,25 +1749,25 @@ private List computeTurnDetails() { // add line for last moves turnDetails.add(String.format(turnDetailsFormat, - accumLegal ? validTextColor : invalidTextColor, - accumTypeCount == 1 ? "" : "x" + accumTypeCount, - accumType, unicodeIcon, accumMP, "*".repeat(accumDanger))); + accumLegal ? validTextColor : invalidTextColor, + accumTypeCount == 1 ? "" : "x" + accumTypeCount, + accumType, unicodeIcon, accumMP, "*".repeat(accumDanger))); return turnDetails; } private boolean shouldDesignateFlightPath(Entity entity) { return entity != null - && game.hasBoardLocation(finalPosition(), finalBoardId()) - && entity.isAirborne() - && game.getBoard(entity).isLowAltitude() - && game.getBoard(entity).getEmbeddedBoardHexes().contains(finalPosition()) - && cmd != null - // Only designate a flight path when movement ends in that hex legally (all velocity used) - && cmd.isMoveLegal() - // Interpreting TW p.242 to include that the unit must have entered that hex in this movement and cannot - // just hover at velocity 0, doing flight paths at will; with aero-on-ground move, there is naturally - // no flight path without actually moving - && cmd.getDistanceTravelled() > 0; + && game.hasBoardLocation(finalPosition(), finalBoardId()) + && entity.isAirborne() + && game.getBoard(entity).isLowAltitude() + && game.getBoard(entity).getEmbeddedBoardHexes().contains(finalPosition()) + && cmd != null + // Only designate a flight path when movement ends in that hex legally (all velocity used) + && cmd.isMoveLegal() + // Interpreting TW p.242 to include that the unit must have entered that hex in this movement and cannot + // just hover at velocity 0, doing flight paths at will; with aero-on-ground move, there is naturally + // no flight path without actually moving + && cmd.getDistanceTravelled() > 0; } private int flightPathTarget(Entity entity) { @@ -1655,8 +1780,8 @@ private int flightPathTarget(Entity entity) { private int groundMapAtAtmosphericHex() { if (game.hasBoardLocation(finalPosition(), finalBoardId()) - && game.getBoard(finalBoardId()).isLowAltitude() - && game.getBoard(finalBoardId()).getEmbeddedBoardHexes().contains(finalPosition())) { + && game.getBoard(finalBoardId()).isLowAltitude() + && game.getBoard(finalBoardId()).getEmbeddedBoardHexes().contains(finalPosition())) { return game.getBoard(finalBoardId()).getEmbeddedBoardAt(finalPosition()); } else { // fall back to the standard map to be safe @@ -1684,6 +1809,7 @@ private void beginMyTurn() { } startTimer(); + } /** @@ -1697,16 +1823,17 @@ private synchronized void endMyTurn() { // end my turn, then. disableButtons(); Entity next = game - .getNextEntity(game.getTurnIndex()); + .getNextEntity(game.getTurnIndex()); if (game.getPhase().isMovement() - && (null != next) - && (null != currentlySelectedEntity) - && (next.getOwnerId() != currentlySelectedEntity.getOwnerId())) { + && (null != next) + && (null != currentlySelectedEntity) + && (next.getOwnerId() != currentlySelectedEntity.getOwnerId())) { clientgui.maybeShowUnitDisplay(); } currentEntity = Entity.NONE; clearFlightPath(); clientgui.boardViews().forEach(IBoardView::clearMarkedHexes); + markDeploymentHexes(null); // Return the highlight sprite back to its original color clientgui.boardViews().forEach(bv -> ((BoardView) bv).setHighlightColor(Color.WHITE)); clientgui.setSelectedEntityNum(Entity.NONE); @@ -1806,10 +1933,21 @@ private void disableButtons() { } /** - * Clears out the currently selected movement data and resets it. + * Clears out the previous movement by calling the clear with deployment kept */ @Override public void clear() { + clear(true); + } + + /** + * Clears out the currently selected movement data and resets it. + */ + private void clear(boolean keepDeployment) { + boolean wasWalkOn = (deploymentAnchor(cmd) != null); + DeploymentAnchor anchor = keepDeployment ? deploymentAnchor(cmd) : null; + int savedGear = gear; + final Entity currentlySelectedEntity = currentEntity(); // Cancel escape pod hex selection if active @@ -1844,14 +1982,52 @@ public void clear() { currentlySelectedEntity.setMovementMode(EntityMovementMode.QUAD); } + // create new current and considered paths cmd = new MovePath(game, currentlySelectedEntity); + gear = MovementDisplay.GEAR_LAND; + + if (anchor != null) { + // Press escape once + // entity.isAero will check if a unit is a LAM in Fighter mode + if ((currentlySelectedEntity instanceof IAero aero) && (currentlySelectedEntity.isAero())) { + currentlySelectedEntity.setAltitude(anchor.elevation()); + if (anchor.elevation() == 0) { + aero.land(); + } else { + aero.liftOff(anchor.elevation()); + } + } else { + currentlySelectedEntity.setElevation(anchor.elevation()); + } + currentlySelectedEntity.setPosition(anchor.coords()); + currentlySelectedEntity.setBoardId(anchor.boardId()); + currentlySelectedEntity.setFacing(anchor.facing()); + currentlySelectedEntity.setDeployed(true); + addStepToMovePath(MoveStepType.DEPLOY); + markDeploymentHexes(null); + if (savedGear == GEAR_JUMP) { + gear = GEAR_JUMP; + initializeJumpMovePath(); + } + } else if (wasWalkOn) { + // Press escape twice + if (currentlySelectedEntity.isDeployed()) { + clientgui.boardViews().forEach(bv -> bv.clearMarkedHexes()); + clearMovementSprites(); + markDeploymentHexes(currentlySelectedEntity); + currentlySelectedEntity.setDeployed(false); + currentlySelectedEntity.setPosition(null); + clientgui.boardViews().forEach(bv -> ((BoardView) bv).redrawEntity(currentlySelectedEntity)); + refreshButtons(); + } + return; + } + clientgui.updateFiringArc(currentlySelectedEntity); clientgui.showSensorRanges(currentlySelectedEntity, cmd.getFinalCoords()); computeCFWarningHexes(currentlySelectedEntity); - // set to "walk," or the equivalent - gear = MovementDisplay.GEAR_LAND; jumpSubGear = GEAR_SUB_STANDARD; clearFlightPath(); Color walkColor = GUIP.getMoveDefaultColor(); @@ -1899,7 +2075,7 @@ public void clear() { if (currentlySelectedEntity instanceof LandAirMek) { updateConvertModeButton(); if (currentlySelectedEntity.getMovementMode().isWiGE() && - (currentlySelectedEntity.getAltitude() <= 3)) { + (currentlySelectedEntity.getAltitude() <= 3)) { updateHoverButton(); } getBtn(MoveCommand.MOVE_MORE).setEnabled(numButtonGroups > 1); @@ -1924,14 +2100,24 @@ private void removeLastStep() { LOGGER.warn("Cannot process removeLastStep() request, cmd is null!"); return; } - - cmd.removeLastStep(); - final Entity currentlySelectedEntity = currentEntity(); if (currentlySelectedEntity == null) { LOGGER.warn("Cannot process removeLastStep for a null currentlySelectedEntity."); return; - } else if (cmd.length() == 0) { + } + if (cmd.getLastStep() == null) { + LOGGER.warn("No steps to remove."); + return; + } + if (cmd.getLastStep().getType() == MoveStepType.DEPLOY) { + currentlySelectedEntity.setDeployed(false); + currentlySelectedEntity.setPosition(null); + clientgui.boardViews().forEach(bv -> ((BoardView) bv).redrawEntity(currentlySelectedEntity)); + markDeploymentHexes(currentlySelectedEntity); + } + cmd.removeLastStep(); + + if (cmd.length() == 0) { clear(); if ((gear == MovementDisplay.GEAR_JUMP) && !cmd.isJumping()) { initializeJumpMovePath(); @@ -1958,9 +2144,12 @@ private void removeLastStep() { } private void initializeJumpMovePath() { - addStepToMovePath(MoveStepType.START_JUMP); - if (jumpSubGear == GEAR_SUB_MEK_BOOSTERS) { - addStepToMovePath(MoveStepType.JUMP_MEK_MECHANICAL_BOOSTER); + Entity currentlySelectedEntity = currentEntity(); + if ((currentlySelectedEntity != null) && currentlySelectedEntity.isDeployed()) { + addStepToMovePath(MoveStepType.START_JUMP); + if (jumpSubGear == GEAR_SUB_MEK_BOOSTERS) { + addStepToMovePath(MoveStepType.JUMP_MEK_MECHANICAL_BOOSTER); + } } } @@ -1989,27 +2178,27 @@ private boolean removeIllegalSteps() { /** * Whether this move would put the unit under water somewhere its existing damage turns into a hole. *

- * A location already stripped of armour is breached the moment it goes under, with no roll to survive it - * (TW p.121), and that destroys a vehicle outright. The player cannot see that coming from the movement - * display, so it is worth asking before the water closes over it. + * A location already stripped of armour is breached the moment it goes under, with no roll to survive it (TW + * p.121), and that destroys a vehicle outright. The player cannot see that coming from the movement display, so it + * is worth asking before the water closes over it. * * @param movingEntity the unit being moved, or {@code null} if none is selected * @param movePath the move being considered - * * @return {@code true} if the move ends the unit's war */ - private boolean movesIntoWaterThatWouldDestroyIt(@Nullable Entity movingEntity, MovePath movePath) { + private boolean movesIntoWaterThatWouldDestroyIt(@Nullable Entity movingEntity, + MovePath movePath) { if (!EnvironmentalSealingRules.wouldBeDestroyedByWaterBreach(movingEntity)) { return false; } for (MoveStep step : movePath.getStepVector()) { Hex hex = game.getHex(step.getPosition(), step.getBoardId()); boolean goesUnderTheSurface = (hex != null) - && (hex.terrainLevel(Terrains.WATER) > 0) - && (step.getElevation() < hex.getLevel()); + && (hex.terrainLevel(Terrains.WATER) > 0) + && (step.getElevation() < hex.getLevel()); if (goesUnderTheSurface) { LOGGER.debug("[EnvironmentalSealing] {}: warning about a doomed move - a location has no armour " - + "left and hex {} puts it under water", movingEntity.getShortName(), step.getPosition()); + + "left and hex {} puts it under water", movingEntity.getShortName(), step.getPosition()); return true; } } @@ -2023,7 +2212,7 @@ private boolean checkNags() { Entity currentlySelectedEntity = currentEntity(); if (needNagForNoAction()) { - if ((currentlySelectedEntity != null) && (cmd.length() == 0) && !currentlySelectedEntity.isAirborne()) { + if ((currentlySelectedEntity != null) && cmd.length() == 0 && !currentlySelectedEntity.isAirborne()) { // Hmm... no movement steps confirm this action String title = Messages.getString("MovementDisplay.ConfirmNoMoveDlg.title"); String body = Messages.getString("MovementDisplay.ConfirmNoMoveDlg.message"); @@ -2046,12 +2235,12 @@ private boolean checkNags() { if (needNagForMASC()) { if ((currentlySelectedEntity != null) && - cmd.hasActiveMASC() && - !(currentlySelectedEntity instanceof VTOL)) { + cmd.hasActiveMASC() && + !(currentlySelectedEntity instanceof VTOL)) { // pop up are you sure dialog String title = Messages.getString("MovementDisplay.areYouSure"); String body = Messages.getString("MovementDisplay.ConfirmMASCRoll", - currentlySelectedEntity.getMASCTarget()); + currentlySelectedEntity.getMASCTarget()); if (checkNagForMASC(title, body)) { return true; } @@ -2060,11 +2249,11 @@ private boolean checkNags() { if (needNagForMASC()) { if ((currentlySelectedEntity != null) && - !(currentlySelectedEntity instanceof VTOL) && - cmd.hasActiveSupercharger()) { + !(currentlySelectedEntity instanceof VTOL) && + cmd.hasActiveSupercharger()) { String title = Messages.getString("MovementDisplay.areYouSure"); String body = Messages.getString("MovementDisplay.ConfirmSuperchargerRoll", - currentlySelectedEntity.getSuperchargerTarget()); + currentlySelectedEntity.getSuperchargerTarget()); if (checkNagForMASC(title, body)) { return true; } @@ -2073,9 +2262,9 @@ private boolean checkNags() { if (needNagForSprint()) { boolean sprintOrVtolSprint = cmd.getLastStepMovementType() == EntityMovementType.MOVE_SPRINT || - cmd.getLastStepMovementType() == EntityMovementType.MOVE_VTOL_SPRINT; + cmd.getLastStepMovementType() == EntityMovementType.MOVE_VTOL_SPRINT; boolean quadVeeVehicle = cmd.getEntity() instanceof QuadVee && - cmd.getEntity().getConversionMode() == QuadVee.CONV_MODE_VEHICLE; + cmd.getEntity().getConversionMode() == QuadVee.CONV_MODE_VEHICLE; boolean tankOrQuadVee = cmd.getEntity() instanceof Tank || quadVeeVehicle; // no need to nag for vehicles using overdrive if they already get a PSR nag boolean psrNag = tankOrQuadVee && needNagForPSR(); @@ -2101,7 +2290,7 @@ private boolean checkNags() { // check for unsafe takeoffs if (needNagForPSR()) { boolean verticalTakeoffOrTakeoff = cmd.contains(MoveStepType.VERTICAL_TAKE_OFF) || - cmd.contains(MoveStepType.TAKEOFF); + cmd.contains(MoveStepType.TAKEOFF); if ((currentlySelectedEntity != null) && verticalTakeoffOrTakeoff) { boolean unsecure = false; for (Entity loaded : currentlySelectedEntity.getLoadedUnits()) { @@ -2135,10 +2324,10 @@ private boolean checkNags() { // changing altitude voluntarily. if (needNagForPSR()) { if (Compute.useSpheroidAtmosphere(game, currentlySelectedEntity) && - !cmd.contains(MoveStepType.HOVER) && - !cmd.contains(MoveStepType.VERTICAL_LAND) && - !cmd.contains(MoveStepType.UP) && - !cmd.contains(MoveStepType.DOWN)) { + !cmd.contains(MoveStepType.HOVER) && + !cmd.contains(MoveStepType.VERTICAL_LAND) && + !cmd.contains(MoveStepType.UP) && + !cmd.contains(MoveStepType.DOWN)) { String title = Messages.getString("MovementDisplay.areYouSure"); String body = Messages.getString("MovementDisplay.SpheroidAltitudeLoss") + thrustCheck; if (checkNagForPSR(title, body)) { @@ -2152,9 +2341,9 @@ private boolean checkNags() { if ((currentlySelectedEntity != null) && cmd.shouldMechanicalJumpCauseFallDamage()) { String title = Messages.getString("MovementDisplay.areYouSure"); String body = Messages.getString("MovementDisplay.ConfirmMechanicalJumpFallDamage", - cmd.getJumpMaxElevationChange(), - currentlySelectedEntity.getMechanicalJumpBoosterMP(), - cmd.getJumpMaxElevationChange() - currentlySelectedEntity.getMechanicalJumpBoosterMP()); + cmd.getJumpMaxElevationChange(), + currentlySelectedEntity.getMechanicalJumpBoosterMP(), + cmd.getJumpMaxElevationChange() - currentlySelectedEntity.getMechanicalJumpBoosterMP()); if (checkNagForMechanicalJumpFallDamage(title, body)) { return true; } @@ -2165,7 +2354,7 @@ private boolean checkNags() { if (movesIntoWaterThatWouldDestroyIt(currentlySelectedEntity, cmd)) { String title = Messages.getString("MovementDisplay.areYouSure"); String body = Messages.getString("MovementDisplay.ConfirmDoomedMove", - currentlySelectedEntity.getShortName()); + currentlySelectedEntity.getShortName()); if (checkNagForDoomedMove(title, body)) { return true; } @@ -2196,29 +2385,29 @@ private boolean checkNags() { if (needNagForOther()) { if ((currentlySelectedEntity != null) && (null != cmd) && currentlySelectedEntity.isAero()) { boolean airborneOrSpaceborne = currentlySelectedEntity.isAirborne() || - currentlySelectedEntity.isSpaceborne(); + currentlySelectedEntity.isSpaceborne(); boolean unusedVelocity; if (null != cmd.getLastStep()) { unusedVelocity = cmd.getLastStep().getVelocityLeft() > 0; } else { unusedVelocity = (((IAero) currentlySelectedEntity).getCurrentVelocity() > 0) && - (currentlySelectedEntity.delta_distance == 0); + (currentlySelectedEntity.delta_distance == 0); } boolean offOrReturn = cmd.contains(MoveStepType.OFF) || cmd.contains(MoveStepType.RETURN); if (airborneOrSpaceborne && - !game.useVectorMove() && - !((IAero) currentlySelectedEntity).isOutControlTotal() && - unusedVelocity && - !offOrReturn && - !cmd.contains(MoveStepType.LAND) && - !cmd.contains(MoveStepType.VERTICAL_LAND) && - !cmd.contains(MoveStepType.EJECT) && - !cmd.contains(MoveStepType.FLEE)) { + !game.useVectorMove() && + !((IAero) currentlySelectedEntity).isOutControlTotal() && + unusedVelocity && + !offOrReturn && + !cmd.contains(MoveStepType.LAND) && + !cmd.contains(MoveStepType.VERTICAL_LAND) && + !cmd.contains(MoveStepType.EJECT) && + !cmd.contains(MoveStepType.FLEE)) { clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.VelocityLeft.title") + ": " - + Messages.getString("MovementDisplay.VelocityLeft.message"), currentEntity()); + Messages.getString("MovementDisplay.VelocityLeft.title") + ": " + + Messages.getString("MovementDisplay.VelocityLeft.message"), currentEntity()); // always return true here, or airborne units will make illegal moves. return true; } @@ -2228,8 +2417,8 @@ private boolean checkNags() { // check to see if spheroids will drop an elevation if (needNagForOther()) { if (currentlySelectedEntity instanceof LandAirMek && - currentlySelectedEntity.isAssaultDropInProgress() && - cmd.getFinalConversionMode() == EntityMovementMode.AERODYNE) { + currentlySelectedEntity.isAssaultDropInProgress() && + cmd.getFinalConversionMode() == EntityMovementMode.AERODYNE) { String title = Messages.getString("MovementDisplay.areYouSure"); String body = Messages.getString("MovementDisplay.insufficientAltitudeForConversion") + thrustCheck; if (!clientgui.doYesNoDialog(title, body)) { @@ -2240,21 +2429,21 @@ private boolean checkNags() { if (needNagForOther()) { if (currentEntity() != null - && (currentEntity() instanceof ConvInfantry infantry) - && infantry.hasMicrolite()) { + && (currentEntity() instanceof ConvInfantry infantry) + && infantry.hasMicrolite()) { boolean finalElevation = (currentEntity().getElevation() != cmd.getFinalElevation()); boolean airborneVTOLOrWIGEOrFinalElevation = currentEntity().isAirborneVTOLorWIGE() - || finalElevation; + || finalElevation; int terrainLevelBuilding = game.getBoard(currentEntity()).getHex(cmd.getFinalCoords()) - .terrainLevel(Terrains.BLDG_ELEV); + .terrainLevel(Terrains.BLDG_ELEV); int terrainLevelBridge = game.getBoard(currentEntity()).getHex(cmd.getFinalCoords()) - .terrainLevel(Terrains.BRIDGE_ELEV); + .terrainLevel(Terrains.BRIDGE_ELEV); if (airborneVTOLOrWIGEOrFinalElevation - && !cmd.contains(MoveStepType.FORWARDS) - && !cmd.contains(MoveStepType.FLEE) - && cmd.getFinalElevation() > 0 - && terrainLevelBuilding < cmd.getFinalElevation() - && terrainLevelBridge < cmd.getFinalElevation()) { + && !cmd.contains(MoveStepType.FORWARDS) + && !cmd.contains(MoveStepType.FLEE) + && cmd.getFinalElevation() > 0 + && terrainLevelBuilding < cmd.getFinalElevation() + && terrainLevelBridge < cmd.getFinalElevation()) { String title = Messages.getString("MovementDisplay.MicroliteMove.title"); String body = Messages.getString("MovementDisplay.MicroliteMove.message"); if (!clientgui.doYesNoDialog(title, body)) { @@ -2268,12 +2457,12 @@ private boolean checkNags() { boolean landOrVerticalLand = cmd.contains(MoveStepType.LAND) || cmd.contains(MoveStepType.VERTICAL_LAND); if ((currentlySelectedEntity != null) && landOrVerticalLand) { Set landingPath = ((IAero) currentlySelectedEntity).getLandingCoords(cmd.contains(MoveStepType.VERTICAL_LAND), - cmd.getFinalCoords(), - cmd.getFinalFacing()); + cmd.getFinalCoords(), + cmd.getFinalFacing()); if (landingPath.stream() - .map(c -> game.getBoard(currentEntity()).getHex(c)) - .filter(Objects::nonNull) - .anyMatch(h -> h.containsTerrain(Terrains.ROUGH) || h.containsTerrain(Terrains.RUBBLE))) { + .map(c -> game.getBoard(currentEntity()).getHex(c)) + .filter(Objects::nonNull) + .anyMatch(h -> h.containsTerrain(Terrains.ROUGH) || h.containsTerrain(Terrains.RUBBLE))) { String title = Messages.getString("MovementDisplay.areYouSure"); String body = Messages.getString("MovementDisplay.ConfirmLandingGearDamage"); if (!clientgui.doYesNoDialog(title, body)) { @@ -2287,9 +2476,9 @@ private boolean checkNags() { if (currentlySelectedEntity instanceof ConvInfantry infantry) { InfantryMount mount = infantry.getMount(); if ((mount != null) && - currentlySelectedEntity.getMovementMode().isSubmarine() && - (currentlySelectedEntity.underwaterRounds >= mount.getUWEndurance()) && - cmd.isAllUnderwater(game)) { + currentlySelectedEntity.getMovementMode().isSubmarine() && + (currentlySelectedEntity.underwaterRounds >= mount.getUWEndurance()) && + cmd.isAllUnderwater(game)) { String title = Messages.getString("MovementDisplay.areYouSure"); String body = Messages.getString("MovementDisplay.ConfirmMountSuffocation"); if (!clientgui.doYesNoDialog(title, body)) { @@ -2348,7 +2537,8 @@ public synchronized void ready() { /** * Returns new {@link MovePath} for the currently selected movement type */ - private void currentMove(Coords dest, int boardId) { + private void currentMove(Coords dest, + int boardId) { if (shiftHeld || (gear == GEAR_TURN)) { if (buttons.get(MoveCommand.MOVE_TURN).isEnabled()) { cmd.rotatePathfinder(cmd.getFinalCoords().direction(dest), false, ManeuverType.MAN_NONE); @@ -2362,7 +2552,7 @@ private void currentMove(Coords dest, int boardId) { Coords src = (cmd.getLastStep() != null) ? cmd.getLastStep().getPosition() : currentEntity().getPosition(); int direction = src.direction(dest); MoveStepType moveStepType = MoveStepType.stepTypeForRelativeDirection(direction, - currentEntity().getFacing()); + currentEntity().getFacing()); cmd.findSimplePathTo(dest, moveStepType, src.direction(dest), currentEntity().getFacing()); } else if (gear == GEAR_STRAFE) { @@ -2417,21 +2607,21 @@ private void currentMove(Coords dest, int boardId) { extendPathTo(dest, boardId, MoveStepType.FORWARDS); } else if (gear == GEAR_IM_MEL) { addStepsToMovePath(true, - true, - ManeuverType.MAN_IMMELMAN, - MoveStepType.UP, - MoveStepType.UP, - MoveStepType.DEC, - MoveStepType.DEC); + true, + ManeuverType.MAN_IMMELMAN, + MoveStepType.UP, + MoveStepType.UP, + MoveStepType.DEC, + MoveStepType.DEC); cmd.rotatePathfinder(cmd.getFinalCoords().direction(dest), true, ManeuverType.MAN_IMMELMAN); gear = GEAR_LAND; } else if (gear == GEAR_SPLIT_S) { addStepsToMovePath(true, - true, - ManeuverType.MAN_SPLIT_S, - MoveStepType.DOWN, - MoveStepType.DOWN, - MoveStepType.ACC); + true, + ManeuverType.MAN_SPLIT_S, + MoveStepType.DOWN, + MoveStepType.DOWN, + MoveStepType.ACC); cmd.rotatePathfinder(cmd.getFinalCoords().direction(dest), true, ManeuverType.MAN_SPLIT_S); gear = GEAR_LAND; } @@ -2477,14 +2667,15 @@ private void currentMove(Coords dest, int boardId) { * @param dest the rubble hex the player clicked * @param boardId the board the destination hex is on */ - private void planClearRubbleMove(Coords dest, int boardId) { + private void planClearRubbleMove(Coords dest, + int boardId) { // Drive into the chosen rubble hex (the bulldozer override makes the entry legal) and clear it. Hex targetHex = game.getBoard(boardId).getHex(dest); if (!BulldozerRules.hasClearableRubble(targetHex)) { LOGGER.debug("[Bulldozer] {}: clear-rubble target {} has no clearable rubble; ignoring click", - currentEntity().getDisplayName(), dest); + currentEntity().getDisplayName(), dest); clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.clearRubbleIllegalTerrain.toast"), currentEntity()); + Messages.getString("MovementDisplay.clearRubbleIllegalTerrain.toast"), currentEntity()); return; } Entity clearingUnit = currentEntity(); @@ -2492,31 +2683,31 @@ private void planClearRubbleMove(Coords dest, int boardId) { // (QA feedback); the prompt names the actual tool - bulldozer or backhoe. int requiredTurns = BulldozerRules.totalClearingTurns(clearingUnit, targetHex, game); boolean confirmed = clientgui.doYesNoDialog( - Messages.getString("MovementDisplay.clearRubbleConfirm.title"), - Messages.getString("MovementDisplay.clearRubbleConfirm.message", - BulldozerRules.clearingToolName(clearingUnit), requiredTurns)); + Messages.getString("MovementDisplay.clearRubbleConfirm.title"), + Messages.getString("MovementDisplay.clearRubbleConfirm.message", + BulldozerRules.clearingToolName(clearingUnit), requiredTurns)); if (!confirmed) { LOGGER.debug("[Bulldozer] {}: player declined to clear rubble at {}", - clearingUnit.getDisplayName(), dest); + clearingUnit.getDisplayName(), dest); return; } // The blade must engage the rubble: a front-mounted bulldozer drives in forwards; a rear-mounted // one (e.g. the Reverse Buffel) backs into the hex so its rear blade leads. boolean rearBlade = !clearingUnit.hasFrontMountedBulldozer() - && clearingUnit.hasRearMountedBulldozer(); + && clearingUnit.hasRearMountedBulldozer(); MoveStepType approach = rearBlade ? MoveStepType.BACKWARDS : MoveStepType.FORWARDS; LOGGER.debug("[Bulldozer] {}: clear-rubble target at {} (rubble level {}), {}-mounted blade -> {}", - clearingUnit.getDisplayName(), dest, targetHex.terrainLevel(Terrains.RUBBLE), - rearBlade ? "rear" : "front", approach); + clearingUnit.getDisplayName(), dest, targetHex.terrainLevel(Terrains.RUBBLE), + rearBlade ? "rear" : "front", approach); extendPathTo(dest, boardId, approach); if (cmd.getFinalCoords().equals(dest)) { addStepToMovePath(MoveStepType.CLEAR_RUBBLE); } else { LOGGER.debug("[Bulldozer] {}: could not reach rubble hex {} {} (path ended at {})", - clearingUnit.getDisplayName(), dest, rearBlade ? "in reverse" : "forwards", - cmd.getFinalCoords()); + clearingUnit.getDisplayName(), dest, rearBlade ? "in reverse" : "forwards", + cmd.getFinalCoords()); clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.clearRubbleUnreachable.toast"), clearingUnit); + Messages.getString("MovementDisplay.clearRubbleUnreachable.toast"), clearingUnit); } } @@ -2528,7 +2719,9 @@ private void planClearRubbleMove(Coords dest, int boardId) { * @param boardId The destination * @param type The step type to use */ - private void extendPathTo(Coords dest, int boardId, MoveStepType type) { + private void extendPathTo(Coords dest, + int boardId, + MoveStepType type) { if (cmd.getFinalBoardId() == boardId) { cmd.findPathTo(dest, type); } @@ -2552,7 +2745,7 @@ public synchronized void hexMoused(BoardViewEvent boardViewEvent) { // don't make a movement path for aeros if advanced movement is on boolean noPath = (currentlySelectedEntity != null && currentlySelectedEntity.isAero()) - && game.useVectorMove(); + && game.useVectorMove(); // ignore buttons other than 1 if (!clientgui.getClient().isMyTurn() || ((boardViewEvent.getButton() != MouseEvent.BUTTON1))) { @@ -2561,7 +2754,7 @@ public synchronized void hexMoused(BoardViewEvent boardViewEvent) { // control pressed means a line of sight check. // added ALT_MASK by kenn if (((boardViewEvent.getModifiers() & InputEvent.CTRL_DOWN_MASK) != 0) || - ((boardViewEvent.getModifiers() & InputEvent.ALT_DOWN_MASK) != 0)) { + ((boardViewEvent.getModifiers() & InputEvent.ALT_DOWN_MASK) != 0)) { return; } @@ -2570,25 +2763,25 @@ public synchronized void hexMoused(BoardViewEvent boardViewEvent) { // selection and discard it). A click is handled as a selection here and consumes the event. if (bridgeSelectionStage != BridgeSelectionStage.NONE) { if ((boardViewEvent.getType() == BoardViewEvent.BOARD_HEX_CLICKED) - && (boardViewEvent.getCoords() != null)) { + && (boardViewEvent.getCoords() != null)) { LOGGER.debug("[BuildBridge] board click during stage {} at {}", bridgeSelectionStage, - boardViewEvent.getCoords()); + boardViewEvent.getCoords()); handleBridgeSelectionClick(boardViewEvent.getCoords()); } return; } if ((currentlySelectedEntity != null) && (gear == GEAR_FLIGHTPATH) - && (boardViewEvent.getBoardId() == flightPathTarget(currentlySelectedEntity)) - && (boardViewEvent.getType() == BoardViewEvent.BOARD_HEX_CLICKED)) { + && (boardViewEvent.getBoardId() == flightPathTarget(currentlySelectedEntity)) + && (boardViewEvent.getType() == BoardViewEvent.BOARD_HEX_CLICKED)) { if (flightPath != null) { boardViewEvent.getBoardView().removeSprite(flightPath); } clearFlightPath(); flightPathPosition = boardViewEvent.getCoords(); List line = BoardHelper.coordsLine(game.getBoard(boardViewEvent.getBoardId()), - flightPathPosition, - finalFacing()); + flightPathPosition, + finalFacing()); currentlySelectedEntity.setPassedThrough(new Vector<>(line)); flightPath = new FlyOverSprite(boardViewEvent.getBoardView(), currentlySelectedEntity); boardViewEvent.getBoardView().addSprite(flightPath); @@ -2597,24 +2790,113 @@ public synchronized void hexMoused(BoardViewEvent boardViewEvent) { } if ((currentlySelectedEntity != null) - && (gear == GEAR_LANDING_AERO) - && (boardViewEvent.getBoardId() - == game.getBoard(currentlySelectedEntity).getEmbeddedBoardAt(currentlySelectedEntity.getPosition())) - && (boardViewEvent.getType() == BoardViewEvent.BOARD_HEX_CLICKED) - && (currentlySelectedEntity instanceof IAero aero) - && hasLandingMoveStep()) { + && (gear == GEAR_LANDING_AERO) + && (boardViewEvent.getBoardId() + == game.getBoard(currentlySelectedEntity).getEmbeddedBoardAt(currentlySelectedEntity.getPosition())) + && (boardViewEvent.getType() == BoardViewEvent.BOARD_HEX_CLICKED) + && (currentlySelectedEntity instanceof IAero aero) + && hasLandingMoveStep()) { finalizeAeroLandFromAtmosphereMap(aero, boardViewEvent); return; } + if (Game.rulesManager.getRulesGame().isWalkOnDeployment() && currentlySelectedEntity != null) { + if (cmd == null) { + cmd = new MovePath(game, currentlySelectedEntity); + } + Coords coords = boardViewEvent.getCoords(); + int boardId = boardViewEvent.getBoardId(); + if (!currentlySelectedEntity.isDeployed() && boardViewEvent.getType() == BoardViewEvent.BOARD_HEX_DRAGGED) { + DeploymentHelper deploymentHelper = new DeploymentHelper(clientgui); + if (!deploymentHelper.checkDeployment(game.getBoard(boardId), + currentlySelectedEntity, + coords, + false)) { + return; + } + if (originalFacing == -1) { + deploymentHelper.setStartingFacing(currentlySelectedEntity, game.getPlayersList(), coords); + } + DeploymentPosition deploymentPosition = deploymentHelper.determineDeploymentPosition( + currentlySelectedEntity, + coords, + game.getBoard(boardId), + lastHexDeploymentOptions, + lastDeploymentOption); + if (deploymentPosition == null) { + + return; + } + int elevation = deploymentPosition.elevation(); + int facing = deploymentPosition.facing(); + originalFacing = facing; + lastDeploymentOption = deploymentPosition.lastDeploymentOption(); + if (game.getBoard(boardId).isLegalDeployment(coords, currentlySelectedEntity) + && !currentlySelectedEntity.isLocationProhibited( + coords, + boardId, + elevation)) { + // entity.isAero will check if a unit is a LAM in Fighter mode + if ((currentlySelectedEntity instanceof IAero aero) && (currentlySelectedEntity.isAero())) { + currentlySelectedEntity.setAltitude(elevation); + if (elevation == 0) { + aero.land(); + } else { + aero.liftOff(elevation); + } + } else { + currentlySelectedEntity.setElevation(elevation); + } + currentlySelectedEntity.setPosition(coords); + currentlySelectedEntity.setBoardId(boardId); + currentlySelectedEntity.setFacing(facing); + currentlySelectedEntity.setSecondaryFacing(facing); + currentlySelectedEntity.setDeployed(true); + cmd = new MovePath(game, currentlySelectedEntity); + addStepToMovePath(MoveStepType.DEPLOY); + if (gear == GEAR_JUMP) { + initializeJumpMovePath(); + } + } else { + String msg = Messages.getString("DeploymentDisplay.cantDeployInto", + currentlySelectedEntity.getShortName(), + coords.getBoardNum()); + clientgui.addToast(ToastLevel.ERROR, msg, currentlySelectedEntity); + } + clientgui.boardViews().forEach(bv -> ((BoardView) bv).redrawEntity(currentlySelectedEntity)); + clientgui.updateFiringArc(currentlySelectedEntity); + clientgui.showSensorRanges(currentlySelectedEntity); + clientgui.boardViews().forEach(IBoardView::repaint); + refreshButtons(); + return; + + } + } + // check for shifty goodness if (shiftHeld == ((boardViewEvent.getModifiers() & InputEvent.SHIFT_DOWN_MASK) == 0)) { shiftHeld = (boardViewEvent.getModifiers() & InputEvent.SHIFT_DOWN_MASK) != 0; } + // Check for deployment and shift held + if (shiftHeld && + (cmd != null) && + cmd.getLastStep() != null && + Game.rulesManager.getRulesGame().isWalkOnDeployment()) { + MoveStep lastStep = cmd.getLastStep(); + if (lastStep.getType() == MoveStepType.START_JUMP) { + lastStep = cmd.getStep(0); + } + if ((lastStep != null) && lastStep.getType() == MoveStepType.DEPLOY) { + processDeploymentTurn(currentlySelectedEntity, boardViewEvent.getCoords()); + refreshButtons(); + return; + } + } + Coords currPosition = cmd != null ? - cmd.getFinalCoords() : - currentlySelectedEntity != null ? currentlySelectedEntity.getPosition() : null; + cmd.getFinalCoords() : + currentlySelectedEntity != null ? currentlySelectedEntity.getPosition() : null; if ((boardViewEvent.getType() == BoardViewEvent.BOARD_HEX_DRAGGED) && !noPath) { if (!boardViewEvent.getCoords().equals(currPosition) || shiftHeld || (gear == MovementDisplay.GEAR_TURN)) { @@ -2649,8 +2931,8 @@ && hasLandingMoveStep()) { final Targetable target = chooseTarget(boardViewEvent.getCoords()); if ((target == null) || target.equals(currentlySelectedEntity) || !target.isAero()) { clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.CantRam") + ": " - + Messages.getString("MovementDisplay.NoTarget"), currentEntity()); + Messages.getString("MovementDisplay.CantRam") + ": " + + Messages.getString("MovementDisplay.NoTarget"), currentEntity()); clear(); return; } @@ -2658,31 +2940,31 @@ && hasLandingMoveStep()) { // check if it's a valid ram // First I need to add moves to the path if advanced if (currentlySelectedEntity != null && - currentlySelectedEntity.isAero() && - game.useVectorMove()) { + currentlySelectedEntity.isAero() && + game.useVectorMove()) { cmd.clipToPossible(); } addStepToMovePath(MoveStepType.RAM); ToHitData toHit = new RamAttackAction(currentEntity, - target.getTargetType(), - target.getId(), - target.getPosition()).toHit(game, cmd); + target.getTargetType(), + target.getId(), + target.getPosition()).toHit(game, cmd); if (toHit.getValue() != TargetRoll.IMPOSSIBLE && - target instanceof IAero targetAero && - currentlySelectedEntity instanceof IAero attackingEntity) { + target instanceof IAero targetAero && + currentlySelectedEntity instanceof IAero attackingEntity) { // Determine how much damage the charger will take. int toAttacker = RamAttackAction.getDamageTakenBy(attackingEntity, - (Entity) targetAero, - cmd.getSecondFinalPosition(currentlySelectedEntity.getPosition()), - cmd.getHexesMoved(), - targetAero.getCurrentVelocity()); + (Entity) targetAero, + cmd.getSecondFinalPosition(currentlySelectedEntity.getPosition()), + cmd.getHexesMoved(), + targetAero.getCurrentVelocity()); int toDefender = RamAttackAction.getDamageFor(attackingEntity, - (Entity) targetAero, - cmd.getSecondFinalPosition(currentlySelectedEntity.getPosition()), - cmd.getHexesMoved(), - targetAero.getCurrentVelocity()); + (Entity) targetAero, + cmd.getSecondFinalPosition(currentlySelectedEntity.getPosition()), + cmd.getHexesMoved(), + targetAero.getCurrentVelocity()); // Warn if this ram would dishonor the player in the eyes of a Forced Withdrawal bot. String ramDishonorWarning = needNagForDishonor() @@ -2698,15 +2980,16 @@ && hasLandingMoveStep()) { // Ask the player if they want to charge. if (clientgui.doYesNoDialog(Messages.getString("MovementDisplay.RamDialog.title", - target.getDisplayName()), - Messages.getString("MovementDisplay.RamDialog.message", - toHit.getValueAsString(), - Compute.oddsAbove(toHit.getValue(), - currentlySelectedEntity.hasAbility(OptionsConstants.PILOT_APTITUDE_PILOTING)), - toHit.getDesc(), - toDefender, - toHit.getTableDesc(), - toAttacker))) { + target.getDisplayName()), + Messages.getString("MovementDisplay.RamDialog.message", + toHit.getValueAsString(), + Compute.oddsAbove(toHit.getValue(), + currentlySelectedEntity.hasAbility( + OptionsConstants.PILOT_APTITUDE_PILOTING)), + toHit.getDesc(), + toDefender, + toHit.getTableDesc(), + toAttacker))) { // if they answer yes, charge the target. cmd.getLastStep().setTarget(target); ready(); @@ -2719,7 +3002,8 @@ && hasLandingMoveStep()) { } // if not valid, tell why clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.CantRam") + ": " + toHit.getDesc(), currentEntity()); + Messages.getString("MovementDisplay.CantRam") + ": " + toHit.getDesc(), + currentEntity()); clear(); return; } else if (gear == MovementDisplay.GEAR_CHARGE) { @@ -2727,10 +3011,11 @@ && hasLandingMoveStep()) { final Targetable target = chooseTarget(boardViewEvent.getCoords()); if (currentlySelectedEntity != null && ((target == null) || target.equals(currentlySelectedEntity))) { String cantMsg = Messages.getString(currentlySelectedEntity.isAirborneVTOLorWIGE() ? - "MovementDisplay.CantRam" : - "MovementDisplay.CantCharge"); + "MovementDisplay.CantRam" : + "MovementDisplay.CantCharge"); clientgui.addToast(ToastLevel.WARNING, - cantMsg + ": " + Messages.getString("MovementDisplay.NoTarget"), currentEntity()); + cantMsg + ": " + Messages.getString("MovementDisplay.NoTarget"), + currentEntity()); clear(); computeMovementEnvelope(currentlySelectedEntity); return; @@ -2741,14 +3026,14 @@ && hasLandingMoveStep()) { if (target != null) { if (currentlySelectedEntity != null && currentlySelectedEntity.isAirborneVTOLorWIGE()) { toHit = new AirMekRamAttackAction(currentEntity, - target.getTargetType(), - target.getId(), - target.getPosition()).toHit(game, cmd); + target.getTargetType(), + target.getId(), + target.getPosition()).toHit(game, cmd); } else { toHit = new ChargeAttackAction(currentEntity, - target.getTargetType(), - target.getId(), - target.getPosition()).toHit(game, cmd); + target.getTargetType(), + target.getId(), + target.getPosition()).toHit(game, cmd); } } @@ -2758,31 +3043,31 @@ && hasLandingMoveStep()) { int toAttacker = 0; if (currentlySelectedEntity.isAirborneVTOLorWIGE()) { toAttacker = AirMekRamAttackAction.getDamageTakenBy(currentlySelectedEntity, - target, - cmd.getHexesMoved()); + target, + cmd.getHexesMoved()); toDefender = AirMekRamAttackAction.getDamageFor(currentlySelectedEntity, cmd.getHexesMoved()); } else { // Front-mounted saw charge uses flat damage (TM pp.241-243) if (ChargeAttackAction.hasFrontMountedSaw(currentlySelectedEntity) - && target.getTargetType() == Targetable.TYPE_ENTITY) { + && target.getTargetType() == Targetable.TYPE_ENTITY) { toDefender = ChargeAttackAction.getMaxSawChargeDamage( - currentlySelectedEntity, (Entity) target); + currentlySelectedEntity, (Entity) target); } else { toDefender = ChargeAttackAction.getDamageFor( - currentlySelectedEntity, game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_COMBAT_TAC_OPS_CHARGE_DAMAGE), - cmd.getHexesMoved()); + currentlySelectedEntity, game.getOptions() + .booleanOption(OptionsConstants.ADVANCED_COMBAT_TAC_OPS_CHARGE_DAMAGE), + cmd.getHexesMoved()); } if (target.getTargetType() == Targetable.TYPE_ENTITY) { Entity te = (Entity) target; toAttacker = ChargeAttackAction.getDamageTakenBy(currentlySelectedEntity, - te, - game - .getOptions() - .booleanOption(OptionsConstants.ADVANCED_COMBAT_TAC_OPS_CHARGE_DAMAGE), - cmd.getHexesMoved()); + te, + game + .getOptions() + .booleanOption(OptionsConstants.ADVANCED_COMBAT_TAC_OPS_CHARGE_DAMAGE), + cmd.getHexesMoved()); } else if ((target.getTargetType() == Targetable.TYPE_FUEL_TANK) || - (target.getTargetType() == Targetable.TYPE_BUILDING)) { + (target.getTargetType() == Targetable.TYPE_BUILDING)) { IBuilding bldg = game.getBoard(currentlySelectedEntity).getBuildingAt(moveto); toAttacker = ChargeAttackAction.getDamageTakenBy(currentlySelectedEntity, bldg, moveto); } @@ -2808,22 +3093,22 @@ && hasLandingMoveStep()) { // Ask the player if they want to charge. if (clientgui.doYesNoDialog(Messages.getString(title, target.getDisplayName()), - Messages.getString(msg, - toHit.getValueAsString(), - Compute.oddsAbove(toHit.getValue()), - toHit.getDesc(), - toDefender, - toHit.getTableDesc(), - toAttacker))) { + Messages.getString(msg, + toHit.getValueAsString(), + Compute.oddsAbove(toHit.getValue()), + toHit.getDesc(), + toDefender, + toHit.getTableDesc(), + toAttacker))) { // if they answer yes, charge the target. cmd.getLastStep().setTarget(target); if (currentlySelectedEntity.hasShield()) { boolean hasLance = false; for (MiscMounted getClub : currentlySelectedEntity.getClubs()) { if (getClub.getType().hasFlag(MiscTypeFlag.S_LANCE) - && !getClub.isDestroyed() - && !getClub.isBreached() - && !getClub.isMissing()) { + && !getClub.isDestroyed() + && !getClub.isBreached() + && !getClub.isMissing()) { hasLance = true; } } @@ -2831,15 +3116,16 @@ && hasLandingMoveStep()) { if (!hasLance) { // Do we want to raise the shield? if (clientgui.doYesNoDialog(Messages.getString( - "MovementDisplay.ChargeDialog.RaiseShield"), - Messages.getString("MovementDisplay.ChargeDialog.ShieldMessage"))) { + "MovementDisplay.ChargeDialog.RaiseShield"), + Messages.getString( + "MovementDisplay.ChargeDialog.ShieldMessage"))) { for (MiscMounted m : currentlySelectedEntity.getMisc()) { MiscType type = m.getType(); if (((m.getLocation() == Mek.LOC_LEFT_ARM) || (m.getLocation() - == Mek.LOC_RIGHT_ARM)) - && type.hasFlag(MiscType.F_SHIELD) - && !m.isInoperable() - && (currentlySelectedEntity.getInternal(m.getLocation()) > 0)) { + == Mek.LOC_RIGHT_ARM)) + && type.hasFlag(MiscType.F_SHIELD) + && !m.isInoperable() + && (currentlySelectedEntity.getInternal(m.getLocation()) > 0)) { // Only one shield needs to be raised m.setMode(MiscType.S_ACTIVE_SHIELD); Enumeration shieldModes = m.getType().getModes(); @@ -2852,7 +3138,7 @@ && hasLandingMoveStep()) { nMode++; } clientgui.getClient().sendModeChange(currentlySelectedEntity.getId(), - m.equipmentIndex(), nMode); + m.equipmentIndex(), nMode); break; } } @@ -2868,9 +3154,10 @@ && hasLandingMoveStep()) { } // if not valid, tell why String chargeReason = (toHit != null) ? toHit.getDesc() - : Messages.getString("MovementDisplay.CantCharge.unknown"); + : Messages.getString("MovementDisplay.CantCharge.unknown"); clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.CantCharge") + ": " + chargeReason, currentEntity()); + Messages.getString("MovementDisplay.CantCharge") + ": " + chargeReason, + currentEntity()); clear(); @@ -2884,8 +3171,8 @@ && hasLandingMoveStep()) { final Targetable target = chooseTarget(boardViewEvent.getCoords()); if ((target == null) || target.equals(currentlySelectedEntity)) { clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.CantDFA") + ": " - + Messages.getString("MovementDisplay.NoTarget"), currentEntity()); + Messages.getString("MovementDisplay.CantDFA") + ": " + + Messages.getString("MovementDisplay.NoTarget"), currentEntity()); clear(); if (currentlySelectedEntity != null) { @@ -2901,12 +3188,12 @@ && hasLandingMoveStep()) { if (currentlySelectedEntity != null) { // Calculate piloting roll to stay standing after DFA PilotingRollData pilotRoll = currentlySelectedEntity.getBasePilotingRoll( - EntityMovementType.MOVE_JUMP); + EntityMovementType.MOVE_JUMP); pilotRoll.addModifier(Game.rulesManager.getRulesPSR().getSuccessfulDFAModifier(), - Messages.getString( - "MovementDisplay" - + ".DFADialog" - + ".dfaModifier")); + Messages.getString( + "MovementDisplay" + + ".DFADialog" + + ".dfaModifier")); // Warn if this DFA would dishonor the player in the eyes of a Forced Withdrawal bot. String dfaDishonorWarning = needNagForDishonor() @@ -2921,18 +3208,20 @@ && hasLandingMoveStep()) { } if (clientgui.doYesNoDialog(Messages.getString("MovementDisplay.DFADialog.title", - target.getDisplayName()), - Messages.getString("MovementDisplay.DFADialog.message", - toHit.getValueAsString(), - Compute.oddsAbove(toHit.getValue()), - toHit.getDesc(), - DfaAttackAction.getDamageFor(currentlySelectedEntity, - target.isConventionalInfantry()), - toHit.getTableDesc(), - DfaAttackAction.getDamageTakenBy(currentlySelectedEntity), - pilotRoll.getValueAsString(), - Compute.oddsAbove(pilotRoll.getValue()), - pilotRoll.getDesc()))) { + target.getDisplayName()), + Messages.getString("MovementDisplay.DFADialog.message", + toHit.getValueAsString(), + Compute.oddsAbove(toHit.getValue()), + toHit.getDesc(), + DfaAttackAction.getDamageFor( + currentlySelectedEntity, + target.isConventionalInfantry()), + toHit.getTableDesc(), + DfaAttackAction.getDamageTakenBy( + currentlySelectedEntity), + pilotRoll.getValueAsString(), + Compute.oddsAbove(pilotRoll.getValue()), + pilotRoll.getDesc()))) { // if they answer yes, DFA the target cmd.getLastStep().setTarget(target); ready(); @@ -2947,48 +3236,50 @@ && hasLandingMoveStep()) { if (toHit != null) { // if not valid, tell why clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.CantDFA") + ": " + toHit.getDesc(), currentEntity()); + Messages.getString("MovementDisplay.CantDFA") + ": " + toHit.getDesc(), + currentEntity()); } clear(); return; } - updateDonePanel(); - updateProneButtons(); - updateChaffButton(); - updateRACButton(); - updateSearchlightButton(); - updateLoadButtons(); - updateElevationButtons(); - updateElevatorButtons(); - updateTakeOffButtons(); - updateLandButtons(); - updateEvadeButton(); - updateBootleggerButton(); - updateShutdownButton(); - updateStartupButton(); - updateSelfDestructButton(); - updateTraitorButton(); - updateFlyOffButton(); - updateLaunchButton(); - updateDropButton(); - updateConvertModeButton(); - updateRecklessButton(); - updateBraceButton(); - updateHoverButton(); - updateManeuverButton(); - updateSpeedButtons(); - updateThrustButton(); - updateRollButton(); - updateTurnButton(); - updateTakeCoverButton(); - updateLayMineButton(); - checkFuel(); - checkOOC(); - checkAtmosphere(); + refreshButtons(); } } + private void refreshButtons() { + updateProneButtons(); + updateRACButton(); + updateSearchlightButton(); + updateElevationButtons(); + updateElevatorButtons(); + updateTakeOffButtons(); + updateLandButtons(); + updateFlyOffButton(); + updateLaunchButton(); + updateLoadButtons(); + updateDropButton(); + updateConvertModeButton(); + updateRecklessButton(); + updateHoverButton(); + updateManeuverButton(); + updateEvadeButton(); + updateBootleggerButton(); + updateShutdownButton(); + updateStartupButton(); + updateSelfDestructButton(); + updateTraitorButton(); + updateSpeedButtons(); + updateThrustButton(); + updateRollButton(); + updateTakeCoverButton(); + updateLayMineButton(); + updateBraceButton(); + checkFuel(); + checkOOC(); + checkAtmosphere(); + } + private void updateTakeCoverButton() { final GameOptions gOpts = game.getOptions(); boolean isInfantry = (currentEntity() instanceof Infantry); @@ -3038,8 +3329,8 @@ private synchronized void updateProneButtons() { } else if (cmd.getFinalHullDown()) { if (isMek) { setGetUpEnabled(!currentEntity.isImmobile() - && !currentEntity.isStuck() - && !((Mek) currentEntity).cannotStandUpFromHullDown()); + && !currentEntity.isStuck() + && !((Mek) currentEntity).cannotStandUpFromHullDown()); } else { setGetUpEnabled(!currentEntity.isImmobile() && !currentEntity.isStuck()); } @@ -3048,22 +3339,22 @@ private synchronized void updateProneButtons() { } else { setGetUpEnabled(false); setGoProneEnabled(!currentEntity.isImmobile() && - isMek && - !currentEntity.isStuck() && - !(getBtn(MoveCommand.MOVE_GET_UP).isEnabled())); + isMek && + !currentEntity.isStuck() && + !(getBtn(MoveCommand.MOVE_GET_UP).isEnabled())); if (!(currentEntity instanceof Tank) && - !(currentEntity instanceof QuadVee - && currentEntity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)) { + !(currentEntity instanceof QuadVee + && currentEntity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)) { setHullDownEnabled(currentEntity.canGoHullDown()); } else { // So that the vehicle can move and go hull-down, we have to check if it's moved into a fortified // position if (cmd.getLastStep() != null) { boolean hullDownEnabled = game - .getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_HULL_DOWN); + .getOptions() + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_HULL_DOWN); Hex occupiedHex = game.getBoard(currentEntity) - .getHex(cmd.getLastStep().getPosition()); + .getHex(cmd.getLastStep().getPosition()); boolean fortifiedHex = occupiedHex.containsTerrain(Terrains.FORTIFIED); // Large Vehicles cannot use infantry-built (fortified) hexes for cover (TO:AR p.19). boolean isLargeVehicle = (currentEntity instanceof Tank tank) && tank.isLargeVehicleForHullDown(); @@ -3079,8 +3370,8 @@ private synchronized void updateProneButtons() { // Two-state Hull Down button: "Go Hull Down" when the unit can enter hull-down, "Hull Down" (current // state) once it already is, so the player can tell at a glance whether the unit is in cover. getBtn(MoveCommand.MOVE_HULL_DOWN).setText(Messages.getString(cmd.getFinalHullDown() - ? "MovementDisplay.moveHullDownActive" - : "MovementDisplay.moveHullDownGo")); + ? "MovementDisplay.moveHullDownActive" + : "MovementDisplay.moveHullDownGo")); } private void updateRACButton() { @@ -3091,16 +3382,16 @@ private void updateRACButton() { isUnJammingRAC = false; GameOptions opts = game.getOptions(); setUnjamEnabled(currentEntity.canUnjamRAC() && - ((gear == MovementDisplay.GEAR_LAND) || - (gear == MovementDisplay.GEAR_TURN) || - (gear == MovementDisplay.GEAR_BACKUP)) && - ((cmd.getMpUsed() <= currentEntity.getWalkMP()) || - (cmd.getLastStep().isOnlyPavementOrRoad() && + ((gear == MovementDisplay.GEAR_LAND) || + (gear == MovementDisplay.GEAR_TURN) || + (gear == MovementDisplay.GEAR_BACKUP)) && + ((cmd.getMpUsed() <= currentEntity.getWalkMP()) || + (cmd.getLastStep().isOnlyPavementOrRoad() && (cmd.getMpUsed() <= (currentEntity.getWalkMP() + 1)))) && - !(opts.booleanOption(OptionsConstants.ADVANCED_TAC_OPS_TANK_CREWS) && - (cmd.getMpUsed() > 0) && - (currentEntity instanceof Tank) && - (currentEntity.getCrew().getSize() < 2))); + !(opts.booleanOption(OptionsConstants.ADVANCED_TAC_OPS_TANK_CREWS) && + (cmd.getMpUsed() > 0) && + (currentEntity instanceof Tank) && + (currentEntity.getCrew().getSize() < 2))); } private void updateSearchlightButton() { @@ -3109,11 +3400,11 @@ private void updateSearchlightButton() { return; } boolean isNight = game - .getPlanetaryConditions() - .getLight() - .isDuskOrFullMoonOrMoonlessOrPitchBack(); + .getPlanetaryConditions() + .getLight() + .isDuskOrFullMoonOrMoonlessOrPitchBack(); setSearchlightEnabled(isNight && currentEntity.hasSearchlight() && !cmd.contains(MoveStepType.SEARCHLIGHT), - currentEntity.isUsingSearchlight()); + currentEntity.isUsingSearchlight()); } private synchronized void updateElevationButtons() { @@ -3126,20 +3417,20 @@ private synchronized void updateElevationButtons() { // then use altitude not elevation setRaiseEnabled(currentEntity.canGoUp(cmd.getFinalAltitude(), cmd.getFinalCoords(), cmd.getFinalBoardId())); setLowerEnabled(currentEntity.canGoDown(cmd.getFinalAltitude(), - cmd.getFinalCoords(), - cmd.getFinalBoardId())); + cmd.getFinalCoords(), + cmd.getFinalBoardId())); return; } // WiGEs (and LAMs and glider ProtoMeks) cannot go up if they've used ground movement. if (currentEntity.getMovementMode().isWiGE() && - !currentEntity.isAirborneVTOLorWIGE() && - (cmd.getMpUsed() > 0) && - !cmd.contains(MoveStepType.UP)) { + !currentEntity.isAirborneVTOLorWIGE() && + (cmd.getMpUsed() > 0) && + !cmd.contains(MoveStepType.UP)) { setRaiseEnabled(false); } else { setRaiseEnabled(currentEntity.canGoUp(cmd.getFinalElevation(), - cmd.getFinalCoords(), - cmd.getFinalBoardId())); + cmd.getFinalCoords(), + cmd.getFinalBoardId())); } setLowerEnabled(currentEntity.canGoDown(cmd.getFinalElevation(), cmd.getFinalCoords(), cmd.getFinalBoardId())); } @@ -3184,16 +3475,17 @@ private synchronized void updateElevatorButtons() { IndustrialElevator elevator = game.getIndustrialElevator(BoardLocation.of(finalPos, finalBoardId)); if (elevator == null) { LOGGER.debug("[IndustrialElevator] Buttons disabled for {}: elevator terrain at {} but no elevator " - + "registered with the game", currentEntity.getShortName(), finalPos); + + "registered with the game", currentEntity.getShortName(), finalPos); setElevatorUpEnabled(false); setElevatorDownEnabled(false); return; } if (!elevator.isFunctional()) { LOGGER.debug("[IndustrialElevator] Buttons disabled for {}: elevator at {} is disabled", - currentEntity.getShortName(), finalPos); + currentEntity.getShortName(), finalPos); showElevatorStateToast(currentEntity, finalPos, "disabled", ToastLevel.WARNING, - Messages.getString("MovementDisplay.ElevatorToast.disabled", currentEntity.getShortName())); + Messages.getString("MovementDisplay.ElevatorToast.disabled", + currentEntity.getShortName())); setElevatorUpEnabled(false); setElevatorDownEnabled(false); return; @@ -3201,20 +3493,22 @@ private synchronized void updateElevatorButtons() { int currentLoad = (int) elevator.getCurrentLoad(game); if (currentLoad > elevator.getCapacityTons()) { LOGGER.debug("[IndustrialElevator] Buttons disabled for {}: elevator at {} overloaded ({}t / {}t)", - currentEntity.getShortName(), finalPos, currentLoad, elevator.getCapacityTons()); + currentEntity.getShortName(), finalPos, currentLoad, elevator.getCapacityTons()); showElevatorStateToast(currentEntity, finalPos, "overloaded", ToastLevel.WARNING, - Messages.getString("MovementDisplay.ElevatorToast.overloaded", - currentEntity.getShortName(), currentLoad, elevator.getCapacityTons())); + Messages.getString("MovementDisplay.ElevatorToast.overloaded", + currentEntity.getShortName(), + currentLoad, + elevator.getCapacityTons())); setElevatorUpEnabled(false); setElevatorDownEnabled(false); return; } if (!elevator.isPlatformAt(finalElevation)) { LOGGER.debug("[IndustrialElevator] Buttons disabled for {}: platform at level {}, unit at level {}", - currentEntity.getShortName(), elevator.getPlatformLevel(), finalElevation); + currentEntity.getShortName(), elevator.getPlatformLevel(), finalElevation); showElevatorStateToast(currentEntity, finalPos, "platform" + elevator.getPlatformLevel(), ToastLevel.INFO, - Messages.getString("MovementDisplay.ElevatorToast.platformElsewhere", - currentEntity.getShortName(), elevator.getPlatformLevel())); + Messages.getString("MovementDisplay.ElevatorToast.platformElsewhere", + currentEntity.getShortName(), elevator.getPlatformLevel())); setElevatorUpEnabled(false); setElevatorDownEnabled(false); return; @@ -3235,7 +3529,11 @@ private synchronized void updateElevatorButtons() { * @param level the toast level * @param message the ready-to-display toast text */ - private void showElevatorStateToast(Entity entity, Coords hexPos, String state, ToastLevel level, String message) { + private void showElevatorStateToast(Entity entity, + Coords hexPos, + String state, + ToastLevel level, + String message) { String toastKey = entity.getId() + "|" + hexPos + "|" + state; if (toastKey.equals(lastElevatorToastKey)) { return; @@ -3254,10 +3552,10 @@ private synchronized void updateTakeOffButtons() { final Entity currentEntity = currentEntity(); if ((currentEntity instanceof IAero aero) - && currentEntity.isAero() - && !currentEntity.isAirborne() - && !currentEntity.isShutDown() - && (usingAeroOnGroundMovement() || hasAtmosphericMapForLiftOff(game, currentEntity))) { + && currentEntity.isAero() + && !currentEntity.isAirborne() + && !currentEntity.isShutDown() + && (usingAeroOnGroundMovement() || hasAtmosphericMapForLiftOff(game, currentEntity))) { setTakeOffEnabled(aero.canTakeOffHorizontally()); setVTakeOffEnabled(aero.canTakeOffVertically()); } else { @@ -3272,12 +3570,13 @@ private boolean usingAeroOnGroundMovement() { /** * @return True when there is a position on an existing atmospheric map that corresponds to the entity's current - * ground map. TW p.88 + * ground map. TW p.88 */ - public static boolean hasAtmosphericMapForLiftOff(IGame game, Entity entity) { + public static boolean hasAtmosphericMapForLiftOff(IGame game, + Entity entity) { Board groundBoard = game.getBoard(entity); return (groundBoard != null) && (game.getEnclosingBoard(groundBoard) != null) - && game.getEnclosingBoard(groundBoard).isLowAltitude(); + && game.getEnclosingBoard(groundBoard).isLowAltitude(); } private synchronized void updateLandButtons() { @@ -3300,10 +3599,10 @@ private synchronized void updateLandButtons() { // Without aero on ground movement, allow landing when over a ground map hex on an atmospheric map if (!usingAeroOnGroundMovement() - && (!game.hasBoardLocationOf(selectedEntity) - || !selectedEntity.isAirborne() - || !game.isOnAtmosphericMap(selectedEntity) - || !game.getBoard(selectedEntity).getEmbeddedBoardHexes().contains(finalPosition()))) { + && (!game.hasBoardLocationOf(selectedEntity) + || !selectedEntity.isAirborne() + || !game.isOnAtmosphericMap(selectedEntity) + || !game.getBoard(selectedEntity).getEmbeddedBoardHexes().contains(finalPosition()))) { return; } @@ -3353,9 +3652,9 @@ private void updateHoverButton() { return; } } else if (!(currentEntity instanceof ProtoMek) && - !(currentEntity instanceof LandAirMek && (currentEntity.getConversionMode() - == LandAirMek.CONV_MODE_AIR_MEK)) && - (currentEntity.getAltitude() <= 3)) { + !(currentEntity instanceof LandAirMek && (currentEntity.getConversionMode() + == LandAirMek.CONV_MODE_AIR_MEK)) && + (currentEntity.getAltitude() <= 3)) { return; } @@ -3388,10 +3687,20 @@ private synchronized void updateSpeedButtons() { return; } + if (!currentEntity.isDeployed()) { + return; + } if (!currentEntity.isAero()) { return; } + // Large craft only use aero speed controls when not landed on the ground map. + if (currentEntity.isDropShip() && currentEntity.isAeroLandedOnGroundMap()) { + setAccEnabled(false); + setDecEnabled(false); + return; + } + IAero a = (IAero) currentEntity; // only allow acceleration and deceleration if the cmd is empty or the @@ -3409,7 +3718,7 @@ private synchronized void updateSpeedButtons() { nextVelocity = last.getVelocityN(); } - if (null == last) { + if (null == last || pathZeroOrDeploy()) { setAccEnabled(true); if (currentVelocity > 0) { setDecEnabled(true); @@ -3436,14 +3745,14 @@ private synchronized void updateSpeedButtons() { setAccNEnabled(false); setDecNEnabled(false); if (Stream.of(MoveStepType.ACC, MoveStepType.DEC, MoveStepType.DECELERATION) - .noneMatch(moveStepType -> cmd.contains(moveStepType))) { + .noneMatch(moveStepType -> cmd.contains(moveStepType))) { setAccNEnabled(true); } if (!cmd.contains(MoveStepType.ACC) && - !cmd.contains(MoveStepType.DEC) && - !cmd.contains(MoveStepType.ACCELERATION) && - (nextVelocity > 0)) { + !cmd.contains(MoveStepType.DEC) && + !cmd.contains(MoveStepType.ACCELERATION) && + (nextVelocity > 0)) { setDecNEnabled(true); } @@ -3456,8 +3765,8 @@ private synchronized void updateSpeedButtons() { // Disable accelerate/decelerate if a jumpship has changed facing if ((a instanceof Jumpship) && - ((Jumpship) a).hasStationKeepingDrive() && - (cmd.contains(MoveStepType.TURN_LEFT) || cmd.contains(MoveStepType.TURN_RIGHT))) { + ((Jumpship) a).hasStationKeepingDrive() && + (cmd.contains(MoveStepType.TURN_LEFT) || cmd.contains(MoveStepType.TURN_RIGHT))) { setDecNEnabled(false); setAccNEnabled(false); } @@ -3506,8 +3815,8 @@ private void updateFlyOffButton() { // Check if at altitude 10 - can climb out of atmosphere (requires both return flyover and climb out options) boolean canClimbOut = altitude == 10 && board.isGround() - && game.getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_RETURN_FLYOVER) - && game.getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_CLIMB_OUT); + && game.getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_RETURN_FLYOVER) + && game.getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_CLIMB_OUT); // Check if can fly off edge (calculate before using) boolean canFlyOffEdge = false; @@ -3515,27 +3824,27 @@ private void updateFlyOffButton() { // for spheroids in atmosphere we just need to check being on the edge if (a.isSpheroid() && !board.isSpace()) { canFlyOffEdge = (position != null) && - (currentEntity.getWalkMP() > 0) && - ((position.getX() == 0) || - (position.getX() == (board.getWidth() - 1)) || - (position.getY() == 0) || - (position.getY() == (board.getHeight() - 1))); + (currentEntity.getWalkMP() > 0) && + ((position.getX() == 0) || + (position.getX() == (board.getWidth() - 1)) || + (position.getY() == 0) || + (position.getY() == (board.getHeight() - 1))); } else if (position != null) { // for all aerodynes and spheroids in space, it is more complicated - the nose of the aircraft must be facing // in the right direction, and there must be velocity remaining boolean evenX = (position.getX() % 2) == 0; canFlyOffEdge = (velocityLeft > 0) && - (((position.getX() == 0) && ((facing == 5) || (facing == 4))) || - ((position.getX() == (board.getWidth() - 1)) && + (((position.getX() == 0) && ((facing == 5) || (facing == 4))) || + ((position.getX() == (board.getWidth() - 1)) && ((facing == 1) || (facing == 2))) || - ((position.getY() == 0) && + ((position.getY() == 0) && ((facing == 1) || (facing == 5) || (facing == 0)) && evenX) || - ((position.getY() == 0) && (facing == 0)) || - ((position.getY() == (board.getHeight() - 1)) && + ((position.getY() == 0) && (facing == 0)) || + ((position.getY() == (board.getHeight() - 1)) && ((facing == 2) || (facing == 3) || (facing == 4)) && !evenX) || - ((position.getY() == (board.getHeight() - 1)) && (facing == 3))); + ((position.getY() == (board.getHeight() - 1)) && (facing == 3))); } // Determine button state and label @@ -3561,17 +3870,17 @@ private void updateLaunchButton() { } setLaunchEnabled(!currentEntity.getLaunchableFighters().isEmpty() || - !currentEntity.getLaunchableSmallCraft().isEmpty() || - !currentEntity.getLaunchableDropships().isEmpty()); + !currentEntity.getLaunchableSmallCraft().isEmpty() || + !currentEntity.getLaunchableDropships().isEmpty()); } /** * @param bay Instance * @param droppedUnits Set of unit ids of entities already dropped this turn. - * * @return true if there are available droppable units in this bay */ - private boolean checkBayDropEnable(Bay bay, Set droppedUnits) { + private boolean checkBayDropEnable(Bay bay, + Set droppedUnits) { // If this bay has unloaded more units this turn than // it has doors* for, we should move on // *(excluding Infantry, see StratOps pg. 20) @@ -3615,10 +3924,10 @@ private boolean checkBayDropEnable(Bay bay, Set droppedUnits) { /** * @param compartment Instance * @param droppedUnits Set of unit ids of entities already dropped this turn. - * * @return true if there are available droppable units in this compartment */ - private boolean checkCompartmentDropEnable(InfantryCompartment compartment, Set droppedUnits) { + private boolean checkCompartmentDropEnable(InfantryCompartment compartment, + Set droppedUnits) { List droppableUnits = compartment.getDroppableUnits(); return droppableUnits.stream().map(Entity::getId).anyMatch(u -> !droppedUnits.contains(u)); } @@ -3666,7 +3975,7 @@ private void updateEvadeButton() { } setEvadeEnabled((cmd.getLastStepMovementType() != EntityMovementType.MOVE_JUMP) && - (cmd.getLastStepMovementType() != EntityMovementType.MOVE_SPRINT)); + (cmd.getLastStepMovementType() != EntityMovementType.MOVE_SPRINT)); } private void updateBootleggerButton() { @@ -3677,8 +3986,8 @@ private void updateBootleggerButton() { } if (!game - .getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_ADVANCED_MANEUVERS)) { + .getOptions() + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_ADVANCED_MANEUVERS)) { return; } @@ -3687,8 +3996,8 @@ private void updateBootleggerButton() { } if (currentEntity.getMovementMode() != EntityMovementMode.WHEELED && - currentEntity.getMovementMode() != EntityMovementMode.HOVER && - currentEntity.getMovementMode() != EntityMovementMode.VTOL) { + currentEntity.getMovementMode() != EntityMovementMode.HOVER && + currentEntity.getMovementMode() != EntityMovementMode.VTOL) { return; } @@ -3731,8 +4040,8 @@ private void updateSelfDestructButton() { } if (!game - .getOptions() - .booleanOption(OptionsConstants.ADVANCED_TAC_OPS_SELF_DESTRUCT)) { + .getOptions() + .booleanOption(OptionsConstants.ADVANCED_TAC_OPS_SELF_DESTRUCT)) { return; } @@ -3741,9 +4050,9 @@ private void updateSelfDestructButton() { } setSelfDestructEnabled(currentEntity.hasEngine() && - currentEntity.getEngine().isFusion() && - !currentEntity.getSelfDestructing() && - !currentEntity.getSelfDestructInitiated()); + currentEntity.getEngine().isFusion() && + !currentEntity.getSelfDestructing() && + !currentEntity.getSelfDestructInitiated()); } private void updateTraitorButton() { @@ -3777,7 +4086,7 @@ private void updateConvertModeButton() { boolean canConvert = false; for (int i = 0; i < 3; i++) { if (i != currentEntity.getConversionMode() - && ((LandAirMek) currentEntity).canConvertTo(currentEntity.getConversionMode(), i)) { + && ((LandAirMek) currentEntity).canConvertTo(currentEntity.getConversionMode(), i)) { canConvert = true; } } @@ -3786,7 +4095,7 @@ private void updateConvertModeButton() { return; } } else if (!((currentEntity instanceof QuadVee) || ((currentEntity instanceof Mek) - && ((Mek) currentEntity).hasTracks()))) { + && ((Mek) currentEntity).hasTracks()))) { setModeConvertEnabled(false); return; } @@ -3803,7 +4112,7 @@ private void updateConvertModeButton() { } if ((currentEntity instanceof QuadVee) && (((QuadVee) currentEntity).conversionCost() - > currentEntity.getRunMP())) { + > currentEntity.getRunMP())) { setModeConvertEnabled(false); return; } @@ -3825,7 +4134,7 @@ private void updateRecklessButton() { if (currentEntity instanceof ProtoMek) { setRecklessEnabled(false); } else { - setRecklessEnabled((null == cmd) || (cmd.length() == 0)); + setRecklessEnabled((null == cmd) || (pathZeroOrDeploy())); } } @@ -3840,8 +4149,8 @@ private void updateBraceButton() { } setBraceEnabled(!movePath.contains(MoveStepType.BRACE) && - movePath.isValidPositionForBrace(movePath.getFinalCoords(), finalBoardId(), - movePath.getFinalFacing())); + movePath.isValidPositionForBrace(movePath.getFinalCoords(), finalBoardId(), + movePath.getFinalFacing())); } private void updateClimbButton() { @@ -3858,11 +4167,13 @@ private void updateClimbButton() { * @param edgeTarget the lower hex the player clicked (adjacent to entity start) * @param isRoutedAround true if {@link #cmd} contains 2+ FORWARDS steps from a planner detour */ - private void rebuildPathForEdgeDescent(Mek edgeMek, Coords edgeTarget, boolean isRoutedAround) { + private void rebuildPathForEdgeDescent(Mek edgeMek, + Coords edgeTarget, + boolean isRoutedAround) { if (isRoutedAround) { cmd = new MovePath(game, edgeMek); cmd.rotatePathfinder(edgeMek.getPosition().direction(edgeTarget), false, - ManeuverType.MAN_NONE); + ManeuverType.MAN_NONE); cmd.addStep(MoveStepType.CLIMB_MODE_ON); cmd.addStep(MoveStepType.FORWARDS); } else { @@ -3907,7 +4218,7 @@ private ClimbingChoiceDialog.ClimbingOption showContinueClimbingDialog(Mek mek) * which can be wrong if the path walked or turned before reaching the cliff. */ private ClimbingChoiceDialog.ClimbingOption showStartClimbingDialog(Mek mek, - @Nullable MoveStep climbingStep) { + @Nullable MoveStep climbingStep) { return showClimbingLevelDialog(mek, false, climbingStep); } @@ -3919,15 +4230,15 @@ private ClimbingChoiceDialog.ClimbingOption showStartClimbingDialog(Mek mek, * @param climbingStep for a fresh climb, the path's climbing step (used to derive source/target hexes when the * path walks or turns before climbing); may be null to fall back to entity-derived * position/facing (the continuation case) - * * @return the chosen option, or null if cancelled / no levels remain */ - private ClimbingChoiceDialog.ClimbingOption showClimbingLevelDialog(Mek mek, boolean isContinuation, - @megamek.common.annotations.Nullable MoveStep climbingStep) { + private ClimbingChoiceDialog.ClimbingOption showClimbingLevelDialog(Mek mek, + boolean isContinuation, + @megamek.common.annotations.Nullable MoveStep climbingStep) { LOGGER.debug("[CLIMB-TRACE] showClimbingLevelDialog: entity={}, isContinuation={}, " + - "position={}, elevation={}, facing={}, climbingStep={}", - mek.getDisplayName(), isContinuation, mek.getPosition(), mek.getElevation(), mek.getFacing(), - (climbingStep != null) ? climbingStep.getPosition() : "null"); + "position={}, elevation={}, facing={}, climbingStep={}", + mek.getDisplayName(), isContinuation, mek.getPosition(), mek.getElevation(), mek.getFacing(), + (climbingStep != null) ? climbingStep.getPosition() : "null"); int costPerLevel = ClimbingHelper.getClimbingMPCostPerLevel(mek); int walkMP = mek.getWalkMP(); int currentElevation = mek.getElevation(); @@ -3966,9 +4277,9 @@ private ClimbingChoiceDialog.ClimbingOption showClimbingLevelDialog(Mek mek, boo int totalLevelsRemaining = targetLevel - currentAbsolute; LOGGER.debug("[CLIMB-TRACE] showClimbingLevelDialog: sourceCoords={}, sourceElevation={}, " - + "targetLevel={}, currentAbsolute={}, totalLevelsRemaining={}, targetHex={}, isBuilding={}", - sourceCoords, sourceElevation, targetLevel, currentAbsolute, totalLevelsRemaining, - targetCoords, targetHex.containsTerrain(Terrains.BUILDING)); + + "targetLevel={}, currentAbsolute={}, totalLevelsRemaining={}, targetHex={}, isBuilding={}", + sourceCoords, sourceElevation, targetLevel, currentAbsolute, totalLevelsRemaining, + targetCoords, targetHex.containsTerrain(Terrains.BUILDING)); if (totalLevelsRemaining <= 0) { LOGGER.debug("[CLIMB-TRACE] showClimbingLevelDialog: no levels remaining, returning null"); @@ -3999,12 +4310,12 @@ private ClimbingChoiceDialog.ClimbingOption showClimbingLevelDialog(Mek mek, boo // player knows the click on the cliff hex had no effect (otherwise it looks // like the path silently truncates). LOGGER.debug("[CLIMB-TRACE] showClimbingLevelDialog: no MP left for climbing " - + "(walkMP={}, mpAlreadyUsed={}), returning null", - walkMP, mpAlreadyUsed); + + "(walkMP={}, mpAlreadyUsed={}), returning null", + walkMP, mpAlreadyUsed); clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.ClimbingDialog.noMpToast", - mek.getDisplayName(), mpAlreadyUsed, walkMP, costPerLevel), - mek); + Messages.getString("MovementDisplay.ClimbingDialog.noMpToast", + mek.getDisplayName(), mpAlreadyUsed, walkMP, costPerLevel), + mek); return null; } @@ -4013,25 +4324,25 @@ private ClimbingChoiceDialog.ClimbingOption showClimbingLevelDialog(Mek mek, boo String headerMessage; if (isContinuation) { messageKey = isBuilding - ? "MovementDisplay.ClimbingDialog.continueBuilding" - : "MovementDisplay.ClimbingDialog.continueCliff"; + ? "MovementDisplay.ClimbingDialog.continueBuilding" + : "MovementDisplay.ClimbingDialog.continueCliff"; headerMessage = Messages.getString(messageKey, - mek.getDisplayName(), currentElevation, totalLevelsRemaining, - costPerLevel, availableMP); + mek.getDisplayName(), currentElevation, totalLevelsRemaining, + costPerLevel, availableMP); } else { messageKey = isBuilding - ? "MovementDisplay.ClimbingDialog.startBuilding" - : "MovementDisplay.ClimbingDialog.startCliff"; + ? "MovementDisplay.ClimbingDialog.startBuilding" + : "MovementDisplay.ClimbingDialog.startCliff"; headerMessage = Messages.getString(messageKey, - mek.getDisplayName(), totalLevelsRemaining, - costPerLevel, availableMP); + mek.getDisplayName(), totalLevelsRemaining, + costPerLevel, availableMP); } // Calculate PSR info for climbing int basePiloting = mek.getCrew().getPiloting(); int climbableArms = ClimbingHelper.countClimbableArms(mek); int climbPsrMod = ClimbingHelper.CLIMBING_PSR_MODIFIER - + ((climbableArms == 1) ? ClimbingHelper.ONE_ARM_PSR_MODIFIER : 0); + + ((climbableArms == 1) ? ClimbingHelper.ONE_ARM_PSR_MODIFIER : 0); int climbPsrTarget = basePiloting + climbPsrMod; // Build climbing options @@ -4039,11 +4350,11 @@ private ClimbingChoiceDialog.ClimbingOption showClimbingLevelDialog(Mek mek, boo for (int i = 1; i <= maxLevels; i++) { int cost = i * costPerLevel; String baseLabel = (i == 1) - ? Messages.getString("MovementDisplay.ClimbingDialog.levelOption", i, cost) - : Messages.getString("MovementDisplay.ClimbingDialog.levelsOption", i, cost); + ? Messages.getString("MovementDisplay.ClimbingDialog.levelOption", i, cost) + : Messages.getString("MovementDisplay.ClimbingDialog.levelsOption", i, cost); String label = baseLabel + " - PSR " + climbPsrTarget + "+ per level"; climbingOptions.add(new ClimbingChoiceDialog.ClimbingOption(i, cost, label, - ClimbingChoiceDialog.ClimbingActionType.CLIMB_UP)); + ClimbingChoiceDialog.ClimbingActionType.CLIMB_UP)); } // Climb Down: controlled descent at the same MP cost and PSRs as climbing up // (TO:AR p.20). Available whenever there's anywhere to descend — including the @@ -4056,11 +4367,11 @@ private ClimbingChoiceDialog.ClimbingOption showClimbingLevelDialog(Mek mek, boo for (int i = 1; i <= maxDescend; i++) { int cost = i * costPerLevel; String baseLabel = (i == 1) - ? Messages.getString("MovementDisplay.ClimbingDialog.descendOption", i, cost) - : Messages.getString("MovementDisplay.ClimbingDialog.descendOptions", i, cost); + ? Messages.getString("MovementDisplay.ClimbingDialog.descendOption", i, cost) + : Messages.getString("MovementDisplay.ClimbingDialog.descendOptions", i, cost); String label = baseLabel + " - PSR " + climbPsrTarget + "+ per level"; climbingOptions.add(new ClimbingChoiceDialog.ClimbingOption(i, cost, label, - ClimbingChoiceDialog.ClimbingActionType.CLIMB_DOWN)); + ClimbingChoiceDialog.ClimbingActionType.CLIMB_DOWN)); } } // Dangle-and-Drop: available mid-climb or mid-dangle with 2 functional arms @@ -4069,9 +4380,10 @@ private ClimbingChoiceDialog.ClimbingOption showClimbingLevelDialog(Mek mek, boo if (isContinuation && (descendableLevels > 0) && ClimbingHelper.canDangle(mek)) { int dangleLevels = Math.min(ClimbingHelper.DANGLE_LEVELS_PER_TURN, descendableLevels); climbingOptions.add(new ClimbingChoiceDialog.ClimbingOption(dangleLevels, 0, - Messages.getString("MovementDisplay.ClimbingDialog.dangleOption", - dangleLevels) + " - no PSR", - ClimbingChoiceDialog.ClimbingActionType.DANGLE_DOWN)); + Messages.getString( + "MovementDisplay.ClimbingDialog.dangleOption", + dangleLevels) + " - no PSR", + ClimbingChoiceDialog.ClimbingActionType.DANGLE_DOWN)); } // Drop option: available from any climbing or dangling position (TO:AR p.20). // From dangling: PSR modifiers reduced by 2. From climbing: standard leaping modifiers. @@ -4091,23 +4403,24 @@ private ClimbingChoiceDialog.ClimbingOption showClimbingLevelDialog(Mek mek, boo int legTarget = basePiloting + (2 * effectiveDrop); int fallTarget = basePiloting + effectiveDrop; String dropLabel = Messages.getString("MovementDisplay.ClimbingDialog.dropOption", - ClimbingHelper.DROP_MP_COST) - + ((effectiveDrop > 0) - ? " - Leg PSR " + legTarget + "+, Fall PSR " + fallTarget + "+" - : " - sink to floor (no PSR)"); + ClimbingHelper.DROP_MP_COST) + + ((effectiveDrop > 0) + ? " - Leg PSR " + legTarget + "+, Fall PSR " + fallTarget + "+" + : " - sink to floor (no PSR)"); climbingOptions.add(new ClimbingChoiceDialog.ClimbingOption(totalDropDistance, - ClimbingHelper.DROP_MP_COST, dropLabel, - ClimbingChoiceDialog.ClimbingActionType.DROP)); + ClimbingHelper.DROP_MP_COST, dropLabel, + ClimbingChoiceDialog.ClimbingActionType.DROP)); } if (isContinuation) { climbingOptions.add(new ClimbingChoiceDialog.ClimbingOption(0, 0, - Messages.getString("MovementDisplay.ClimbingDialog.clingOption"), - ClimbingChoiceDialog.ClimbingActionType.CLING)); + Messages.getString( + "MovementDisplay.ClimbingDialog.clingOption"), + ClimbingChoiceDialog.ClimbingActionType.CLING)); } // Show the dialog ClimbingChoiceDialog dialog = new ClimbingChoiceDialog( - clientgui.getFrame(), headerMessage, climbingOptions); + clientgui.getFrame(), headerMessage, climbingOptions); dialog.setVisible(true); ClimbingChoiceDialog.ClimbingOption chosen = dialog.getFirstChoice(); @@ -4121,10 +4434,10 @@ private ClimbingChoiceDialog.ClimbingOption showClimbingLevelDialog(Mek mek, boo private void updateClimbModeButtonText() { boolean climbModeOn = (cmd != null) ? cmd.getFinalClimbMode() - : (currentEntity() != null && currentEntity().climbMode()); + : (currentEntity() != null && currentEntity().climbMode()); String baseLabel = climbModeOn - ? Messages.getString("MovementDisplay.moveClimbModeOn") - : Messages.getString("MovementDisplay.moveClimbModeOff"); + ? Messages.getString("MovementDisplay.moveClimbModeOn") + : Messages.getString("MovementDisplay.moveClimbModeOff"); ClimbModeContext context = computeClimbModeContext(climbModeOn); String label; if (context.fullLabel() != null) { @@ -4150,8 +4463,10 @@ private void updateClimbModeButtonText() { * @param tooltip HTML tooltip describing the toggle's current effect */ private record ClimbModeContext(@megamek.common.annotations.Nullable String suffix, - @megamek.common.annotations.Nullable String fullLabel, - String tooltip) {} + @megamek.common.annotations.Nullable String fullLabel, + String tooltip) { + + } /** * Inspects the entity's current/pending position and facing to determine what the Climb Mode toggle currently @@ -4178,9 +4493,9 @@ private ClimbModeContext computeClimbModeContext(boolean climbModeOn) { // each mode does. Leaping matters for the at-edge case (no climb dialog, but leaping // still gates the fall-vs-illegal behaviour). boolean tacOpsClimbing = game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING); + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING); boolean tacOpsLeaping = game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_LEAPING); + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_LEAPING); Hex curHex = game.getBoard(entity).getHex(curPos); Coords frontPos = curPos.translated(curFacing); Hex frontHex = game.getBoard(entity).getHex(frontPos); @@ -4189,16 +4504,16 @@ private ClimbModeContext computeClimbModeContext(boolean climbModeOn) { boolean bridgeFront = (frontHex != null) && frontHex.containsTerrain(Terrains.BRIDGE); if (bridgeHere || bridgeFront) { String bridgeTipKey = tacOpsClimbing - ? "MovementDisplay.climbModeTip.bridge" - : "MovementDisplay.climbModeTip.bridge.standard"; + ? "MovementDisplay.climbModeTip.bridge" + : "MovementDisplay.climbModeTip.bridge.standard"; // Bridge gets an action-style label override — "Move onto Bridge" / "Move under // Bridge" reads more clearly than "Climbing Bridge" / "Move Thru Bridge" because // it tells the player which physical level the current toggle puts them at. String bridgeBtn = climbModeOn - ? Messages.getString("MovementDisplay.climbModeBtn.bridgeOnto") - : Messages.getString("MovementDisplay.climbModeBtn.bridgeUnder"); + ? Messages.getString("MovementDisplay.climbModeBtn.bridgeOnto") + : Messages.getString("MovementDisplay.climbModeBtn.bridgeUnder"); return new ClimbModeContext(Messages.getString("MovementDisplay.climbModeCtx.bridge"), - bridgeBtn, Messages.getString(bridgeTipKey)); + bridgeBtn, Messages.getString(bridgeTipKey)); } if (frontHex != null) { int curAlt = ((curHex != null) ? curHex.getLevel() : 0) + curElevation; @@ -4212,20 +4527,20 @@ private ClimbModeContext computeClimbModeContext(boolean climbModeOn) { if (diff >= ClimbingHelper.MIN_CLIMBING_LEVELS) { boolean isBuilding = frontHex.containsTerrain(Terrains.BUILDING); String suffixKey = isBuilding - ? "MovementDisplay.climbModeCtx.upBuilding" - : "MovementDisplay.climbModeCtx.upCliff"; + ? "MovementDisplay.climbModeCtx.upBuilding" + : "MovementDisplay.climbModeCtx.upCliff"; String tipKey; if (tacOpsClimbing) { tipKey = isBuilding - ? "MovementDisplay.climbModeTip.upBuilding" - : "MovementDisplay.climbModeTip.upCliff"; + ? "MovementDisplay.climbModeTip.upBuilding" + : "MovementDisplay.climbModeTip.upCliff"; } else { tipKey = isBuilding - ? "MovementDisplay.climbModeTip.upBuilding.standard" - : "MovementDisplay.climbModeTip.upCliff.standard"; + ? "MovementDisplay.climbModeTip.upBuilding.standard" + : "MovementDisplay.climbModeTip.upCliff.standard"; } return new ClimbModeContext(Messages.getString(suffixKey), null, - Messages.getString(tipKey, diff)); + Messages.getString(tipKey, diff)); } // At edge of a 3+ level drop: with TacOps Climbing ON the descent dialog fires; // with it OFF, behaviour depends on TacOps Leaping (leap-with-fall-PSRs vs illegal). @@ -4239,14 +4554,14 @@ private ClimbModeContext computeClimbModeContext(boolean climbModeOn) { tipKey = "MovementDisplay.climbModeTip.atEdge.standardIllegal"; } return new ClimbModeContext(Messages.getString("MovementDisplay.climbModeCtx.atEdge"), null, - Messages.getString(tipKey, -diff)); + Messages.getString(tipKey, -diff)); } // Building adjacent (small one): toggle controls roof vs walk-through. Works in // both rule modes since the step is within normal max elevation change. if (frontHex.containsTerrain(Terrains.BUILDING) - || ((curHex != null) && curHex.containsTerrain(Terrains.BUILDING))) { + || ((curHex != null) && curHex.containsTerrain(Terrains.BUILDING))) { return new ClimbModeContext(Messages.getString("MovementDisplay.climbModeCtx.building"), null, - Messages.getString("MovementDisplay.climbModeTip.building")); + Messages.getString("MovementDisplay.climbModeTip.building")); } } return new ClimbModeContext(null, null, Messages.getString("MovementDisplay.climbModeTip.none")); @@ -4305,10 +4620,10 @@ private void updateBombButton() { } if (currentEntity().isBomber() - && ((currentEntity() instanceof LandAirMek) - || game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_COMBAT_TAC_OPS_VTOL_ATTACKS)) - && ((IBomber) currentEntity()).getBombPoints() > 0) { + && ((currentEntity() instanceof LandAirMek) + || game.getOptions() + .booleanOption(OptionsConstants.ADVANCED_COMBAT_TAC_OPS_VTOL_ATTACKS)) + && ((IBomber) currentEntity()).getBombPoints() > 0) { setBombEnabled(true); } } @@ -4325,19 +4640,21 @@ private synchronized void updateLoadButtons() { updateDropCargoButton(); } - /** Updates the status of the "pickup cargo" button */ + /** + * Updates the status of the "pickup cargo" button + */ private void updatePickupCargoButton() { final Entity currentEntity = currentEntity(); // there has to be an entity, objects are on the ground, // the entity can pick them up if ((currentEntity == null) || - ((game.getGroundObjects(finalPosition(), currentEntity).isEmpty()) - && (game.getEntitiesVector(finalPosition()) - .stream() - .filter(currentEntity::canPickupCarryableObject) - .toList() - .isEmpty())) || - ((cmd.getLastStep() != null) && (cmd.getLastStep().getType() == MoveStepType.PICKUP_CARGO))) { + ((game.getGroundObjects(finalPosition(), currentEntity).isEmpty()) + && (game.getEntitiesVector(finalPosition()) + .stream() + .filter(currentEntity::canPickupCarryableObject) + .toList() + .isEmpty())) || + ((cmd.getLastStep() != null) && (cmd.getLastStep().getType() == MoveStepType.PICKUP_CARGO))) { setPickupCargoEnabled(false); return; } @@ -4345,17 +4662,19 @@ private void updatePickupCargoButton() { setPickupCargoEnabled(true); } - /** Updates the status of the "drop cargo" button */ + /** + * Updates the status of the "drop cargo" button + */ private void updateDropCargoButton() { final Entity currentlySelectedEntity = currentEntity(); // there has to be an entity, objects are on the ground, the entity can pick them up if ((currentlySelectedEntity == null) - || (currentlySelectedEntity.getCarriedObjects().isEmpty() - && currentlySelectedEntity.getTransports() - .stream() - .filter(t -> t instanceof ExternalCargo) - .flatMap(t -> t.getCarryables().stream()) - .toList().isEmpty())) { + || (currentlySelectedEntity.getCarriedObjects().isEmpty() + && currentlySelectedEntity.getTransports() + .stream() + .filter(t -> t instanceof ExternalCargo) + .flatMap(t -> t.getCarryables().stream()) + .toList().isEmpty())) { setDropCargoEnabled(false); return; } @@ -4363,12 +4682,18 @@ private void updateDropCargoButton() { setDropCargoEnabled(true); } - /** Updates the status of the Load button. */ + /** + * Updates the status of the Load button. + */ private void updateLoadButton() { final Entity currentEntity = currentEntity(); + if (!currentEntity.isDeployed()) { + setLoadEnabled(false); + return; + } if ((currentEntity == null) || (currentEntity.getWalkMP() <= 0 && !currentEntity.isAerospace()) || ( - currentEntity.isAerospace() - && currentEntity.isAirborne())) { + currentEntity.isAerospace() + && currentEntity.isAirborne())) { setLoadEnabled(false); return; } @@ -4380,17 +4705,23 @@ private void updateLoadButton() { } final boolean canLoad = candidates - .stream() - .filter(other -> !currentEntity.canTow(other.getId())) - .filter(Entity::isLoadableThisTurn) - .anyMatch(other -> currentEntity.canLoad(other, true, cmd.getFinalElevation()) && - other.getTargetBay() == UNSET_BAY); + .stream() + .filter(other -> !currentEntity.canTow(other.getId())) + .filter(Entity::isLoadableThisTurn) + .anyMatch(other -> currentEntity.canLoad(other, true, cmd.getFinalElevation()) && + other.getTargetBay() == UNSET_BAY); setLoadEnabled(canLoad); } - /** Updates the status of the Unload button. */ + /** + * Updates the status of the Unload button. + */ private void updateUnloadButton() { final Entity currentEntity = currentEntity(); + if (!currentEntity.isDeployed()) { + setUnloadEnabled(false); + return; + } if (currentEntity == null) { setUnloadEnabled(false); return; @@ -4402,9 +4733,9 @@ private void updateUnloadButton() { } final boolean legalGear = ((gear == GEAR_LAND) || - (gear == GEAR_TURN) || - (gear == GEAR_BACKUP) || - (gear == GEAR_JUMP)); + (gear == GEAR_TURN) || + (gear == GEAR_BACKUP) || + (gear == GEAR_JUMP)); final int unloadEl = cmd.getFinalElevation(); final Hex hex = game.getBoard(currentEntity).getHex(cmd.getFinalCoords()); boolean canUnloadHere = false; @@ -4412,19 +4743,19 @@ private void updateUnloadButton() { // A unit that has somehow exited the map is assumed to be unable to unload if (isFinalPositionOnBoard()) { canUnloadHere = unloadableUnits.stream() - .anyMatch(en -> en.isElevationValid(unloadEl, hex) || (en.getJumpMP() > 0)); + .anyMatch(en -> en.isElevationValid(unloadEl, hex) || (en.getJumpMP() > 0)); // Zip lines, TO pg 219 if (game.getOptions().booleanOption(ADVANCED_GROUND_MOVEMENT_TAC_OPS_ZIPLINES) - && (currentEntity instanceof VTOL)) { + && (currentEntity instanceof VTOL)) { canUnloadHere |= unloadableUnits.stream() - .filter(Entity::isInfantry) - .anyMatch(en -> !((Infantry) en).isMechanized()); + .filter(Entity::isInfantry) + .anyMatch(en -> !((Infantry) en).isMechanized()); } // Glider wings allow infantry to exit VTOLs as if jump infantry (IO p.85) if (currentEntity instanceof VTOL) { canUnloadHere |= unloadableUnits.stream() - .filter(Entity::isInfantry) - .anyMatch(en -> ((Infantry) en).canExitVTOLWithGliderWings()); + .filter(Entity::isInfantry) + .anyMatch(en -> ((Infantry) en).canExitVTOLWithGliderWings()); } } setUnloadEnabled(legalGear && canUnloadHere && !unloadableUnits.isEmpty()); @@ -4445,15 +4776,17 @@ private void updateMountButton() { mpUsed = cmd.getMpUsed(); } final boolean canMount = isFinalPositionOnBoard() && - !movingEntity.isAirborne() && - (mpUsed <= Math.ceil(movingEntity.getWalkMP() / 2.0)) && - !Compute.getMountableUnits(movingEntity, pos, finalBoardId(), - elev + game.getBoard(movingEntity).getHex(pos).getLevel(), - game).isEmpty(); + !movingEntity.isAirborne() && + (mpUsed <= Math.ceil(movingEntity.getWalkMP() / 2.0)) && + !Compute.getMountableUnits(movingEntity, pos, finalBoardId(), + elev + game.getBoard(movingEntity).getHex(pos).getLevel(), + game).isEmpty(); setMountEnabled(canMount); } - /** Updates the status of the Tow and Disconnect buttons. */ + /** + * Updates the status of the Tow and Disconnect buttons. + */ private void updateTowingButtons() { final Entity currentEntity = currentEntity(); if ((currentEntity == null) || (currentEntity instanceof SmallCraft)) { @@ -4463,21 +4796,21 @@ private void updateTowingButtons() { } final boolean legalGear = ((gear == GEAR_LAND) || - (gear == GEAR_TURN) || - (gear == GEAR_BACKUP) || - (gear == GEAR_JUMP)); + (gear == GEAR_TURN) || + (gear == GEAR_BACKUP) || + (gear == GEAR_JUMP)); final Hex hex = game.getBoard(currentEntity).getHex(cmd.getFinalCoords()); final int unloadEl = cmd.getFinalElevation(); final boolean canDropTrailerHere = towedUnits.stream().anyMatch(en -> en.isElevationValid(unloadEl, hex)); // An off board train stays hooked up. There is no board hex to leave a trailer in, and the train is the only // thing holding the trailer's place off the map edge. setDisconnectEnabled(legalGear && isFinalPositionOnBoard() && canDropTrailerHere - && !currentEntity.isOffBoard()); + && !currentEntity.isOffBoard()); final boolean canTow = currentEntity.getHitchLocations() - .stream() - .flatMap(c -> game.getEntitiesVector(c).stream()) - .anyMatch(e -> currentEntity.canTow(e.getId())); + .stream() + .flatMap(c -> game.getEntitiesVector(c).stream()) + .anyMatch(e -> currentEntity.canTow(e.getId())); setTowEnabled(canTow); } @@ -4493,7 +4826,7 @@ private Coords finalPosition() { */ private boolean isFinalPositionOnBoard() { return game.getBoard(currentEntity().getBoardId()) - .contains(cmd == null ? currentEntity().getPosition() : cmd.getFinalCoords()); + .contains(cmd == null ? currentEntity().getPosition() : cmd.getFinalCoords()); } private int finalBoardId() { @@ -4514,8 +4847,8 @@ private void updateLayMineButton() { setLayMineEnabled(false); } else if (currentEntity 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); } @@ -4543,12 +4876,15 @@ private Entity getMountedUnit() { } else if (mountableUnits.size() > 1) { // If we have multiple choices, display a selection dialog. String input = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.MountUnitDialog.message", currentEntity.getShortName()), - Messages.getString("MovementDisplay.MountUnitDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - SharedUtility.getDisplayArray(mountableUnits), - null); + Messages.getString( + "MovementDisplay.MountUnitDialog.message", + currentEntity.getShortName()), + Messages.getString( + "MovementDisplay.MountUnitDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + SharedUtility.getDisplayArray(mountableUnits), + null); choice = (Entity) SharedUtility.getTargetPicked(mountableUnits, input); } else { // Only one choice. @@ -4569,12 +4905,15 @@ private Entity getMountedUnit() { } if (bayChoices.size() > 1) { String bayString = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.MountUnitBayNumberDialog.message", choice.getShortName()), - Messages.getString("MovementDisplay.MountUnitBayNumberDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - retVal, - null); + Messages.getString( + "MovementDisplay.MountUnitBayNumberDialog.message", + choice.getShortName()), + Messages.getString( + "MovementDisplay.MountUnitBayNumberDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + retVal, + null); currentEntity.setTargetBay(MathUtility.parseInt(bayString.substring(0, bayString.indexOf(" ")))); // We need to update the entity here so that the server knows // about our target bay @@ -4598,7 +4937,7 @@ private Entity getMountedUnit() { for (Entity other : game.getEntitiesVector(coords)) { // Only allow selecting units that aren't already getting loaded if (other.isLoadableThisTurn() && (currentEntity() != null) && currentEntity().canLoad(other, true, - cmd.getFinalElevation()) && (other.getTargetBay() == UNSET_BAY)) { + cmd.getFinalElevation()) && (other.getTargetBay() == UNSET_BAY)) { choices.addElement(other); } } @@ -4613,24 +4952,25 @@ private Entity getMountedUnit() { // If we have multiple choices, display a selection dialog. if (choices.size() > 1) { String input = (String) JOptionPane - .showInputDialog(clientgui.getFrame(), - Messages.getString( - "DeploymentDisplay.loadUnitDialog.message", - currentEntity().getShortName(), - currentEntity().getUnusedString()), - Messages.getString("DeploymentDisplay.loadUnitDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - SharedUtility.getDisplayArray(choices), - null); + .showInputDialog(clientgui.getFrame(), + Messages.getString( + "DeploymentDisplay.loadUnitDialog.message", + currentEntity().getShortName(), + currentEntity().getUnusedString()), + Messages.getString("DeploymentDisplay.loadUnitDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + SharedUtility.getDisplayArray(choices), + null); choice = (Entity) SharedUtility.getTargetPicked(choices, input); // Add matching notification below } else { // Only one choice. choice = choices.getFirst(); // Notify user clientgui.addToast(ToastLevel.INFO, - Messages.getString("DeploymentDisplay.loadUnitToDefault.message", - currentEntity().getShortName(), choice.getShortName()), currentEntity()); + Messages.getString("DeploymentDisplay.loadUnitToDefault.message", + currentEntity().getShortName(), choice.getShortName()), + currentEntity()); } // Handle canceled dialog @@ -4650,27 +4990,29 @@ private Entity getMountedUnit() { if (bayChoices.size() == 1) { clientgui.addToast(ToastLevel.INFO, - Messages.getString("MovementDisplay.loadUnitBayNumberDefault.message", - currentEntity().getShortName(), choice.getShortName(), - String.format("Bay %s", bayChoices.getFirst())), currentEntity()); + Messages.getString("MovementDisplay.loadUnitBayNumberDefault.message", + currentEntity().getShortName(), choice.getShortName(), + String.format("Bay %s", bayChoices.getFirst())), currentEntity()); choice.setTargetBay(bayChoices.getFirst()); } else if (bayChoices.size() > 1) { String[] bayChoicesArray = new String[bayChoices.size()]; int i = 0; for (Integer bayNumber : bayChoices) { bayChoicesArray[i++] = bayNumber.toString() - + " (Free Slots: " - + (int) currentEntity().getBayById(bayNumber).getUnused() - + ")"; + + " (Free Slots: " + + (int) currentEntity().getBayById(bayNumber).getUnused() + + ")"; } String bayString = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.loadUnitBayNumberDialog.message", - currentEntity().getShortName()), - Messages.getString("MovementDisplay.loadUnitBayNumberDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - bayChoicesArray, - null); + Messages.getString( + "MovementDisplay.loadUnitBayNumberDialog.message", + currentEntity().getShortName()), + Messages.getString( + "MovementDisplay.loadUnitBayNumberDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + bayChoicesArray, + null); // Handle canceled dialog if (bayString == null) { return null; @@ -4692,17 +5034,19 @@ private Entity getMountedUnit() { int i = 0; for (Integer bayNumber : bayChoices) { clampChoicesArray[i++] = bayNumber > 0 ? - Messages.getString("MovementDisplay.loadProtoClampMountDialog.rear") : - Messages.getString("MovementDisplay.loadProtoClampMountDialog.front"); + Messages.getString("MovementDisplay.loadProtoClampMountDialog.rear") : + Messages.getString("MovementDisplay.loadProtoClampMountDialog.front"); } String bayString = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.loadProtoClampMountDialog.message", - currentEntity().getShortName()), - Messages.getString("MovementDisplay.loadProtoClampMountDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - clampChoicesArray, - null); + Messages.getString( + "MovementDisplay.loadProtoClampMountDialog.message", + currentEntity().getShortName()), + Messages.getString( + "MovementDisplay.loadProtoClampMountDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + clampChoicesArray, + null); if (bayString == null) { // Cancelled out, best to cancel the loading. @@ -4710,7 +5054,7 @@ private Entity getMountedUnit() { } choice.setTargetBay(bayString.equals(Messages.getString( - "MovementDisplay.loadProtoClampMountDialog.front")) ? 0 : 1); + "MovementDisplay.loadProtoClampMountDialog.front")) ? 0 : 1); // We need to update the entity here so that the server knows // about our target bay clientgui.getClient().sendUpdateEntity(choice); @@ -4726,7 +5070,7 @@ private Entity getMountedUnit() { * loaded units. * * @return The Entity that the player wants to tow. This value may be null if there are no eligible - * targets + * targets */ private Entity getTowedUnit() { Entity choice; @@ -4752,12 +5096,15 @@ private Entity getTowedUnit() { // If we have multiple choices, display a selection dialog. if (choices.size() > 1) { String input = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("DeploymentDisplay.towUnitDialog.message", currentEntity().getShortName()), - Messages.getString("DeploymentDisplay.towUnitDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - SharedUtility.getDisplayArray(choices), - null); + Messages.getString( + "DeploymentDisplay.towUnitDialog.message", + currentEntity().getShortName()), + Messages.getString( + "DeploymentDisplay.towUnitDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + SharedUtility.getDisplayArray(choices), + null); choice = (Entity) SharedUtility.getTargetPicked(choices, input); } else { // Only one choice. @@ -4768,11 +5115,14 @@ private Entity getTowedUnit() { // We need lots of data about the hitch to store in different places. Save that // here final class HitchChoice { + private final int id; private final int number; private final TankTrailerHitch hitch; - private HitchChoice(int id, int number, TankTrailerHitch t) { + private HitchChoice(int id, + int number, + TankTrailerHitch t) { this.id = id; this.number = number; this.hitch = t; @@ -4823,8 +5173,8 @@ public String toString() { if (transporter.canTow(choice)) { TankTrailerHitch tankTrailerHitch = (TankTrailerHitch) transporter; HitchChoice hitch = new HitchChoice(entity.getId(), - entity.getTransports().indexOf(transporter), - tankTrailerHitch); + entity.getTransports().indexOf(transporter), + tankTrailerHitch); hitchChoices.add(hitch); } } @@ -4839,12 +5189,15 @@ public String toString() { retVal[i++] = hc.toString(); } String selection = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.loadUnitHitchDialog.message", currentEntity().getShortName()), - Messages.getString("MovementDisplay.loadUnitHitchDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - retVal, - null); + Messages.getString( + "MovementDisplay.loadUnitHitchDialog.message", + currentEntity().getShortName()), + Messages.getString( + "MovementDisplay.loadUnitHitchDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + retVal, + null); HitchChoice hc = null; if (selection != null) { for (HitchChoice hitchChoice : hitchChoices) { @@ -4894,14 +5247,16 @@ private Entity getDisconnectedUnit() { } else if (currentEntity.getAllTowedUnits().size() > 1) { // If we have multiple choices, display a selection dialog. String input = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.DisconnectUnitDialog.message", - currentEntity.getShortName(), - currentEntity.getUnusedString()), - Messages.getString("MovementDisplay.DisconnectUnitDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - SharedUtility.getDisplayArray(towedUnits), - null); + Messages.getString( + "MovementDisplay.DisconnectUnitDialog.message", + currentEntity.getShortName(), + currentEntity.getUnusedString()), + Messages.getString( + "MovementDisplay.DisconnectUnitDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + SharedUtility.getDisplayArray(towedUnits), + null); choice = (Entity) SharedUtility.getTargetPicked(towedUnits, input); } else { // Only one choice. @@ -4929,18 +5284,20 @@ private Entity getDisconnectedUnit() { } else if (unloadableUnits.size() > 1) { // Only show the units we are not already planning to unload List filteredUnits = unloadableUnits - .stream() - .filter(entity -> entity.getTargetBay() == UNSET_BAY).collect(Collectors.toList()); + .stream() + .filter(entity -> entity.getTargetBay() == UNSET_BAY).collect(Collectors.toList()); // If we have multiple choices, display a selection dialog. String input = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.UnloadUnitDialog.message", - currentEntity.getShortName(), - currentEntity.getUnusedString()), - Messages.getString("MovementDisplay.UnloadUnitDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - SharedUtility.getDisplayArray(filteredUnits), - null); + Messages.getString( + "MovementDisplay.UnloadUnitDialog.message", + currentEntity.getShortName(), + currentEntity.getUnusedString()), + Messages.getString( + "MovementDisplay.UnloadUnitDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + SharedUtility.getDisplayArray(filteredUnits), + null); choice = (Entity) SharedUtility.getTargetPicked(filteredUnits, input); } else { // Only one choice. @@ -4956,7 +5313,6 @@ private Entity getDisconnectedUnit() { * Returns a position to unload a unit into or null if the player cancels the dialog. * * @param unloaded The unit to unload - * * @return The position to unload to */ private @Nullable Coords getUnloadPosition(Entity unloaded) { @@ -5012,7 +5368,7 @@ private Entity getDisconnectedUnit() { if (ring.isEmpty()) { clientgui.addToast(ToastLevel.ERROR, - Messages.getString("MovementDisplay.NoPlaceToUnload.message"), currentEntity()); + Messages.getString("MovementDisplay.NoPlaceToUnload.message"), currentEntity()); return null; } String[] choices = new String[ring.size()]; @@ -5021,15 +5377,15 @@ private Entity getDisconnectedUnit() { choices[i++] = c.toString(); } String selected = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.ChooseHex" + ".message", - currentEntity.getShortName(), - currentEntity.getUnusedString()), - Messages.getString("MovementDisplay.ChooseHex.title"), - - JOptionPane.QUESTION_MESSAGE, - null, - choices, - null); + Messages.getString("MovementDisplay.ChooseHex" + ".message", + currentEntity.getShortName(), + currentEntity.getUnusedString()), + Messages.getString("MovementDisplay.ChooseHex.title"), + + JOptionPane.QUESTION_MESSAGE, + null, + choices, + null); if (selected == null) { return null; } @@ -5046,7 +5402,6 @@ private Entity getDisconnectedUnit() { /** * @param abandoned - The vessel we're escaping from - * * @return Uses player input to find a legal hex where an EjectedCrew unit can be placed */ private Coords getEjectPosition(Entity abandoned) { @@ -5066,7 +5421,7 @@ private Coords getEjectPosition(Entity abandoned) { ring = Compute.getAcceptableUnloadPositions(ring, finalBoardId(), crew, game, elev); if (ring.isEmpty()) { clientgui.addToast(ToastLevel.ERROR, - Messages.getString("MovementDisplay.NoPlaceToEject.message"), currentEntity()); + Messages.getString("MovementDisplay.NoPlaceToEject.message"), currentEntity()); return null; } String[] choices = new String[ring.size()]; @@ -5075,10 +5430,12 @@ private Coords getEjectPosition(Entity abandoned) { choices[i++] = c.toString(); } String selected = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.ChooseEjectHex.message", - abandoned.getShortName(), abandoned.getUnusedString()), - Messages.getString("MovementDisplay.ChooseHex.title"), - JOptionPane.QUESTION_MESSAGE, null, choices, null); + Messages.getString( + "MovementDisplay.ChooseEjectHex.message", + abandoned.getShortName(), + abandoned.getUnusedString()), + Messages.getString("MovementDisplay.ChooseHex.title"), + JOptionPane.QUESTION_MESSAGE, null, choices, null); if (selected == null) { return null; } @@ -5116,10 +5473,10 @@ private synchronized void updateRecoveryButton() { // Is the other unit friendly and not the current entity? must be done with its movement it also must // be the same heading and velocity if ((other instanceof Aero oa) && - other.isDone() && - other.canLoad(currentEntity) && - (cmd.getFinalFacing() == other.getFacing()) && - !other.isCapitalFighter()) { + other.isDone() && + other.canLoad(currentEntity) && + (cmd.getFinalFacing() == other.getFacing()) && + !other.isCapitalFighter()) { // now let's check velocity // depends on movement rules if (game.useVectorMove()) { @@ -5183,10 +5540,10 @@ private synchronized void updateJoinButton() { // Is the other unit friendly and not the current entity? Must be done with its movement it also must be // the same heading and velocity if (currentEntity.getOwner().equals(other.getOwner()) && - other.isCapitalFighter() && - other.isDone() && - other.canLoad(currentEntity) && - (cmd.getFinalFacing() == other.getFacing())) { + other.isCapitalFighter() && + other.isDone() && + other.canLoad(currentEntity) && + (cmd.getFinalFacing() == other.getFacing())) { // now let's check velocity // depends on movement rules Aero oa = (Aero) other; @@ -5256,9 +5613,9 @@ private TreeMap> getLaunchedUnits() { } String[] names = new String[currentFighters.size()]; String question = Messages.getString("MovementDisplay.LaunchFighterDialog.message", - currentEntity.getShortName(), - doors * 2, - bayNum); + currentEntity.getShortName(), + doors * 2, + bayNum); for (int loop = 0; loop < names.length; loop++) { names[loop] = currentFighters.get(loop).getShortName(); } @@ -5267,11 +5624,11 @@ private TreeMap> getLaunchedUnits() { ChoiceDialog choiceDialog = null; while (!doIt) { choiceDialog = new ChoiceDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.LaunchFighterDialog.title", - currentBay.getTransporterType(), - bayNum), - question, - names); + Messages.getString("MovementDisplay.LaunchFighterDialog.title", + currentBay.getTransporterType(), + bayNum), + question, + names); choiceDialog.setVisible(true); if (choiceDialog.getChoices() == null) { doIt = true; @@ -5290,8 +5647,8 @@ private TreeMap> getLaunchedUnits() { } modifier += currentFighters.get(choice).getCrew().getPiloting(); String damageMsg = Messages.getString("MovementDisplay.LaunchFighterDialog.controlroll", - names[choice], - modifier); + names[choice], + modifier); pilotSkillRolls.append("\t").append(damageMsg).append("\n"); } String title = Messages.getString("MovementDisplay.areYouSure"); @@ -5352,33 +5709,34 @@ private TreeMap> getUndockedUnits() { if (!currentDropships.isEmpty()) { String[] names = new String[currentDropships.size()]; String question = Messages.getString("MovementDisplay.LaunchDropshipDialog.message", - currentlySelectedEntity.getShortName(), - 1, - collarNum); + currentlySelectedEntity.getShortName(), + 1, + collarNum); for (int loop = 0; loop < names.length; loop++) { names[loop] = currentDropships.get(loop).getShortName(); } boolean doIt = false; ChoiceDialog choiceDialog = new ChoiceDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.LaunchDropshipDialog.title", - collar.getTransporterType(), - collarNum), - question, - names); + Messages.getString( + "MovementDisplay.LaunchDropshipDialog.title", + collar.getTransporterType(), + collarNum), + question, + names); while (!doIt) { choiceDialog = new ChoiceDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.LaunchDropshipDialog.title", - collar.getTransporterType(), - collarNum), - question, - names); + Messages.getString("MovementDisplay.LaunchDropshipDialog.title", + collar.getTransporterType(), + collarNum), + question, + names); choiceDialog.setVisible(true); if ((choiceDialog.getChoices() != null) && (choiceDialog.getChoices().length > 1)) { ConfirmDialog nag = new ConfirmDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.areYouSure"), - Messages.getString("MovementDisplay.ConfirmLaunch"), - true); + Messages.getString("MovementDisplay.areYouSure"), + Messages.getString("MovementDisplay.ConfirmLaunch"), + true); nag.setVisible(true); doIt = nag.getAnswer(); } else { @@ -5393,7 +5751,7 @@ private TreeMap> getUndockedUnits() { collarChoices.add(currentDropships.get(element).getId()); // Prompt the player to load passengers aboard the launching ship(s) Entity en = game - .getEntity(currentDropships.get(element).getId()); + .getEntity(currentDropships.get(element).getId()); if (en instanceof SmallCraft) { loadPassengerAtLaunch((SmallCraft) en); } @@ -5427,13 +5785,15 @@ private void loadPassengerAtLaunch(SmallCraft craft) { int space = getSpace(craft, currentEntity); ConfirmDialog takePassenger = new ConfirmDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.FillSmallCraftPassengerDialog.Title"), - Messages.getString("MovementDisplay.FillSmallCraftPassengerDialog.message", - craft.getShortName(), - space, - currentEntity.getShortName() + "'", - currentEntity.getNPassenger()), - false); + 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 @@ -5447,7 +5807,8 @@ private void loadPassengerAtLaunch(SmallCraft craft) { } } - private static int getSpace(SmallCraft craft, Entity currentEntity) { + private static int getSpace(SmallCraft craft, + Entity currentEntity) { int space = 0; for (Bay b : craft.getTransportBays()) { if ((b instanceof CargoBay) || (b instanceof InfantryBay) || (b instanceof BattleArmorBay)) { @@ -5527,21 +5888,21 @@ private TreeMap> getDroppedUnits() { if (!currentUnits.isEmpty() && (isInfantryTransporter || (doorsEligibleForDrop > 0))) { String[] names = new String[currentUnits.size()]; String question = Messages.getString("MovementDisplay.DropUnitDialog.message", - doorsEligibleForDrop, - bayNum); + doorsEligibleForDrop, + bayNum); for (int loop = 0; loop < names.length; loop++) { names[loop] = currentUnits.get(loop).getShortName(); } // If this is an infantry-transporting bay (cargo, Infantry Bay, etc.), no limit on drops int max = (isInfantryTransporter) ? -1 : doorsEligibleForDrop; ChoiceDialog choiceDialog = new ChoiceDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.DropUnitDialog.title", - currentTransporter.getTransporterType(), - bayNum), - question, - names, - false, - max); + Messages.getString("MovementDisplay.DropUnitDialog.title", + currentTransporter.getTransporterType(), + bayNum), + question, + names, + false, + max); choiceDialog.setVisible(true); if (choiceDialog.getAnswer()) { // load up the choices @@ -5579,11 +5940,11 @@ private int getRecoveryUnit() { // Is the other unit friendly and not the current entity? Must be done with its movement it also must be // the same heading and velocity if ((other instanceof Aero oa) && - !oa.isOutControlTotal() && - other.isDone() && - other.canLoad(currentEntity) && - currentEntity.isLoadableThisTurn() && - (cmd.getFinalFacing() == other.getFacing())) { + !oa.isOutControlTotal() && + other.isDone() && + other.canLoad(currentEntity) && + currentEntity.isLoadableThisTurn() && + (cmd.getFinalFacing() == other.getFacing())) { // now let's check velocity // depends on movement rules if (game.useVectorMove()) { @@ -5604,7 +5965,7 @@ private int getRecoveryUnit() { if (choices.size() == 1) { if (choices.getFirst().mpUsed > 0) { if (clientgui.doYesNoDialog(Messages.getString("MovementDisplay.RecoverSureDialog.title"), - Messages.getString("MovementDisplay.RecoverSureDialog.message"))) { + Messages.getString("MovementDisplay.RecoverSureDialog.message"))) { return choices.getFirst().getId(); } } else { @@ -5614,19 +5975,21 @@ private int getRecoveryUnit() { } String input = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.RecoverFighterDialog.message"), - Messages.getString("MovementDisplay.RecoverFighterDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - SharedUtility.getDisplayArray(choices), - null); + Messages.getString( + "MovementDisplay.RecoverFighterDialog.message"), + Messages.getString( + "MovementDisplay.RecoverFighterDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + SharedUtility.getDisplayArray(choices), + null); Entity picked = (Entity) SharedUtility.getTargetPicked(choices, input); if (picked != null) { // if this unit is thrusting, make sure they are aware if (picked.mpUsed > 0) { if (clientgui.doYesNoDialog(Messages.getString("MovementDisplay.RecoverSureDialog.title"), - Messages.getString("MovementDisplay.RecoverSureDialog.message"))) { + Messages.getString("MovementDisplay.RecoverSureDialog.message"))) { return picked.getId(); } } else { @@ -5653,11 +6016,11 @@ private int getUnitJoined() { // Is the other unit friendly and not the current entity? Must be done with its movement it also must be // the same heading and velocity if ((other instanceof Aero oa) && - !oa.isOutControlTotal() && - other.isDone() && - other.canLoad(currentEntity) && - currentEntity.isLoadableThisTurn() && - (cmd.getFinalFacing() == other.getFacing())) { + !oa.isOutControlTotal() && + other.isDone() && + other.canLoad(currentEntity) && + currentEntity.isLoadableThisTurn() && + (cmd.getFinalFacing() == other.getFacing())) { // now let's check velocity // depends on movement rules if (game.useVectorMove()) { @@ -5680,12 +6043,14 @@ private int getUnitJoined() { } String input = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.JoinSquadronDialog.message"), - Messages.getString("MovementDisplay.JoinSquadronDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - SharedUtility.getDisplayArray(choices), - null); + Messages.getString( + "MovementDisplay.JoinSquadronDialog.message"), + Messages.getString( + "MovementDisplay.JoinSquadronDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + SharedUtility.getDisplayArray(choices), + null); Entity picked = (Entity) SharedUtility.getTargetPicked(choices, input); if (picked != null) { return picked.getId(); @@ -5716,8 +6081,8 @@ private void checkOOC() { setForwardIniEnabled(true); if (currentEntity instanceof Aero) { setLaunchEnabled(!currentEntity.getLaunchableFighters().isEmpty() || - !currentEntity.getLaunchableSmallCraft().isEmpty() || - !currentEntity.getLaunchableDropships().isEmpty()); + !currentEntity.getLaunchableSmallCraft().isEmpty() || + !currentEntity.getLaunchableDropships().isEmpty()); } } } @@ -5731,7 +6096,9 @@ private void updateMoreButton() { } } - /** Checks Aerospace for remaining fuel and adjusts buttons when necessary. */ + /** + * Checks Aerospace for remaining fuel and adjusts buttons when necessary. + */ private void checkFuel() { final Entity currentEntity = currentEntity(); if ((currentEntity == null) || !currentEntity.isAero()) { @@ -5746,8 +6113,8 @@ private void checkFuel() { setForwardIniEnabled(true); if (currentEntity instanceof Aero) { setLaunchEnabled(!currentEntity.getLaunchableFighters().isEmpty() || - !currentEntity.getLaunchableSmallCraft().isEmpty() || - !currentEntity.getLaunchableDropships().isEmpty()); + !currentEntity.getLaunchableSmallCraft().isEmpty() || + !currentEntity.getLaunchableDropships().isEmpty()); } updateRACButton(); updateJoinButton(); @@ -5807,11 +6174,13 @@ private Targetable chooseTarget(Coords pos) { } else if (targets.size() > 1) { // If we have multiple choices, display a selection dialog. choice = TargetChoiceDialog.showSingleChoiceDialog(clientgui.getFrame(), - "MovementDisplay.ChooseTargetDialog.title", - Messages.getString("MovementDisplay.ChooseTargetDialog.message", pos.getBoardNum()), - targets, - clientgui, - currentEntity()); + "MovementDisplay.ChooseTargetDialog.title", + Messages.getString( + "MovementDisplay.ChooseTargetDialog.message", + pos.getBoardNum()), + targets, + clientgui, + currentEntity()); } @@ -5837,12 +6206,13 @@ private void dumpBombs() { // available int numFighters = currentEntity().getActiveSubEntities().size(); BombPayloadDialog dumpBombsDialog = new BombPayloadDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.BombDumpDialog.title"), - currentEntity().getBombLoadout(), - false, - true, - -1, - numFighters); + Messages.getString( + "MovementDisplay.BombDumpDialog.title"), + currentEntity().getBombLoadout(), + false, + true, + -1, + numFighters); dumpBombsDialog.setVisible(true); if (dumpBombsDialog.getAnswer()) { dumpBombsDialog.getChoices(); @@ -5859,14 +6229,14 @@ private void dumpBombs() { if (diceRoll.getIntValue() < psr.getValue()) { report.choose(false); clientgui.addToast(ToastLevel.ERROR, - Messages.getString("MovementDisplay.DumpFailure.message"), currentEntity()); + Messages.getString("MovementDisplay.DumpFailure.message"), currentEntity()); // failed the roll, so dump all bombs currentEntity().getBombLoadout(); } else { // avoided damage report.choose(true); clientgui.addToast(ToastLevel.SUCCESS, - Messages.getString("MovementDisplay.DumpSuccessful.message"), currentEntity()); + Messages.getString("MovementDisplay.DumpSuccessful.message"), currentEntity()); } } @@ -5945,7 +6315,8 @@ private boolean addManeuver(int type) { } } - private void setStatusBarTextOthersTurn(@Nullable Player player, String s) { + private void setStatusBarTextOthersTurn(@Nullable Player player, + String s) { String playerName = (player != null) ? player.getName() : "Unknown"; setStatusBarText(Messages.getString("MovementDisplay.its_others_turn", playerName) + s); } @@ -5965,8 +6336,8 @@ public void gameTurnChange(GameTurnChangeEvent e) { // generated // Except on the first turn if (game.getPhase().isSimultaneous(game) && - (e.getPreviousPlayerId() != clientgui.getClient().getLocalPlayerNumber()) && - (game.getTurnIndex() != 0)) { + (e.getPreviousPlayerId() != clientgui.getClient().getLocalPlayerNumber()) && + (game.getTurnIndex() != 0)) { return; } @@ -5974,9 +6345,9 @@ public void gameTurnChange(GameTurnChangeEvent e) { // if all our entities are actually done, don't start up the turn. if (game - .getPlayerEntities(clientgui.getClient().getLocalPlayer(), false) - .stream() - .allMatch(Entity::isDone)) { + .getPlayerEntities(clientgui.getClient().getLocalPlayer(), false) + .stream() + .allMatch(Entity::isDone)) { setStatusBarTextOthersTurn(e.getPlayer(), s); clientgui.bingOthersTurn(); return; @@ -6010,6 +6381,7 @@ public void gameTurnChange(GameTurnChangeEvent e) { @Override public void gamePhaseChange(GamePhaseChangeEvent e) { // In case of a /reset command, ensure the state gets reset + markDeploymentHexes(null); if (game.getPhase().isLounge()) { endMyTurn(); } @@ -6028,7 +6400,8 @@ public void gamePhaseChange(GamePhaseChangeEvent e) { } } - private int maxMP(Entity en, int mvMode) { + private int maxMP(Entity en, + int mvMode) { int maxMP; if (mvMode == GEAR_DFA) { maxMP = en.getJumpMP(); @@ -6040,8 +6413,8 @@ private int maxMP(Entity en, int mvMode) { } else if (mvMode == GEAR_BACKUP) { maxMP = en.getWalkMP(); } else if ((currentEntity() instanceof Mek) && - !(currentEntity() instanceof QuadVee) && - (currentEntity().getMovementMode() == EntityMovementMode.TRACKED)) { + !(currentEntity() instanceof QuadVee) && + (currentEntity().getMovementMode() == EntityMovementMode.TRACKED)) { // A non-QuadVee `Mek that is using tracked movement is limited to walking maxMP = en.getWalkMP(); } else { @@ -6132,7 +6505,9 @@ private void computeSimpleMovementEnvelope(Entity suggestion) { clientgui.showMovementEnvelope(entity, movementEnvelopeMP, movementGear); } - private ShortestPathFinder getShortestPathFinder(Entity en, int maxMP, MoveStepType stepType) { + private ShortestPathFinder getShortestPathFinder(Entity en, + int maxMP, + MoveStepType stepType) { ShortestPathFinder shortestPathFinder; if (en.isAerodyne() && !en.isAeroLandedOnGroundMap()) { shortestPathFinder = ShortestPathFinder.newInstanceOfOneToAllAero(maxMP, stepType, game); @@ -6201,7 +6576,7 @@ public void computeModifierEnvelope() { LongestPathFinder lpf = LongestPathFinder.newInstanceOfLongestPath(maxMP, stepType, currentEntity.getGame()); final int timeLimit = PreferenceManager.getClientPreferences().getMaxPathfinderTime(); StopConditionTimeout timeoutCondition = new StopConditionTimeout<>( - timeLimit * 10); + timeLimit * 10); lpf.addStopCondition(timeoutCondition); lpf.run(movePath); clientgui.showMovementModifiers(lpf.getLongestComputedPaths()); @@ -6214,9 +6589,9 @@ private int getMaxMP(Entity currentEntity) { } else if (gear == GEAR_BACKUP) { maxMP = currentEntity.getWalkMP(); } else if ( - (currentEntity instanceof Mek mek) && - !(mek instanceof QuadVee) && - (mek.getMovementMode() == EntityMovementMode.TRACKED)) { + (currentEntity instanceof Mek mek) && + !(mek instanceof QuadVee) && + (mek.getMovementMode() == EntityMovementMode.TRACKED)) { // A non-QuadVee `Mek that is using tracked movement (or converting to it) is limited to walking maxMP = mek.getWalkMP(); } else { @@ -6230,8 +6605,8 @@ private int getMaxMP(Entity currentEntity) { * *

The warnings mark hexes where the unit's weight would bring a building down, and they are worked out once * when the unit is selected. A gamemaster removing a building, or changing its construction factor, changes the - * answer without the unit having moved, so they have to be worked out again - otherwise the marker sits over a - * hex whose building is no longer there.

+ * answer without the unit having moved, so they have to be worked out again - otherwise the marker sits over a hex + * whose building is no longer there.

*/ @Override public void gameBoardChanged(GameBoardChangeEvent event) { @@ -6287,17 +6662,17 @@ public synchronized void actionPerformed(ActionEvent ev) { String title = Messages.getString("MovementDisplay.UnjamRAC.title"); String msg = Messages.getString("MovementDisplay.UnjamRAC.message"); if ((gear == MovementDisplay.GEAR_JUMP) || - (gear == MovementDisplay.GEAR_CHARGE) || - (gear == MovementDisplay.GEAR_DFA) || - ((cmd.getMpUsed() > entity.getWalkMP()) && - !(cmd.getLastStep().isOnlyPavementOrRoad() && - (cmd.getMpUsed() <= (entity.getWalkMP() + 1)))) || - (opts.booleanOption("tacops_tank_crews") && - (cmd.getMpUsed() > 0) && - (entity instanceof Tank) && - (entity.getCrew().getSize() < 2)) || - (gear == MovementDisplay.GEAR_SWIM) || - (gear == MovementDisplay.GEAR_RAM)) { + (gear == MovementDisplay.GEAR_CHARGE) || + (gear == MovementDisplay.GEAR_DFA) || + ((cmd.getMpUsed() > entity.getWalkMP()) && + !(cmd.getLastStep().isOnlyPavementOrRoad() && + (cmd.getMpUsed() <= (entity.getWalkMP() + 1)))) || + (opts.booleanOption("tacops_tank_crews") && + (cmd.getMpUsed() > 0) && + (entity instanceof Tank) && + (entity.getCrew().getSize() < 2)) || + (gear == MovementDisplay.GEAR_SWIM) || + (gear == MovementDisplay.GEAR_RAM)) { setUnjamEnabled(false); } else if (clientgui.doYesNoDialog(title, msg)) { @@ -6324,9 +6699,9 @@ public synchronized void actionPerformed(ActionEvent ev) { computeMovementEnvelope(entity); } 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() != null) && + cmd.getLastStep().isFirstStep() && + (cmd.getLastStep().getType() == MoveStepType.LAY_MINE))) { clear(); } gear = MovementDisplay.GEAR_JUMP; @@ -6335,9 +6710,9 @@ public synchronized void actionPerformed(ActionEvent ev) { String[] choices = { "Mechanical Jump Boosters", "Jump Jets" }; int jumpChoice = JOptionPane.showOptionDialog(JOptionPane.getFrameForComponent(this), - "Choose jump type:", "Choose Jump Type", - JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, - null, choices, choices[1]); + "Choose jump type:", "Choose Jump Type", + JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, + null, choices, choices[1]); if (jumpChoice == 0) { jumpSubGear = GEAR_SUB_MEK_BOOSTERS; @@ -6360,8 +6735,8 @@ public synchronized void actionPerformed(ActionEvent ev) { } gear = MovementDisplay.GEAR_SWIM; entity.setMovementMode((entity instanceof BipedMek) ? - EntityMovementMode.BIPED_SWIM : - EntityMovementMode.QUAD_SWIM); + EntityMovementMode.BIPED_SWIM : + EntityMovementMode.QUAD_SWIM); } else if (actionCmd.equals(MoveCommand.MOVE_MODE_CONVERT.getCmd())) { EntityMovementMode nextMode = entity.nextConversionMode(cmd.getFinalConversionMode()); // LAMs may have to skip the next mode due to damage @@ -6419,8 +6794,8 @@ public synchronized void actionPerformed(ActionEvent ev) { clear(); if (!game.containsMinefield(entity.getPosition())) { clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.CantClearMinefield") + ": " - + Messages.getString("MovementDisplay.NoMinefield"), currentEntity()); + Messages.getString("MovementDisplay.CantClearMinefield") + ": " + + Messages.getString("MovementDisplay.NoMinefield"), currentEntity()); return; } @@ -6453,12 +6828,12 @@ public synchronized void actionPerformed(ActionEvent ev) { String title = Messages.getString("MovementDisplay.ChooseMinefieldDialog.title"); String body = Messages.getString("MovementDisplay.ChooseMinefieldDialog.message"); String input = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - body, - title, - JOptionPane.QUESTION_MESSAGE, - null, - choices, - null); + body, + title, + JOptionPane.QUESTION_MESSAGE, + null, + choices, + null); Minefield mf = null; if (input != null) { for (int loop = 0; loop < choices.length; loop++) { @@ -6506,7 +6881,7 @@ public synchronized void actionPerformed(ActionEvent ev) { } if (opts.booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CAREFUL_STAND) - && (entity.getWalkMP() > 2)) { + && (entity.getWalkMP() > 2)) { String title = Messages.getString("MovementDisplay.CarefulStand.title"); String body = Messages.getString("MovementDisplay.CarefulStand.message"); boolean response = clientgui.doYesNoDialog(title, body); @@ -6551,12 +6926,12 @@ public synchronized void actionPerformed(ActionEvent ev) { String title = "Choose Brace Location"; String body = "Choose the location to brace:"; String option = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - body, - title, - JOptionPane.QUESTION_MESSAGE, - null, - locationNames, - locationNames[0]); + body, + title, + JOptionPane.QUESTION_MESSAGE, + null, + locationNames, + locationNames[0]); // Verify that we have a valid option... if (option != null) { @@ -6566,8 +6941,8 @@ 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"))) { + clientgui.doYesNoDialog(Messages.getString("MovementDisplay.EscapeDialog.title"), + Messages.getString("MovementDisplay.EscapeDialog.message"))) { addStepToMovePath(MoveStepType.FLEE); ready(); clear(); @@ -6576,14 +6951,14 @@ public synchronized void actionPerformed(ActionEvent ev) { } else if (actionCmd.equals(MoveCommand.MOVE_EJECT.getCmd())) { if (entity instanceof Tank) { if (clientgui.doYesNoDialog(Messages.getString("MovementDisplay.AbandonDialog.title"), - Messages.getString("MovementDisplay.AbandonDialog.message"))) { + Messages.getString("MovementDisplay.AbandonDialog.message"))) { clear(); addStepToMovePath(MoveStepType.EJECT); ready(); } } else if (entity.isLargeCraft()) { if (clientgui.doYesNoDialog(Messages.getString("MovementDisplay.AbandonDialog.title"), - Messages.getString("MovementDisplay.AbandonDialog.message"))) { + Messages.getString("MovementDisplay.AbandonDialog.message"))) { clear(); // If we're abandoning while grounded, find a legal position to put an // EjectedCrew unit @@ -6596,7 +6971,7 @@ public synchronized void actionPerformed(ActionEvent ev) { ready(); } } else if (clientgui.doYesNoDialog(Messages.getString("MovementDisplay.AbandonDialog1.title"), - Messages.getString("MovementDisplay.AbandonDialog1.message"))) { + Messages.getString("MovementDisplay.AbandonDialog1.message"))) { clear(); addStepToMovePath(MoveStepType.EJECT); ready(); @@ -6605,8 +6980,8 @@ public synchronized void actionPerformed(ActionEvent ev) { // Mek abandonment (two-phase per TacOps:AR p.165) if ((entity instanceof Mek mek) && mek.canAbandon()) { if (clientgui.doYesNoDialog( - Messages.getString("MovementDisplay.MekAbandonDialog.title"), - Messages.getString("MovementDisplay.MekAbandonDialog.message"))) { + Messages.getString("MovementDisplay.MekAbandonDialog.title"), + Messages.getString("MovementDisplay.MekAbandonDialog.message"))) { clear(); addStepToMovePath(MoveStepType.ABANDON); ready(); @@ -6653,13 +7028,13 @@ public synchronized void actionPerformed(ActionEvent ev) { Entity other = getUnloadedUnit(); if (other != null) { if (!other.isInfantry() || - currentEntity() instanceof SmallCraft || - (currentEntity().isSupportVehicle() && (currentEntity().getWeightClass() - == EntityWeightClass.WEIGHT_LARGE_SUPPORT)) - // FIXME: unclear why towed/towing is checked here: - || - !currentEntity().getAllTowedUnits().isEmpty() || - currentEntity().getTowedBy() != Entity.NONE) { + currentEntity() instanceof SmallCraft || + (currentEntity().isSupportVehicle() && (currentEntity().getWeightClass() + == EntityWeightClass.WEIGHT_LARGE_SUPPORT)) + // FIXME: unclear why towed/towing is checked here: + || + !currentEntity().getAllTowedUnits().isEmpty() || + currentEntity().getTowedBy() != Entity.NONE) { // unload into adjacent hexes Coords pos = getUnloadPosition(other); if (null != pos) { @@ -6679,9 +7054,9 @@ public synchronized void actionPerformed(ActionEvent ev) { ready(); } else { List filteredUnits = unloadableUnits - .stream() - .filter(unloadable -> unloadable.getTargetBay() == UNSET_BAY) - .toList(); + .stream() + .filter(unloadable -> unloadable.getTargetBay() == UNSET_BAY) + .toList(); if (filteredUnits.isEmpty()) { ready(); } @@ -6697,11 +7072,14 @@ public synchronized void actionPerformed(ActionEvent ev) { } else if (actionCmd.equals(MoveCommand.MOVE_DROP_CARGO.getCmd())) { var options = currentEntity().getDistinctCarriedObjects(); List moreOptions = entity.getTransports() - .stream() - .filter(t -> t instanceof ExternalCargo) - .map(t -> t.getCarryables().toArray(ICarryable[]::new)) - .flatMap(Arrays::stream) - .toList().stream().filter(carryable -> !options.contains(carryable)).toList(); + .stream() + .filter(t -> t instanceof ExternalCargo) + .map(t -> t.getCarryables().toArray(ICarryable[]::new)) + .flatMap(Arrays::stream) + .toList() + .stream() + .filter(carryable -> !options.contains(carryable)) + .toList(); List fullOptions = new ArrayList<>(options); fullOptions.addAll(moreOptions); @@ -6719,12 +7097,12 @@ public synchronized void actionPerformed(ActionEvent ev) { String title = "Choose Cargo to Drop"; String body = "Choose the cargo to drop:"; String option = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - body, - title, - JOptionPane.QUESTION_MESSAGE, - null, - locationMap.keySet().toArray(), - locationMap.keySet().toArray()[0]); + body, + title, + JOptionPane.QUESTION_MESSAGE, + null, + locationMap.keySet().toArray(), + locationMap.keySet().toArray()[0]); // Verify that we have a valid option... if (option != null) { @@ -6740,18 +7118,18 @@ public synchronized void actionPerformed(ActionEvent ev) { // On-demand Descend dialog: only meaningful for a mid-climb / dangling Mek. // Replaces the auto-prompt that fired at turn start. if ((entity instanceof Mek climbingMek) && entity.isClimbing() - && ClimbingHelper.canClimb(entity)) { + && ClimbingHelper.canClimb(entity)) { promptContinueClimbing(climbingMek); } } else if (actionCmd.equals(MoveCommand.MOVE_LOWER_ELEVATION.getCmd())) { if (entity.isAero()) { PlanetaryConditions conditions = game.getPlanetaryConditions(); boolean spheroidOrLessThanThin = ((IAero) entity).isSpheroid() || - conditions.getAtmosphere().isLighterThan(Atmosphere.THIN); + conditions.getAtmosphere().isLighterThan(Atmosphere.THIN); if ((null != cmd.getLastStep()) && - (cmd.getLastStep().getNDown() == 1) && - (cmd.getLastStep().getVelocity() < 12) && - !spheroidOrLessThanThin) { + (cmd.getLastStep().getNDown() == 1) && + (cmd.getLastStep().getVelocity() < 12) && + !spheroidOrLessThanThin) { addStepToMovePath(MoveStepType.ACC, true); computeMovementEnvelope(entity); } @@ -6766,22 +7144,22 @@ public synchronized void actionPerformed(ActionEvent ev) { boolean turningOn = !cmd.getFinalClimbMode(); MoveStep ms = cmd.getLastStep(); if ((ms != null) && - ((ms.getType() == MoveStepType.CLIMB_MODE_ON) || (ms.getType() == MoveStepType.CLIMB_MODE_OFF))) { + ((ms.getType() == MoveStepType.CLIMB_MODE_ON) || (ms.getType() == MoveStepType.CLIMB_MODE_OFF))) { turningOn = (ms.getType() == MoveStepType.CLIMB_MODE_OFF); } if (turningOn && game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING)) { + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING)) { String reason = ClimbingHelper.getClimbingImpossibleReason(entity); if (reason != null) { JOptionPane.showMessageDialog(clientgui.getFrame(), reason, - Messages.getString("MovementDisplay.ClimbingDialog.title"), - JOptionPane.WARNING_MESSAGE); + Messages.getString("MovementDisplay.ClimbingDialog.title"), + JOptionPane.WARNING_MESSAGE); } } ms = cmd.getLastStep(); if ((ms != null) && - ((ms.getType() == MoveStepType.CLIMB_MODE_ON) || (ms.getType() == MoveStepType.CLIMB_MODE_OFF))) { + ((ms.getType() == MoveStepType.CLIMB_MODE_ON) || (ms.getType() == MoveStepType.CLIMB_MODE_OFF))) { MoveStep lastStep = cmd.getLastStep(); cmd.removeLastStep(); // Add another climb mode step. Without this, we end up with 3 effect modes: no climb step, climb step @@ -6813,8 +7191,8 @@ public synchronized void actionPerformed(ActionEvent ev) { m.setEmpSetting(empDialog.getSetting()); } if (cmd.getLastStep() == null && - entity instanceof BattleArmor && - entity.getMovementMode().equals(EntityMovementMode.INF_JUMP)) { + entity instanceof BattleArmor && + entity.getMovementMode().equals(EntityMovementMode.INF_JUMP)) { initializeJumpMovePath(); gear = GEAR_JUMP; Color jumpColor = GUIP.getMoveJumpColor(); @@ -6831,7 +7209,7 @@ public synchronized void actionPerformed(ActionEvent ev) { addStepToMovePath(MoveStepType.DIG_IN); } else { clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.digInIllegalTerrain.toast"), entity); + Messages.getString("MovementDisplay.digInIllegalTerrain.toast"), entity); } } else if (actionCmd.equals(MoveCommand.MOVE_HIT_DECK.getCmd())) { addStepToMovePath(MoveStepType.HIT_THE_DECK); @@ -6839,14 +7217,14 @@ public synchronized void actionPerformed(ActionEvent ev) { // facing-selection dialog, so hint that turning in place (free) designates the facing. if ((entity instanceof ConvInfantry convInfantry) && convInfantry.hasActiveFieldWeapon()) { clientgui.addToast(ToastLevel.INFO, - Messages.getString("MovementDisplay.hitDeckFacing.toast"), entity); + Messages.getString("MovementDisplay.hitDeckFacing.toast"), entity); } } else if (actionCmd.equals(MoveCommand.MOVE_FORTIFY.getCmd())) { if (MoveStep.isFortifiableTerrain(game.getHexOf(entity))) { addStepToMovePath(MoveStepType.FORTIFY); } else { clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.fortifyIllegalTerrain.toast"), entity); + Messages.getString("MovementDisplay.fortifyIllegalTerrain.toast"), entity); } } else if (actionCmd.equals(MoveCommand.MOVE_CLEAR_RUBBLE.getCmd())) { // Enter target-selection: the player clicks the rubble hex to drive into and clear (TacOps). @@ -6855,7 +7233,7 @@ public synchronized void actionPerformed(ActionEvent ev) { } gear = MovementDisplay.GEAR_CLEAR_RUBBLE; LOGGER.debug("[Bulldozer] {}: entering clear-rubble target mode - awaiting rubble hex selection", - entity.getDisplayName()); + entity.getDisplayName()); setStatusBarText(Messages.getString("MovementDisplay.status.selectRubbleHex")); computeMovementEnvelope(entity); } else if (actionCmd.equals(MoveCommand.MOVE_BUILD_BRIDGE.getCmd())) { @@ -6898,7 +7276,7 @@ public synchronized void actionPerformed(ActionEvent ev) { addStepToMovePath(MoveStepType.BOOTLEGGER); } else if (actionCmd.equals(MoveCommand.MOVE_SHUTDOWN.getCmd())) { if (clientgui.doYesNoDialog(Messages.getString("MovementDisplay.ShutdownDialog.title"), - Messages.getString("MovementDisplay.ShutdownDialog.message"))) { + Messages.getString("MovementDisplay.ShutdownDialog.message"))) { addStepToMovePath(MoveStepType.SHUTDOWN); ready(); } @@ -6907,12 +7285,12 @@ public synchronized void actionPerformed(ActionEvent ev) { boolean proceedWithStartup; if (entity.isPendingAbandon()) { proceedWithStartup = clientgui.doYesNoDialog( - Messages.getString("MovementDisplay.StartupCancelAbandonDialog.title"), - Messages.getString("MovementDisplay.StartupCancelAbandonDialog.message")); + Messages.getString("MovementDisplay.StartupCancelAbandonDialog.title"), + Messages.getString("MovementDisplay.StartupCancelAbandonDialog.message")); } else { proceedWithStartup = clientgui.doYesNoDialog( - Messages.getString("MovementDisplay.StartupDialog.title"), - Messages.getString("MovementDisplay.StartupDialog.message")); + Messages.getString("MovementDisplay.StartupDialog.title"), + Messages.getString("MovementDisplay.StartupDialog.message")); } if (proceedWithStartup) { clear(); @@ -6921,7 +7299,7 @@ public synchronized void actionPerformed(ActionEvent ev) { } } else if (actionCmd.equals(MoveCommand.MOVE_SELF_DESTRUCT.getCmd())) { if (clientgui.doYesNoDialog(Messages.getString("MovementDisplay.SelfDestructDialog.title"), - Messages.getString("MovementDisplay.SelfDestructDialog.message"))) { + Messages.getString("MovementDisplay.SelfDestructDialog.message"))) { addStepToMovePath(MoveStepType.SELF_DESTRUCT); ready(); } @@ -6934,13 +7312,14 @@ public synchronized void actionPerformed(ActionEvent ev) { } else if (actionCmd.equals(MoveCommand.MOVE_HOVER.getCmd())) { addStepToMovePath(MoveStepType.HOVER); if (entity instanceof LandAirMek - && entity.getMovementMode() == EntityMovementMode.WIGE - && entity.isAirborne()) { + && entity.getMovementMode() == EntityMovementMode.WIGE + && entity.isAirborne()) { gear = GEAR_LAND; } } else if (actionCmd.equals(MoveCommand.MOVE_MANEUVER.getCmd())) { ManeuverChoiceDialog choiceDialog = new ManeuverChoiceDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.ManeuverDialog.title")); + Messages.getString( + "MovementDisplay.ManeuverDialog.title")); IAero a = (IAero) entity; MoveStep last = cmd.getLastStep(); @@ -6961,7 +7340,7 @@ public synchronized void actionPerformed(ActionEvent ev) { ceil = 0; } choiceDialog.checkPerformability(vel, altitude, ceil, a.isVSTOL(), distance, board, - cmd); + cmd); choiceDialog.setVisible(true); int manType = choiceDialog.getChoice(); updateMove((manType > ManeuverType.MAN_NONE) && addManeuver(manType)); @@ -6975,7 +7354,7 @@ public synchronized void actionPerformed(ActionEvent ev) { addStepToMovePath(MoveStepType.LAUNCH, launched); } } else if (actionCmd.equals(MoveCommand.MOVE_RECOVER.getCmd()) || - actionCmd.equals(MoveCommand.MOVE_DOCK.getCmd())) { + actionCmd.equals(MoveCommand.MOVE_DOCK.getCmd())) { // if more than one unit is available as a carrier, then bring up an option dialog int recoverer = getRecoveryUnit(); if (recoverer != NO_UNIT_SELECTED) { @@ -7010,11 +7389,12 @@ public synchronized void actionPerformed(ActionEvent ev) { } else if (actionCmd.equals(MoveCommand.MOVE_TAKE_OFF.getCmd())) { if (currentEntity().isAero() && (null != ((IAero) currentEntity()).hasRoomForHorizontalTakeOff())) { clientgui.addToast(ToastLevel.ERROR, - Messages.getString("MovementDisplay.NoTakeOffDialog.message", - ((IAero) currentEntity()).hasRoomForHorizontalTakeOff()), currentEntity()); + Messages.getString("MovementDisplay.NoTakeOffDialog.message", + ((IAero) currentEntity()).hasRoomForHorizontalTakeOff()), + currentEntity()); } else { if (clientgui.doYesNoDialog(Messages.getString("MovementDisplay.TakeOffDialog.title"), - Messages.getString("MovementDisplay.TakeOffDialog.message"))) { + Messages.getString("MovementDisplay.TakeOffDialog.message"))) { clear(); addStepToMovePath(MoveStepType.TAKEOFF); ready(); @@ -7022,7 +7402,7 @@ public synchronized void actionPerformed(ActionEvent ev) { } } else if (actionCmd.equals(MoveCommand.MOVE_VERT_TAKE_OFF.getCmd())) { if (clientgui.doYesNoDialog(Messages.getString("MovementDisplay.TakeOffDialog.title"), - Messages.getString("MovementDisplay.TakeOffDialog.message"))) { + Messages.getString("MovementDisplay.TakeOffDialog.message"))) { clear(); addStepToMovePath(MoveStepType.VERTICAL_TAKE_OFF); ready(); @@ -7062,18 +7442,18 @@ public synchronized void actionPerformed(ActionEvent ev) { // No players available? if (idx == 0) { clientgui.addToast(ToastLevel.WARNING, - Messages.getString("MovementDisplay.NoTraitorPlayers")); + Messages.getString("MovementDisplay.NoTraitorPlayers")); 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 (" + e.getDisplayName() + ") when it turns traitor", - "Traitor", - JOptionPane.QUESTION_MESSAGE, - null, - options, - options[0]); + "Choose the player to gain ownership of this unit (" + e.getDisplayName() + ") when it turns traitor", + "Traitor", + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); // Verify that we have a valid option... if (option != null) { @@ -7083,53 +7463,34 @@ 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); + 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) { // the /traitor command sets the pending switch on the server's own copy of the unit, where the // END phase resolves it; sending the whole unit back for one field would carry stale state along LOGGER.info("[Traitor] Requesting switch of {} (unit id {}) to player {} ({})", - e.getDisplayName(), e.getId(), id, name); + e.getDisplayName(), e.getId(), id, name); clientgui.getClient().sendChat(String.format("/traitor %d %d", e.getId(), id)); } } } else if (actionCmd.equals(MoveCommand.MOVE_CHAFF.getCmd())) { if (clientgui.doYesNoDialog(Messages.getString("MovementDisplay.ConfirmChaff.title"), - Messages.getString("MovementDisplay.ConfirmChaff.message"))) { + Messages.getString("MovementDisplay.ConfirmChaff.message"))) { isUsingChaff = true; } + } else if (actionCmd.equals(MoveCommand.MOVE_CLEAR_DEPLOY.getCmd())) { + clear(false); + Entity currentlySelectedEntity = currentEntity(); + if (currentlySelectedEntity != null) { + lastDeploymentOption = null; + lastHexDeploymentOptions.clear(); + originalFacing = -1; + } + } - updateProneButtons(); - updateRACButton(); - updateSearchlightButton(); - updateElevationButtons(); - updateElevatorButtons(); - updateTakeOffButtons(); - updateLandButtons(); - updateFlyOffButton(); - updateLaunchButton(); - updateLoadButtons(); - updateDropButton(); - updateConvertModeButton(); - updateRecklessButton(); - updateHoverButton(); - updateManeuverButton(); - updateEvadeButton(); - updateBootleggerButton(); - updateShutdownButton(); - updateStartupButton(); - updateSelfDestructButton(); - updateTraitorButton(); - updateSpeedButtons(); - updateThrustButton(); - updateRollButton(); - updateTakeCoverButton(); - updateLayMineButton(); - updateBraceButton(); - checkFuel(); - checkOOC(); - checkAtmosphere(); + + refreshButtons(); // If small craft / DropShip that has unloaded units, then only allowed to // unload more @@ -7142,7 +7503,6 @@ public synchronized void actionPerformed(ActionEvent ev) { /** * @param ce The entity to test - * * @return True when the given unit is a Mek with both jump jets and mechanical jump boosters. */ private boolean mustChooseJumpType(Entity ce) { @@ -7156,7 +7516,7 @@ private void processPickupCargoCommand() { var options = game.getGroundObjects(finalPosition()); options.addAll(game.getEntitiesVector(finalPosition()).stream().filter(Entity::isCarryableObject).toList()); var displayedOptions = - options.stream().filter(o -> currentEntity().canPickupCarryableObject(o)).toList(); + options.stream().filter(o -> currentEntity().canPickupCarryableObject(o)).toList(); // if there's only one thing to pick up, pick it up. regardless of how many objects we are picking up, we may // have to choose the location with which to pick it up @@ -7177,12 +7537,12 @@ private void processPickupCargoCommand() { String title = "Choose Cargo to Pick Up"; String body = "Choose the cargo to pick up:"; ICarryable option = (ICarryable) JOptionPane.showInputDialog(clientgui.getFrame(), - body, - title, - JOptionPane.QUESTION_MESSAGE, - null, - displayedOptions.toArray(), - displayedOptions.getFirst()); + body, + title, + JOptionPane.QUESTION_MESSAGE, + null, + displayedOptions.toArray(), + displayedOptions.getFirst()); if (option != null) { Integer pickupLocation = getPickupLocation(option); @@ -7215,12 +7575,12 @@ private Integer getPickupLocation(ICarryable cargo) { String title = "Choose Pickup Location"; String body = "Choose the location with which to pick up cargo:"; String locationChoice = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - body, - title, - JOptionPane.QUESTION_MESSAGE, - null, - locationMap.keySet().toArray(), - locationMap.keySet().toArray()[0]); + body, + title, + JOptionPane.QUESTION_MESSAGE, + null, + locationMap.keySet().toArray(), + locationMap.keySet().toArray()[0]); if (locationChoice != null) { pickupLocation = locationMap.get(locationChoice); @@ -7244,7 +7604,7 @@ private void adjustConvertSteps(EntityMovementMode endMode) { // Since conversion is not allowed in water, we shouldn't have to deal with the possibility of `swim` modes. // Account for grounded LAMs in fighter mode with movement type wheeled if (currentEntity().getMovementMode() == endMode || (currentEntity().isAero() - && endMode == EntityMovementMode.AERODYNE)) { + && endMode == EntityMovementMode.AERODYNE)) { cmd.clear(); return; } @@ -7301,16 +7661,16 @@ public boolean accept(Entity acc) { buffer = entity.getDisplayName(); } else { buffer = Messages.getString("MovementDisplay.EntityAt", - entity.getDisplayName(), - transport.getPosition().getBoardNum()); + entity.getDisplayName(), + transport.getPosition().getBoardNum()); } names[index] = buffer; } // Show the choices to the player int[] indexes = clientgui.doChoiceDialog(Messages.getString("MovementDisplay.UnloadStrandedUnitsDialog.title"), - Messages.getString("MovementDisplay.UnloadStrandedUnitsDialog.message"), - names); + Messages.getString("MovementDisplay.UnloadStrandedUnitsDialog.message"), + names); // Convert the indexes into selected entity IDs and tell the server. int[] ids = null; @@ -7467,10 +7827,11 @@ private void setFlyOffEnabled(boolean enabled) { setFlyOffEnabled(enabled, false); } - private void setFlyOffEnabled(boolean enabled, boolean isClimbOut) { + private void setFlyOffEnabled(boolean enabled, + boolean isClimbOut) { String label = isClimbOut - ? Messages.getString("MovementDisplay.butClimbOut") - : Messages.getString("MovementDisplay.MoveOff"); + ? Messages.getString("MovementDisplay.butClimbOut") + : Messages.getString("MovementDisplay.MoveOff"); getBtn(MoveCommand.MOVE_FLY_OFF).setText(label); getBtn(MoveCommand.MOVE_FLY_OFF).setEnabled(enabled); clientgui.getMenuBar().setEnabled(MoveCommand.MOVE_FLY_OFF.getCmd(), enabled); @@ -7506,8 +7867,8 @@ private void handleFlyOffOrClimbOut() { // Check if can climb out (altitude 10 on ground map, requires both return flyover and climb out options) boolean canClimbOut = (altitude == 10) && isGroundBoard - && game.getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_RETURN_FLYOVER) - && game.getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_CLIMB_OUT); + && game.getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_RETURN_FLYOVER) + && game.getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_CLIMB_OUT); // Check if can fly off edge (same logic as updateFlyOffButton) boolean canFlyOffEdge = false; @@ -7516,25 +7877,25 @@ private void handleFlyOffOrClimbOut() { if (aero.isSpheroid() && !board.isSpace()) { // Spheroids in atmosphere just need to be on edge canFlyOffEdge = (entity.getWalkMP() > 0) && - ((position.getX() == 0) || - (position.getX() == (board.getWidth() - 1)) || - (position.getY() == 0) || - (position.getY() == (board.getHeight() - 1))); + ((position.getX() == 0) || + (position.getX() == (board.getWidth() - 1)) || + (position.getY() == 0) || + (position.getY() == (board.getHeight() - 1))); } else { // Aerodynes and space - need correct facing and velocity boolean evenX = (position.getX() % 2) == 0; canFlyOffEdge = (velocityLeft > 0) && - (((position.getX() == 0) && ((facing == 5) || (facing == 4))) || - ((position.getX() == (board.getWidth() - 1)) && + (((position.getX() == 0) && ((facing == 5) || (facing == 4))) || + ((position.getX() == (board.getWidth() - 1)) && ((facing == 1) || (facing == 2))) || - ((position.getY() == 0) && + ((position.getY() == 0) && ((facing == 1) || (facing == 5) || (facing == 0)) && evenX) || - ((position.getY() == 0) && (facing == 0)) || - ((position.getY() == (board.getHeight() - 1)) && + ((position.getY() == 0) && (facing == 0)) || + ((position.getY() == (board.getHeight() - 1)) && ((facing == 2) || (facing == 3) || (facing == 4)) && !evenX) || - ((position.getY() == (board.getHeight() - 1)) && (facing == 3))); + ((position.getY() == (board.getHeight() - 1)) && (facing == 3))); } } @@ -7544,8 +7905,8 @@ private void handleFlyOffOrClimbOut() { // Both options available - ask player to choose // Yes = climb out, No = fly off edge doClimbOut = clientgui.doYesNoDialog( - Messages.getString("MovementDisplay.ClimbOrFlyOff.title"), - Messages.getString("MovementDisplay.ClimbOrFlyOff.message")); + Messages.getString("MovementDisplay.ClimbOrFlyOff.title"), + Messages.getString("MovementDisplay.ClimbOrFlyOff.message")); } else if (canClimbOut) { doClimbOut = true; } @@ -7553,8 +7914,8 @@ private void handleFlyOffOrClimbOut() { if (doClimbOut) { // Climb out path if (clientgui.doYesNoDialog( - Messages.getString("MovementDisplay.ClimbOutDialog.title"), - Messages.getString("MovementDisplay.ClimbOutDialog.message"))) { + Messages.getString("MovementDisplay.ClimbOutDialog.title"), + Messages.getString("MovementDisplay.ClimbOutDialog.message"))) { IGameOptions opts = game.getOptions(); if (opts.booleanOption(OptionsConstants.ADVANCED_AERO_RULES_RETURN_FLYOVER)) { @@ -7572,12 +7933,12 @@ private void handleFlyOffOrClimbOut() { } else { // Normal fly off edge path (existing behavior) if (clientgui.doYesNoDialog( - Messages.getString("MovementDisplay.FlyOffDialog.title"), - Messages.getString("MovementDisplay.FlyOffDialog.message"))) { + Messages.getString("MovementDisplay.FlyOffDialog.title"), + Messages.getString("MovementDisplay.FlyOffDialog.message"))) { IGameOptions opts = game.getOptions(); if (opts.booleanOption(OptionsConstants.ADVANCED_AERO_RULES_RETURN_FLYOVER) && - clientgui.doYesNoDialog( + clientgui.doYesNoDialog( Messages.getString("MovementDisplay.ReturnFly.title"), Messages.getString("MovementDisplay.ReturnFly.message"))) { addStepToMovePath(MoveStepType.RETURN); @@ -7597,21 +7958,21 @@ private void handleFlyOffOrClimbOut() { @Deprecated(since = "0.51.0", forRemoval = true) private OffBoardDirection showEdgeSelectionDialog() { String[] options = { - Messages.getString("MovementDisplay.Edge.North"), - Messages.getString("MovementDisplay.Edge.South"), - Messages.getString("MovementDisplay.Edge.East"), - Messages.getString("MovementDisplay.Edge.West") + Messages.getString("MovementDisplay.Edge.North"), + Messages.getString("MovementDisplay.Edge.South"), + Messages.getString("MovementDisplay.Edge.East"), + Messages.getString("MovementDisplay.Edge.West") }; int result = JOptionPane.showOptionDialog( - clientgui.getFrame(), - Messages.getString("MovementDisplay.ClimbOutEdge.message"), - Messages.getString("MovementDisplay.ClimbOutEdge.title"), - JOptionPane.DEFAULT_OPTION, - JOptionPane.QUESTION_MESSAGE, - null, - options, - options[0]); + clientgui.getFrame(), + Messages.getString("MovementDisplay.ClimbOutEdge.message"), + Messages.getString("MovementDisplay.ClimbOutEdge.title"), + JOptionPane.DEFAULT_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); return switch (result) { case 0 -> OffBoardDirection.NORTH; @@ -7647,7 +8008,8 @@ private void setChaffEnabled(boolean enabled) { clientgui.getMenuBar().setEnabled(MoveCommand.MOVE_CHAFF.getCmd(), enabled); } - private void setSearchlightEnabled(boolean enabled, boolean state) { + private void setSearchlightEnabled(boolean enabled, + boolean state) { if (state) { getBtn(MoveCommand.MOVE_SEARCHLIGHT).setText(Messages.getString("MovementDisplay.butSearchlightOff")); } else { @@ -7873,9 +8235,9 @@ private void updateTurnButton() { } setTurnEnabled(!currentEntity.isImmobile() && - !currentEntity.isStuck() && - ((currentEntity.getWalkMP() > 0) || (currentEntity.getJumpMP() > 0)) && - !(cmd.isJumping() && (currentEntity instanceof Mek) && (jumpSubGear == GEAR_SUB_MEK_BOOSTERS))); + !currentEntity.isStuck() && + ((currentEntity.getWalkMP() > 0) || (currentEntity.getJumpMP() > 0)) && + !(cmd.isJumping() && (currentEntity instanceof Mek) && (jumpSubGear == GEAR_SUB_MEK_BOOSTERS))); } @Nullable @@ -7896,7 +8258,8 @@ private void performAeroLand(LandingDirection landingDirection) { } } - private void finalizeAeroLandFromGroundMap(IAero aero, LandingDirection landingDirection) { + private void finalizeAeroLandFromGroundMap(IAero aero, + LandingDirection landingDirection) { if (!game.isOnGroundMap((Entity) aero)) { LOGGER.warn("Selected aero landing from ground map for unit that isn't on a ground map!"); return; @@ -7904,7 +8267,8 @@ private void finalizeAeroLandFromGroundMap(IAero aero, LandingDirection landingD String failMessage = aero.hasRoomForLanding(landingDirection); if (failMessage != null) { clientgui.addToast(ToastLevel.ERROR, - Messages.getString("MovementDisplay.NoLandingDialog.message", failMessage), currentEntity()); + Messages.getString("MovementDisplay.NoLandingDialog.message", failMessage), + currentEntity()); } else { landingConfirmation.ask(); if (landingConfirmation.isOkSelected()) { @@ -7915,11 +8279,12 @@ private void finalizeAeroLandFromGroundMap(IAero aero, LandingDirection landingD } } - private void startAeroLandNoGroundMove(Entity entity, LandingDirection landingDirection) { + private void startAeroLandNoGroundMove(Entity entity, + LandingDirection landingDirection) { if (!game.hasBoardLocationOf(entity) - || !entity.isAirborne() - || !game.isOnAtmosphericMap(entity) - || !game.getBoard(entity).getEmbeddedBoardHexes().contains(finalPosition())) { + || !entity.isAirborne() + || !game.isOnAtmosphericMap(entity) + || !game.getBoard(entity).getEmbeddedBoardHexes().contains(finalPosition())) { LOGGER.warn("No ground map to land on in the present hex!"); return; } @@ -7933,7 +8298,8 @@ private void startAeroLandNoGroundMove(Entity entity, LandingDirection landingDi new LandingHexNotice(clientgui).show(); } - private void finalizeAeroLandFromAtmosphereMap(IAero aero, BoardViewEvent event) { + private void finalizeAeroLandFromAtmosphereMap(IAero aero, + BoardViewEvent event) { if (!hasLandingMoveStep()) { LOGGER.error("No landing move path; can't determine if vertical or horizontal landing!"); return; @@ -7942,7 +8308,8 @@ private void finalizeAeroLandFromAtmosphereMap(IAero aero, BoardViewEvent event) String failMessage = aero.hasRoomForLanding(event.getBoardId(), event.getCoords(), landingDirection); if (failMessage != null) { clientgui.addToast(ToastLevel.ERROR, - Messages.getString("MovementDisplay.NoLandingDialog.message", failMessage), currentEntity()); + Messages.getString("MovementDisplay.NoLandingDialog.message", failMessage), + currentEntity()); } else { landingConfirmation.ask(); if (landingConfirmation.isOkSelected()) { @@ -7979,7 +8346,7 @@ private void startEscapePodHexSelection(Tank tank) { List hexesAtRange = tankPos.allAtDistance(range); for (Coords coords : hexesAtRange) { if (board.contains(coords) && - ComputeArc.isInArc(tankPos, facing, coords, Compute.ARC_REAR)) { + ComputeArc.isInArc(tankPos, facing, coords, Compute.ARC_REAR)) { validEscapePodHexes.add(coords); } } @@ -8054,7 +8421,7 @@ private void handleBridgeSelectionClick(Coords clicked) { // player WHY the hex is not valid for this step rather than just that it isn't. String reason = bridgeInvalidClickReason(clicked); LOGGER.debug("[BuildBridge] ignoring {} at stage {} - {} (valid: {})", clicked, bridgeSelectionStage, - reason, validBridgeSelectionHexes); + reason, validBridgeSelectionHexes); clientgui.addToast(ToastLevel.WARNING, reason, currentEntity()); return; } @@ -8062,7 +8429,8 @@ private void handleBridgeSelectionClick(Coords clicked) { switch (bridgeSelectionStage) { case SECTION -> advanceToBridgeDirectionStage(clicked); case DIRECTION -> resolveBridgeDirection(clicked); - case NONE -> {} + case NONE -> { + } } } @@ -8070,7 +8438,6 @@ private void handleBridgeSelectionClick(Coords clicked) { * Builds a player-facing explanation of why the clicked hex is not valid for the current bridge selection stage. * * @param clicked the rejected hex - * * @return a localized reason, ending with how to proceed */ private String bridgeInvalidClickReason(Coords clicked) { @@ -8088,7 +8455,9 @@ private String bridgeInvalidClickReason(Coords clicked) { /** * @return why the clicked hex cannot be the bridge hex (stage 1) */ - private String bridgeSectionClickReason(ConvInfantry convInfantry, Board board, Coords clicked) { + private String bridgeSectionClickReason(ConvInfantry convInfantry, + Board board, + Coords clicked) { if ((convInfantry.getPosition() == null) || (convInfantry.getPosition().distance(clicked) != 1)) { return Messages.getString("MovementDisplay.BuildBridge.reason.middleNotAdjacent"); } @@ -8117,11 +8486,11 @@ private String bridgeSectionClickReason(ConvInfantry convInfantry, Board board, * * @param board the board the gap is on * @param clicked the rejected hex - * * @return a localized repair reason, or null if the hex is not next to a bridge at all (the caller falls back to - * the generic reason) + * the generic reason) */ - private @Nullable String bridgeRepairClickReason(Board board, Coords clicked) { + private @Nullable String bridgeRepairClickReason(Board board, + Coords clicked) { for (int spanDirection = 0; spanDirection < 6; spanDirection++) { Hex neighbor = board.getHex(clicked.translated(spanDirection)); if ((neighbor == null) || !neighbor.containsTerrain(Terrains.BRIDGE)) { @@ -8135,9 +8504,9 @@ private String bridgeSectionClickReason(ConvInfantry convInfantry, Board board, // The hex is the broken section of this bridge; report why the straight repair across the span fails. int straightExits = BridgeConstruction.exitsFor(spanDirection, backDirection); BridgeConstruction.BridgeRepairIssue issue = BridgeConstruction.bridgeRepairIssue(board, clicked, - straightExits); + straightExits); LOGGER.debug("[BridgeRepair] {} rejected as a repair site: straight orientation across the span toward {} " - + "gives {}", clicked, clicked.translated(spanDirection), issue); + + "gives {}", clicked, clicked.translated(spanDirection), issue); return switch (issue) { case VALID -> null; case FAR_SIDE_UNANCHORED -> Messages.getString("MovementDisplay.BuildBridge.reason.repairFarSide"); @@ -8150,9 +8519,11 @@ private String bridgeSectionClickReason(ConvInfantry convInfantry, Board board, /** * @return why the clicked far end is not a valid bridge end for the chosen bridge hex (stage 2), using the site - * validator's reason for the span from the engineer's hex to the clicked far end + * validator's reason for the span from the engineer's hex to the clicked far end */ - private String bridgeDirectionClickReason(ConvInfantry convInfantry, Board board, Coords clicked) { + private String bridgeDirectionClickReason(ConvInfantry convInfantry, + Board board, + Coords clicked) { Coords middle = selectedBridgeMiddle; if (middle.distance(clicked) != 1) { return Messages.getString("MovementDisplay.BuildBridge.reason.endNotAdjacent"); @@ -8168,15 +8539,17 @@ private String bridgeDirectionClickReason(ConvInfantry convInfantry, Board board /** * @return the localized reason a bridge span at the given hex and exits is not a valid site */ - private String bridgeSiteIssueReason(Board board, Coords middle, int exits) { + private String bridgeSiteIssueReason(Board board, + Coords middle, + int exits) { BridgeConstruction.BridgeSiteIssue issue = BridgeConstruction.bridgeSiteIssue(board, middle, exits); return switch (issue) { case OFF_BOARD -> Messages.getString("MovementDisplay.BuildBridge.reason.offBoard"); case OCCUPIED -> Messages.getString("MovementDisplay.BuildBridge.reason.occupied"); case RIMS_TOO_STEEP -> Messages.getString("MovementDisplay.BuildBridge.reason.tooSteep"); case NO_ANCHOR -> BridgeConstruction.isOverWater(board.getHex(middle)) - ? Messages.getString("MovementDisplay.BuildBridge.reason.noAnchorWater") - : Messages.getString("MovementDisplay.BuildBridge.reason.noAnchorDry"); + ? Messages.getString("MovementDisplay.BuildBridge.reason.noAnchorWater") + : Messages.getString("MovementDisplay.BuildBridge.reason.noAnchorDry"); case BAD_EXITS, VALID -> Messages.getString("MovementDisplay.BuildBridge.invalidHex"); }; } @@ -8189,7 +8562,8 @@ private String bridgeSiteIssueReason(Board board, Coords middle, int exits) { * @param selectedUnit the currently selected unit, or null if none * @param gameOptions the active game options */ - private void updateBridgeBuildButton(@Nullable Entity selectedUnit, GameOptions gameOptions) { + private void updateBridgeBuildButton(@Nullable Entity selectedUnit, + GameOptions gameOptions) { MegaMekButton button = getBtn(MoveCommand.MOVE_BUILD_BRIDGE); if ((selectedUnit instanceof ConvInfantry building) && building.isBuildingBridge()) { // Actively building: the button opens a chooser (Pause / Dismantle / Abandon) @@ -8197,7 +8571,7 @@ private void updateBridgeBuildButton(@Nullable Entity selectedUnit, GameOptions button.setToolTipText(Messages.getString("MovementDisplay.moveCancelBridge.tooltip")); button.setEnabled(true); } else if ((selectedUnit instanceof ConvInfantry resuming) - && (resuming.isDismantlingBridge() || resuming.isBridgePaused())) { + && (resuming.isDismantlingBridge() || resuming.isBridgePaused())) { // Paused or dismantling: the button opens a chooser (Resume / Abandon) button.setText(Messages.getString("MovementDisplay.moveResumeBridge")); button.setToolTipText(Messages.getString("MovementDisplay.moveResumeBridge.tooltip")); @@ -8207,9 +8581,9 @@ private void updateBridgeBuildButton(@Nullable Entity selectedUnit, GameOptions // label reflects both so players know the engineer can do either. boolean repairAllowed = gameOptions.booleanOption(OptionsConstants.UNOFFICIAL_BRIDGE_REPAIR_ENGINEERS); button.setText(Messages.getString(repairAllowed ? "MovementDisplay.moveBuildRepairBridge" - : "MovementDisplay.moveBuildBridge")); + : "MovementDisplay.moveBuildBridge")); button.setToolTipText(Messages.getString(repairAllowed ? "MovementDisplay.moveBuildRepairBridge.tooltip" - : "MovementDisplay.moveBuildBridge.tooltip")); + : "MovementDisplay.moveBuildBridge.tooltip")); button.setEnabled(canSelectBridgeBuild(selectedUnit, gameOptions)); } } @@ -8221,10 +8595,10 @@ private void updateBridgeBuildButton(@Nullable Entity selectedUnit, GameOptions * * @param unit the currently selected unit, or null if none * @param gameOptions the active game options - * * @return {@code true} if the Build Bridge button should be enabled. */ - private boolean canSelectBridgeBuild(@Nullable Entity unit, GameOptions gameOptions) { + private boolean canSelectBridgeBuild(@Nullable Entity unit, + GameOptions gameOptions) { if (!(unit instanceof ConvInfantry convInfantry)) { return false; } @@ -8232,12 +8606,12 @@ private boolean canSelectBridgeBuild(@Nullable Entity unit, GameOptions gameOpti String unitName = unit.getShortName(); if (!gameOptions.booleanOption(OptionsConstants.ADVANCED_BRIDGE_BUILDING_ENGINEERS)) { LOGGER.debug("[BuildBridge] {}: button disabled - game option {} is off", unitName, - OptionsConstants.ADVANCED_BRIDGE_BUILDING_ENGINEERS); + OptionsConstants.ADVANCED_BRIDGE_BUILDING_ENGINEERS); return false; } if (!convInfantry.hasSpecialization(ConvInfantry.BRIDGE_ENGINEERS)) { LOGGER.debug("[BuildBridge] {}: button disabled - no Bridge-Building Engineers specialization " - + "(specialization bitmask is {})", unitName, convInfantry.getSpecializations()); + + "(specialization bitmask is {})", unitName, convInfantry.getSpecializations()); return false; } if (!convInfantry.hasBridgeKit()) { @@ -8246,30 +8620,30 @@ private boolean canSelectBridgeBuild(@Nullable Entity unit, GameOptions gameOpti } if (!convInfantry.canAffordBridge(ConvInfantry.BRIDGE_TYPE_LIGHT)) { LOGGER.debug("[BuildBridge] {}: button disabled - bridge building budget spent ({} points left)", - unitName, convInfantry.getBridgeBuildPoints()); + unitName, convInfantry.getBridgeBuildPoints()); return false; } if (convInfantry.isBuildingBridge()) { LOGGER.debug("[BuildBridge] {}: button disabled - already raising a bridge (turn {} of {})", unitName, - convInfantry.getBridgeBuildTurns(), convInfantry.getBridgeBuildRequiredTurns()); + convInfantry.getBridgeBuildTurns(), convInfantry.getBridgeBuildRequiredTurns()); return false; } // The platoon must be at ground level, or standing on a bridge deck - the latter lets engineers build the // next span of a multi-hex crossing while standing on the span they just finished (TO:AUE). Hex unitHex = game.getHex(unit.getPosition(), unit.getBoardId()); boolean onBridgeDeck = (unitHex != null) && unitHex.containsTerrain(Terrains.BRIDGE) - && (unit.getElevation() == unitHex.terrainLevel(Terrains.BRIDGE_ELEV)); + && (unit.getElevation() == unitHex.terrainLevel(Terrains.BRIDGE_ELEV)); if (!game.isOnGroundMap(unit) || (unit.getAltitude() != 0) - || ((unit.getElevation() != 0) && !onBridgeDeck)) { + || ((unit.getElevation() != 0) && !onBridgeDeck)) { LOGGER.debug("[BuildBridge] {}: button disabled - not at ground level or on a bridge deck " - + "(altitude {}, elevation {}, onBridgeDeck {})", unitName, unit.getAltitude(), - unit.getElevation(), onBridgeDeck); + + "(altitude {}, elevation {}, onBridgeDeck {})", unitName, unit.getAltitude(), + unit.getElevation(), onBridgeDeck); return false; } List plans = computeValidBridgePlans(convInfantry); if (plans.isEmpty()) { LOGGER.debug("[BuildBridge] {}: button disabled - no valid bridge can be raised adjacent to {}", - unitName, unit.getPosition()); + unitName, unit.getPosition()); return false; } LOGGER.debug("[BuildBridge] {}: button enabled - {} valid bridge plan(s)", unitName, plans.size()); @@ -8284,7 +8658,6 @@ private boolean canSelectBridgeBuild(@Nullable Entity unit, GameOptions gameOpti * later. TO:AUE. * * @param convInfantry the engineer platoon - * * @return the valid bridge plans, possibly empty */ private List computeValidBridgePlans(ConvInfantry convInfantry) { @@ -8335,11 +8708,12 @@ private List computeValidBridgePlans(ConvInfantry convInfantry) * @param board the board the gap is on * @param engineerPosition the engineer platoon's hex * @param middle the candidate gap hex (adjacent to the engineer) - * * @return {@code true} if the hex is a repairable gap (at least one repair plan was added) */ - private boolean addRepairPlansForGap(List plans, Board board, Coords engineerPosition, - Coords middle) { + private boolean addRepairPlansForGap(List plans, + Board board, + Coords engineerPosition, + Coords middle) { int originSide = middle.direction(engineerPosition); boolean isRepairableGap = false; for (int firstSide = 0; firstSide < 6; firstSide++) { @@ -8393,15 +8767,20 @@ private void showBridgeActionChooser(ConvInfantry bridgePlatoon) { labels.add(Messages.getString("MovementDisplay.BridgeAction.keep")); String message = (bridgePlatoon.isBridgePaused() && !bridgePlatoon.isAdjacentToBridgeSite()) - ? Messages.getString("MovementDisplay.BridgeAction.messagePausedAway") - : Messages.getString("MovementDisplay.BridgeAction.message"); + ? Messages.getString("MovementDisplay.BridgeAction.messagePausedAway") + : Messages.getString("MovementDisplay.BridgeAction.message"); Object[] options = labels.toArray(); - int choice = JOptionPane.showOptionDialog(clientgui.getFrame(), message, - Messages.getString("MovementDisplay.BridgeAction.title"), - JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[options.length - 1]); + int choice = JOptionPane.showOptionDialog(clientgui.getFrame(), + message, + Messages.getString("MovementDisplay.BridgeAction.title"), + JOptionPane.DEFAULT_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[options.length - 1]); if ((choice < 0) || (choice >= steps.size())) { LOGGER.debug("[BuildBridge] {}: bridge action chooser dismissed with no change", - bridgePlatoon.getShortName()); + bridgePlatoon.getShortName()); return; } MoveStepType step = steps.get(choice); @@ -8429,25 +8808,26 @@ private void startBridgeBuildSelection() { choices.add(Messages.getString("MovementDisplay.BuildBridgeDialog.medium")); } String input = (String) JOptionPane.showInputDialog(clientgui.getFrame(), - Messages.getString("MovementDisplay.BuildBridgeDialog.message"), - Messages.getString("MovementDisplay.BuildBridgeDialog.title"), - JOptionPane.QUESTION_MESSAGE, - null, - choices.toArray(new String[0]), - choices.getFirst()); + Messages.getString( + "MovementDisplay.BuildBridgeDialog.message"), + Messages.getString("MovementDisplay.BuildBridgeDialog.title"), + JOptionPane.QUESTION_MESSAGE, + null, + choices.toArray(new String[0]), + choices.getFirst()); if (input == null) { return; } selectedBridgeType = input.equals(Messages.getString("MovementDisplay.BuildBridgeDialog.medium")) - ? ConvInfantry.BRIDGE_TYPE_MEDIUM : ConvInfantry.BRIDGE_TYPE_LIGHT; + ? ConvInfantry.BRIDGE_TYPE_MEDIUM : ConvInfantry.BRIDGE_TYPE_LIGHT; LOGGER.debug("[BuildBridge] {}: chose bridge type {} (1=light, 2=medium)", convInfantry.getShortName(), - selectedBridgeType); + selectedBridgeType); bridgeBuildPlans.clear(); bridgeBuildPlans.addAll(computeValidBridgePlans(convInfantry)); if (bridgeBuildPlans.isEmpty()) { LOGGER.debug("[BuildBridge] {}: selection aborted - no valid bridge plan adjacent to {}", - convInfantry.getShortName(), convInfantry.getPosition()); + convInfantry.getShortName(), convInfantry.getPosition()); return; } @@ -8485,9 +8865,9 @@ private void advanceToBridgeDirectionStage(Coords middle) { bridgeSelectionStage = BridgeSelectionStage.DIRECTION; showBridgeSelectionHexes(convInfantry, farEnds, "MovementDisplay.BuildBridge.selectDirection"); clientgui.addToast(ToastLevel.INFO, Messages.getString("MovementDisplay.BuildBridge.toast.sectionSet", - middle.getBoardNum()), convInfantry); + middle.getBoardNum()), convInfantry); LOGGER.debug("[BuildBridge] {}: bridge hex {} set, selecting far end from {}", - convInfantry.getShortName(), middle, farEnds); + convInfantry.getShortName(), middle, farEnds); } /** @@ -8514,21 +8894,22 @@ private void resolveBridgeDirection(Coords farEnd) { * @param middle the hex the bridge will occupy * @param exits the exits bitmask of the two hexsides the bridge connects */ - private void declareBridgeBuild(Coords middle, int exits) { + private void declareBridgeBuild(Coords middle, + int exits) { int bridgeType = selectedBridgeType; Entity engineer = currentEntity(); // Rebuilding a destroyed section (unofficial repair option) reads differently from raising a new bridge; the // server makes the same determination from the site, this only chooses the player-facing wording. boolean isRepair = game.getOptions().booleanOption(OptionsConstants.UNOFFICIAL_BRIDGE_REPAIR_ENGINEERS) - && BridgeConstruction.isBridgeRepairSite(game.getBoard(engineer == null ? 0 : engineer.getBoardId()), - middle, exits); + && BridgeConstruction.isBridgeRepairSite(game.getBoard(engineer == null ? 0 : engineer.getBoardId()), + middle, exits); cancelBridgeBuildSelection(); clear(); LOGGER.info("[BuildBridge] declaring bridge {}: bridge hex {}, exits bitmask {}, type {} (1=light, 2=medium)", - isRepair ? "repair" : "build", middle, exits, bridgeType); + isRepair ? "repair" : "build", middle, exits, bridgeType); Map bridgeData = new HashMap<>(); bridgeData.put(MoveStep.BRIDGE_TARGET_X_KEY, middle.getX()); bridgeData.put(MoveStep.BRIDGE_TARGET_Y_KEY, middle.getY()); @@ -8537,9 +8918,11 @@ private void declareBridgeBuild(Coords middle, int exits) { addStepToMovePath(MoveStepType.BUILD_BRIDGE, bridgeData); if (engineer != null) { String startToastKey = isRepair ? "MovementDisplay.repairBridge.toast.start" - : "MovementDisplay.buildBridge.toast.start"; + : "MovementDisplay.buildBridge.toast.start"; clientgui.addToast(ToastLevel.INFO, Messages.getString(startToastKey, - engineer.getShortName(), middle.getBoardNum(), ConvInfantry.BRIDGE_BUILD_TURNS), engineer); + engineer.getShortName(), + middle.getBoardNum(), + ConvInfantry.BRIDGE_BUILD_TURNS), engineer); } ready(); } @@ -8563,10 +8946,12 @@ private void cancelBridgeBuildSelection() { * @param hexes the valid hexes to highlight for this stage * @param statusBarKey the message key for the stage's status bar prompt */ - private void showBridgeSelectionHexes(ConvInfantry convInfantry, Set hexes, String statusBarKey) { + private void showBridgeSelectionHexes(ConvInfantry convInfantry, + Set hexes, + String statusBarKey) { if (hexes.isEmpty()) { LOGGER.debug("[BuildBridge] {}: selection aborted - no valid hexes for stage {}", - convInfantry.getShortName(), bridgeSelectionStage); + convInfantry.getShortName(), bridgeSelectionStage); cancelBridgeBuildSelection(); return; } @@ -8579,4 +8964,25 @@ private void showBridgeSelectionHexes(ConvInfantry convInfantry, Set hex clientgui.showMovementEnvelope(convInfantry, highlightData, GEAR_LAND); setStatusBarText(Messages.getString(statusBarKey)); } + + private void processDeploymentTurn(Entity entity, + Coords coords) { + entity.setFacing(entity.getPosition().direction(coords)); + entity.setSecondaryFacing(entity.getFacing()); + cmd = new MovePath(game, entity); + addStepToMovePath(MoveStepType.DEPLOY); + if (gear == GEAR_JUMP) { + initializeJumpMovePath(); + } + clientgui.boardViews().forEach(bv -> ((BoardView) bv).redrawEntity(entity)); + clientgui.updateFiringArc(entity); + clientgui.showSensorRanges(entity); + } + + private boolean pathZeroOrDeploy() { + if (cmd.length() == 0 || (cmd.length() == 1 && cmd.getLastStep().getType() == MoveStepType.DEPLOY)) { + return true; + } + return false; + } } diff --git a/megamek/src/megamek/client/ui/panels/phaseDisplay/commands/MoveCommand.java b/megamek/src/megamek/client/ui/panels/phaseDisplay/commands/MoveCommand.java index 9e0b714c88e..aa8ef62889a 100644 --- a/megamek/src/megamek/client/ui/panels/phaseDisplay/commands/MoveCommand.java +++ b/megamek/src/megamek/client/ui/panels/phaseDisplay/commands/MoveCommand.java @@ -52,8 +52,9 @@ public enum MoveCommand implements StatusBarPhaseDisplay.PhaseCommand { MOVE_TURN("moveTurn", MovementDisplay.CMD_GROUND | MovementDisplay.CMD_AERO), MOVE_WALK("moveWalk", MovementDisplay.CMD_GROUND), MOVE_JUMP("moveJump", - MovementDisplay.CMD_MEK | MovementDisplay.CMD_TANK | MovementDisplay.CMD_INF | MovementDisplay.CMD_PROTOMEK), - MOVE_BACK_UP("moveBackUp", MovementDisplay.CMD_MEK | MovementDisplay.CMD_TANK | MovementDisplay.CMD_VTOL | MovementDisplay.CMD_PROTOMEK), + MovementDisplay.CMD_MEK | MovementDisplay.CMD_TANK | MovementDisplay.CMD_INF | MovementDisplay.CMD_PROTOMEK), + MOVE_BACK_UP("moveBackUp", + MovementDisplay.CMD_MEK | MovementDisplay.CMD_TANK | MovementDisplay.CMD_VTOL | MovementDisplay.CMD_PROTOMEK), MOVE_GET_UP("moveGetUp", MovementDisplay.CMD_MEK), MOVE_FORWARD_INI("moveForwardIni", MovementDisplay.CMD_ALL), MOVE_CHARGE("moveCharge", MovementDisplay.CMD_MEK | MovementDisplay.CMD_TANK), @@ -77,15 +78,16 @@ public enum MoveCommand implements StatusBarPhaseDisplay.PhaseCommand { MOVE_ELEVATOR_DOWN("moveElevatorDown", MovementDisplay.CMD_GROUND), MOVE_SEARCHLIGHT("moveSearchlight", MovementDisplay.CMD_GROUND), MOVE_LAY_MINE("moveLayMine", - MovementDisplay.CMD_MEK | MovementDisplay.CMD_TANK | MovementDisplay.CMD_INF | MovementDisplay.CMD_PROTOMEK), + MovementDisplay.CMD_MEK | MovementDisplay.CMD_TANK | MovementDisplay.CMD_INF | MovementDisplay.CMD_PROTOMEK), MOVE_HULL_DOWN("moveHullDown", MovementDisplay.CMD_MEK | MovementDisplay.CMD_TANK), MOVE_CLIMB_MODE("moveClimbMode", - MovementDisplay.CMD_MEK | MovementDisplay.CMD_TANK | MovementDisplay.CMD_INF | MovementDisplay.CMD_PROTOMEK), + MovementDisplay.CMD_MEK | MovementDisplay.CMD_TANK | MovementDisplay.CMD_INF | MovementDisplay.CMD_PROTOMEK), MOVE_DESCEND("moveDescend", MovementDisplay.CMD_MEK), MOVE_SWIM("moveSwim", MovementDisplay.CMD_MEK), MOVE_SHAKE_OFF("moveShakeOff", MovementDisplay.CMD_TANK | MovementDisplay.CMD_VTOL), MOVE_BRACE("moveBrace", MovementDisplay.CMD_MEK), MOVE_CHAFF("moveChaff", MovementDisplay.CMD_NON_INF), + MOVE_CLEAR_DEPLOY("moveClearDeploy", MovementDisplay.CMD_GROUND | MovementDisplay.CMD_AERO), // Convert command to a single button, which can cycle through modes because MovePath state is available MOVE_MODE_CONVERT("moveModeConvert", MovementDisplay.CMD_CONVERTER), @@ -167,7 +169,8 @@ public enum MoveCommand implements StatusBarPhaseDisplay.PhaseCommand { */ public int priority; - MoveCommand(String commandString, int commandFlag) { + MoveCommand(String commandString, + int commandFlag) { cmd = commandString; flag = commandFlag; priority = ordinal(); @@ -210,9 +213,9 @@ public String getHotKeyDesc() { String msgToggleMoveJump = Messages.getString("MovementDisplay.tooltip.ToggleMoveJump"); result += "  " + - msgToggleMoveJump + - ": " + - KeyCommandBind.getDesc(KeyCommandBind.TOGGLE_MOVE_MODE); + msgToggleMoveJump + + ": " + + KeyCommandBind.getDesc(KeyCommandBind.TOGGLE_MOVE_MODE); break; case MOVE_BACK_UP: result += KeyCommandBind.getDesc(KeyCommandBind.MOVE_BACKUP); @@ -237,14 +240,13 @@ public String getHotKeyDesc() { String msgToggleMode = Messages.getString("MovementDisplay.tooltip.ToggleMode"); result += "  " + - msgToggleMode + - ": " + - KeyCommandBind.getDesc(KeyCommandBind.TOGGLE_CONVERSION_MODE); + msgToggleMode + + ": " + + KeyCommandBind.getDesc(KeyCommandBind.TOGGLE_CONVERSION_MODE); break; default: break; } - return result; } @@ -254,10 +256,11 @@ public String getHotKeyDesc() { * @param unitFlag The unit flag to specify what unit type the commands are for. * @param opts A {@link GameOptions} reference for checking game options * @param forwardIni A flag to see if we can pass the turn to a teammate - * * @return An array of valid commands for the given parameters */ - public static MoveCommand[] values(int unitFlag, GameOptions opts, boolean forwardIni) { + public static MoveCommand[] values(int unitFlag, + GameOptions opts, + boolean forwardIni) { boolean selfDestruct = false; boolean advVehicle = false; boolean vtolStrafe = false; diff --git a/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/LobbyMekCellFormatter.java b/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/LobbyMekCellFormatter.java index e26357ed196..fe274faae51 100644 --- a/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/LobbyMekCellFormatter.java +++ b/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/LobbyMekCellFormatter.java @@ -71,28 +71,43 @@ class LobbyMekCellFormatter { private static final GUIPreferences GUIP = GUIPreferences.getInstance(); - /** Corner of the branch drawn before a carried or towed unit. Escaped to keep the source plain ASCII. */ + /** + * Corner of the branch drawn before a carried or towed unit. Escaped to keep the source plain ASCII. + */ private static final String BRANCH_CORNER = "\u2514"; - /** One length of branch. Repeated once per level, so a deeper load reads as a longer arm. */ + /** + * One length of branch. Repeated once per level, so a deeper load reads as a longer arm. + */ private static final String BRANCH_ARM = "\u2500"; - /** Top of the bracket drawn on the first member of a masterless C3 network (C3i, NC3, Nova CEWS). */ + /** + * Top of the bracket drawn on the first member of a masterless C3 network (C3i, NC3, Nova CEWS). + */ private static final String BRANCH_TOP = "\u250c"; - /** Middle rung of the bracket drawn on inner members of a masterless C3 network. */ + /** + * Middle rung of the bracket drawn on inner members of a masterless C3 network. + */ private static final String BRANCH_TEE = "\u251c"; - /** Stops a malformed C3 master chain from spinning while counting how deep a unit sits. */ + /** + * Stops a malformed C3 master chain from spinning while counting how deep a unit sits. + */ private static final int MAX_C3_DEPTH = 3; - /** Stops a malformed load loop from spinning while counting how deep a unit sits. */ + /** + * Stops a malformed load loop from spinning while counting how deep a unit sits. + */ private static final int MAX_CARRIER_DEPTH = 16; private LobbyMekCellFormatter() { } - static String unitTableEntry(InGameObject unit, ChatLounge lobby, boolean forceView, boolean compactView) { + static String unitTableEntry(InGameObject unit, + ChatLounge lobby, + boolean forceView, + boolean compactView) { if (unit instanceof Entity) { return compactView ? formatUnitCompact((Entity) unit, lobby, forceView) : formatUnitFull((Entity) unit, lobby, forceView); @@ -104,7 +119,10 @@ static String unitTableEntry(InGameObject unit, ChatLounge lobby, boolean forceV } } - static String pilotTableEntry(InGameObject unit, boolean compactView, boolean hide, boolean rpgSkills) { + static String pilotTableEntry(InGameObject unit, + boolean compactView, + boolean hide, + boolean rpgSkills) { if (unit instanceof Entity) { return compactView ? formatPilotCompact((Entity) unit, hide, rpgSkills) : formatPilotFull((Entity) unit, hide); @@ -117,9 +135,9 @@ static String pilotTableEntry(InGameObject unit, boolean compactView, boolean hi } /** - * Returns the branch drawn in front of a unit that rides on another, indented and lengthened by how deeply it - * sits. A Mek inside a DropShip inside a JumpShip is drawn further in than the DropShip carrying it, so a stack - * reads as a tree rather than a flat run of identical marks. + * Returns the branch drawn in front of a unit that rides on another, indented and lengthened by how deeply it sits. + * A Mek inside a DropShip inside a JumpShip is drawn further in than the DropShip carrying it, so a stack reads as + * a tree rather than a flat run of identical marks. */ static String carriedBranch(Entity entity) { int depth = carriedDepth(entity); @@ -131,7 +149,9 @@ static String carriedBranch(Entity entity) { return " ".repeat(depth) + BRANCH_CORNER + BRANCH_ARM.repeat(depth) + " "; } - /** How many carriers sit above this unit: 1 for a DropShip in a JumpShip, 2 for a Mek inside that DropShip. */ + /** + * How many carriers sit above this unit: 1 for a DropShip in a JumpShip, 2 for a Mek inside that DropShip. + */ private static int carriedDepth(Entity entity) { int depth = 0; Entity current = entity; @@ -149,7 +169,9 @@ private static int carriedDepth(Entity entity) { return depth; } - /** The unit this one rides on, carried in a bay or towed behind, or {@code null} when neither. */ + /** + * The unit this one rides on, carried in a bay or towed behind, or {@code null} when neither. + */ private static Entity carrierOf(Entity entity) { if (entity.getGame() == null) { return null; @@ -168,8 +190,8 @@ private static Entity carrierOf(Entity entity) { /** * Returns the tractor heading the train this unit is towed by, or {@code null} when it is not towed. *

- * Deployment belongs to that tractor: a train goes where it goes. A trailer only gets a setting of its own once - * the game starts and the tractor's is copied onto it, so the lobby has to read it from the head of the train. + * Deployment belongs to that tractor: a train goes where it goes. A trailer only gets a setting of its own once the + * game starts and the tractor's is copied onto it, so the lobby has to read it from the head of the train. *

*/ private static Entity trainHeadOf(Entity entity) { @@ -182,11 +204,11 @@ private static Entity trainHeadOf(Entity entity) { /** * Returns the branch drawn in front of a C3 network member, so a network reads as the tree from the rulebook's - * configuration diagram. The C3 sorter wrapper keeps members adjacent in hierarchy order under every sorter, so - * the branch always points at the row above it. Hierarchical C3 draws a corner per level below the network's - * top unit (lance masters one level in, their slaves two). C3i, NC3 and Nova CEWS networks have no master, so - * their members are drawn as a flat bracket instead: a top corner on the first member, rungs on inner members - * and a bottom corner on the last, showing they belong together without inventing a hierarchy. + * configuration diagram. The C3 sorter wrapper keeps members adjacent in hierarchy order under every sorter, so the + * branch always points at the row above it. Hierarchical C3 draws a corner per level below the network's top unit + * (lance masters one level in, their slaves two). C3i, NC3 and Nova CEWS networks have no master, so their members + * are drawn as a flat bracket instead: a top corner on the first member, rungs on inner members and a bottom corner + * on the last, showing they belong together without inventing a hierarchy. */ static String c3Branch(Entity entity) { if (!entity.hasAnyC3System()) { @@ -208,7 +230,9 @@ static String c3Branch(Entity entity) { return " ".repeat(depth) + BRANCH_CORNER + BRANCH_ARM.repeat(depth) + " "; } - /** The opening corner for a unit heading a hierarchical C3 network; empty for units networked with no one. */ + /** + * The opening corner for a unit heading a hierarchical C3 network; empty for units networked with no one. + */ private static String hierarchicalNetworkTopBranch(Entity entity) { Game game = entity.getGame(); if ((game == null) || !entity.hasC3()) { @@ -222,7 +246,9 @@ private static String hierarchicalNetworkTopBranch(Entity entity) { return ""; } - /** How many masters sit above this unit: 1 for a lance master under a company commander, 2 for its slaves. */ + /** + * How many masters sit above this unit: 1 for a lance master under a company commander, 2 for its slaves. + */ private static int c3Depth(Entity entity) { int depth = 0; Entity current = entity; @@ -240,7 +266,9 @@ private static int c3Depth(Entity entity) { return depth; } - /** The flat bracket for masterless networks; empty when the unit is not networked with anyone. */ + /** + * The flat bracket for masterless networks; empty when the unit is not networked with anyone. + */ private static String peerNetworkBracket(Entity entity) { Game game = entity.getGame(); String netId = entity.getC3NetId(); @@ -280,7 +308,9 @@ private static String peerNetworkBracket(Entity entity) { * Creates and returns the display content of the Unit column for the given entity and for the non-compact display * mode. When blindDrop is true, the unit details are not given. */ - static String formatUnitFull(Entity entity, ChatLounge lobby, boolean forceView) { + static String formatUnitFull(Entity entity, + ChatLounge lobby, + boolean forceView) { StringBuilder result = new StringBuilder("" + fontHTML()); Client client = lobby.getClientGUI().getClient(); @@ -513,7 +543,7 @@ static String formatUnitFull(Entity entity, ChatLounge lobby, boolean forceView) } int so = entity.getStartingOffset(true); int sw = entity.getStartingWidth(true); - if ((so != 0) || (sw != 3)) { + if ((so != 0) || !(sw == 3 || (sw == 1 && Game.rulesManager.getRulesGame().isWalkOnDeployment()))) { result.append(", ").append(so); result.append(", ").append(sw); } @@ -705,6 +735,10 @@ static String formatUnitFull(Entity entity, ChatLounge lobby, boolean forceView) result.append(", ").append(entity.getOffBoardDistance()); } + if (entity.getDeployRound() == Entity.DEPLOY_ROUND_PRE_GAME) { + firstEntry = dotSpacer(result, firstEntry); + result.append(getString("ChatLounge.deploysPreGame")); + } if (entity.getDeployRound() > 0) { firstEntry = dotSpacer(result, firstEntry); result.append(getString("ChatLounge.deploysAfterRound", entity.getDeployRound())); @@ -783,7 +817,9 @@ static String formatUnitFull(Entity entity, ChatLounge lobby, boolean forceView) * Creates and returns the display content of the C3-MekTree cell for the given entity and for the compact display * mode. Assumes that no enemy or blind-drop-hidden units are provided. */ - static String formatUnitCompact(Entity entity, ChatLounge lobby, boolean forceView) { + static String formatUnitCompact(Entity entity, + ChatLounge lobby, + boolean forceView) { Client client = lobby.getClientGUI().getClient(); Game game = client.getGame(); GameOptions options = game.getOptions(); @@ -1116,7 +1152,8 @@ static String formatUnitCompact(Entity entity, ChatLounge lobby, boolean forceVi * Creates and returns the display content of the C3-MekTree cell for the given entity and for the compact display * mode. Assumes that no enemy or blind-drop-hidden units are provided. */ - static String formatForceCompact(Force force, ChatLounge lobby) { + static String formatForceCompact(Force force, + ChatLounge lobby) { return formatForce(force, lobby); } @@ -1124,11 +1161,13 @@ static String formatForceCompact(Force force, ChatLounge lobby) { * Creates and returns the display content of the C3-MekTree cell for the given entity and for the compact display * mode. Assumes that no enemy or blind-drop-hidden units are provided. */ - static String formatForceFull(Force force, ChatLounge lobby) { + static String formatForceFull(Force force, + ChatLounge lobby) { return formatForce(force, lobby); } - private static String formatForce(Force force, ChatLounge lobby) { + private static String formatForce(Force force, + ChatLounge lobby) { Client client = lobby.getClientGUI().getClient(); Game game = client.getGame(); Player localPlayer = client.getLocalPlayer(); @@ -1207,7 +1246,9 @@ private static String formatForce(Force force, ChatLounge lobby) { * Creates and returns the display content of the Pilot column for the given entity and for the compact display * mode. When blindDrop is true, the pilot details are not given. */ - static String formatPilotCompact(Entity entity, boolean blindDrop, boolean rpgSkills) { + static String formatPilotCompact(Entity entity, + boolean blindDrop, + boolean rpgSkills) { Crew pilot = entity.getCrew(); StringBuilder result = new StringBuilder(""); result.append(fontHTML()); @@ -1249,7 +1290,8 @@ static String formatPilotCompact(Entity entity, boolean blindDrop, boolean rpgSk * Creates and returns the display content of the Pilot column for the given entity and for the non-compact display * mode. When blindDrop is true, the pilot details are not given. */ - static String formatPilotFull(Entity entity, boolean blindDrop) { + static String formatPilotFull(Entity entity, + boolean blindDrop) { StringBuilder result = new StringBuilder(""); final Crew crew = entity.getCrew(); @@ -1308,26 +1350,30 @@ static String formatPilotFull(Entity entity, boolean blindDrop) { return result.toString(); } - static void formatSpan(StringBuilder current, Color color) { + static void formatSpan(StringBuilder current, + Color color) { current.append(""); } @Deprecated(since = "0.51.0", forRemoval = true) - static void formatSpan(StringBuilder current, String hexColor) { + static void formatSpan(StringBuilder current, + String hexColor) { current.append(""); } @Deprecated(since = "0.51.0", forRemoval = true) - static void fullIDString(StringBuilder current, int id) { + static void fullIDString(StringBuilder current, + int id) { formatSpan(current, uiGray()); current.append(" [ID: ").append(id).append("]"); } - static boolean dotSpacer(StringBuilder current, boolean firstElement) { + static boolean dotSpacer(StringBuilder current, + boolean firstElement) { if (!firstElement) { current.append(MekTableModel.DOT_SPACER); } diff --git a/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/LobbyMekPopup.java b/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/LobbyMekPopup.java index 7c6cdbcf7df..dfa041cd3b4 100644 --- a/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/LobbyMekPopup.java +++ b/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/LobbyMekPopup.java @@ -642,6 +642,12 @@ private static JMenu deployMenu(ClientGUI clientGui, boolean enabled, ActionList // Late deployment JMenu lateMenu = new JMenu("Deployment round"); + if (Game.rulesManager.getRulesGame().isWalkOnDeployment()) { + lateMenu.add(menuItem(Messages.getString("ChatLounge.deploysPreGame"), + LMP_DEPLOY + "|" + Entity.DEPLOY_ROUND_PRE_GAME + eIds, + true, + listener)); + } lateMenu.add(menuItem("At game start", LMP_DEPLOY + "|0" + eIds, true, listener)); for (int i = 1; i < 11; i++) { lateMenu.add(menuItem("Before round " + i, LMP_DEPLOY + "|" + i + eIds, true, listener)); @@ -800,7 +806,7 @@ private static JMenu c3Menu(boolean enabled, Collection entities, Client } else if (!entity.isC3CompanyCommander() && (entity.hasC3M() ? lanceRolesCompatible(game, entity, other) - : other.isC3IndependentMaster())) { + : other.isC3IndependentMaster())) { // Slaves connect to lance masters; masters connect to company commanders or - forming an // All-C3-Master lance (CR p.199) - to lance masters whose dependents are all masters too. String item = "Connect to " + other.getShortNameRaw() + idString(game, other.getId()); @@ -823,9 +829,9 @@ private static JMenu c3Menu(boolean enabled, Collection entities, Client } /** - * Returns true when the joining unit's role fits the dependents already connected to the given master. A lance - * is homogeneous (CR p.199): all C3 Slaves, or - under the All-C3-Master rule - all C3 Masters in slave roles, - * so a master may not join a lance of slaves and vice versa. + * Returns true when the joining unit's role fits the dependents already connected to the given master. A lance is + * homogeneous (CR p.199): all C3 Slaves, or - under the All-C3-Master rule - all C3 Masters in slave roles, so a + * master may not join a lance of slaves and vice versa. */ private static boolean lanceRolesCompatible(Game game, Entity joiningUnit, Entity master) { boolean joinerIsMaster = joiningUnit.hasC3M(); diff --git a/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/PlayerSettingsDialog.java b/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/PlayerSettingsDialog.java index e9e1e998ad8..e594010b422 100644 --- a/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/PlayerSettingsDialog.java +++ b/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/PlayerSettingsDialog.java @@ -38,12 +38,7 @@ import static megamek.client.ui.util.UIUtil.teamColor; import static megamek.client.ui.util.UIUtil.uiYellow; -import java.awt.Component; -import java.awt.Container; -import java.awt.FlowLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.GridLayout; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.Serial; @@ -117,7 +112,9 @@ public class PlayerSettingsDialog extends AbstractButtonDialog { private static final String CMD_REMOVE_GROUND_OBJECT = "CMD_REMOVE_GROUND_OBJECT_%d"; private static final String CMD_REMOVE_GROUND_OBJECT_PREFIX = "CMD_REMOVE_GROUND_OBJECT_"; - public PlayerSettingsDialog(ClientGUI cg, Client cl, BoardView bv) { + public PlayerSettingsDialog(ClientGUI cg, + Client cl, + BoardView bv) { super(cg.getFrame(), "PlayerSettingsDialog", "PlayerSettingsDialog.title"); client = cl; clientgui = cg; @@ -160,8 +157,11 @@ public PlayerSettingsDialog(ClientGUI cg, Client cl, BoardView bv) { private static final long serialVersionUID = -333065979253244440L; @Override - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, - boolean cellHasFocus) { + public Component getListCellRendererComponent(JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus) { if (value == null) { setText("General"); } else { @@ -173,7 +173,8 @@ public Component getListCellRendererComponent(JList list, Object value, int i private final Comparator factionSorter = new Comparator<>() { @Override - public int compare(FactionRecord o1, FactionRecord o2) { + public int compare(FactionRecord o1, + FactionRecord o2) { return o1.getName(year).compareTo(o2.getName(year)); } }; @@ -183,62 +184,86 @@ protected void okAction() { apply(); } - /** Returns the chosen initiative modifier. */ + /** + * Returns the chosen initiative modifier. + */ public int getInit() { return parseField(fldInit); } - /** Returns the chosen conventional mines. */ + /** + * Returns the chosen conventional mines. + */ public int getCnvMines() { return parseField(fldConventional); } - /** Returns the chosen inferno mines. */ + /** + * Returns the chosen inferno mines. + */ public int getInfMines() { return parseField(fldInferno); } - /** Returns the chosen active mines. */ + /** + * Returns the chosen active mines. + */ public int getActMines() { return parseField(fldActive); } - /** Returns the chosen vibrabombs. */ + /** + * Returns the chosen vibrabombs. + */ public int getVibMines() { return parseField(fldVibrabomb); } - /** Returns the chosen EMP mines. */ + /** + * Returns the chosen EMP mines. + */ public int getEmpMines() { return parseField(fldEMP); } - /** Returns the chosen number of fortified hexes. */ + /** + * Returns the chosen number of fortified hexes. + */ public int getFortifiedHexes() { return parseField(fldFortifiedHexes); } - - /** Returns the chosen number of tripwires */ + + /** + * Returns the chosen number of tripwires + */ public int getTripwires() { - return parseField(fldTripwires); + return parseField(fldTripwires); } - - /** Returns the chosen number of pitfalls */ + + /** + * Returns the chosen number of pitfalls + */ public int getPitfalls() { - return parseField(fldPitfalls); + return parseField(fldPitfalls); } - /** Returns the start location offset */ + /** + * Returns the start location offset + */ public int getStartOffset() { return parseField(txtOffset); } - /** Returns the player start location width */ + /** + * Returns the player start location width + */ public int getStartWidth() { return parseField(txtWidth); } - /** Returns the chosen deployment position. */ + /** + * Returns the chosen deployment position. + */ public int getStartPos() { return currentPlayerStartPos; } @@ -266,7 +291,9 @@ public SkillGenerationOptionsPanel getSkillGenerationOptionsPanel() { return skillGenerationOptionsPanel; } - /** Returns the player's email address. */ + /** + * Returns the player's email address. + */ public String getEmail() { return fldEmail.getText().trim(); } @@ -280,14 +307,14 @@ public String getEmail() { // Initiative Section private final JLabel labInit = new TipLabel(Messages.getString("PlayerSettingsDialog.initMod"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final TipTextField fldInit = new TipTextField(3); // Mines Section private final JLabel labConventional = new JLabel(getString("PlayerSettingsDialog.labConventional"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JLabel labVibrabomb = new JLabel(getString("PlayerSettingsDialog.labVibrabomb"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JLabel labActive = new JLabel(getString("PlayerSettingsDialog.labActive"), SwingConstants.RIGHT); private final JLabel labInferno = new JLabel(getString("PlayerSettingsDialog.labInferno"), SwingConstants.RIGHT); private final JLabel labEMP = new JLabel(getString("PlayerSettingsDialog.labEMP"), SwingConstants.RIGHT); @@ -303,7 +330,7 @@ public String getEmail() { // Fortifications Section private final JLabel labFortifiedHexes = new JLabel(getString("PlayerSettingsDialog.labFortifiedHexes"), - SwingConstants.RIGHT); + SwingConstants.RIGHT); private final JTextField fldFortifiedHexes = new JTextField(3); // Skills Section @@ -319,6 +346,8 @@ public String getEmail() { private final JFormattedTextField txtOffset; private final JFormattedTextField txtWidth; + private final JLabel labWalkOn = new JLabel(); + private final JLabel labWalkOnMore = new JLabel(); private JSpinner spinStartingAnyNWx; private JSpinner spinStartingAnyNWy; private JSpinner spinStartingAnySEx; @@ -341,7 +370,7 @@ public String getEmail() { private transient ReconfigurationParameters rp; private int year; private final JLabel labelAutoconfig = new TipLabel(Messages.getString("PlayerSettingsDialog.autoConfigFaction"), - SwingConstants.LEFT); + SwingConstants.LEFT); private final JComboBox cmbFaction = new JComboBox<>(); private final JButton butAutoconfigure = new JButton(Messages.getString("PlayerSettingsDialog.autoConfig")); private final JButton butRandomize = new JButton(Messages.getString("PlayerSettingsDialog.randomize")); @@ -369,7 +398,10 @@ protected Container createCenterPane() { } mainPanel.add(startSection()); mainPanel.add(initiativeSection()); - if (Game.rulesManager.getRulesGame().allowMinefields(client.getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_MINEFIELDS))) { + if (Game.rulesManager.getRulesGame() + .allowMinefields(client.getGame() + .getOptions() + .booleanOption(OptionsConstants.ADVANCED_MINEFIELDS))) { mainPanel.add(mineSection()); } mainPanel.add(fortificationSection()); @@ -505,7 +537,7 @@ private void addGroundObjectToUI(ICarryable groundObject) { gbc.gridx = 3; JButton btnRemove = new JButton("Remove"); btnRemove.setActionCommand(String.format(CMD_REMOVE_GROUND_OBJECT, - player.getGroundObjectsToPlace().size() - 1)); + player.getGroundObjectsToPlace().size() - 1)); btnRemove.addActionListener(listener); groundSectionContent.add(btnRemove, gbc); row.add(btnRemove); @@ -558,8 +590,8 @@ private void removeGroundObject(String command) { // they need to for (int componentIndex = index; componentIndex < groundSectionComponents.size(); componentIndex++) { ((JButton) groundSectionComponents.get(index).get(2)).setActionCommand(String.format( - CMD_REMOVE_GROUND_OBJECT, - componentIndex)); + CMD_REMOVE_GROUND_OBJECT, + componentIndex)); } validate(); @@ -592,7 +624,6 @@ private JPanel deploymentParametersPanel() { lblOffset.setToolTipText(Messages.getString("CustomMekDialog.labDeploymentOffsetTip")); JLabel lblWidth = new JLabel(Messages.getString("CustomMekDialog.labDeploymentWidth")); lblWidth.setToolTipText(Messages.getString("CustomMekDialog.labDeploymentWidthTip")); - txtOffset.setColumns(4); txtWidth.setColumns(4); @@ -600,6 +631,18 @@ private JPanel deploymentParametersPanel() { result.add(txtOffset, GBC.eol()); result.add(lblWidth, GBC.std()); result.add(txtWidth, GBC.eol()); + result.add(labWalkOn, GBC.eol()); + result.add(labWalkOnMore, GBC.eol()); + if (Game.rulesManager.getRulesGame().isWalkOnDeployment()) { + labWalkOn.setText(Messages.getString("PlayerSettingsDialog.labWalkOnDeployment")); + } + if (Game.rulesManager.getRulesGame().restrictDeploymentWidth(player, currentPlayerStartPos)) { + labWalkOnMore.setText(Messages.getString("PlayerSettingsDialog.labDeploymentWidthWalkOn")); + txtWidth.setEnabled(false); + } else { + labWalkOnMore.setText(""); + txtWidth.setEnabled(true); + } result.add(new JLabel(" "), GBC.eol()); result.add(new JLabel(Messages.getString("CustomMekDialog.labDeploymentCustomBox")), GBC.eol()); @@ -653,7 +696,7 @@ private void apply() { // the newly selected home edge. OffBoardDirection direction = OffBoardDirection.translateStartPosition(getStartPos()); if (direction != OffBoardDirection.NONE && - gOpts.booleanOption(OptionsConstants.BASE_SET_ARTY_PLAYER_HOME_EDGE)) { + gOpts.booleanOption(OptionsConstants.BASE_SET_ARTY_PLAYER_HOME_EDGE)) { for (Entity entity : client.getGame().getPlayerEntities(client.getLocalPlayer(), false)) { if (entity.getOffBoardDirection() != OffBoardDirection.NONE) { entity.setOffBoard(entity.getOffBoardDistance(), direction); @@ -715,13 +758,13 @@ private JPanel mineSection() { panContent.add(fldInferno); panContent.add(labEMP); panContent.add(fldEMP); - + String tooltip = Messages.getString("PlayerSettingsDialog.tripwireTT"); labTripwires.setToolTipText(tooltip); - fldTripwires.setToolTipText(tooltip); + fldTripwires.setToolTipText(tooltip); panContent.add(labTripwires); panContent.add(fldTripwires); - + tooltip = Messages.getString("PlayerSettingsDialog.pitfallTT"); labPitfalls.setToolTipText(tooltip); fldPitfalls.setToolTipText(tooltip); @@ -786,6 +829,12 @@ private void setupValues() { txtWidth.setText(Integer.toString(player.getStartWidth())); txtOffset.setText(Integer.toString(player.getStartOffset())); + if (Game.rulesManager.getRulesGame().restrictDeploymentWidth(player, player.getStartingPos())) { + txtWidth.setEnabled(false); + } else { + txtWidth.setEnabled(true); + } + MapSettings ms = clientgui.getClient().getMapSettings(); int bh = ms.getBoardHeight() * ms.getMapHeight(); int bw = ms.getBoardWidth() * ms.getMapWidth(); @@ -819,8 +868,8 @@ private void setupStartGrid() { } var currentBoard = clientgui.getClient().getGame().getPhase().isLounge() ? - ServerBoardHelper.getPossibleGameBoard(clientgui.getClient().getMapSettings(), true) : - clientgui.getClient().getGame().getBoard(); + ServerBoardHelper.getPossibleGameBoard(clientgui.getClient().getMapSettings(), true) : + clientgui.getClient().getGame().getBoard(); var deploymentZones = currentBoard.getCustomDeploymentZones(); int extraRowCount = (int) Math.ceil(deploymentZones.size() / 3.0); @@ -869,7 +918,9 @@ private void setupStartGrid() { updateStartGrid(); } - /** Assigns texts and tooltips to the starting positions grid. */ + /** + * Assigns texts and tooltips to the starting positions grid. + */ private void updateStartGrid() { Map butText = new HashMap<>(); Map butTT = new HashMap<>(); @@ -914,6 +965,14 @@ private void updateStartGrid() { butText.get(currentPlayerStartPos).append(UIUtil.fontHTML(GUIPreferences.getInstance().getMyUnitColor())); butText.get(currentPlayerStartPos).append("\u2B24"); + if (Game.rulesManager.getRulesGame().restrictDeploymentWidth(player, currentPlayerStartPos)) { + txtWidth.setEnabled(false); + labWalkOnMore.setText(Messages.getString("PlayerSettingsDialog.labDeploymentWidthWalkOn")); + } else { + labWalkOnMore.setText(""); + txtWidth.setEnabled(true); + } + // Turn off custom deployment if start is not Any if (currentPlayerStartPos == Board.START_ANY) { spinStartingAnyNWx.setEnabled(true); @@ -995,10 +1054,10 @@ public void actionPerformed(ActionEvent e) { } else if (butBotSettings.equals(e.getSource()) && client instanceof BotClient botClient) { BehaviorSettings behavior = botClient.getBehaviorSettings(); var bcd = new BotConfigDialog(clientgui.getFrame(), - client.getLocalPlayer().getName(), - behavior, - clientgui, - botClient.getAIType()); + client.getLocalPlayer().getName(), + behavior, + clientgui, + botClient.getAIType()); bcd.setVisible(true); if (bcd.getResult() == DialogResult.CONFIRMED) { botClient.setBehaviorSettings(bcd.getBehaviorSettings()); @@ -1074,12 +1133,13 @@ public String getFactionCode() { return getFaction().getKey(); } - public FactionRecord getFactionFromCode(String code, int year) { + public FactionRecord getFactionFromCode(String code, + int year) { for (FactionRecord fRec : RATGenerator.getInstance().getFactionList()) { if ((!fRec.isMinor()) && - !fRec.getKey().contains(".") && - fRec.isActiveInYear(year) && - fRec.getKey().equals(code)) { + !fRec.getKey().contains(".") && + fRec.isActiveInYear(year) && + fRec.getKey().equals(code)) { return fRec; } } diff --git a/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/PlayerTable.java b/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/PlayerTable.java index 62fc43e61a9..bab289f90ca 100644 --- a/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/PlayerTable.java +++ b/megamek/src/megamek/client/ui/panels/phaseDisplay/lobby/PlayerTable.java @@ -37,19 +37,12 @@ import static megamek.client.ui.util.UIUtil.uiGreen; import static megamek.client.ui.util.UIUtil.uiYellow; -import java.awt.Color; -import java.awt.Component; -import java.awt.GridLayout; -import java.awt.Image; -import java.awt.Point; +import java.awt.*; import java.awt.event.MouseEvent; import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; -import javax.swing.BorderFactory; -import javax.swing.ImageIcon; -import javax.swing.JTable; -import javax.swing.ListSelectionModel; +import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; @@ -60,10 +53,10 @@ import megamek.client.ui.Messages; import megamek.client.ui.clientGUI.GUIPreferences; import megamek.client.ui.util.UIUtil; +import megamek.common.Player; import megamek.common.board.Board; import megamek.common.game.Game; import megamek.common.interfaces.IStartingPositions; -import megamek.common.Player; import megamek.common.options.OptionsConstants; class PlayerTable extends JTable { @@ -73,7 +66,8 @@ class PlayerTable extends JTable { PlayerTableModel model; ChatLounge lobby; - public PlayerTable(PlayerTableModel pm, ChatLounge cl) { + public PlayerTable(PlayerTableModel pm, + ChatLounge cl) { super(pm); model = pm; lobby = cl; @@ -129,9 +123,12 @@ public String getToolTipText(MouseEvent e) { String msgNoInitiativeModifier = Messages.getString("ChatLounge.NoInitiativeModifier"); result.append(msgNoInitiativeModifier); } - if (Game.rulesManager.getRulesGame().allowMinefields(lobby.game().getOptions().booleanOption(OptionsConstants.ADVANCED_MINEFIELDS))) { + if (Game.rulesManager.getRulesGame() + .allowMinefields(lobby.game() + .getOptions() + .booleanOption(OptionsConstants.ADVANCED_MINEFIELDS))) { int mines = player.getNbrMFConventional() + player.getNbrMFActive() - + player.getNbrMFInferno() + player.getNbrMFVibra(); + + player.getNbrMFInferno() + player.getNbrMFVibra(); String msgTotalMinefields = Messages.getString("ChatLounge.TotalMinefields"); result.append("
").append(msgTotalMinefields).append(": ").append(mines); } @@ -175,7 +172,8 @@ public Class getColumnClass(int c) { } @Override - public Object getValueAt(int row, int col) { + public Object getValueAt(int row, + int col) { return getPlayerAt(row); } @@ -197,8 +195,12 @@ private void setImage(Image img) { } @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, - boolean hasFocus, int row, int column) { + public Component getTableCellRendererComponent(JTable table, + Object value, + boolean isSelected, + boolean hasFocus, + int row, + int column) { Player player = (Player) value; super.getTableCellRendererComponent(table, getPlayerDescription(player), isSelected, hasFocus, row, column); @@ -212,7 +214,7 @@ private StringBuilder getPlayerDescription(Player player) { StringBuilder result = new StringBuilder("
"); // First Line - Player Name if ((lobby.client() instanceof BotClient) && player.equals(lobby.localPlayer()) - || lobby.client().getBots().containsKey(player.getName())) { + || lobby.client().getBots().containsKey(player.getName())) { result.append(UIUtil.BOT_MARKER); } result.append(player.getName()); @@ -232,16 +234,16 @@ private StringBuilder getPlayerDescription(Player player) { final var gOpts = lobby.game().getOptions(); if (gOpts.booleanOption(OptionsConstants.BASE_SET_PLAYER_DEPLOYMENT_TO_PLAYER_0) - && !player.isBot() - && player.getId() != 0) { + && !player.isBot() + && player.getId() != 0) { result.append(msg_start).append(": ").append(Messages.getString("ChatLounge.Player0")); } else if ((!lobby.client().getLocalPlayer().isGameMaster() - && (isEnemy) - && (gOpts.booleanOption(OptionsConstants.BASE_BLIND_DROP) - || gOpts.booleanOption(OptionsConstants.BASE_REAL_BLIND_DROP)))) { + && (isEnemy) + && (gOpts.booleanOption(OptionsConstants.BASE_BLIND_DROP) + || gOpts.booleanOption(OptionsConstants.BASE_REAL_BLIND_DROP)))) { result.append(msg_start).append(": ").append(Messages.getString("ChatLounge.Blind")); } else if ((player.getStartingPos() >= 0) - && (player.getStartingPos() <= IStartingPositions.START_LOCATION_NAMES.length)) { + && (player.getStartingPos() <= IStartingPositions.START_LOCATION_NAMES.length)) { result.append(msg_start) .append(": ") .append(IStartingPositions.START_LOCATION_NAMES[player.getStartingPos()]); @@ -265,7 +267,7 @@ private StringBuilder getPlayerDescription(Player player) { } int so = player.getStartOffset(); int sw = player.getStartWidth(); - if ((so != 0) || (sw != 3)) { + if ((so != 0) || !(sw == 3 || (sw == 1 && Game.rulesManager.getRulesGame().isWalkOnDeployment()))) { result.append(", ").append(so); result.append(", ").append(sw); } diff --git a/megamek/src/megamek/common/Player.java b/megamek/src/megamek/common/Player.java index fe7da6f3773..bf7d44a6f37 100644 --- a/megamek/src/megamek/common/Player.java +++ b/megamek/src/megamek/common/Player.java @@ -147,7 +147,8 @@ public final class Player extends TurnOrdered { //endregion Variable Declarations //region Constructors - public Player(int id, String name) { + public Player(int id, + String name) { this.name = name; this.id = id; } @@ -194,15 +195,16 @@ public boolean hasMinefields() { } return hasMinefields || - (numFortifiedHexes > 0) || - !getGroundObjectsToPlace().isEmpty(); + (numFortifiedHexes > 0) || + !getGroundObjectsToPlace().isEmpty(); } /** - * Given a minefield type from one of the TYPE_[MINEFIELDTYPE] constants in Minefield.java - * and a count (preferably more than 0), set the count of that type of mine for this player. + * Given a minefield type from one of the TYPE_[MINEFIELDTYPE] constants in Minefield.java and a count (preferably + * more than 0), set the count of that type of mine for this player. */ - public void setMinefieldCount(int minefieldType, int count) { + public void setMinefieldCount(int minefieldType, + int count) { minefieldCounts[minefieldType] = count; } @@ -231,8 +233,8 @@ public void setNbrMFEMP(int nbrMF) { } /** - * Given a minefield type from one of the TYPE_[MINEFIELDTYPE] constants in Minefield.java - * returns how many mines of that type this player has + * Given a minefield type from one of the TYPE_[MINEFIELDTYPE] constants in Minefield.java returns how many mines of + * that type this player has */ public int getMinefieldCount(int minefieldType) { return minefieldCounts[minefieldType]; @@ -264,7 +266,7 @@ public int getNbrMFEMP() { /** * @return the number of fortified hexes this player may place during the minefield deployment phase - * (Trench/Fieldworks Engineers, TO:AUE p.153) + * (Trench/Fieldworks Engineers, TO:AUE p.153) */ public int getNbrFortifiedHexes() { return numFortifiedHexes; @@ -352,12 +354,16 @@ public void setBot(boolean bot) { this.bot = bot; } - /** @return true if this player may become a Game Master. Any human may be a GM */ + /** + * @return true if this player may become a Game Master. Any human may be a GM + */ public boolean isGameMasterPermitted() { return !bot; } - /** @return true if {@link #gameMaster} flag is true and {@link #isGameMasterPermitted()} */ + /** + * @return true if {@link #gameMaster} flag is true and {@link #isGameMasterPermitted()} + */ public boolean isGameMaster() { return (isGameMasterPermitted() && gameMaster); } @@ -379,7 +385,9 @@ public void setGameMaster(boolean gameMaster) { this.gameMaster = gameMaster; } - /** @return true if {@link #observer} flag is true and not in VICTORY phase */ + /** + * @return true if {@link #observer} flag is true and not in VICTORY phase + */ public boolean isObserver() { if ((game != null) && game.getPhase().isVictory()) { return false; @@ -389,7 +397,6 @@ public boolean isObserver() { /** * @return true if this Player is not considered an observer. - * * @see #isObserver() */ public boolean isNotObserver() { @@ -423,7 +430,7 @@ public void setArtilleryRevealAll(boolean artilleryRevealAll) { /** * @return {@code true} if the server should include enemy artillery attacks in this player's artillery packet (the - * Rounds in the Air testing reveal); {@code false} for normal team-only (double-blind) behavior + * Rounds in the Air testing reveal); {@code false} for normal team-only (double-blind) behavior */ public boolean isArtilleryRevealAll() { return artilleryRevealAll; @@ -447,7 +454,9 @@ public boolean isSeeAllPermitted() { return gameMaster || observer; } - /** set the {@link #observer} flag. Observers have no units add no team */ + /** + * set the {@link #observer} flag. Observers have no units add no team + */ public void setObserver(boolean observer) { this.observer = observer; } @@ -506,7 +515,7 @@ public PlayerColour getColour() { * to say what the player looks like. Anything drawing something in a player's colour wants this. * * @return The colour of this player's colour camouflage, or the plain colour field when the camouflage - * is an image rather than a colour + * is an image rather than a colour */ public PlayerColour getDisplayColour() { if ((camouflage != null) && camouflage.isColourCamouflage()) { @@ -668,18 +677,17 @@ public void changeInitialEntityCount(final int initialEntityCountChange) { */ public int getBV() { return List.copyOf(game.getInGameObjects()) - .stream() - .filter(this::isMyUnit) - .filter(InGameObject::countForStrengthSum) - .mapToInt(InGameObject::getStrength) - .sum(); + .stream() + .filter(this::isMyUnit) + .filter(InGameObject::countForStrengthSum) + .mapToInt(InGameObject::getStrength) + .sum(); } /** * Returns true when the given unit belongs to this Player. * * @param unit The unit - * * @return True when the unit belongs to "me", this Player */ public boolean isMyUnit(InGameObject unit) { @@ -718,7 +726,7 @@ public int getConstantInitBonus() { /** * @return The victory points this player's side starts the game with, as set by a scenario's faction - * definition; 0 unless a scenario set it + * definition; 0 unless a scenario set it */ public int getStartingVictoryPoints() { return startingVictoryPoints; @@ -763,7 +771,7 @@ public int getHQInitBonus() { int bonus = 0; for (InGameObject object : game.getInGameObjects()) { if (object instanceof Entity entity && entity.getOwner().equals(this) - && isActiveForCommandBonus(entity)) { + && isActiveForCommandBonus(entity)) { bonus = Math.max(entity.getHQIniBonus(), bonus); } } @@ -781,7 +789,7 @@ public int getQuirkInitBonus() { int bonus = 0; for (InGameObject object : game.getInGameObjects()) { if (object instanceof Entity entity && entity.getOwner().equals(this) - && isActiveForCommandBonus(entity)) { + && isActiveForCommandBonus(entity)) { bonus = Math.max(bonus, entity.getQuirkIniBonus()); } } @@ -800,7 +808,7 @@ public String getQuirkInitBonusName() { String bestQuirkName = null; for (InGameObject object : game.getInGameObjects()) { if (object instanceof Entity entity && entity.getOwner().equals(this) - && isActiveForCommandBonus(entity)) { + && isActiveForCommandBonus(entity)) { int entityBonus = entity.getQuirkIniBonus(); if (entityBonus > bestBonus) { bestBonus = entityBonus; @@ -868,12 +876,12 @@ public int getOverallCommandBonus() { boolean useCommandInit = game.getOptions().booleanOption(OptionsConstants.RPG_COMMAND_INIT); // entities are owned by this player, active, and not individual pilots ArrayList entities = game.getInGameObjects() - .stream() - .filter(Entity.class::isInstance) - .map(Entity.class::cast) - .filter(entity -> (null != entity.getOwner()) && - entity.getOwner().equals(this)) - .collect(Collectors.toCollection(ArrayList::new)); + .stream() + .filter(Entity.class::isInstance) + .map(Entity.class::cast) + .filter(entity -> (null != entity.getOwner()) && + entity.getOwner().equals(this)) + .collect(Collectors.toCollection(ArrayList::new)); int commandBonus = 0; for (Entity entity : entities) { int bonus = getIndividualCommandBonus(entity, useCommandInit); @@ -892,7 +900,8 @@ public int getOverallCommandBonus() { * @param useCommandInit boolean based on game options * */ - public int getIndividualCommandBonus(Entity entity, boolean useCommandInit) { + public int getIndividualCommandBonus(Entity entity, + boolean useCommandInit) { int bonus = 0; // Only consider this during normal rounds when unit is deployed on board, or about to deploy this round. if (isActiveForCommandBonus(entity)) { @@ -939,7 +948,7 @@ public int getTCPInitBonus() { continue; } boolean eligibleForBonus = (entity.isDeployed() && !entity.isOffBoard()) || - (entity.getDeployRound() == (game.getCurrentRound() + 1)); + (entity.getDeployRound() == (game.getCurrentRound() + 1)); if (!eligibleForBonus) { LOGGER.debug("TCP: {} skipped - not deployed or deploying next round", entity.getDisplayName()); continue; @@ -950,7 +959,7 @@ public int getTCPInitBonus() { continue; } if (!entity.hasAbility(OptionsConstants.MD_VDNI) - && !entity.hasAbility(OptionsConstants.MD_BVDNI)) { + && !entity.hasAbility(OptionsConstants.MD_BVDNI)) { LOGGER.debug("TCP: {} skipped - no VDNI/BVDNI", entity.getDisplayName()); continue; } @@ -983,7 +992,7 @@ public int getTCPInitBonus() { } LOGGER.debug("TCP: {} qualifies with bonus {} (deployed={}, deployRound={})", - entity.getDisplayName(), bonus, entity.isDeployed(), entity.getDeployRound()); + entity.getDisplayName(), bonus, entity.isDeployed(), entity.getDeployRound()); bestBonus = Math.max(bestBonus, bonus); } LOGGER.debug("TCP: Final TCP bonus for player {}: {}", name, bestBonus); @@ -1030,7 +1039,6 @@ private boolean isEntityECMAffected(Entity entity) { * crew, not captured, not an ejected pilot, and either deployed on-board or deploying next round. * * @param entity the entity to check - * * @return true if the entity can provide command bonuses */ private boolean isActiveForCommandBonus(Entity entity) { @@ -1048,12 +1056,12 @@ public String getColorForPlayer() { public String getColoredPlayerNameWithTeam() { return "" + - getName() + - " (" + - getTeamName() + - ")"; + getColour().getHexString(0x00F0F0F0) + + "'>" + + getName() + + " (" + + getTeamName() + + ")"; } /** @@ -1145,7 +1153,7 @@ public Player copy() { /** * @return The area of the board this player's units are allowed to flee from; An empty area as return value means - * they may not flee at all. + * they may not flee at all. */ public HexArea getFleeZone() { return fleeArea; @@ -1156,7 +1164,6 @@ public HexArea getFleeZone() { * flee. * * @param fleeArea The new flee area. - * * @see megamek.common.hexArea.BorderHexArea */ public void setFleeZone(HexArea fleeArea) { diff --git a/megamek/src/megamek/common/board/Board.java b/megamek/src/megamek/common/board/Board.java index 8dc0f8fcad6..848090a104b 100644 --- a/megamek/src/megamek/common/board/Board.java +++ b/megamek/src/megamek/common/board/Board.java @@ -71,6 +71,7 @@ import megamek.logging.MMLogger; public class Board implements Serializable { + @Serial private static final long serialVersionUID = -5744058872091016636L; private static final MMLogger logger = MMLogger.create(Board.class); @@ -115,29 +116,33 @@ public class Board implements Serializable { * current year when saving. */ public static final String LICENSE_HEADER = """ - # MegaMek Data (C) %s by The MegaMek Team is licensed under CC BY-NC-SA 4.0. - # To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ - # - # NOTICE: The MegaMek organization is a non-profit group of volunteers - # creating free software for the BattleTech community. - # - # MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks - # of The Topps Company, Inc. All Rights Reserved. - # - # Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of - # InMediaRes Productions, LLC. - # - # MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under - # Microsoft's "Game Content Usage Rules" - # and it is not endorsed by or - # affiliated with Microsoft. - """; - - /** Regex pattern to extract the copyright year(s) from board file headers. */ + # MegaMek Data (C) %s by The MegaMek Team is licensed under CC BY-NC-SA 4.0. + # To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ + # + # NOTICE: The MegaMek organization is a non-profit group of volunteers + # creating free software for the BattleTech community. + # + # MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks + # of The Topps Company, Inc. All Rights Reserved. + # + # Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of + # InMediaRes Productions, LLC. + # + # MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under + # Microsoft's "Game Content Usage Rules" + # and it is not endorsed by or + # affiliated with Microsoft. + """; + + /** + * Regex pattern to extract the copyright year(s) from board file headers. + */ private static final Pattern COPYRIGHT_YEAR_PATTERN = Pattern.compile( - "#\\s*MegaMek Data \\(C\\)\\s*(\\d{4})(?:-(\\d{4}))?"); + "#\\s*MegaMek Data \\(C\\)\\s*(\\d{4})(?:-(\\d{4}))?"); - /** The original copyright year from the loaded board file, or -1 if none found. */ + /** + * The original copyright year from the loaded board file, or -1 if none found. + */ private int originalCopyrightYear = -1; // The min and max elevation values for this board. @@ -187,7 +192,9 @@ public class Board implements Serializable { */ private final Map> annotations = new HashMap<>(); - /** Tags associated with this board to facilitate searching for it. */ + /** + * Tags associated with this board to facilitate searching for it. + */ private final Set tags = new HashSet<>(); private int boardId = 0; @@ -239,7 +246,8 @@ public Board() { * @param width the width dimension. * @param height the height dimension. */ - public Board(int width, int height) { + public Board(int width, + int height) { this.width = width; this.height = height; data = new Hex[width * height]; @@ -253,7 +261,9 @@ public Board(int width, int height) { * @param height the height dimension * @param data the Hexes of the new board */ - public Board(int width, int height, Hex... data) { + public Board(int width, + int height, + Hex... data) { this.width = width; this.height = height; this.data = Arrays.copyOf(data, data.length); @@ -264,10 +274,10 @@ public Board(int width, int height, Hex... data) { * * @param width the width of the board * @param height the height of the board - * * @return the new board, ready to be used */ - public static Board getSkyBoard(int width, int height) { + public static Board getSkyBoard(int width, + int height) { Hex[] data = new Hex[width * height]; int index = 0; for (int h = 0; h < height; h++) { @@ -285,10 +295,10 @@ public static Board getSkyBoard(int width, int height) { * * @param width the width of the board * @param height the height of the board - * * @return the new board, ready to be used */ - public static Board getSpaceBoard(int width, int height) { + public static Board getSpaceBoard(int width, + int height) { Hex[] data = new Hex[width * height]; int index = 0; for (int h = 0; h < height; h++) { @@ -320,6 +330,36 @@ public Coords getCenter() { return new Coords(getWidth() / 2, getHeight() / 2); } + /** + * for a given deployment position, return the center point of it + * + * @param deploymentPosition Deployment zone + * @return a coordinate + */ + public Coords getDeploymentCenter(int deploymentPosition) { + switch (deploymentPosition) { + case Board.START_W: + return new Coords(0, getHeight() / 2); + case Board.START_SW: + return new Coords(0, getHeight() - 1); + case Board.START_SE: + return new Coords(getWidth() - 1, getHeight() - 1); + case Board.START_E: + return new Coords(getWidth() - 1, getHeight() / 2); + case Board.START_NW: + return new Coords(0, 0); + case Board.START_NE: + return new Coords(getWidth() - 1, 0); + case Board.START_N: + return new Coords(getWidth() / 2, 0); + case Board.START_S: + return new Coords(getWidth() / 2, getHeight() - 1); + default: + // Any, Center, custom zones + return getCenter(); + } + } + /** * Creates a new data set for the board, with the specified dimensions and data; notifies listeners that a new data * set has been created. @@ -329,8 +369,10 @@ public Coords getCenter() { * @param data new hex data appropriate for the board. * @param errors A buffer for storing error messages, if any. This is allowed to be null. */ - public void newData(final int width, final int height, final Hex[] data, - final @Nullable List errors) { + public void newData(final int width, + final int height, + final Hex[] data, + final @Nullable List errors) { this.width = width; this.height = height; this.data = data; @@ -344,20 +386,20 @@ public void newData(final int width, final int height, final Hex[] data, * * @param x the x Coords. * @param y the y Coords. - * * @return the Hex, if this Board contains the (x, y) location; null otherwise. */ - public @Nullable Hex getHex(final int x, final int y) { + public @Nullable Hex getHex(final int x, + final int y) { return contains(x, y) ? data[(y * width) + x] : null; } /** * @param c starting coordinates * @param dir direction - * * @return the hex in the specified direction from the specified starting coordinates. */ - public Hex getHexInDir(Coords c, int dir) { + public Hex getHexInDir(Coords c, + int dir) { return getHex(c.xInDir(dir), c.yInDir(dir)); } @@ -368,10 +410,11 @@ public Hex getHexInDir(Coords c, int dir) { * @param x starting x coordinate * @param y starting y coordinate * @param dir direction - * * @return the hex in the specified direction from the specified starting coordinates. */ - public Hex getHexInDir(int x, int y, int dir) { + public Hex getHexInDir(int x, + int y, + int dir) { return getHex(Coords.xInDir(x, y, dir), Coords.yInDir(x, y, dir)); } @@ -399,7 +442,7 @@ protected void initializeAll(final @Nullable List errors) { // Nope. Try to create an object for the new building. try { IBuilding bldg = new BuildingTerrain(coords, this, Terrains.BUILDING, - BasementType.getType(curHex.terrainLevel(Terrains.BLDG_BASEMENT_TYPE))); + BasementType.getType(curHex.terrainLevel(Terrains.BLDG_BASEMENT_TYPE))); addBuildingToBoard(bldg); } catch (IllegalArgumentException exception) { // Log the error and remove the building from the board. @@ -471,7 +514,8 @@ protected void initializeAll(final @Nullable List errors) { /** * Initialize a hex and the hexes around it */ - public void initializeAround(int x, int y) { + public void initializeAround(int x, + int y) { initializeHex(x, y); for (int i = 0; i < 6; i++) { initializeInDir(x, y, i); @@ -481,7 +525,9 @@ public void initializeAround(int x, int y) { /** * Initializes a hex in a specific direction from an origin hex */ - private void initializeInDir(int x, int y, int dir) { + private void initializeInDir(int x, + int y, + int dir) { initializeHex(Coords.xInDir(x, y, dir), Coords.yInDir(x, y, dir)); } @@ -489,11 +535,14 @@ private void initializeInDir(int x, int y, int dir) { * Initializes a hex in its surroundings. Currently, sets the connects parameter appropriately to the surrounding * hexes. If a surrounding hex is off the board, it checks the hex opposite the missing hex. */ - public void initializeHex(int x, int y) { + public void initializeHex(int x, + int y) { initializeHex(x, y, true); } - private void initializeHex(int x, int y, boolean event) { + private void initializeHex(int x, + int y, + boolean event) { Hex hex = getHex(x, y); if (hex == null) { @@ -520,14 +569,17 @@ private void initializeHex(int x, int y, boolean event) { } } - /** Adds the FOLIAGE_ELEV terrain when none is present. */ - private void initializeFoliageElev(int x, int y) { + /** + * Adds the FOLIAGE_ELEV terrain when none is present. + */ + private void initializeFoliageElev(int x, + int y) { Hex hex = getHex(x, y); // If the foliage elevation is present or the hex doesn't even have foliage, // nothing needs to be done if (hex.containsTerrain(Terrains.FOLIAGE_ELEV) || - (!hex.containsTerrain(Terrains.WOODS) && !hex.containsTerrain(Terrains.JUNGLE))) { + (!hex.containsTerrain(Terrains.WOODS) && !hex.containsTerrain(Terrains.JUNGLE))) { return; } @@ -547,7 +599,8 @@ private void initializeFoliageElev(int x, int y) { * @param x The hex X-Coordinate. * @param y The hex Y-Coordinate. */ - private void initializeAutomaticTerrain(int x, int y) { + private void initializeAutomaticTerrain(int x, + int y) { Hex hex = getHex(x, y); int origCliffTopExits = 0; int correctedCliffTopExits = 0; @@ -560,7 +613,7 @@ private void initializeAutomaticTerrain(int x, int y) { // Get the currently set cliff-tops for correction. When exits // are not specified, the cliff-tops are removed. if (hex.containsTerrain(Terrains.CLIFF_TOP) - && hex.getTerrain(Terrains.CLIFF_TOP).hasExitsSpecified()) { + && hex.getTerrain(Terrains.CLIFF_TOP).hasExitsSpecified()) { origCliffTopExits = hex.getTerrain(Terrains.CLIFF_TOP).getExits(); } @@ -585,23 +638,23 @@ private void initializeAutomaticTerrain(int x, int y) { // Should there be an incline top? if (((levelDiff == 1) || (levelDiff == 2)) - && !cliffTopExitInThisDir - && !inWater - && !towardsWater) { + && !cliffTopExitInThisDir + && !inWater + && !towardsWater) { inclineTopExits += (1 << i); } if (towardsWater - && !inWater - && !cliffTopExitInThisDir - && ((levelDiffToWaterSurface == 1) || levelDiffToWaterSurface == 2)) { + && !inWater + && !cliffTopExitInThisDir + && ((levelDiffToWaterSurface == 1) || levelDiffToWaterSurface == 2)) { inclineTopExits += (1 << i); } // Should there be a high level cliff top? if (levelDiff > 2 - && !inWater - && (!towardsWater || levelDiffToWaterSurface > 2)) { + && !inWater + && (!towardsWater || levelDiffToWaterSurface > 2)) { highInclineTopExits += (1 << i); } @@ -639,7 +692,9 @@ private void initializeAutomaticTerrain(int x, int y) { /** * Adds automatically handled terrain such as inclines when the given exits value is not 0, otherwise removes it. */ - private void addOrRemoveAutoTerrain(Hex hex, int terrainType, int exits) { + private void addOrRemoveAutoTerrain(Hex hex, + int terrainType, + int exits) { if (exits > 0) { hex.addTerrain(new Terrain(terrainType, 1, true, exits)); } else { @@ -665,10 +720,10 @@ public void initializeAllAutomaticTerrain() { * * @param x the x Coords. * @param y the y Coords. - * * @return true if the board contains the specified coords */ - public boolean contains(int x, int y) { + public boolean contains(int x, + int y) { return (x >= 0) && (y >= 0) && (x < width) && (y < height); } @@ -676,7 +731,6 @@ public boolean contains(int x, int y) { * Determines whether this Board "contains" the specified Coords. * * @param coords the Coords. - * * @return true if the board contains the specified coords */ public boolean contains(@Nullable Coords coords) { @@ -688,7 +742,6 @@ public boolean contains(@Nullable Coords coords) { * coords of the location are within the borders of the board. * * @param location the location to test - * * @return true if the board contains the specified location */ public boolean contains(@Nullable BoardLocation location) { @@ -699,7 +752,6 @@ public boolean contains(@Nullable BoardLocation location) { * Returns the Hex at the given Coords, both of which may be null. * * @param coords the Coords to look for the Hex - * * @return the Hex at the specified Coords, or null if there is not a hex there */ public @Nullable Hex getHex(final @Nullable Coords coords) { @@ -711,7 +763,6 @@ public boolean contains(@Nullable BoardLocation location) { * Coords collection. If the given Coords collection is null, the returned list will be empty. * * @param coords the Coords to query - * * @return the Hexes at the specified Coords */ public List getHexes(final @Nullable Collection coords) { @@ -731,7 +782,9 @@ public List getHexes(final @Nullable Collection coords) { * @param y the y Coords. * @param hex the hex to be set into position. */ - public void setHex(int x, int y, Hex hex) { + public void setHex(int x, + int y, + Hex hex) { Map changedHex = new HashMap<>(); changedHex.put(BoardLocation.of(new Coords(x, y), boardId), hex); setHexes(changedHex); @@ -785,7 +838,8 @@ public void setHexes(Map changedHexes) { * @param c the Coords. * @param hex the hex to be set into position. */ - public void setHex(Coords c, Hex hex) { + public void setHex(Coords c, + Hex hex) { setHex(c.getX(), c.getY(), hex); if (hex.getLevel() < minElevation && minElevation != UNDEFINED_MIN_ELEV) { minElevation = hex.getLevel(); @@ -800,10 +854,10 @@ public void setHex(Coords c, Hex hex) { * * @param filepath The path to the board file. * @param size The dimensions of the board to test. - * * @return {@code true} if the dimensions match. */ - public static boolean boardIsSize(final File filepath, final BoardDimensions size) { + public static boolean boardIsSize(final File filepath, + final BoardDimensions size) { int boardX = 0; int boardY = 0; try (FileReader fr = new FileReader(filepath); BufferedReader br = new BufferedReader(fr)) { @@ -815,7 +869,7 @@ public static boolean boardIsSize(final File filepath, final BoardDimensions siz streamTokenizer.wordChars('_', '_'); while (streamTokenizer.nextToken() != StreamTokenizer.TT_EOF) { if ((streamTokenizer.ttype == StreamTokenizer.TT_WORD) - && streamTokenizer.sval.equalsIgnoreCase("size")) { + && streamTokenizer.sval.equalsIgnoreCase("size")) { streamTokenizer.nextToken(); boardX = (int) streamTokenizer.nval; streamTokenizer.nextToken(); @@ -835,14 +889,13 @@ public static boolean boardIsSize(final File filepath, final BoardDimensions siz * Inspect specified board file and return its dimensions. * * @param filepath The path to the board file. - * * @return A {@link BoardDimensions} object containing the dimension. */ public static BoardDimensions getSize(final File filepath) { int boardX = 0; int boardY = 0; try (FileReader fileReader = new FileReader(filepath); - BufferedReader bufferedReader = new BufferedReader(fileReader)) { + BufferedReader bufferedReader = new BufferedReader(fileReader)) { // read board, looking for "size" StreamTokenizer streamTokenizer = new StreamTokenizer(bufferedReader); streamTokenizer.eolIsSignificant(true); @@ -851,7 +904,7 @@ public static BoardDimensions getSize(final File filepath) { streamTokenizer.wordChars('_', '_'); while (streamTokenizer.nextToken() != StreamTokenizer.TT_EOF) { if ((streamTokenizer.ttype == StreamTokenizer.TT_WORD) - && streamTokenizer.sval.equalsIgnoreCase("size")) { + && streamTokenizer.sval.equalsIgnoreCase("size")) { streamTokenizer.nextToken(); boardX = (int) streamTokenizer.nval; streamTokenizer.nextToken(); @@ -865,7 +918,9 @@ public static BoardDimensions getSize(final File filepath) { return new BoardDimensions(boardX, boardY); } - /** Inspects the given board file and returns a set of its tags. */ + /** + * Inspects the given board file and returns a set of its tags. + */ public static Set getTags(final File filepath) { var result = new HashSet(); try (FileReader fr = new FileReader(filepath); BufferedReader br = new BufferedReader(fr)) { @@ -911,29 +966,48 @@ public static boolean isValid(String board) { /** * Can the given player deploy at these coordinates? */ - public boolean isLegalDeployment(Coords c, Player p) { + public boolean isLegalDeployment(Coords c, + Player p) { return isLegalDeployment(c, p.getStartingPos(), p.getStartWidth(), p.getStartOffset(), p.getStartingAnyNWx(), - p.getStartingAnyNWy(), p.getStartingAnySEx(), p.getStartingAnySEy()); + p.getStartingAnyNWy(), p.getStartingAnySEx(), p.getStartingAnySEy()); } /** * Can the given entity be deployed at these coordinates */ - public boolean isLegalDeployment(Coords c, Entity e) { + public boolean isLegalDeployment(Coords c, + Entity e) { if (e == null) { return false; } - - return isLegalDeployment(c, e.getStartingPos(), e.getStartingWidth(), e.getStartingOffset(), - e.getStartingAnyNWx(), e.getStartingAnyNWy(), e.getStartingAnySEx(), e.getStartingAnySEy()); + int startingWidth = e.getGame().rulesManager.getRulesGame() + .getDeploymentWidth(e.getOwner(), + e.getStartingPos(), e.getStartingWidth()); + if (e.isDropShip()) { + startingWidth = e.getStartingWidth(); + } + return isLegalDeployment(c, + e.getStartingPos(), + startingWidth, + 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, int startingAnyNWx, - int startingAnyNWy, int startingAnySEx, int startingAnySEy) { + 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; } @@ -943,46 +1017,46 @@ public boolean isLegalDeployment(Coords c, int zoneType, int startingWidth, int return switch (zoneType) { case START_ANY -> (((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))); + && ((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 -> ((c.getX() < (startingOffset + startingWidth)) && (c.getX() >= startingOffset) && (c.getY() - >= startingOffset) - && (c.getY() < (height / 2))) - || ((c.getY() < (startingOffset + startingWidth)) && (c.getY() >= startingOffset) && (c.getX() - >= startingOffset) - && (c.getX() < (width / 2))); + >= startingOffset) + && (c.getY() < (height / 2))) + || ((c.getY() < (startingOffset + startingWidth)) && (c.getY() >= startingOffset) && (c.getX() + >= startingOffset) + && (c.getX() < (width / 2))); case START_N -> (c.getY() < (startingOffset + startingWidth)) && (c.getY() >= startingOffset); case START_NE -> ((c.getX() >= (maxX - startingWidth)) && (c.getX() < maxX) && (c.getY() >= startingOffset) - && (c.getY() < (height / 2))) - || ((c.getY() < (startingOffset + startingWidth)) && (c.getY() >= startingOffset) && (c.getX() - < maxX) - && (c.getX() > (width / 2))); + && (c.getY() < (height / 2))) + || ((c.getY() < (startingOffset + startingWidth)) && (c.getY() >= startingOffset) && (c.getX() + < maxX) + && (c.getX() > (width / 2))); case START_E -> (c.getX() >= (maxX - startingWidth)) && (c.getX() < maxX); case START_SE -> ((c.getX() >= (maxX - startingWidth)) && (c.getX() < maxX) && (c.getY() < maxy) - && (c.getY() > (height / 2))) - || ((c.getY() >= (maxy - startingWidth)) && (c.getY() < maxy) && (c.getX() < maxX) - && (c.getX() > (width / 2))); + && (c.getY() > (height / 2))) + || ((c.getY() >= (maxy - startingWidth)) && (c.getY() < maxy) && (c.getX() < maxX) + && (c.getX() > (width / 2))); case START_S -> (c.getY() >= (maxy - startingWidth)) && (c.getY() < maxy); case START_SW -> ((c.getX() < (startingOffset + startingWidth)) && (c.getX() >= startingOffset) && (c.getY() - < maxy) - && (c.getY() > (height / 2))) - || ((c.getY() >= (maxy - startingWidth)) && (c.getY() < maxy) && (c.getX() >= startingOffset) - && (c.getX() < (width / 2))); + < maxy) + && (c.getY() > (height / 2))) + || ((c.getY() >= (maxy - startingWidth)) && (c.getY() < maxy) && (c.getX() >= startingOffset) + && (c.getX() < (width / 2))); case START_W -> (c.getX() < (startingOffset + startingWidth)) && (c.getX() >= startingOffset); case START_EDGE -> - ((c.getX() < (startingOffset + startingWidth)) && (c.getX() >= startingOffset) && (c.getY() - >= startingOffset) && (c.getY() < maxy)) - || ((c.getY() < (startingOffset + startingWidth)) && (c.getY() >= startingOffset) && (c.getX() - >= startingOffset) + ((c.getX() < (startingOffset + startingWidth)) && (c.getX() >= startingOffset) && (c.getY() + >= startingOffset) && (c.getY() < maxy)) + || ((c.getY() < (startingOffset + startingWidth)) && (c.getY() >= startingOffset) && (c.getX() + >= startingOffset) && (c.getX() < maxX)) - || ((c.getX() >= (maxX - startingWidth)) && (c.getX() < maxX) && (c.getY() >= startingOffset) + || ((c.getX() >= (maxX - startingWidth)) && (c.getX() < maxX) && (c.getY() >= startingOffset) && (c.getY() < maxy)) - || ((c.getY() >= (maxy - startingWidth)) && (c.getY() < maxy) && (c.getX() >= startingOffset) + || ((c.getY() >= (maxy - startingWidth)) && (c.getY() < maxy) && (c.getX() >= startingOffset) && (c.getX() < maxX)); case START_CENTER -> - (c.getX() >= (width / 3)) && (c.getX() <= ((2 * width) / 3)) && (c.getY() >= (height / 3)) - && (c.getY() <= ((2 * height) / 3)); + (c.getX() >= (width / 3)) && (c.getX() <= ((2 * width) / 3)) && (c.getY() >= (height / 3)) + && (c.getY() <= ((2 * height) / 3)); default -> { Set customDeploymentZone = getCustomDeploymentZone(decodeCustomDeploymentZoneID(zoneType)); yield customDeploymentZone.contains(c); @@ -995,7 +1069,6 @@ public boolean isLegalDeployment(Coords c, int zoneType, int startingWidth, int * East) * * @param cardinalEdge The edge to return the opposite of - * * @return Constant representing the opposite edge */ public int getOppositeEdge(int cardinalEdge) { @@ -1043,7 +1116,8 @@ public void load(InputStream is) { load(is, null, false); } - public void load(String boardString, @Nullable List errors) { + public void load(String boardString, + @Nullable List errors) { try (InputStream is = new ByteArrayInputStream(boardString.getBytes(StandardCharsets.UTF_8))) { load(is, errors, false); } catch (IOException ex) { @@ -1052,7 +1126,9 @@ public void load(String boardString, @Nullable List errors) { } } - public void load(InputStream is, @Nullable List errors, boolean continueLoadOnError) { + public void load(InputStream is, + @Nullable List errors, + boolean continueLoadOnError) { int nw = 0, nh = 0, di = 0; Hex[] nd = new Hex[0]; int index = 0; @@ -1079,7 +1155,7 @@ public void load(InputStream is, @Nullable List errors, boolean continue } try (InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))); - BufferedReader br = new BufferedReader(isr)) { + BufferedReader br = new BufferedReader(isr)) { StreamTokenizer st = new StreamTokenizer(br); st.eolIsSignificant(true); st.commentChar('#'); @@ -1091,7 +1167,7 @@ public void load(InputStream is, @Nullable List errors, boolean continue String[] args = { "0", "0" }; int i = 0; while ((st.nextToken() == StreamTokenizer.TT_WORD) || (st.ttype == '"') - || (st.ttype == StreamTokenizer.TT_NUMBER)) { + || (st.ttype == StreamTokenizer.TT_NUMBER)) { args[i++] = st.ttype == StreamTokenizer.TT_NUMBER ? (int) st.nval + "" : st.sval; } nw = Integer.parseInt(args[0]); @@ -1102,7 +1178,7 @@ public void load(InputStream is, @Nullable List errors, boolean continue String[] args = { "", "" }; int i = 0; while ((st.nextToken() == StreamTokenizer.TT_WORD) || (st.ttype == '"') - || (st.ttype == StreamTokenizer.TT_NUMBER)) { + || (st.ttype == StreamTokenizer.TT_NUMBER)) { args[i++] = st.ttype == StreamTokenizer.TT_NUMBER ? (int) st.nval + "" : st.sval; } // Only expect certain options. @@ -1114,7 +1190,7 @@ public void load(InputStream is, @Nullable List errors, boolean continue String[] args = { "", "0", "", "" }; int i = 0; while ((st.nextToken() == StreamTokenizer.TT_WORD) || (st.ttype == '"') - || (st.ttype == StreamTokenizer.TT_NUMBER)) { + || (st.ttype == StreamTokenizer.TT_NUMBER)) { args[i++] = st.ttype == StreamTokenizer.TT_NUMBER ? (int) st.nval + "" : st.sval; } int elevation = Integer.parseInt(args[1]); @@ -1191,7 +1267,10 @@ public boolean isValid(@Nullable List errors) { return isValid(data, width, height, errors); } - private boolean isValid(Hex[] data, int width, int height, @Nullable List errors) { + private boolean isValid(Hex[] data, + int width, + int height, + @Nullable List errors) { List newErrors = new ArrayList<>(); // Search for black-listed hexes for (int x = 0; x < width; x++) { @@ -1214,22 +1293,22 @@ private boolean isValid(Hex[] data, int width, int height, @Nullable ListCoords of the hit. * @param round the kind of round that hit the hex. * @param hits the int number of rounds that hit - * * @throws IllegalArgumentException if the hits number is negative */ - public void addInfernoTo(Coords coords, Inferno round, int hits) { + public void addInfernoTo(Coords coords, + Inferno round, + int hits) { // Make sure the # of hits is valid. if (hits < 0) { throw new IllegalArgumentException("Board can't track negative hits. "); @@ -1379,8 +1460,8 @@ public void removeInfernoFrom(Coords coords) { } /** - * Record that a fire at the given coordinates was started by a fuel-fed flamer (TO:AuE p.153). Such fires are harder - * for firefighting engineers to extinguish. + * Record that a fire at the given coordinates was started by a fuel-fed flamer (TO:AuE p.153). Such fires are + * harder for firefighting engineers to extinguish. * * @param coords the Coords of the flamer-started fire */ @@ -1406,7 +1487,6 @@ public void removeFlamerStartedFire(Coords coords) { /** * @param coords the Coords being checked - * * @return true if the fire at these coordinates was started by a fuel-fed flamer */ public boolean isFlamerStartedFire(Coords coords) { @@ -1433,9 +1513,8 @@ public void clearBombIcons() { * Determine if the given coordinates has a burning inferno. * * @param coords - the Coords being checked. - * * @return true if those coordinates have a burning inferno - * round. false if no inferno has hit those coordinates or if it has burned out. + * round. false if no inferno has hit those coordinates or if it has burned out. */ public boolean isInfernoBurning(Coords coords) { boolean result = false; @@ -1490,9 +1569,8 @@ public Vector getBuildingsVector() { * Get the building at the given coordinates. * * @param coords the Coords being examined. - * * @return a Building object, if there is one at the given coordinates, otherwise a - * null will be returned. + * null will be returned. */ public @Nullable IBuilding getBuildingAt(Coords coords) { return bldgByCoords.get(coords); @@ -1504,9 +1582,8 @@ public Vector getBuildingsVector() { * * @param other - a Building object which may or may not be represented on this board. This value may * be null . - * * @return The local Building object if we can find a match. If the other building is not on this - * board, a null is returned instead. + * board, a null is returned instead. */ private IBuilding getLocalBuilding(IBuilding other) { return buildings.stream().filter(building -> building.equals(other)).findFirst().orElse(null); @@ -1656,7 +1733,7 @@ public void updateBuilding(IBuilding receivedBuilding) { localBuilding.setPhaseCF(receivedBuilding.getPhaseCF(coords), coords); localBuilding.setArmor(receivedBuilding.getArmor(coords), coords); localBuilding.setBasement(coords, - BasementType.getType(getHex(coords).terrainLevel(Terrains.BLDG_BASEMENT_TYPE))); + BasementType.getType(getHex(coords).terrainLevel(Terrains.BLDG_BASEMENT_TYPE))); localBuilding.setBasementCollapsed(coords, receivedBuilding.getBasementCollapsed(coords)); localBuilding.setDemolitionCharges(receivedBuilding.getDemolitionCharges()); } @@ -1666,8 +1743,8 @@ public void updateBuilding(IBuilding receivedBuilding) { * Get the current value of the "road auto-exit" option. * * @return true if roads should automatically exit onto all - * adjacent pavement hexes. - * false otherwise. + * adjacent pavement hexes. + * false otherwise. */ public boolean getRoadsAutoExit() { return roadsAutoExit; @@ -1805,7 +1882,7 @@ public void setRandomBasementsOff() { /** * @return Special events that should be marked on hexes, such as artillery fire as well as notes players can leave - * manually on hexes. Always returns at least an empty list, never null. + * manually on hexes. Always returns at least an empty list, never null. */ public Collection getSpecialHexDisplay(Coords coords) { return specialHexes.getOrDefault(coords, Collections.emptyList()); @@ -1819,7 +1896,9 @@ public Collection getSpecialHexDisplay(Coords coords) { * @param shd The SpecialHexDisplay to add * @param fireEvent When true, a BoardEvent is fired for the affected coords */ - public void addSpecialHexDisplay(Coords coords, SpecialHexDisplay shd, boolean fireEvent) { + public void addSpecialHexDisplay(Coords coords, + SpecialHexDisplay shd, + boolean fireEvent) { Collection col; if (!specialHexes.containsKey(coords)) { col = new LinkedList<>(); @@ -1845,7 +1924,8 @@ public void addSpecialHexDisplay(Coords coords, SpecialHexDisplay shd, boolean f * @param coords The position of the SHD on this board * @param shd The SpecialHexDisplay to add */ - public void addSpecialHexDisplay(Coords coords, SpecialHexDisplay shd) { + public void addSpecialHexDisplay(Coords coords, + SpecialHexDisplay shd) { addSpecialHexDisplay(coords, shd, false); } @@ -1855,7 +1935,8 @@ public void addSpecialHexDisplay(Coords coords, SpecialHexDisplay shd) { * @param coords The position of the SHD on this board * @param shd The SpecialHexDisplay to remove */ - public void removeSpecialHexDisplay(Coords coords, SpecialHexDisplay shd) { + public void removeSpecialHexDisplay(Coords coords, + SpecialHexDisplay shd) { removeSpecialHexDisplay(coords, shd, false); } @@ -1865,7 +1946,9 @@ public void removeSpecialHexDisplay(Coords coords, SpecialHexDisplay shd) { * @param coords The position of the SHD on this board * @param shd The SpecialHexDisplay to remove */ - public void removeSpecialHexDisplay(Coords coords, SpecialHexDisplay shd, boolean fireEvent) { + public void removeSpecialHexDisplay(Coords coords, + SpecialHexDisplay shd, + boolean fireEvent) { Collection col = specialHexes.get(coords); if (col != null) { col.remove(shd); @@ -1891,7 +1974,7 @@ public void setSpecialHexDisplayTable(Map> specialHexes = shd; toRedraw.addAll(shd.keySet()); toRedraw.forEach(coords -> - processBoardEvent(new BoardEvent(this, coords, BoardEvent.BOARD_CHANGED_HEX))); + processBoardEvent(new BoardEvent(this, coords, BoardEvent.BOARD_CHANGED_HEX))); //TODO: Add a BoardEvent for a set of coords to avoid many events } @@ -1994,7 +2077,6 @@ public Map> getAnnotations() { * Gets the annotations associated with a hex. * * @param c Coordinates of the hex. - * * @return A collection of annotations for the hex. */ public Collection getAnnotations(Coords c) { @@ -2007,7 +2089,8 @@ public Collection getAnnotations(Coords c) { * @param c Coordinates of the hex to apply the annotations to. * @param a A collection of annotations to assign to the hex. This may be null. */ - public void setAnnotations(Coords c, @Nullable Collection a) { + public void setAnnotations(Coords c, + @Nullable Collection a) { if (null == a || a.isEmpty()) { annotations.remove(c); } else { @@ -2037,10 +2120,11 @@ public void setTheme(final @Nullable String newTheme) { */ public boolean isOnBoardEdge(Coords coords) { return (coords.getX() == 0) || (coords.getY() == 0) || (coords.getX() == (width - 1)) || (coords.getY() == ( - height - 1)); + height - 1)); } - public static Board createEmptyBoard(int width, int height) { + public static Board createEmptyBoard(int width, + int height) { Hex[] hexes = new Hex[width * height]; for (int i = 0; i < width * height; i++) { hexes[i] = new Hex(); @@ -2055,7 +2139,9 @@ public void addTag(String newTag) { tags.add(newTag); } - /** Removes the given tag string from the board's tags list. */ + /** + * Removes the given tag string from the board's tags list. + */ public void removeTag(String tag) { tags.remove(tag); @@ -2068,7 +2154,9 @@ public Set getTags() { return Collections.unmodifiableSet(tags); } - /** @return The name of this map; this is meant to be displayed in the GUI. */ + /** + * @return The name of this map; this is meant to be displayed in the GUI. + */ public String getBoardName() { return mapName; } @@ -2079,7 +2167,7 @@ public void setMapName(String mapName) { /** * @return Given an "exits" value, returns it in a list form. (i.e. exits value of 4 returns {3}, exit value of 5 - * returns {1, 3} + * returns {1, 3} */ public static List exitsAsIntList(int exits) { List results = new ArrayList<>(); @@ -2132,7 +2220,8 @@ private void initializeDeploymentZones() { * Converts a custom deployment zone from the hex area definition to board hexes; also translates the ID. Note that * the deploymentZones field must not be null. */ - private void convertDeploymentZone(int zoneId, HexArea hexArea) { + private void convertDeploymentZone(int zoneId, + HexArea hexArea) { deploymentZones.put(zoneId - NUM_ZONES_X2, hexArea.getCoords(this)); } @@ -2145,7 +2234,8 @@ private void convertDeploymentZone(int zoneId, HexArea hexArea) { * @param zoneId The zone Id * @param hexArea The hexes comprising this deployment zone */ - public void addDeploymentZone(int zoneId, HexArea hexArea) { + public void addDeploymentZone(int zoneId, + HexArea hexArea) { areas.put(zoneId, hexArea); } @@ -2214,7 +2304,9 @@ public void setEnclosingBoard(int enclosingBoardId) { enclosingBoard = enclosingBoardId; } - /** @return The ID of the enclosing board of this board, or -1 if it has no enclosing board. */ + /** + * @return The ID of the enclosing board of this board, or -1 if it has no enclosing board. + */ public int getEnclosingBoardId() { return enclosingBoard; } @@ -2225,10 +2317,10 @@ public int getEnclosingBoardId() { * * @param boardId The board ID to embed * @param coords The location to place the given board - * * @throws IllegalArgumentException When this board does not contain the given coords */ - public void setEmbeddedBoard(int boardId, Coords coords) { + public void setEmbeddedBoard(int boardId, + Coords coords) { if (contains(coords)) { embeddedBoards.put(coords, boardId); } else { @@ -2263,7 +2355,7 @@ public boolean isGround() { /** * @return True if this board is a low altitude (a.k.a. atmospheric) board, either with terrain or without terrain - * ("sky"). + * ("sky"). */ public boolean isLowAltitude() { return boardType.isLowAltitude(); @@ -2278,7 +2370,7 @@ public boolean isSky() { /** * @return True if this board is a space board, either close to a planet with some atmospheric hexes ("high - * altitude") or in deeper space. + * altitude") or in deeper space. */ public boolean isSpace() { return boardType.isSpace(); @@ -2286,7 +2378,7 @@ public boolean isSpace() { /** * @return True if this board is a high altitude board, i.e. a space board close to a planet with some atmospheric - * hexes. + * hexes. */ public boolean isHighAltitude() { return boardType.isHighAltitude(); diff --git a/megamek/src/megamek/common/enums/MoveStepType.java b/megamek/src/megamek/common/enums/MoveStepType.java index 08ab533d9f1..aa7783935a4 100644 --- a/megamek/src/megamek/common/enums/MoveStepType.java +++ b/megamek/src/megamek/common/enums/MoveStepType.java @@ -118,7 +118,8 @@ public enum MoveStepType { CHAFF(false, "Chaff"), PICKUP_CARGO(false, "Pickup Cargo"), DROP_CARGO(false, "Drop Cargo"), - CHANGE_BOARD(true, "Change Board"); + CHANGE_BOARD(true, "Change Board"), + DEPLOY(false, "Deploy"); private final boolean entersNewHex; private final String humanReadableLabel; diff --git a/megamek/src/megamek/common/game/Game.java b/megamek/src/megamek/common/game/Game.java index 579925dfc88..a249b015f46 100644 --- a/megamek/src/megamek/common/game/Game.java +++ b/megamek/src/megamek/common/game/Game.java @@ -120,7 +120,9 @@ * The game class is the root of all data about the game in progress. Both the Client and the Server should have one of * these objects, and it is their job to keep it synced. */ -public final class Game extends AbstractGame implements Serializable, PlanetaryConditionsUsing { +public final class Game extends AbstractGame implements Serializable, + PlanetaryConditionsUsing { + private static final MMLogger logger = MMLogger.create(Game.class); @Serial @@ -199,10 +201,14 @@ public final class Game extends AbstractGame implements Serializable, PlanetaryC private final Vector vibraBombs = new Vector<>(); private final Vector empMines = new Vector<>(); - /** Tracks ongoing woods clearing operations for chainsaws and dual saws. Serialized with game saves. */ + /** + * Tracks ongoing woods clearing operations for chainsaws and dual saws. Serialized with game saves. + */ private WoodsClearingTracker woodsClearingTracker = new WoodsClearingTracker(); - /** Hex locations being cleared by saws, mapped to turns remaining. For board view rendering. */ + /** + * Hex locations being cleared by saws, mapped to turns remaining. For board view rendering. + */ private Map hexesBeingCut = new HashMap<>(); private Vector attacks = new Vector<>(); private Vector offboardArtilleryAttacks = new Vector<>(); @@ -314,7 +320,7 @@ public int getNbrMinefields(Coords coords) { * Get the coordinates of all mined hexes in the game. * * @return an Enumeration of the Coords containing minefields. This will not be - * null. + * null. */ public Enumeration getMinedCoords() { return minefields.keys(); @@ -338,6 +344,13 @@ public WoodsClearingTracker getWoodsClearingTracker() { public void initializeRulesManager(String system) { if (system.equals(OptionsConstants.RULES_TW)) { rulesManager = new TWRulesManager(); + + // Check for walk-on deployment here + if (getOptions().booleanOption(OptionsConstants.BASE_WALK_ON_DEPLOYMENT)) { + rulesManager.getRulesGame().setWalkOnDeployment(true); + } else { + rulesManager.getRulesGame().setWalkOnDeployment(false); + } } else if (system.equals(OptionsConstants.RULES_CORE)) { rulesManager = new CoreRulesManager(); } @@ -463,7 +476,6 @@ public void removeVibrabomb(Minefield mf) { * Checks if the game contains the specified Vibrabomb * * @param mf the Vibrabomb to check - * * @return true if the minefield contains a vibrabomb. */ public boolean containsVibrabomb(Minefield mf) { @@ -486,7 +498,6 @@ public void removeEMPMine(Minefield mf) { * Checks if the game contains the specified EMP mine * * @param mf the EMP mine to check - * * @return true if the minefield contains an EMP mine. */ public boolean containsEMPMine(Minefield mf) { @@ -515,12 +526,18 @@ public void setOptions(final @Nullable GameOptions options) { // Check Rules system and reapply as needed. IOption rules_system = this.options.getOption(OptionsConstants.RULES_SYSTEM); String loadedOption = (rulesManager instanceof CoreRulesManager) ? - OptionsConstants.RULES_CORE : OptionsConstants.RULES_TW; + OptionsConstants.RULES_CORE : OptionsConstants.RULES_TW; if (rules_system == null) { initializeRulesManager(OptionsConstants.RULES_CORE); } else if (!rules_system.stringValue().equals(loadedOption)) { initializeRulesManager(rules_system.stringValue()); } + if (loadedOption.equals(OptionsConstants.RULES_TW)) { + boolean shouldWalkOn = this.options.booleanOption(OptionsConstants.BASE_WALK_ON_DEPLOYMENT); + if (shouldWalkOn != Game.rulesManager.getRulesGame().isWalkOnDeployment()) { + Game.rulesManager.getRulesGame().setWalkOnDeployment(shouldWalkOn); + } + } processGameEvent(new GameSettingsChangeEvent(this)); } } @@ -533,8 +550,8 @@ public void setOptions(final @Nullable GameOptions options) { */ public boolean usesStandardGhostTargetMode() { return getOptions().booleanOption(OptionsConstants.ADVANCED_TAC_OPS_GHOST_TARGET) - && OptionsConstants.GHOST_TARGET_MODE_STANDARD.equals( - getOptions().stringOption(OptionsConstants.ADVANCED_GHOST_TARGET_MODE)); + && OptionsConstants.GHOST_TARGET_MODE_STANDARD.equals( + getOptions().stringOption(OptionsConstants.ADVANCED_GHOST_TARGET_MODE)); } /** @@ -604,7 +621,8 @@ public void setupTeams() { } @Override - public void addPlayer(int id, Player player) { + public void addPlayer(int id, + Player player) { player.setGame(this); if ((player.isBot()) && (!player.getSingleBlind())) { @@ -646,31 +664,31 @@ private boolean isOffboardPlayable() { // per errata, TAG will spot for LRMs and such if ((ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.LRM) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.LRM_IMP) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.MML) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.NLRM) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.MEK_MORTAR) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.TBOLT_5) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.TBOLT_10) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.TBOLT_15) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.TBOLT_20)) { + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.LRM_IMP) || + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.MML) || + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.NLRM) || + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.MEK_MORTAR) || + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.TBOLT_5) || + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.TBOLT_10) || + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.TBOLT_15) || + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.TBOLT_20)) { return true; } if (((ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.ARROW_IV) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.LONG_TOM) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.SNIPER) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.THUMPER)) && - (ammoType.getMunitionType().contains(AmmoType.Munitions.M_HOMING))) { + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.LONG_TOM) || + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.SNIPER) || + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.THUMPER)) && + (ammoType.getMunitionType().contains(AmmoType.Munitions.M_HOMING))) { return true; } } if (entity.getBombs() - .stream() - .anyMatch(bomb -> !bomb.isDestroyed() && - (bomb.getUsableShotsLeft() > 0) && - (bomb.getType().getBombType() == BombTypeEnum.LG))) { + .stream() + .anyMatch(bomb -> !bomb.isDestroyed() && + (bomb.getUsableShotsLeft() > 0) && + (bomb.getType().getBombType() == BombTypeEnum.LG))) { return true; } } @@ -679,13 +697,14 @@ private boolean isOffboardPlayable() { // prevents issues from aerospace homing artillery with the aerospace unit having left the field already, for // example return getAttacksVector().stream() - .map(AttackHandler::getWeaponAttackAction) - .filter(Objects::nonNull) - .anyMatch(waa -> waa.getAmmoMunitionType().contains(AmmoType.Munitions.M_HOMING)); + .map(AttackHandler::getWeaponAttackAction) + .filter(Objects::nonNull) + .anyMatch(waa -> waa.getAmmoMunitionType().contains(AmmoType.Munitions.M_HOMING)); } @Override - public void setPlayer(int id, Player player) { + public void setPlayer(int id, + Player player) { player.setGame(this); players.put(id, player); setupTeams(); @@ -739,16 +758,16 @@ public int getLiveEntitiesOwnedBy(Player player) { /** * @return the number of non-destroyed entities owned by the player, including entities not yet deployed. Ignores - * off board units and captured Mek pilots. + * off board units and captured Mek pilots. */ public int getLiveDeployedEntitiesOwnedBy(Player player) { int count = 0; for (Entity entity : inGameTWEntities()) { if (entity.getOwner().equals(player) && - !entity.isDestroyed() && - !entity.isCarcass() && - !entity.isOffBoard() && - !entity.isCaptured()) { + !entity.isDestroyed() && + !entity.isCarcass() && + !entity.isOffBoard() && + !entity.isCaptured()) { count++; } } @@ -757,17 +776,17 @@ public int getLiveDeployedEntitiesOwnedBy(Player player) { /** * @return the number of non-destroyed commander entities owned by the player. Ignores off board units and captured - * Mek pilots. + * Mek pilots. */ public int getLiveCommandersOwnedBy(Player player) { int count = 0; for (Entity entity : inGameTWEntities()) { if (entity.getOwner().equals(player) && - !entity.isDestroyed() && - !entity.isCarcass() && - entity.isCommander() && - !entity.isOffBoard() && - !entity.isCaptured()) { + !entity.isDestroyed() && + !entity.isCarcass() && + entity.isCommander() && + !entity.isOffBoard() && + !entity.isCaptured()) { count++; } } @@ -781,11 +800,11 @@ public boolean hasTacticalGenius(Player player) { // Note: rules do not state that Tactical Genius cannot apply to deployment initiative roll (CamOps pg 80) for (Entity entity : inGameTWEntities()) { if (entity.hasAbility(OptionsConstants.MISC_TACTICAL_GENIUS) && - entity.getOwner().equals(player) && - !entity.isDestroyed() && - (entity.isDeployed() || (getPhase() == INITIATIVE_REPORT)) && - !entity.isCarcass() && - !entity.getCrew().isUnconscious()) { + entity.getOwner().equals(player) && + !entity.isDestroyed() && + (entity.isDeployed() || (getPhase() == INITIATIVE_REPORT)) && + !entity.isCarcass() && + !entity.getCrew().isUnconscious()) { return true; } } @@ -807,18 +826,17 @@ public boolean hasTacticalGenius(Player player) { * * * @param player The player whose units will be checked. - * * @return {@code true} if the player has a valid unit with the "Combat Sense" ability, {@code false} otherwise. */ public boolean commanderHasCombatSense(Player player) { for (Entity entity : inGameTWEntities()) { if (entity.hasAbility(ATOW_COMBAT_SENSE) && - entity.isCommander() && - entity.getOwner().equals(player) && - !entity.isDestroyed() && - entity.isDeployed() && - !entity.isCarcass() && - !entity.getCrew().isUnconscious()) { + entity.isCommander() && + entity.getOwner().equals(player) && + !entity.isDestroyed() && + entity.isDeployed() && + !entity.isCarcass() && + !entity.getCrew().isUnconscious()) { return true; } } @@ -840,19 +858,18 @@ public boolean commanderHasCombatSense(Player player) { * * * @param player The player whose commander will be checked. - * * @return {@code true} if the player's commander has the "Combat Paralysis" special ability and meets all - * conditions, {@code false} otherwise. + * conditions, {@code false} otherwise. */ public boolean commanderHasCombatParalysis(Player player) { for (Entity entity : inGameTWEntities()) { if (entity.hasAbility(ATOW_COMBAT_PARALYSIS) && - entity.isCommander() && - entity.getOwner().equals(player) && - !entity.isDestroyed() && - entity.isDeployed() && - !entity.isCarcass() && - !entity.getCrew().isUnconscious()) { + entity.isCommander() && + entity.getOwner().equals(player) && + !entity.isDestroyed() && + entity.isDeployed() && + !entity.isCarcass() && + !entity.getCrew().isUnconscious()) { return true; } } @@ -871,12 +888,12 @@ public List getValidTargets(Entity entity) { // Even if friendly fire is acceptable, do not shoot yourself // Enemy units not on the board can not be shot. if ((otherEntity.getPosition() != null) && - !otherEntity.isOffBoard() && - otherEntity.isTargetable() && - !otherEntity.isHidden() && - !otherEntity.isSensorReturn(entity.getOwner()) && - otherEntity.hasSeenEntity(entity.getOwner()) && - (entity.isEnemyOf(otherEntity) || (friendlyFire && (entity.getId() != otherEntity.getId())))) { + !otherEntity.isOffBoard() && + otherEntity.isTargetable() && + !otherEntity.isHidden() && + !otherEntity.isSensorReturn(entity.getOwner()) && + otherEntity.hasSeenEntity(entity.getOwner()) && + (entity.isEnemyOf(otherEntity) || (friendlyFire && (entity.getId() != otherEntity.getId())))) { // Air to Ground - target must be on flight path if (Compute.isAirToGround(entity, otherEntity)) { if (entity.getPassedThrough().contains(otherEntity.getPosition())) { @@ -939,7 +956,8 @@ public void insertNextTurn(GameTurn turn) { /** * Inserts a turn after the specific index */ - public void insertTurnAfter(GameTurn turn, int index) { + public void insertTurnAfter(GameTurn turn, + int index) { synchronized (turnVector) { if ((index + 1) >= turnVector.size()) { turnVector.add(turn); @@ -952,7 +970,8 @@ public void insertTurnAfter(GameTurn turn, int index) { /** * Swaps the turn at index 1 with the turn at index 2. */ - public void swapTurnOrder(int index1, int index2) { + public void swapTurnOrder(int index1, + int index2) { synchronized (turnVector) { GameTurn turn1 = turnVector.get(index1); GameTurn turn2 = turnVector.get(index2); @@ -974,7 +993,8 @@ public Enumeration getTurns() { * @param turnIndex The new turn index. * @param prevPlayerId The ID of the player who triggered the turn index change. */ - public void setTurnIndex(int turnIndex, int prevPlayerId) { + public void setTurnIndex(int turnIndex, + int prevPlayerId) { this.turnIndex = turnIndex; GameTurn turn = getTurn(); @@ -1085,9 +1105,8 @@ public void setLastPhase(GamePhase lastPhase) { /** * @param current The Entity whose list position you wish to start from. - * * @return The previous Entity from the master list of entities. Will wrap around to the end of the - * list if necessary, returning null if there are no entities. + * list if necessary, returning null if there are no entities. */ public @Nullable Entity getPreviousEntityFromList(final @Nullable Entity current) { if ((current != null) && inGameTWEntities().contains(current)) { @@ -1102,9 +1121,8 @@ public void setLastPhase(GamePhase lastPhase) { /** * @param current The Entity whose list position you wish to start from. - * * @return The next Entity from the master list of entities. Will wrap around to the beginning of the - * list if necessary, returning null if there are no entities. + * list if necessary, returning null if there are no entities. */ public @Nullable Entity getNextEntityFromList(final @Nullable Entity current) { if ((current != null) && inGameTWEntities().contains(current)) { @@ -1142,7 +1160,6 @@ public Vector getOutOfGameEntitiesVector() { * * @param vOutOfGame - the new Vector of dead or fled units. This value should not be * null. - * * @throws IllegalArgumentException if the new list is null. */ public void setOutOfGameEntitiesVector(final List vOutOfGame) { @@ -1165,9 +1182,8 @@ public void setOutOfGameEntitiesVector(final List vOutOfGame) { * Returns an out-of-game entity. * * @param id the int ID of the out-of-game entity. - * * @return the out-of-game Entity with that ID. If no out-of-game entity has that ID, returns a - * null. + * null. */ public @Nullable Entity getOutOfGameEntity(int id) { Entity match = null; @@ -1189,11 +1205,9 @@ public void setOutOfGameEntitiesVector(final List vOutOfGame) { * * @param entity - the Entity whose C3 network co- members is required. This value may be * null. - * * @return a Vector that will contain all other - * Entitys that are in the same C3 network as the - * passed-in unit. This Vector may be empty, but it will not be null. - * + * Entitys that are in the same C3 network as the + * passed-in unit. This Vector may be empty, but it will not be null. * @see #getC3SubNetworkMembers(Entity) */ public Vector getC3NetworkMembers(Entity entity) { @@ -1222,20 +1236,18 @@ public Vector getC3NetworkMembers(Entity entity) { * * @param entity - the Entity whose C3 network sub-members is required. This value may be * null. - * * @return a Vector that will contain all other - * Entitys that are in the same C3 network under the - * passed-in unit. This Vector may be empty, but it will not be null. - * + * Entitys that are in the same C3 network under the + * passed-in unit. This Vector may be empty, but it will not be null. * @see #getC3NetworkMembers(Entity) */ public Vector getC3SubNetworkMembers(Entity entity) { // WOR. Handle null, C3i, NC3, and company commander units. if ((entity == null) || - entity.hasC3i() || - entity.hasNavalC3() || - entity.hasActiveNovaCEWS() || - entity.C3MasterIs(entity)) { + entity.hasC3i() || + entity.hasNavalC3() || + entity.hasActiveNovaCEWS() || + entity.C3MasterIs(entity)) { return getC3NetworkMembers(entity); } @@ -1260,7 +1272,7 @@ public Vector getC3SubNetworkMembers(Entity entity) { * getPositionMapMulti() * * @return a Hashtable that maps the Coords positions or each unit in the game to a Vector of Entity's at that - * position. + * position. */ public Hashtable> getPositionMap() { Hashtable> positionMap = new Hashtable<>(); @@ -1277,7 +1289,7 @@ public Hashtable> getPositionMap() { /** * @return a Map that maps the location of each unit in this game to a list of Entity's at the same location. Units - * that have no position (e.g. loaded units) will not be in the map. + * that have no position (e.g. loaded units) will not be in the map. */ public Map> getPositionMapMulti() { var positionMap = new HashMap>(); @@ -1293,7 +1305,7 @@ public Map> getPositionMapMulti() { final BoardLocation secondaryLocation = new BoardLocation(coords, entity.getBoardId(), false); if (hasBoardLocation(secondaryLocation)) { List listForLocation = positionMap.computeIfAbsent(secondaryLocation, - k -> new ArrayList<>()); + k -> new ArrayList<>()); listForLocation.add(entity); } } @@ -1310,7 +1322,7 @@ public Enumeration getGraveyardEntities() { for (Entity entity : vOutOfGame) { if ((entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_SALVAGEABLE) || - (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_EJECTED)) { + (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_EJECTED)) { graveyard.addElement(entity); } } @@ -1325,8 +1337,8 @@ public Enumeration getWreckedEntities() { Vector wrecks = new Vector<>(); for (Entity entity : vOutOfGame) { if ((entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_SALVAGEABLE) || - (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_EJECTED) || - (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_DEVASTATED)) { + (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_EJECTED) || + (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_DEVASTATED)) { wrecks.addElement(entity); } } @@ -1343,8 +1355,8 @@ public Enumeration getRetreatedEntities() { for (Entity entity : vOutOfGame) { if ((entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_IN_RETREAT) || - (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_CAPTURED) || - (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_PUSHED)) { + (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_CAPTURED) || + (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_PUSHED)) { sanctuary.addElement(entity); } } @@ -1392,7 +1404,8 @@ public int getNoOfEntities() { /** * Returns the appropriate target for this game given a type and id */ - public @Nullable Targetable getTarget(int targetType, int targetId) { + public @Nullable Targetable getTarget(int targetType, + int targetId) { try { return switch (targetType) { case Targetable.TYPE_ENTITY -> getEntity(targetId); @@ -1401,14 +1414,14 @@ public int getNoOfEntities() { Targetable.TYPE_MINEFIELD_DELIVER, Targetable.TYPE_FLARE_DELIVER, Targetable.TYPE_HEX_EXTINGUISH, Targetable.TYPE_HEX_ARTILLERY, Targetable.TYPE_HEX_SCREEN, Targetable.TYPE_HEX_AERO_BOMB, Targetable.TYPE_SATURATION, Targetable.TYPE_HEX_TAG -> - new HexTarget(HexTarget.idToLocation(targetId), targetType); + new HexTarget(HexTarget.idToLocation(targetId), targetType); case Targetable.TYPE_FUEL_TANK, Targetable.TYPE_FUEL_TANK_IGNITE, Targetable.TYPE_BUILDING, Targetable.TYPE_BLDG_IGNITE, Targetable.TYPE_BLDG_TAG -> { final BoardLocation boardLocation = HexTarget.idToLocation(targetId); yield getBuildingAt(boardLocation) - .map(b -> new BuildingTarget(this, boardLocation, targetType)) - .orElse(null); + .map(b -> new BuildingTarget(this, boardLocation, targetType)) + .orElse(null); } case Targetable.TYPE_MINEFIELD_CLEAR -> new MinefieldTarget(MinefieldTarget.idToCoords(targetId)); case Targetable.TYPE_I_NARC_POD -> INarcPod.idToInstance(targetId); @@ -1420,7 +1433,9 @@ yield getBuildingAt(boardLocation) } } - /** @return The entity with the given id number, if any. */ + /** + * @return The entity with the given id number, if any. + */ public synchronized @Nullable Entity getEntity(final int id) { InGameObject possibleEntity = inGameObjects.get(id); return (possibleEntity instanceof Entity) ? (Entity) possibleEntity : null; @@ -1432,7 +1447,6 @@ yield getBuildingAt(boardLocation) * will cause a null pointer exception if the entity does not exist. * * @param id The id number of the entity to get. - * * @return The entity with the given id number or throw a no such element exception. */ public Entity getEntityOrThrow(final int id) { @@ -1485,7 +1499,8 @@ public void addEntity(Entity entity) { * @param entity The Entity to add. * @param genEvent A flag that determines whether a GameEntityNewEvent is generated. */ - public synchronized void addEntity(Entity entity, boolean genEvent) { + public synchronized void addEntity(Entity entity, + boolean genEvent) { entity.setGame(this); entity.addIntrinsicTransporters(); @@ -1533,11 +1548,14 @@ private boolean isIdUsed(int id) { return inGameObjects.containsKey(id) || isOutOfGame(id); } - public void setEntity(int id, Entity entity) { + public void setEntity(int id, + Entity entity) { setEntity(id, entity, null); } - public synchronized void setEntity(int id, Entity entity, Vector movePath) { + public synchronized void setEntity(int id, + Entity entity, + Vector movePath) { final Entity oldEntity = getEntity(id); if (oldEntity == null) { addEntity(entity); @@ -1575,7 +1593,7 @@ public List getGraveyard() { /** * @return true if an entity with the specified id number exists in - * this game. + * this game. */ public boolean hasEntity(int entityId) { Optional possibleEntity = getInGameObject(entityId); @@ -1585,7 +1603,8 @@ public boolean hasEntity(int entityId) { /** * Remove an entity from the master list. If we can't find that entity, (probably due to double-blind) ignore it. */ - public synchronized void removeEntity(int id, int condition) { + public synchronized void removeEntity(int id, + int condition) { Entity toRemove = getEntity(id); if (toRemove == null) { return; @@ -1607,7 +1626,8 @@ public synchronized void removeEntity(int id, int condition) { processGameEvent(new GameEntityRemoveEvent(this, toRemove)); } - public void removeEntities(List ids, int condition) { + public void removeEntities(List ids, + int condition) { for (Integer id : ids) { removeEntity(id, condition); } @@ -1695,7 +1715,8 @@ public Entity getFirstEntity(Coords c) { * @param c the coordinates to search at * @param currentEntity the entity that is firing */ - public Entity getFirstEnemyEntity(Coords c, Entity currentEntity) { + public Entity getFirstEnemyEntity(Coords c, + Entity currentEntity) { for (Entity entity : inGameTWEntities()) { if (c.equals(entity.getPosition()) && entity.isTargetable() && entity.isEnemyOf(currentEntity)) { return entity; @@ -1715,7 +1736,6 @@ public Iterator getEntities(Coords c) { * Returns an Iterator for all entities in _all_ of the coordinates provided. Coords must not be null. * * @param coordList ArrayList of coordinates to check. - * * @return Iterator over the vector of entities. The vector must exist to get the iterator. */ public Iterator getEntities(ArrayList coordList) { @@ -1731,7 +1751,8 @@ public Iterator getEntities(ArrayList coordList) { /** * Returns an Enumeration of the active entities at the given coordinates. */ - public Iterator getEntities(Coords c, boolean ignore) { + public Iterator getEntities(Coords c, + boolean ignore) { return getEntitiesVector(c, ignore).iterator(); } @@ -1739,7 +1760,6 @@ public Iterator getEntities(Coords c, boolean ignore) { * Return an {@link Entity} List at {@link Coords} c, checking if they can be targeted. * * @param c The coordinates to check - * * @return the {@link Entity} List */ public synchronized List getEntitiesVector(Coords c) { @@ -1751,10 +1771,10 @@ public synchronized List getEntitiesVector(Coords c) { * * @param c The coordinates to check * @param ignore Flag that determines whether the ability to target is ignored - * * @return the {@link Entity} List */ - public synchronized List getEntitiesVector(Coords c, boolean ignore) { + public synchronized List getEntitiesVector(Coords c, + boolean ignore) { // checkPositionCacheConsistency(); // Make sure the look-up is initialized if (entityPosLookup.isEmpty() && !inGameTWEntities().isEmpty()) { @@ -1789,7 +1809,8 @@ public synchronized List getEntitiesVector(Coords c, boolean ignore) { return Collections.unmodifiableList(vector); } - public List getEntitiesVector(BoardLocation location, boolean ignoreTargetable) { + public List getEntitiesVector(BoardLocation location, + boolean ignoreTargetable) { return getEntitiesVector(location.coords(), location.boardId(), ignoreTargetable); } @@ -1797,19 +1818,21 @@ public List getEntitiesVector(BoardLocation location) { return getEntitiesVector(location.coords(), location.boardId(), true); } - public List getEntitiesVector(Coords coord, int boardId, boolean ignoreTargetable) { + public List getEntitiesVector(Coords coord, + int boardId, + boolean ignoreTargetable) { return getEntitiesVector(coord, ignoreTargetable).stream() - .filter(entity -> entity.isOnBoard(boardId)) - .toList(); + .filter(entity -> entity.isOnBoard(boardId)) + .toList(); } - public List getEntitiesVector(Coords coord, int boardId) { + public List getEntitiesVector(Coords coord, + int boardId) { return getEntitiesVector(coord, boardId, false); } /** * @param player {@link Player} Object - * * @return a list of all off-board enemy entities. */ public synchronized List getAllOffboardEnemyEntities(Player player) { @@ -1832,10 +1855,10 @@ public List getGunEmplacements(Coords c) { * Return a Vector of gun emplacements at Coords c. * * @param c The coordinates to check - * * @return the {@link GunEmplacement} Vector */ - public List getGunEmplacements(Coords c, int boardId) { + public List getGunEmplacements(Coords c, + int boardId) { List result = new ArrayList<>(); // Only build the list if the coords are on the board. // TODO Ensure this works w/ BuildingEntity @@ -1855,7 +1878,8 @@ public List getGunEmplacements(Coords c, int boardId) { * * @param c The coordinates to check */ - public boolean hasRooftopGunEmplacement(Coords c, int boardId) { + public boolean hasRooftopGunEmplacement(Coords c, + int boardId) { if (!hasBoardLocation(c, boardId)) { return false; } // TODO Ensure this works with BuildingEntity @@ -1881,17 +1905,17 @@ public boolean hasRooftopGunEmplacement(Coords c, int boardId) { * * @param coords The Coords of the hex in which the accidental fall from above happens * @param ignore The entity who is falling, so shouldn't be returned - * * @return The Entity that should be an AFFA target. */ - public @Nullable Entity getAFFATarget(Coords coords, Entity ignore) { + public @Nullable Entity getAFFATarget(Coords coords, + Entity ignore) { List candidates = new ArrayList<>(); if (hasBoardLocation(coords, ignore.getBoardId())) { Hex hex = getHex(coords, ignore.getBoardId()); for (Entity entity : getEntitiesVector(coords, ignore.getBoardId())) { if (entity.isTargetable() && ((entity.getElevation() == 0) // Standing on hex surface - || (entity.getElevation() == -hex.depth())) // Standing on hex floor - && (entity.getAltitude() == 0) && !(entity instanceof Infantry) && (entity != ignore)) { + || (entity.getElevation() == -hex.depth())) // Standing on hex floor + && (entity.getAltitude() == 0) && !(entity instanceof Infantry) && (entity != ignore)) { candidates.add(entity); } } @@ -1908,14 +1932,14 @@ public boolean hasRooftopGunEmplacement(Coords c, int boardId) { * * @param coords the Coords of the hex being examined. * @param currentEntity the Entity whose enemies are needed. - * * @return an Enumeration of Entitys at the given coordinates who are enemies of the given - * unit. + * unit. */ - public Iterator getEnemyEntities(final Coords coords, final Entity currentEntity) { + public Iterator getEnemyEntities(final Coords coords, + final Entity currentEntity) { return getSelectedEntities(entity -> coords.equals(entity.getPosition()) && - entity.isTargetable() && - entity.isEnemyOf(currentEntity)); + entity.isTargetable() && + entity.isEnemyOf(currentEntity)); } /** @@ -1923,24 +1947,24 @@ public Iterator getEnemyEntities(final Coords coords, final Entity curre * * @param coords the Coords of the hex being examined. * @param currentEntity the Entity whose enemies are needed. - * * @return an Enumeration of Entitys at the given coordinates who are enemies of the given - * unit. + * unit. */ - public List getEnemyEntities(final Coords coords, final int boardId, Entity currentEntity) { + public List getEnemyEntities(final Coords coords, + final int boardId, + Entity currentEntity) { return getEntitiesVector(coords, boardId).stream() - .filter(Entity::isTargetable) - .filter(entity -> entity.isEnemyOf(currentEntity)) - .toList(); + .filter(Entity::isTargetable) + .filter(entity -> entity.isEnemyOf(currentEntity)) + .toList(); } /** * Returns an Enumeration of active enemy entities * * @param currentEntity the Entity whose enemies are needed. - * * @return an Enumeration of Entitys at the given coordinates who are enemies of the given - * unit. + * unit. */ public Iterator getAllEnemyEntities(final Entity currentEntity) { return getSelectedEntities(entity -> entity.isTargetable() && entity.isEnemyOf(currentEntity)); @@ -1955,14 +1979,14 @@ public Iterator getTeamEntities(final Team team) { * * @param coords the Coords of the hex being examined. * @param currentEntity the Entity whose friends are needed. - * * @return an Enumeration of Entitys at the given coordinates who are friends of the given - * unit. + * unit. */ - public Iterator getFriendlyEntities(final Coords coords, final Entity currentEntity) { + public Iterator getFriendlyEntities(final Coords coords, + final Entity currentEntity) { return getSelectedEntities(entity -> coords.equals(entity.getPosition()) && - entity.isTargetable() && - !entity.isEnemyOf(currentEntity)); + entity.isTargetable() && + !entity.isEnemyOf(currentEntity)); } /** @@ -1976,9 +2000,8 @@ public void moveToGraveyard(int id) { * See if the Entity with the given ID is out of the game. * * @param id - the ID of the Entity to be checked. - * * @return true if the Entity is in the graveyard, - * false otherwise. + * false otherwise. */ public boolean isOutOfGame(int id) { for (Entity entity : vOutOfGame) { @@ -1994,9 +2017,8 @@ public boolean isOutOfGame(int id) { * See if the Entity is out of the game. * * @param entity - the Entity to be checked. - * * @return true if the Entity is in the graveyard, - * false otherwise. + * false otherwise. */ public boolean isOutOfGame(Entity entity) { return isOutOfGame(entity.getId()); @@ -2011,7 +2033,6 @@ public boolean isOutOfGame(Entity entity) { /** * @param turn the current game turn, which may be null - * * @return the first entity that can act in the specified turn, or null if none can. */ public @Nullable Entity getFirstEntity(final @Nullable GameTurn turn) { @@ -2027,7 +2048,6 @@ public int getFirstEntityNum() { /** * @param turn the current game turn, which may be null - * * @return the id of the first entity that can act in the specified turn, or -1 if none can. */ public int getFirstEntityNum(final @Nullable GameTurn turn) { @@ -2046,7 +2066,6 @@ public int getFirstEntityNum(final @Nullable GameTurn turn) { /** * @param start the index number to start at (not an Entity ID) - * * @return the next selectable entity that can act this turn, or null if none can. */ public @Nullable Entity getNextEntity(int start) { @@ -2061,10 +2080,10 @@ public int getFirstEntityNum(final @Nullable GameTurn turn) { /** * @param turn the turn to use, which may be null * @param start the entity id to start at - * * @return the entity id of the next entity that can move during the specified turn */ - public int getNextEntityNum(final @Nullable GameTurn turn, int start) { + public int getNextEntityNum(final @Nullable GameTurn turn, + int start) { List sortedEntities = inGameTWEntities(); sortedEntities.sort(Comparator.comparingInt(Entity::getId)); // If we don't have a turn, return ENTITY_NONE @@ -2096,10 +2115,10 @@ public int getNextEntityNum(final @Nullable GameTurn turn, int start) { /** * @param turn the turn to use * @param start the entity id to start at - * * @return the entity id of the previous entity that can move during the specified turn */ - public int getPrevEntityNum(GameTurn turn, int start) { + public int getPrevEntityNum(GameTurn turn, + int start) { List sortedEntities = inGameTWEntities(); sortedEntities.sort(Comparator.comparingInt(Entity::getId)); boolean hasLooped = false; @@ -2130,7 +2149,6 @@ public int getPrevEntityNum(GameTurn turn, int start) { /** * @param turn the current game turn, which may be null - * * @return the number of the first deployable entity that is valid for the specified turn */ public int getFirstDeployableEntityNum(final @Nullable GameTurn turn) { @@ -2149,7 +2167,8 @@ public int getFirstDeployableEntityNum(final @Nullable GameTurn turn) { /** * @return the number of the next deployable entity that is valid for the specified turn */ - public int getNextDeployableEntityNum(GameTurn turn, int start) { + public int getNextDeployableEntityNum(GameTurn turn, + int start) { if (start >= 0) { for (int i = start; i < inGameTWEntities().size(); i++) { final Entity entity = inGameTWEntities().get(i); @@ -2163,7 +2182,6 @@ public int getNextDeployableEntityNum(GameTurn turn, int start) { /** * @param turn the current game turn, which may be null - * * @return the number of the first hidden entity that is valid for the specified turn */ @Deprecated(since = "0.51.0", forRemoval = true) @@ -2189,7 +2207,8 @@ public int getFirstHiddenEntityNum(final @Nullable GameTurn turn) { * @return the number of the next hidden entity that is valid for the specified turn */ @Deprecated(since = "0.51.0", forRemoval = true) - public int getNextHiddenEntityNum(GameTurn turn, int start) { + public int getNextHiddenEntityNum(GameTurn turn, + int start) { if (start >= 0) { for (int i = start; i < inGameTWEntities().size(); i++) { final Entity entity = inGameTWEntities().get(i); @@ -2206,10 +2225,10 @@ public int getNextHiddenEntityNum(GameTurn turn, int start) { * * @param player - the Player whose entities are required. * @param hide - should fighters loaded into squadrons be excluded? - * * @return a Vector of Entitys. */ - public ArrayList getPlayerEntities(Player player, boolean hide) { + public ArrayList getPlayerEntities(Player player, + boolean hide) { ArrayList output = new ArrayList<>(); for (Entity entity : inGameTWEntities()) { if (entity.isPartOfFighterSquadron() && hide) { @@ -2227,11 +2246,11 @@ public ArrayList getPlayerEntities(Player player, boolean hide) { * * @param player - the Player whose entities are required. * @param hide - should fighters loaded into squadrons be excluded from this list? - * * @return a Vector of Entitys. */ @Deprecated(since = "0.51.0", forRemoval = true) - public ArrayList getPlayerEntityIds(Player player, boolean hide) { + public ArrayList getPlayerEntityIds(Player player, + boolean hide) { ArrayList output = new ArrayList<>(); for (Entity entity : inGameTWEntities()) { if (entity.isPartOfFighterSquadron() && hide) { @@ -2248,16 +2267,15 @@ public ArrayList getPlayerEntityIds(Player player, boolean hide) { * Get the entities for the player. * * @param player - the Player whose entities are required. - * * @return a Vector of Entity that have retreadeds. */ public ArrayList getPlayerRetreatedEntities(Player player) { ArrayList output = new ArrayList<>(); for (Entity entity : vOutOfGame) { if (player.equals(entity.getOwner()) && - ((entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_IN_RETREAT) || - (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_CAPTURED) || - (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_PUSHED))) { + ((entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_IN_RETREAT) || + (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_CAPTURED) || + (entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_PUSHED))) { output.add(entity); } } @@ -2270,9 +2288,8 @@ public ArrayList getPlayerRetreatedEntities(Player player) { * According to Randall Bills, the "minimum move" rule allow stranded units to dismount at the start of the turn. * * @param entity the Entity that may be stranded - * * @return true if the entity is stranded false - * otherwise. + * otherwise. */ public boolean isEntityStranded(Entity entity) { @@ -2294,7 +2311,6 @@ public boolean isEntityStranded(Entity entity) { /** * @param playerId the player's ID - * * @return number of infantry playerId has not selected yet this turn */ public int getInfantryLeft(int playerId) { @@ -2312,7 +2328,6 @@ public int getInfantryLeft(int playerId) { /** * @param playerId the player's ID - * * @return number of ProtoMeks playerId has not selected yet this turn */ public int getProtoMeksLeft(int playerId) { @@ -2330,7 +2345,6 @@ public int getProtoMeksLeft(int playerId) { /** * @param playerId the player's ID - * * @return number of vehicles playerId has not selected yet this turn */ public int getVehiclesLeft(int playerId) { @@ -2348,7 +2362,6 @@ public int getVehiclesLeft(int playerId) { /** * @param playerId the player's ID - * * @return number of 'Meks playerId has not selected yet this turn */ public int getMeksLeft(int playerId) { @@ -2368,9 +2381,7 @@ public int getMeksLeft(int playerId) { * Removes the first turn found that the specified entity can move in. Used when a turn is played out of order. * * @param entity the entity to remove a turn for - * * @return the removed GameTurn, or null if not found - * * @throws Exception if called during the movement phase */ public @Nullable GameTurn removeFirstTurnFor(final Entity entity) throws Exception { @@ -2403,17 +2414,17 @@ public void removeTurnFor(Entity entity) { // If the game option "move multiple infantry per mek" is selected, then we might not need to remove a turn // at all. A turn only needs to be removed when going from 4 inf (2 turns) to 3 inf (1 turn) if (getOptions().booleanOption(OptionsConstants.INIT_INF_MOVE_MULTI) && - (entity instanceof Infantry) && - getPhase().isMovement()) { + (entity instanceof Infantry) && + getPhase().isMovement()) { if ((getInfantryLeft(entity.getOwnerId()) % - getOptions().intOption(OptionsConstants.INIT_INF_PROTO_MOVE_MULTI)) != 1) { + getOptions().intOption(OptionsConstants.INIT_INF_PROTO_MOVE_MULTI)) != 1) { // exception, if the _next_ turn is an infantry turn, remove that contrived, but may come up e.g. one // inf accidentally kills another if (hasMoreTurns()) { GameTurn nextTurn = turnVector.elementAt(turnIndex + 1); if (nextTurn instanceof EntityClassTurn ect) { if (ect.isValidClass(EntityClassTurn.CLASS_INFANTRY) && - !ect.isValidClass(~EntityClassTurn.CLASS_INFANTRY)) { + !ect.isValidClass(~EntityClassTurn.CLASS_INFANTRY)) { turnVector.removeElementAt(turnIndex + 1); } } @@ -2423,17 +2434,17 @@ public void removeTurnFor(Entity entity) { } // Same thing but for ProtoMeks if (getOptions().booleanOption(OptionsConstants.INIT_PROTOMEKS_MOVE_MULTI) && - (entity instanceof ProtoMek) && - getPhase().isMovement()) { + (entity instanceof ProtoMek) && + getPhase().isMovement()) { if ((getProtoMeksLeft(entity.getOwnerId()) % - getOptions().intOption(OptionsConstants.INIT_INF_PROTO_MOVE_MULTI)) != 1) { + getOptions().intOption(OptionsConstants.INIT_INF_PROTO_MOVE_MULTI)) != 1) { // exception, if the _next_ turn is an ProtoMek turn, remove that contrived, but may come up e.g. one // inf accidentally kills another if (hasMoreTurns()) { GameTurn nextTurn = turnVector.elementAt(turnIndex + 1); if (nextTurn instanceof EntityClassTurn ect) { if (ect.isValidClass(EntityClassTurn.CLASS_PROTOMEK) && - !ect.isValidClass(~EntityClassTurn.CLASS_PROTOMEK)) { + !ect.isValidClass(~EntityClassTurn.CLASS_PROTOMEK)) { turnVector.removeElementAt(turnIndex + 1); } } @@ -2444,18 +2455,18 @@ public void removeTurnFor(Entity entity) { // Same thing but for vehicles if (getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_LANCE_MOVEMENT) && - (entity instanceof Tank) && - getPhase().isMovement()) { + (entity instanceof Tank) && + getPhase().isMovement()) { if ((getVehiclesLeft(entity.getOwnerId()) % - getOptions().intOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_LANCE_MOVEMENT_NUMBER)) - != 1) { + getOptions().intOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_LANCE_MOVEMENT_NUMBER)) + != 1) { // exception, if the _next_ turn is a tank turn, remove that contrived, but may come up e.g. one tank // accidentally kills another if (hasMoreTurns()) { GameTurn nextTurn = turnVector.elementAt(turnIndex + 1); if (nextTurn instanceof EntityClassTurn ect) { if (ect.isValidClass(EntityClassTurn.CLASS_TANK) && - !ect.isValidClass(~EntityClassTurn.CLASS_TANK)) { + !ect.isValidClass(~EntityClassTurn.CLASS_TANK)) { turnVector.removeElementAt(turnIndex + 1); } } @@ -2466,18 +2477,18 @@ public void removeTurnFor(Entity entity) { // Same thing but for meks if (getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_MEK_LANCE_MOVEMENT) && - (entity instanceof Mek) && - getPhase().isMovement()) { + (entity instanceof Mek) && + getPhase().isMovement()) { if ((getMeksLeft(entity.getOwnerId()) % - getOptions().intOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_MEK_LANCE_MOVEMENT_NUMBER)) - != 1) { + getOptions().intOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_MEK_LANCE_MOVEMENT_NUMBER)) + != 1) { // exception, if the _next_ turn is a mek turn, remove that contrived, but may come up e.g. one mek // accidentally kills another if (hasMoreTurns()) { GameTurn nextTurn = turnVector.elementAt(turnIndex + 1); if (nextTurn instanceof EntityClassTurn ect) { if (ect.isValidClass(EntityClassTurn.CLASS_MEK) && - !ect.isValidClass(~EntityClassTurn.CLASS_MEK)) { + !ect.isValidClass(~EntityClassTurn.CLASS_MEK)) { turnVector.removeElementAt(turnIndex + 1); } } @@ -2489,9 +2500,9 @@ public void removeTurnFor(Entity entity) { // If we have the "infantry move later" or "ProtoMeks move later" optional rules, then we may be removing an // infantry unit that would be considered invalid unless we don't consider the extra validity checks. boolean useInfantryMoveLaterCheck = (!getOptions().booleanOption(OptionsConstants.INIT_INF_MOVE_LATER) || - (!(entity instanceof Infantry))) && - (!getOptions().booleanOption(OptionsConstants.INIT_PROTOMEKS_MOVE_LATER) || - (!(entity instanceof ProtoMek))); + (!(entity instanceof Infantry))) && + (!getOptions().booleanOption(OptionsConstants.INIT_PROTOMEKS_MOVE_LATER) || + (!(entity instanceof ProtoMek))); for (int i = turnVector.size() - 1; i >= turnIndex; i--) { GameTurn turn = turnVector.elementAt(i); @@ -2509,7 +2520,6 @@ public void removeTurnFor(Entity entity) { * from the game to ensure any turns that only it can take are gone. * * @param entity the entity to remove turns for - * * @return The number of turns returned */ public int removeSpecificEntityTurnsFor(Entity entity) { @@ -2578,7 +2588,7 @@ public void setEnemyArtilleryInbound(List inbound) { /** * @return The redacted enemy artillery-in-flight summaries (landing time only; target and munition withheld); never - * {@code null} (empty when none, or after deserializing a saved game where this transient field is unset) + * {@code null} (empty when none, or after deserializing a saved game where this transient field is unset) */ public List getEnemyArtilleryInbound() { return (enemyArtilleryInbound != null) ? enemyArtilleryInbound : new ArrayList<>(); @@ -2640,9 +2650,9 @@ public void rollInitAndResolveTies() { } TurnOrdered.rollInitAndResolveTies(teams, - initiativeRerollRequests, - getOptions().booleanOption(OptionsConstants.INIT_INITIATIVE_STREAK_COMPENSATION), - initiativeAptitude); + initiativeRerollRequests, + getOptions().booleanOption(OptionsConstants.INIT_INITIATIVE_STREAK_COMPENSATION), + initiativeAptitude); } initiativeRerollRequests.removeAllElements(); @@ -2651,7 +2661,7 @@ public void rollInitAndResolveTies() { public void handleInitiativeCompensation() { if (getOptions().booleanOption(OptionsConstants.INIT_INITIATIVE_STREAK_COMPENSATION)) { TurnOrdered.resetInitiativeCompensation(teams, - getOptions().booleanOption(OptionsConstants.INIT_INITIATIVE_STREAK_COMPENSATION)); + getOptions().booleanOption(OptionsConstants.INIT_INITIATIVE_STREAK_COMPENSATION)); } } @@ -2742,7 +2752,9 @@ public void initializeAfterLoad() { // Reverse traverse the pendingDisplacementAttacks, otherwise when we remove things, it causes problems for (int attack = (pendingDisplacementAttacks.size() - 1); attack >= 0; attack--) { AttackAction pendingAttack = pendingDisplacementAttacks.get(attack); - if (pendingAttack == null) {continue;} + if (pendingAttack == null) { + continue; + } if (pendingAttack instanceof RamAttackAction) { addRam(pendingAttack); pendingDisplacementAttacks.remove(attack); @@ -2791,7 +2803,6 @@ public List getTeleMissileAttacksVector() { * Adds a pending PSR to the list for this phase. * * @param psr Pending PSR. - * * @see PilotingRollData */ public void addPSR(PilotingRollData psr) { @@ -3010,7 +3021,6 @@ public void addReports(List v) { /** * @param r Round number - * * @return a vector of reports for the given round. */ public List getReports(int r) { @@ -3038,7 +3048,8 @@ public void clearAllReports() { gameReports.clear(); } - public void end(int winner, int winnerTeam) { + public void end(int winner, + int winnerTeam) { setVictoryPlayerId(winner); setVictoryTeam(winnerTeam); processGameEvent(new GameEndEvent(this)); @@ -3083,7 +3094,7 @@ public void setVictoryTeam(int victoryTeam) { /** * @return true if the specified player is either the victor, or is on the winning team. Best to call during - * GamePhase.VICTORY. + * GamePhase.VICTORY. */ @Deprecated(since = "0.51.0", forRemoval = true) public boolean isPlayerVictor(Player player) { @@ -3095,9 +3106,9 @@ public boolean isPlayerVictor(Player player) { /** * @return the currently active context-object for VictoryCondition checking. This should be a mutable object, and - * it will be modified by the victory condition checkers. Whoever saves the game state when doing saves is - * also responsible for saving this state. At the start of the game this should be initialized to an empty - * HashMap + * it will be modified by the victory condition checkers. Whoever saves the game state when doing saves is + * also responsible for saving this state. At the start of the game this should be initialized to an empty + * HashMap */ public HashMap getVictoryContext() { return victoryContext; @@ -3131,9 +3142,8 @@ public void setVictoryPointLevels(List victoryPointLevels) { * This value may be * null (in which case all entities in the game * will be returned). - * * @return an Enumeration of all entities that the selector accepts. This value will not be - * null but it may be empty. + * null but it may be empty. */ public Iterator getSelectedEntities(@Nullable EntitySelector selector) { Iterator retVal; @@ -3202,9 +3212,8 @@ public void remove() { * This value may be * null (in which case the count of all entities in * the game will be returned). - * * @return the int count of all entities that the selector accepts. This value will not be - * null but it may be empty. + * null but it may be empty. */ public int getSelectedEntityCount(EntitySelector selector) { int retVal = 0; @@ -3235,9 +3244,8 @@ public int getSelectedEntityCount(EntitySelector selector) { * This value may be * null (in which case all entities in the game * will be returned). - * * @return an Enumeration of all entities that the selector accepts. This value will not be - * null but it may be empty. + * null but it may be empty. */ public Enumeration getSelectedOutOfGameEntities(EntitySelector selector) { Enumeration retVal; @@ -3301,9 +3309,8 @@ public Entity nextElement() { * This value may be * null (in which case the count of all out-of-game * entities will be returned). - * * @return the int count of all entities that the selector accepts. This value will not be - * null but it may be empty. + * null but it may be empty. */ public int getSelectedOutOfGameEntityCount(EntitySelector selector) { int retVal = 0; @@ -3339,7 +3346,7 @@ public boolean checkForValidNonInfantryAndOrProtoMeks(int playerId) { if ((entity instanceof Infantry) && getOptions().booleanOption(OptionsConstants.INIT_INF_MOVE_LATER)) { excluded = true; } else if ((entity instanceof ProtoMek) && - getOptions().booleanOption(OptionsConstants.INIT_PROTOMEKS_MOVE_LATER)) { + getOptions().booleanOption(OptionsConstants.INIT_PROTOMEKS_MOVE_LATER)) { excluded = true; } @@ -3355,11 +3362,11 @@ public boolean checkForValidNonInfantryAndOrProtoMeks(int playerId) { * * @param attacker The attacking Entity. * @param target The Coords of the original target. - * * @return an Enumeration of entities that have nemesis pods attached, are located between attacker and - * target, and are friendly with the attacker. + * target, and are friendly with the attacker. */ - public Enumeration getNemesisTargets(Entity attacker, Coords target) { + public Enumeration getNemesisTargets(Entity attacker, + Coords target) { final Coords attackerPos = attacker.getPosition(); final ArrayList in = Coords.intervening(attackerPos, target); Vector nemesisTargets = new Vector<>(); @@ -3482,7 +3489,7 @@ public Vector ageFlares() { if (!planetaryConditions.getWind().isCalm()) { WindDirection dir = planetaryConditions.getWindDirection(); flare.position = flare.position.translated(dir.ordinal(), - (wind.ordinal() > 1) ? (wind.ordinal() - 1) : wind.ordinal()); + (wind.ordinal() > 1) ? (wind.ordinal() - 1) : wind.ordinal()); if (getBoard().contains(flare.position)) { r = new Report(5236); r.add(flare.position.getBoardNum()); @@ -3519,7 +3526,7 @@ public Vector ageFlares() { public boolean gameTimerIsExpired() { return getOptions().booleanOption(OptionsConstants.VICTORY_USE_GAME_TURN_LIMIT) && - (getRoundCount() == getOptions().intOption(OptionsConstants.VICTORY_GAME_TURN_LIMIT)); + (getRoundCount() == getOptions().intOption(OptionsConstants.VICTORY_GAME_TURN_LIMIT)); } /** @@ -3539,7 +3546,7 @@ public VictoryResult getVictoryResult() { // applicable public boolean useVectorMove() { return getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_ADVANCED_MOVEMENT) - && getBoard().isSpace(); + && getBoard().isSpace(); } /** @@ -3611,8 +3618,9 @@ public boolean checkForValidDropShips(int playerId) { @Deprecated(since = "0.51.0", forRemoval = true) public boolean checkForValidSmallCraft(int playerId) { return getPlayerEntities(getPlayer(playerId), false).stream() - .anyMatch(e -> (e instanceof SmallCraft) && - Objects.requireNonNull(getTurn()).isValidEntity(e, this)); + .anyMatch(e -> (e instanceof SmallCraft) && + Objects.requireNonNull(getTurn()) + .isValidEntity(e, this)); } @Override @@ -3668,7 +3676,6 @@ public void addIndustrialElevator(IndustrialElevator elevator) { * Gets an industrial elevator at the specified location. * * @param location The board location to check - * * @return The elevator at this location, or {@code null} if none exists */ public @Nullable IndustrialElevator getIndustrialElevator(BoardLocation location) { @@ -3680,10 +3687,10 @@ public void addIndustrialElevator(IndustrialElevator elevator) { * * @param coords The coordinates to check * @param boardId The board ID - * * @return The elevator at this location, or {@code null} if none exists */ - public @Nullable IndustrialElevator getIndustrialElevator(Coords coords, int boardId) { + public @Nullable IndustrialElevator getIndustrialElevator(Coords coords, + int boardId) { return getIndustrialElevator(BoardLocation.of(coords, boardId)); } @@ -3700,7 +3707,6 @@ public Collection getIndustrialElevators() { * Checks if there is an industrial elevator at the specified location. * * @param location The board location to check - * * @return {@code true} if an elevator exists at this location */ public boolean hasIndustrialElevator(BoardLocation location) { @@ -3711,7 +3717,6 @@ public boolean hasIndustrialElevator(BoardLocation location) { * Removes an industrial elevator from the game. * * @param location The location of the elevator to remove - * * @return The removed elevator, or {@code null} if none was found */ public @Nullable IndustrialElevator removeIndustrialElevator(BoardLocation location) { @@ -3771,7 +3776,8 @@ public void setTemporaryECMFields(List fields) { * @param currentRound The current game round * @param currentPhase The current game phase */ - public void removeExpiredECMFields(int currentRound, GamePhase currentPhase) { + public void removeExpiredECMFields(int currentRound, + GamePhase currentPhase) { temporaryECMFields.removeIf(field -> field.isExpired(currentRound, currentPhase)); } @@ -3782,9 +3788,7 @@ public void removeExpiredECMFields(int currentRound, GamePhase currentPhase) { * usually unnecessary. * * @param entity Entity we want to get the cached old positions of - * * @return cached coords that contain this entity - * * @see Dropship#setPosition(Coords) */ public synchronized HashSet getEntityPositions(Entity entity) { @@ -3804,7 +3808,8 @@ public synchronized HashSet getEntityPositions(Entity entity) { /** * Updates the map that maps a position to the list of Entity's in that position. */ - public synchronized void updateEntityPositionLookup(Entity e, HashSet oldPositions) { + public synchronized void updateEntityPositionLookup(Entity e, + HashSet oldPositions) { HashSet newPositions = e.getOccupiedCoords(); // Check to see that the position has actually changed if (newPositions.equals(oldPositions)) { @@ -3880,14 +3885,14 @@ private void checkPositionCacheConsistency() { Collections.sort(entitiesInCache); Collections.sort(entitiesInVector); if ((entitiesInCacheCount != entityVectorSize) && - !getPhase().isDeployment() && - !getPhase().isExchange() && - !getPhase().isLounge() && - !getPhase().isInitiativeReport() && - !getPhase().isInitiative()) { + !getPhase().isDeployment() && + !getPhase().isExchange() && + !getPhase().isLounge() && + !getPhase().isInitiativeReport() && + !getPhase().isInitiative()) { logger.warn("Entities vector has {} but pos lookup cache has {} entities!", - inGameTWEntities().size(), - entitiesInCache.size()); + inGameTWEntities().size(), + entitiesInCache.size()); List missingIds = new ArrayList<>(); for (Integer id : entitiesInVector) { if (!entitiesInCache.contains(id)) { @@ -3902,8 +3907,8 @@ private void checkPositionCacheConsistency() { HashSet entityIDs = entityPosLookup.get(coords); if ((entityIDs != null) && !entityIDs.contains(entity.getId())) { logger.warn("Entity {} is in {} however the position cache does not have it in that position!", - entity.getId(), - entity.getPosition()); + entity.getId(), + entity.getPosition()); } } } @@ -3916,9 +3921,9 @@ private void checkPositionCacheConsistency() { HashSet positions = e.getOccupiedCoords(); if (!positions.contains(c)) { logger.warn("Entity Position Cache thinks Entity {} is in {} but the Entity thinks it's in {}", - eId, - c, - e.getPosition()); + eId, + c, + e.getPosition()); } } } @@ -3950,7 +3955,8 @@ public void setBotSettings(Map botSettings) { * @param botPlayerId the reporting bot's player ID * @param dishonoredPlayerIds the player IDs that bot considers dishonored */ - public void setDishonoredPlayers(int botPlayerId, Collection dishonoredPlayerIds) { + public void setDishonoredPlayers(int botPlayerId, + Collection dishonoredPlayerIds) { ensureDishonoredPlayers().put(botPlayerId, new HashSet<>(dishonoredPlayerIds)); } @@ -3964,7 +3970,8 @@ public void setDishonoredPlayers(int botPlayerId, Collection dishonored * @param botPlayerId the bot player that now holds a grudge * @param playerId the player it now considers dishonored */ - public void addDishonoredPlayer(int botPlayerId, int playerId) { + public void addDishonoredPlayer(int botPlayerId, + int playerId) { Map> byBot = ensureDishonoredPlayers(); Set current = byBot.get(botPlayerId); Set updated = (current == null) ? new HashSet<>() : new HashSet<>(current); @@ -3975,12 +3982,12 @@ public void addDishonoredPlayer(int botPlayerId, int playerId) { /** * @param botPlayerId the bot player whose honor opinion is being queried * @param playerId the player who may be dishonored - * * @return true if the bot with player ID {@code botPlayerId} currently considers {@code playerId} dishonored, as - * last reported via {@link megamek.common.net.enums.PacketCommand#PRINCESS_DISHONORED}. False if that bot has - * not reported (e.g. it is not a Princess bot, or the report has not arrived yet). + * last reported via {@link megamek.common.net.enums.PacketCommand#PRINCESS_DISHONORED}. False if that bot has + * not reported (e.g. it is not a Princess bot, or the report has not arrived yet). */ - public boolean isPlayerDishonoredBy(int botPlayerId, int playerId) { + public boolean isPlayerDishonoredBy(int botPlayerId, + int playerId) { Set dishonored = ensureDishonoredPlayers().get(botPlayerId); return (dishonored != null) && dishonored.contains(playerId); } @@ -3995,8 +4002,8 @@ private Map> ensureDishonoredPlayers() { /** * @return which AI implementation the most recent bot connected under each player name was, as an unmodifiable - * view; never {@code null} - games saved before the types were recorded read as an empty map. Record entries - * through {@link #recordBotType(String, AIType)}. + * view; never {@code null} - games saved before the types were recorded read as an empty map. Record entries + * through {@link #recordBotType(String, AIType)}. */ public Map getBotTypes() { return Collections.unmodifiableMap(ensureBotTypes()); @@ -4008,7 +4015,8 @@ public Map getBotTypes() { * @param playerName the bot player's name * @param aiType the AI implementation it reported */ - public void recordBotType(String playerName, AIType aiType) { + public void recordBotType(String playerName, + AIType aiType) { ensureBotTypes().put(playerName, aiType); } @@ -4027,7 +4035,8 @@ private Map ensureBotTypes() { /** * Get a list of all objects on the ground at the given coordinates that can be picked up by the given entity */ - public List getGroundObjects(Coords coords, Entity entity) { + public List getGroundObjects(Coords coords, + Entity entity) { if (!getGroundObjects().containsKey(coords)) { return new ArrayList<>(); } @@ -4079,7 +4088,9 @@ public void setMapSettings(MapSettings mapSettings) { this.mapSettings = mapSettings; } - /** @return The TW Units (Entity) currently in the game. */ + /** + * @return The TW Units (Entity) currently in the game. + */ public List inGameTWEntities() { return filterToEntity(inGameObjects.values()); } @@ -4095,7 +4106,6 @@ public ReportEntry getNewReport(int messageId) { /** * @param playerName The name of the Player to find. - * * @return The ID of the Player with the given name, if there is such a Player. */ public Optional idForPlayerName(String playerName) { @@ -4104,7 +4114,6 @@ public Optional idForPlayerName(String playerName) { /** * @param playerName The name of the Player to find. - * * @return The ID of the Player with the given name, if there is such a Player. */ public Optional playerForPlayerName(String playerName) { @@ -4115,7 +4124,6 @@ public Optional playerForPlayerName(String playerName) { * Returns the Building at the given location, if any. Shortcut to Board.getBuildingAt(). * * @param boardLocation The location to check - * * @return The building at the location, if any */ public Optional getBuildingAt(@Nullable BoardLocation boardLocation) { @@ -4127,10 +4135,10 @@ public Optional getBuildingAt(@Nullable BoardLocation boardLocation) * * @param boardId The board ID * @param coords The position on the board - * * @return The building at the location, if any */ - public Optional getBuildingAt(@Nullable Coords coords, int boardId) { + public Optional getBuildingAt(@Nullable Coords coords, + int boardId) { if (hasBoardLocation(coords, boardId)) { return Optional.ofNullable(getBoard(boardId).getBuildingAt(coords)); } else { @@ -4144,17 +4152,17 @@ public Optional getBuildingAt(@Nullable Coords coords, int boardId) { * * @param boardId The board ID * @param coords The position on the board - * * @return True when there is a building */ - public boolean hasBuildingAt(@Nullable Coords coords, int boardId) { + public boolean hasBuildingAt(@Nullable Coords coords, + int boardId) { return getBuildingAt(coords, boardId).isPresent(); } /** * @return True if the current game round counts as a round in which spaceborne units may act; when this game has - * only space board(s), this is true for every game round; if it has a mixture of space and other boards, only - * every 7th game round is a space round (TW p.78) + * only space board(s), this is true for every game round; if it has a mixture of space and other boards, only + * every 7th game round is a space round (TW p.78) */ public boolean isSpaceRound() { //FIXME only for testing, so that space units can act every round @@ -4165,8 +4173,8 @@ public boolean isSpaceRound() { /** * @return True if the current game round counts as a round in which non-spaceborne units may act; when this game - * has no space boards, this is true for every game round; if it has a mixture of space and other boards, six - * atmospheric game rounds are followed by one space round. (TW p.78) + * has no space boards, this is true for every game round; if it has a mixture of space and other boards, six + * atmospheric game rounds are followed by one space round. (TW p.78) */ public boolean isAtmosphericRound() { return !hasSpaceAndAtmosphericBoards() || (getRoundCount() % 7 != 0) || getRoundCount() == 0; diff --git a/megamek/src/megamek/common/moves/DeployStep.java b/megamek/src/megamek/common/moves/DeployStep.java new file mode 100644 index 00000000000..8cbd251bc77 --- /dev/null +++ b/megamek/src/megamek/common/moves/DeployStep.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + * + * NOTICE: The MegaMek organization is a non-profit group of volunteers + * creating free software for the BattleTech community. + * + * MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks + * of The Topps Company, Inc. All Rights Reserved. + * + * Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of + * InMediaRes Productions, LLC. + * + * MechWarrior Copyright Microsoft Corporation. MegaMek was created under + * Microsoft's "Game Content Usage Rules" + * and it is not endorsed by or + * affiliated with Microsoft. + */ + +package megamek.common.moves; + +import java.util.EnumSet; +import java.util.Set; + +import megamek.common.enums.MoveStepType; +import megamek.common.game.Game; +import megamek.common.pathfinder.CachedEntityState; +import megamek.common.units.Entity; + +/** + * This class handles the deployment step for Walk On Deployment. + */ +class DeployStep implements PhasePass { + + private static final EnumSet TYPES = EnumSet.of(MoveStepType.DEPLOY); + + @Override + public Set getTypesOfInterest() { + return TYPES; + } + + @Override + public PhasePassResult preCompilation(final MoveStep moveStep, + final Game game, + final Entity entity, + MoveStep prev, + final CachedEntityState cachedEntityState) { + // Aerospace units need a clean slate to move + if (entity.isAero()) { + moveStep.setMp(0); + return PhasePassResult.BREAK; + } + return PhasePassResult.COMPILE; + } +} diff --git a/megamek/src/megamek/common/moves/MovePath.java b/megamek/src/megamek/common/moves/MovePath.java index 96d940a3536..e96907b9be9 100644 --- a/megamek/src/megamek/common/moves/MovePath.java +++ b/megamek/src/megamek/common/moves/MovePath.java @@ -63,7 +63,9 @@ /** * Holds movement path for an entity. */ -public class MovePath implements Cloneable, Serializable { +public class MovePath implements Cloneable, + Serializable { + private static final MMLogger LOGGER = MMLogger.create(MovePath.class); @Serial @@ -114,24 +116,27 @@ public void setEntity(Entity entity) { /** * Generates a new, empty, movement path object. */ - public MovePath(final Game game, final Entity entity) { + public MovePath(final Game game, + final Entity entity) { this(game, entity, null); } /** * Generates a new, empty, movement path object. */ - public MovePath(Game game, Entity entity, @Nullable Coords waypoint) { + public MovePath(Game game, + Entity entity, + @Nullable Coords waypoint) { setEntity(entity); setGame(game); this.waypoint = waypoint; // Do we care about gravity when adding steps? gravity = game.getPlanetaryConditions().getGravity(); gravityConcern = ((gravity > 1.0F && cachedEntityState.getJumpMPNoGravity() > 0 || - (gravity < 1.0F && - cachedEntityState.getRunMP() > cachedEntityState.getRunMPNoGravity())) && - game.getBoard(entity.getBoardId()).isGround() && - !entity.isAirborne()); + (gravity < 1.0F && + cachedEntityState.getRunMP() > cachedEntityState.getRunMPNoGravity())) && + game.getBoard(entity.getBoardId()).isGround() && + !entity.isAirborne()); } /** @@ -207,40 +212,52 @@ public MovePath addStep(final MoveStepType type) { * @param target the Targetable object that is the target of this step. For example, the enemy being * charged. */ - public MovePath addStep(final MoveStepType type, final Targetable target) { + public MovePath addStep(final MoveStepType type, + final Targetable target) { return addStep(new MoveStep(this, type, target)); } - public MovePath addStep(final MoveStepType type, final Targetable target, final Coords pos) { + public MovePath addStep(final MoveStepType type, + final Targetable target, + final Coords pos) { return addStep(new MoveStep(this, type, target, pos)); } - public MovePath addStep(final MoveStepType type, final int additionalIntData) { + public MovePath addStep(final MoveStepType type, + final int additionalIntData) { return addStep(new MoveStep(this, type, additionalIntData)); } - public MovePath addStep(final MoveStepType type, final int recover, final int mineToLay) { + public MovePath addStep(final MoveStepType type, + final int recover, + final int mineToLay) { return addStep(new MoveStep(this, type, recover, mineToLay)); } - public MovePath addStep(MoveStepType type, TreeMap> targets) { + public MovePath addStep(MoveStepType type, + TreeMap> targets) { return addStep(new MoveStep(this, type, targets)); } - public MovePath addStep(final MoveStepType type, final boolean noCost) { + public MovePath addStep(final MoveStepType type, + final boolean noCost) { return addStep(new MoveStep(this, type, noCost)); } - public MovePath addStep(final MoveStepType type, final Map additionalIntData) { + public MovePath addStep(final MoveStepType type, + final Map additionalIntData) { return addStep(new MoveStep(this, type, additionalIntData)); } - public MovePath addStep(final MoveStepType type, final boolean noCost, final boolean isManeuver, - final int maneuverType) { + public MovePath addStep(final MoveStepType type, + final boolean noCost, + final boolean isManeuver, + final int maneuverType) { return addStep(new MoveStep(this, type, noCost, isManeuver, maneuverType)); } - public MovePath addStep(final MoveStepType type, final Minefield mf) { + public MovePath addStep(final MoveStepType type, + final Minefield mf) { return addStep(new MoveStep(this, type, mf)); } @@ -250,24 +267,25 @@ public void addManeuver(final int manType) { public boolean canShift() { return ((getEntity() instanceof QuadMek - // QuadVee cannot shift in vee mode - && - !(getEntity() instanceof QuadVee && - (entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE || - getEntity().isConvertingNow()))) - // Maneuvering Ace allows Bipeds and VTOLs moving at cruise - // speed to perform a lateral shift - || - (getEntity().isUsingManAce() && - ((getEntity() instanceof BipedMek) || - ((getEntity() instanceof VTOL) && - (getMpUsed() <= getCachedEntityState().getWalkMP())))) || - (game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_ADVANCED_MANEUVERS) && - getEntity() instanceof Tank && - (getEntity().getMovementMode() == EntityMovementMode.VTOL || - getEntity().getMovementMode() == EntityMovementMode.HOVER)) || - ((getEntity() instanceof TripodMek) && (((Mek) getEntity()).countBadLegs() == 0))) && - !isJumping(); + // QuadVee cannot shift in vee mode + && + !(getEntity() instanceof QuadVee && + (entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE || + getEntity().isConvertingNow()))) + // Maneuvering Ace allows Bipeds and VTOLs moving at cruise + // speed to perform a lateral shift + || + (getEntity().isUsingManAce() && + ((getEntity() instanceof BipedMek) || + ((getEntity() instanceof VTOL) && + (getMpUsed() <= getCachedEntityState().getWalkMP())))) || + (game.getOptions() + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_ADVANCED_MANEUVERS) && + getEntity() instanceof Tank && + (getEntity().getMovementMode() == EntityMovementMode.VTOL || + getEntity().getMovementMode() == EntityMovementMode.HOVER)) || + ((getEntity() instanceof TripodMek) && (((Mek) getEntity()).countBadLegs() == 0))) && + !isJumping(); } /** @@ -276,9 +294,9 @@ public boolean canShift() { */ public boolean containsLateralShift() { return this.contains(MoveStepType.LATERAL_LEFT) || - this.contains(MoveStepType.LATERAL_RIGHT) || - this.contains(MoveStepType.LATERAL_LEFT_BACKWARDS) || - this.contains(MoveStepType.LATERAL_RIGHT_BACKWARDS); + this.contains(MoveStepType.LATERAL_RIGHT) || + this.contains(MoveStepType.LATERAL_LEFT_BACKWARDS) || + this.contains(MoveStepType.LATERAL_RIGHT_BACKWARDS); } @Deprecated(since = "0.51.0", forRemoval = true) @@ -320,7 +338,8 @@ public Set getCoordsSet() { * Initializes a step as part of this movement path. Then adds it to the list. * */ - protected MovePath addStep(final MoveStep step, boolean compile) { + protected MovePath addStep(final MoveStep step, + boolean compile) { if (step == null) { LOGGER.error("", new RuntimeException("Received NULL MoveStep")); return this; @@ -408,30 +427,33 @@ protected MovePath addStep(final MoveStep step, boolean compile) { break; } s.setDanger(s.isDanger() || - Compute.isPilotingSkillNeeded(game, - entity.getId(), - prevStep.getPosition(), - s.getPosition(), - lastStep.getMovementType(true), - prevStep.isTurning(), - prevStep.isPavementStep(), - prevStep.getElevation(), - s.getElevation(), - s)); + Compute.isPilotingSkillNeeded(game, + entity.getId(), + prevStep.getPosition(), + s.getPosition(), + lastStep.getMovementType(true), + prevStep.isTurning(), + prevStep.isPavementStep(), + prevStep.getElevation(), + s.getElevation(), + s)); s.setPastDanger(s.isPastDanger() || s.isDanger()); prevStep = s; } } - - if (steps.size() > 1 && entity instanceof Mek && ((Mek) entity).countBadLegs() > 0 && Game.rulesManager instanceof CoreRulesManager) { + + if (steps.size() > 1 + && entity instanceof Mek + && ((Mek) entity).countBadLegs() > 0 + && Game.rulesManager instanceof CoreRulesManager) { MoveStep lastStep = steps.getLast(); MoveStep prevStep = steps.getFirst(); - if ((lastStep.getPosition().equals(prevStep.getPosition()) - || lastStep.getMovementType(true) != EntityMovementType.MOVE_WALK) - && !(entity instanceof QuadMek && ((QuadMek) entity).countBadLegs() < 3)) { + if ((lastStep.getPosition().equals(prevStep.getPosition()) + || lastStep.getMovementType(true) != EntityMovementType.MOVE_WALK) + && !(entity instanceof QuadMek && ((QuadMek) entity).countBadLegs() < 3)) { for (MoveStep s : steps) { - s.setDanger(true); - s.setPastDanger(s.isPastDanger() || s.isDanger()); + s.setDanger(true); + s.setPastDanger(s.isPastDanger() || s.isDanger()); } } } @@ -472,10 +494,12 @@ protected MovePath addStep(final MoveStep step, boolean compile) { } /** - * Perform all the possible "is this illegal" checks. - * Short-circuits to omit unnecessary checks once the move has been declared illegal + * Perform all the possible "is this illegal" checks. Short-circuits to omit unnecessary checks once the move has + * been declared illegal */ - private void performIllegalCheck(MoveStep step, Coords start, Coords land) { + private void performIllegalCheck(MoveStep step, + Coords start, + Coords land) { // Ensure that the appropriate steps to flee from stuck or prone were taken if (step.getType() == MoveStepType.FLEE) { if (!(getEntity().canFleeInState())) { @@ -483,15 +507,13 @@ private void performIllegalCheck(MoveStep step, Coords start, Coords land) { // A stuck and prone entity has no way to get up and flee during the move path step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; - } - else if (getEntity().isStuck()) { + } else if (getEntity().isStuck()) { // A stuck entity has to jump to move during the move path if (!(contains(MoveStepType.START_JUMP))) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; } - } - else if (getEntity().isProne()) { + } else if (getEntity().isProne()) { // A prone entity can flee if it succeeds in getting up during the move path // The GET_UP step is cleared from the containedStepTypes when reaching this point boolean emptyStepTypes = containedStepTypes.isEmpty(); @@ -505,8 +527,7 @@ else if (getEntity().isProne()) { if (emptyStepTypes) { containedStepTypes.clear(); } - } - else { + } else { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; } @@ -534,10 +555,11 @@ else if (getEntity().isProne()) { // if we're jumping without a mechanical jump booster (?) // or we're acting like a spheroid DropShip in the atmosphere if ((isJumping() && !contains(MoveStepType.JUMP_MEK_MECHANICAL_BOOSTER)) || - (Compute.useSpheroidAtmosphere(game, getEntity()) && (step.getType() != MoveStepType.HOVER))) { + (Compute.useSpheroidAtmosphere(game, getEntity()) && (step.getType() != MoveStepType.HOVER))) { int distance = start.distance(land); - - if (step.isThisStepBackwards() || (step.getDistance() > distance)) { + boolean isWalkOn = (this.contains(MoveStepType.DEPLOY) || step.getType() == MoveStepType.DEPLOY); + boolean distanceInvalid = ((!isWalkOn && (step.getDistance() > distance)) || (isWalkOn && (step.getDistance() > (distance + 1)))); + if (step.isThisStepBackwards() || distanceInvalid) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; } @@ -550,9 +572,9 @@ else if (getEntity().isProne()) { int building = destHex.terrainLevel(Terrains.BLDG_ELEV); if (building > 0) { int maxElevation = (entity.getJumpMP() + - entity.getElevation() + - game.getBoard(entity.getBoardId()).getHex(entity.getPosition()).getLevel()) - - destHex.getLevel(); + entity.getElevation() + + game.getBoard(entity.getBoardId()).getHex(entity.getPosition()).getLevel()) - + destHex.getLevel(); if (building > maxElevation) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; @@ -564,9 +586,9 @@ else if (getEntity().isProne()) { if (isJumping() && (entity instanceof Infantry)) { Hex destHex = game.getBoard(step.getBoardId()).getHex(step.getPosition()); int maxElevation = (entity.getJumpMP() + - entity.getElevation() + - game.getBoard(entity.getBoardId()).getHex(entity.getPosition()).getLevel()) - - destHex.getLevel(); + entity.getElevation() + + game.getBoard(entity.getBoardId()).getHex(entity.getPosition()).getLevel()) - + destHex.getLevel(); if (step.getElevation() > maxElevation) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; @@ -581,7 +603,7 @@ else if (getEntity().isProne()) { // If JumpShips turn, they can't do anything else if (entity.isJumpShip() && !step.isFirstStep() && - (contains(MoveStepType.TURN_LEFT) || contains(MoveStepType.TURN_RIGHT))) { + (contains(MoveStepType.TURN_LEFT) || contains(MoveStepType.TURN_RIGHT))) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; } @@ -626,11 +648,11 @@ else if (getEntity().isProne()) { // and we are not exceeding the maximum five hexes. if (last.isStrafingStep()) { if (step.getFacing() != last.getFacing() || - (step.getElevation() + getGame().getBoard(step.getBoardId()).getHex(step.getPosition()).floor() != - last.getElevation() + getGame().getBoard(step.getBoardId()) - .getHex(last.getPosition()) - .floor()) || - steps.stream().filter(MoveStep::isStrafingStep).count() > 5) { + (step.getElevation() + getGame().getBoard(step.getBoardId()).getHex(step.getPosition()).floor() != + last.getElevation() + getGame().getBoard(step.getBoardId()) + .getHex(last.getPosition()) + .floor()) || + steps.stream().filter(MoveStep::isStrafingStep).count() > 5) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; } @@ -650,10 +672,10 @@ else if (getEntity().isProne()) { // VTOLs using maneuvering ace to make lateral shifts can't flank // unless using controlled sideslip if (containsLateralShift() && - getEntity().isUsingManAce() && - (getEntity() instanceof VTOL) && - getMpUsed() > getCachedEntityState().getWalkMP() && - !game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_ADVANCED_MANEUVERS)) { + getEntity().isUsingManAce() && + (getEntity() instanceof VTOL) && + getMpUsed() > getCachedEntityState().getWalkMP() && + !game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_ADVANCED_MANEUVERS)) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; } @@ -676,17 +698,17 @@ else if (getEntity().isProne()) { // If using TacOps reverse gear option, cannot mix forward and backward movement // in the same round except VTOLs. if (game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_REVERSE_GEAR) && - ((entity instanceof Tank && !(entity instanceof VTOL)) || - (entity instanceof QuadVee && entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE))) { + ((entity instanceof Tank && !(entity instanceof VTOL)) || + (entity instanceof QuadVee && entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE))) { boolean fwd = false; boolean rev = false; for (MoveStep s : steps) { fwd |= s.getType() == MoveStepType.FORWARDS || - s.getType() == MoveStepType.LATERAL_LEFT || - s.getType() == MoveStepType.LATERAL_RIGHT; + s.getType() == MoveStepType.LATERAL_LEFT || + s.getType() == MoveStepType.LATERAL_RIGHT; rev |= s.getType() == MoveStepType.BACKWARDS || - s.getType() == MoveStepType.LATERAL_LEFT_BACKWARDS || - s.getType() == MoveStepType.LATERAL_RIGHT_BACKWARDS; + s.getType() == MoveStepType.LATERAL_LEFT_BACKWARDS || + s.getType() == MoveStepType.LATERAL_RIGHT_BACKWARDS; } if (fwd && rev) { @@ -700,11 +722,14 @@ else if (getEntity().isProne()) { } } - public void compile(final Game g, final Entity en) { + public void compile(final Game g, + final Entity en) { compile(g, en, true); } - public void compile(final Game g, final Entity en, boolean clip) { + public void compile(final Game g, + final Entity en, + boolean clip) { setGame(g); setEntity(en); final Vector temp = new Vector<>(steps); @@ -752,7 +777,7 @@ public void compile(final Game g, final Entity en, boolean clip) { for (MoveStep step : steps) { if (!left) { if (!step.getPosition().equals(getEntity().getPosition()) || - !(step.getElevation() == getEntity().getElevation())) { + !(step.getElevation() == getEntity().getElevation())) { // we left the location left = true; continue; @@ -761,7 +786,7 @@ public void compile(final Game g, final Entity en, boolean clip) { } if (!returned) { if (step.getPosition().equals(getEntity().getPosition()) && - (step.getElevation() == getEntity().getElevation())) { + (step.getElevation() == getEntity().getElevation())) { // we returned to the location returned = true; continue; @@ -770,10 +795,10 @@ public void compile(final Game g, final Entity en, boolean clip) { } // We've returned, only following 5 types are legal if ((step.getType() != MoveStepType.TURN_LEFT) && - (step.getType() != MoveStepType.TURN_RIGHT) && - (step.getType() != MoveStepType.UNLOAD) && - (step.getType() != MoveStepType.DISCONNECT) && - (step.getType() != MoveStepType.GO_PRONE)) { + (step.getType() != MoveStepType.TURN_RIGHT) && + (step.getType() != MoveStepType.UNLOAD) && + (step.getType() != MoveStepType.DISCONNECT) && + (step.getType() != MoveStepType.GO_PRONE)) { // we only need to identify the first illegal move step.setMovementType(EntityMovementType.MOVE_ILLEGAL); break; @@ -856,11 +881,13 @@ public boolean isValidPositionForBrace(MoveStep step) { /** * Given a set of coordinates and a facing, is the entity taking this path in a valid position to execute a brace? */ - public boolean isValidPositionForBrace(Coords coords, int boardId, int facing) { + public boolean isValidPositionForBrace(Coords coords, + int boardId, + int facing) { // situation: can't brace off of jumps; can't brace if you're not a mek with // arms/protomek if (isJumping() || contains(MoveStepType.GO_PRONE) || !getEntity().canBrace() - || !game.hasBoardLocation(coords, boardId)) { + || !game.hasBoardLocation(coords, boardId)) { return false; } @@ -881,11 +908,11 @@ public boolean isValidPositionForBrace(Coords coords, int boardId, int facing) { Hex currentHex = board.getHex(coords); int curHexLevel = currentHex.containsAnyTerrainOf(Terrains.BLDG_ELEV, Terrains.BRIDGE_ELEV) ? - currentHex.ceiling() : - currentHex.floor(); + currentHex.ceiling() : + currentHex.floor(); int nextHexLevel = nextHex.containsAnyTerrainOf(Terrains.BLDG_ELEV, Terrains.BRIDGE_ELEV) ? - nextHex.ceiling() : - nextHex.floor(); + nextHex.ceiling() : + nextHex.floor(); return nextHexLevel == curHexLevel + 1; } @@ -920,7 +947,6 @@ private void regenerateStepTypes() { * Check for any of the specified type of step in the path * * @param type The step type to check for - * * @return Whether this step type is contained within this path */ public boolean contains(final MoveStepType type) { @@ -965,7 +991,7 @@ public boolean hasActiveSupercharger() { /** * @return the final coordinates if a mek were to perform all the steps in this path, or null if there's an issue - * with determining the coords + * with determining the coords */ public @Nullable Coords getFinalCoords() { if (getGame().useVectorMove()) { @@ -1229,8 +1255,8 @@ private void transformLateralShift() { final MoveStep step3 = getStep(index + 2); if (step1.oppositeTurn(step3) && - ((step2.getType() == MoveStepType.BACKWARDS) || - (step2.getType() == MoveStepType.FORWARDS))) { + ((step2.getType() == MoveStepType.BACKWARDS) || + (step2.getType() == MoveStepType.FORWARDS))) { final MoveStepType stepType = step1.getType(); final MoveStepType direction = step2.getType(); // remove all old steps @@ -1246,7 +1272,8 @@ private void transformLateralShift() { /** * Returns the lateral shift that corresponds to the turn direction */ - public static MoveStepType lateralShiftForTurn(final MoveStepType turn, final MoveStepType direction) { + public static MoveStepType lateralShiftForTurn(final MoveStepType turn, + final MoveStepType direction) { if (direction == MoveStepType.FORWARDS) { return switch (turn) { case TURN_LEFT -> MoveStepType.LATERAL_LEFT; @@ -1276,7 +1303,8 @@ static MoveStepType turnForLateralShift(final MoveStepType shift) { * Returns the direction (either MovePath.MoveStepType.TURN_LEFT or MoveStepType.TURN_RIGHT) that the destination * facing lies in. */ - public static MoveStepType getDirection(final int facing, final int destFacing) { + public static MoveStepType getDirection(final int facing, + final int destFacing) { final int rotate = (destFacing + (6 - facing)) % 6; return rotate >= 3 ? MoveStepType.TURN_LEFT : MoveStepType.TURN_RIGHT; } @@ -1284,7 +1312,8 @@ public static MoveStepType getDirection(final int facing, final int destFacing) /** * Returns the adjusted facing, given the start facing. */ - public static int getAdjustedFacing(final int facing, final MoveStepType movement) { + public static int getAdjustedFacing(final int facing, + final MoveStepType movement) { if (movement == MoveStepType.TURN_RIGHT) { return (facing + 1) % 6; } else if (movement == MoveStepType.TURN_LEFT) { @@ -1353,7 +1382,10 @@ public boolean isChangingMode() { * @param type the type of movement step required. * @param direction the direction of movement. */ - public void findSimplePathTo(Coords dest, MoveStepType type, int direction, int facing) { + public void findSimplePathTo(Coords dest, + MoveStepType type, + int direction, + int facing) { Coords currStep = getFinalCoords(); Coords nextStep = currStep.translated(direction); while (dest.distance(nextStep) < dest.distance(currStep)) { @@ -1397,13 +1429,14 @@ public void findSimplePathTo(Coords dest, MoveStepType type, int direction, int * @param dest the destination Coords of the move. * @param type the type of movement step required. */ - public void findPathTo(final Coords dest, final MoveStepType type) { + public void findPathTo(final Coords dest, + final MoveStepType type) { final int timeLimit = PreferenceManager.getClientPreferences().getMaxPathfinderTime(); ShortestPathFinder pf = ShortestPathFinder.newInstanceOfAStar(dest, type, game, getFinalBoardId()); StopConditionTimeout timeoutCondition = new StopConditionTimeout<>( - timeLimit); + timeLimit); pf.addStopCondition(timeoutCondition); pf.run(clone()); @@ -1416,7 +1449,7 @@ public void findPathTo(final Coords dest, final MoveStepType type) { * to the target and greedily extend it. */ MovePath bestMp = Collections.min(pf.getAllComputedPaths().values(), - new MovePathGreedyComparator(dest)); + new MovePathGreedyComparator(dest)); pf = ShortestPathFinder.newInstanceOfGreedy(dest, type, game); pf.run(bestMp); finPath = pf.getComputedPath(dest); @@ -1431,9 +1464,9 @@ public void findPathTo(final Coords dest, final MoveStepType type) { this.steps = finPath.steps; } else { LOGGER.error("Unable to find a path to the destination hex! \tMoving {}from {} to {}", - getEntity(), - getFinalCoords(), - dest); + getEntity(), + getFinalCoords(), + dest); } } @@ -1452,9 +1485,9 @@ public boolean isMoveLegal() { } } else { if ((getLastStep().getVelocityLeft() > 0) && - !getGame().useVectorMove() && - !(getLastStep().getType() == MoveStepType.FLEE || - getLastStep().getType() == MoveStepType.EJECT)) { + !getGame().useVectorMove() && + !(getLastStep().getType() == MoveStepType.FLEE || + getLastStep().getType() == MoveStepType.EJECT)) { return false; } } @@ -1480,7 +1513,9 @@ public boolean isMoveLegal() { * @param timeLimit the maximum int number of milliseconds to take hunting for an ideal path. */ @SuppressWarnings("unused") - private void notSoLazyPathfinder(final Coords dest, final MoveStepType type, final int timeLimit) { + private void notSoLazyPathfinder(final Coords dest, + final MoveStepType type, + final int timeLimit) { final int MAX_CANDIDATES = 100; final long endTime = java.lang.System.currentTimeMillis() + timeLimit; @@ -1521,9 +1556,9 @@ private void notSoLazyPathfinder(final Coords dest, final MoveStepType type, fin // Get next possible steps // Evaluate possible next steps for (MovePath expandedPath : candidatePath.getNextMoves(step == MoveStepType.BACKWARDS, - step == MoveStepType.FORWARDS)) { + step == MoveStepType.FORWARDS)) { if (expandedPath.getLastStep() - .isMovementPossible(getGame(), startingPos, startingElev, getCachedEntityState())) { + .isMovementPossible(getGame(), startingPos, startingElev, getCachedEntityState())) { if (discovered.containsKey(expandedPath.getKey())) { continue; @@ -1543,7 +1578,7 @@ private void notSoLazyPathfinder(final Coords dest, final MoveStepType type, fin MovePath expandedPath = candidatePath.clone(); expandedPath.addStep(type); if (expandedPath.getLastStep() - .isMovementPossible(getGame(), startingPos, startingElev, getCachedEntityState())) { + .isMovementPossible(getGame(), startingPos, startingElev, getCachedEntityState())) { if (discovered.containsKey(expandedPath.getKey())) { continue; @@ -1587,7 +1622,8 @@ private void notSoLazyPathfinder(final Coords dest, final MoveStepType type, fin * @param dest the destination Coords of the move. * @param type the type of movement step required. */ - private void lazyPathfinder(final Coords dest, final MoveStepType type) { + private void lazyPathfinder(final Coords dest, + final MoveStepType type) { MoveStepType step = MoveStepType.FORWARDS; if (type == MoveStepType.BACKWARDS) { step = MoveStepType.BACKWARDS; @@ -1600,14 +1636,14 @@ private void lazyPathfinder(final Coords dest, final MoveStepType type) { while (!getFinalCoords().equals(subDest)) { // adjust facing rotatePathfinder((getFinalCoords().direction(subDest) + (step == MoveStepType.BACKWARDS ? 3 : 0)) % 6, - false, - ManeuverType.MAN_NONE); + false, + ManeuverType.MAN_NONE); // step forwards addStep(step); } rotatePathfinder((getFinalCoords().direction(dest) + (step == MoveStepType.BACKWARDS ? 3 : 0)) % 6, - false, - ManeuverType.MAN_NONE); + false, + ManeuverType.MAN_NONE); if (!dest.equals(getFinalCoords())) { addStep(type); } @@ -1617,7 +1653,8 @@ private void lazyPathfinder(final Coords dest, final MoveStepType type) { * Returns a list of possible moves that result in a facing/position/(jumping|prone) change, special steps (mine * clearing and such) must be handled elsewhere. */ - public List getNextMoves(boolean backward, boolean forward) { + public List getNextMoves(boolean backward, + boolean forward) { final ArrayList result = new ArrayList<>(); final MoveStep last = getLastStep(); @@ -1668,13 +1705,13 @@ public List getNextMoves(boolean backward, boolean forward) { result.add(clone().addStep(MoveStepType.LATERAL_LEFT)); } if (backward && - (!forward || - ((last == null) || (last.getType() != MoveStepType.LATERAL_LEFT_BACKWARDS)))) { + (!forward || + ((last == null) || (last.getType() != MoveStepType.LATERAL_LEFT_BACKWARDS)))) { result.add(clone().addStep(MoveStepType.LATERAL_RIGHT_BACKWARDS)); } if (backward && - (!forward || - ((last == null) || (last.getType() != MoveStepType.LATERAL_RIGHT_BACKWARDS)))) { + (!forward || + ((last == null) || (last.getType() != MoveStepType.LATERAL_RIGHT_BACKWARDS)))) { result.add(clone().addStep(MoveStepType.LATERAL_LEFT_BACKWARDS)); } } @@ -1716,7 +1753,9 @@ protected void copyFields(MovePath copy) { /** * Rotate from the current facing to the destination facing. */ - public void rotatePathfinder(final int destFacing, final boolean isManeuver, int maneuverType) { + public void rotatePathfinder(final int destFacing, + final boolean isManeuver, + int maneuverType) { while (getFinalFacing() != destFacing) { final MoveStepType stepType = getDirection(getFinalFacing(), destFacing); addStep(stepType, isManeuver, isManeuver, maneuverType); @@ -1725,13 +1764,13 @@ public void rotatePathfinder(final int destFacing, final boolean isManeuver, int /** * @return true if a jump using mechanical jump boosters would cause falling damage. Mechanical jump boosters are - * only designed to handle the stress of falls from a height equal to their jumpMP; if a jump has a fall that - * is further than the jumpMP of the unit, fall damage applies. + * only designed to handle the stress of falls from a height equal to their jumpMP; if a jump has a fall that + * is further than the jumpMP of the unit, fall damage applies. */ public boolean shouldMechanicalJumpCauseFallDamage() { return isJumping() && - contains(MoveStepType.JUMP_MEK_MECHANICAL_BOOSTER) && - (getJumpMaxElevationChange() > getEntity().getMechanicalJumpBoosterMP()); + contains(MoveStepType.JUMP_MEK_MECHANICAL_BOOSTER) && + (getJumpMaxElevationChange() > getEntity().getMechanicalJumpBoosterMP()); } /** @@ -1777,7 +1816,6 @@ public boolean willCrushBuildings() { * @param includeMovePathHexes Whether to include the hexes plotted in this MovePath in the total distance moved. * This should be true when plotting movement in the client and false when the server * checks for automatic landing at the end of movement. - * * @return whether the unit is an airborne WiGE that must land at the end of movement. */ public boolean automaticWiGELanding(boolean includeMovePathHexes) { @@ -1787,8 +1825,8 @@ public boolean automaticWiGELanding(boolean includeMovePathHexes) { // A LAM converting from AirMek to Mek mode automatically lands at the end of // movement. if ((getEntity() instanceof LandAirMek) && - (((LandAirMek) getEntity()).getConversionModeFor(getFinalConversionMode()) == - LandAirMek.CONV_MODE_MEK)) { + (((LandAirMek) getEntity()).getConversionModeFor(getFinalConversionMode()) == + LandAirMek.CONV_MODE_MEK)) { if (getLastStep() != null) { return getLastStep().getClearance() > 0; } else { @@ -1814,9 +1852,9 @@ public boolean automaticWiGELanding(boolean includeMovePathHexes) { return false; } if (getEntity().wigeLiftoffHover() || - steps.stream() - .map(MoveStep::getType) - .anyMatch(st -> st == MoveStepType.UP || st == MoveStepType.HOVER)) { + steps.stream() + .map(MoveStep::getType) + .anyMatch(st -> st == MoveStepType.UP || st == MoveStepType.HOVER)) { return false; } if (getLastStep() != null) { @@ -1837,26 +1875,29 @@ public boolean isAllUnderwater(Game game) { } } return game.getBoard(entity.getBoardId()).getHex(entity.getPosition()).containsTerrain(Terrains.WATER) - && entity.relHeight() < 0; + && entity.relHeight() < 0; } protected static class MovePathComparator implements Comparator { + private final Coords destination; boolean backward; - public MovePathComparator(final Coords destination, final boolean backward) { + public MovePathComparator(final Coords destination, + final boolean backward) { this.destination = destination; this.backward = backward; } @Override - public int compare(final MovePath first, final MovePath second) { + public int compare(final MovePath first, + final MovePath second) { final int firstDist = first.getMpUsed() + - first.getFinalCoords().distance(destination) + - getFacingDiff(first); + first.getFinalCoords().distance(destination) + + getFacingDiff(first); final int secondDist = second.getMpUsed() + - second.getFinalCoords().distance(destination) + - getFacingDiff(second); + second.getFinalCoords().distance(destination) + + getFacingDiff(second); return firstDist - secondDist; } @@ -1868,7 +1909,7 @@ private int getFacingDiff(final MovePath first) { return 0; } int firstFacing = Math.abs(((first.getFinalCoords().direction(destination) + (backward ? 3 : 0)) % 6) - - first.getFinalFacing()); + first.getFinalFacing()); if (firstFacing > 3) { firstFacing = 6 - firstFacing; } @@ -1917,9 +1958,9 @@ public int countMp(boolean jumping) { int mp = 0; for (MoveStep step : steps) { if (jumping && - (step.getType() != MoveStepType.TURN_LEFT) && - (step.getType() != MoveStepType.TURN_RIGHT) && - (step.getType() != MoveStepType.JUMP_MEK_MECHANICAL_BOOSTER)) { + (step.getType() != MoveStepType.TURN_LEFT) && + (step.getType() != MoveStepType.TURN_RIGHT) && + (step.getType() != MoveStepType.JUMP_MEK_MECHANICAL_BOOSTER)) { mp += step.getMp(); } else if (!jumping) { mp += step.getMp(); @@ -1928,7 +1969,8 @@ public int countMp(boolean jumping) { return mp; } - public void addSteps(Vector path, boolean compile) { + public void addSteps(Vector path, + boolean compile) { for (MoveStep step : path) { addStep(step, compile); } @@ -2076,23 +2118,23 @@ public boolean equals(Object object) { } return new EqualsBuilder().append(entity, movePath.entity) - .append(careful, movePath.careful) - .append(gravityConcern, movePath.gravityConcern) - .append(gravity, movePath.gravity) - .append(steps, movePath.steps) - .append(containedStepTypes, movePath.containedStepTypes) - .isEquals(); + .append(careful, movePath.careful) + .append(gravityConcern, movePath.gravityConcern) + .append(gravity, movePath.gravity) + .append(steps, movePath.steps) + .append(containedStepTypes, movePath.containedStepTypes) + .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(17, 37).append(entity) - .append(steps) - .append(containedStepTypes) - .append(careful) - .append(gravityConcern) - .append(gravity) - .toHashCode(); + .append(steps) + .append(containedStepTypes) + .append(careful) + .append(gravityConcern) + .append(gravity) + .toHashCode(); } public void setFlightPathHex(BoardLocation flightPathHex) { diff --git a/megamek/src/megamek/common/moves/MoveStep.java b/megamek/src/megamek/common/moves/MoveStep.java index 3bc69921fc4..222473a4f76 100644 --- a/megamek/src/megamek/common/moves/MoveStep.java +++ b/megamek/src/megamek/common/moves/MoveStep.java @@ -35,8 +35,8 @@ import java.io.Serial; import java.io.Serializable; -import java.lang.System; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -69,7 +69,6 @@ import megamek.common.pathfinder.CachedEntityState; import megamek.common.planetaryConditions.Atmosphere; import megamek.common.planetaryConditions.PlanetaryConditions; -import megamek.common.rules.core.CoreRulesManager; import megamek.common.units.*; import megamek.logging.MMLogger; @@ -80,6 +79,7 @@ * @since Aug 28, 2003 */ public class MoveStep implements Serializable { + private static final MMLogger LOGGER = MMLogger.create(MoveStep.class); @Serial @@ -127,7 +127,9 @@ public class MoveStep implements Serializable { private int mineToLay = -1; - /** Distance for Combat Vehicle Escape Pod launch (0-4 hexes per TO:AUE p.121) */ + /** + * Distance for Combat Vehicle Escape Pod launch (0-4 hexes per TO:AUE p.121) + */ private int escapePodDistance = 4; /** @@ -255,7 +257,8 @@ public class MoveStep implements Serializable { * * @param type - should match one of the MovePath constants, but this is not currently checked. */ - public MoveStep(MovePath path, MoveStepType type) { + public MoveStep(MovePath path, + MoveStepType type) { this.type = type; if (path != null) { entity = path.getEntity(); @@ -266,10 +269,10 @@ public MoveStep(MovePath path, MoveStepType type) { } hasEverUnloaded = (type == MoveStepType.UNLOAD) || - (type == MoveStepType.LAUNCH) || - (type == MoveStepType.DROP) || - (type == MoveStepType.UNDOCK) || - (type == MoveStepType.DISCONNECT); + (type == MoveStepType.LAUNCH) || + (type == MoveStepType.DROP) || + (type == MoveStepType.UNDOCK) || + (type == MoveStepType.DISCONNECT); } /** @@ -280,16 +283,19 @@ public MoveStep(MovePath path, MoveStepType type) { * charged. * @param pos = the Coords for the target position. */ - public MoveStep(MovePath path, MoveStepType type, Targetable target, Coords pos) { + public MoveStep(MovePath path, + MoveStepType type, + Targetable target, + Coords pos) { this(path, type); targetId = target.getId(); targetType = target.getTargetType(); targetPos = pos; hasEverUnloaded = (type == MoveStepType.UNLOAD) || - (type == MoveStepType.LAUNCH) || - (type == MoveStepType.DROP) || - (type == MoveStepType.UNDOCK) || - (type == MoveStepType.DISCONNECT); + (type == MoveStepType.LAUNCH) || + (type == MoveStepType.DROP) || + (type == MoveStepType.UNDOCK) || + (type == MoveStepType.DISCONNECT); } /** @@ -299,15 +305,17 @@ public MoveStep(MovePath path, MoveStepType type, Targetable target, Coords pos) * @param target - the Targetable that is the target of this step. For example, the enemy being * charged. */ - public MoveStep(MovePath path, MoveStepType type, Targetable target) { + public MoveStep(MovePath path, + MoveStepType type, + Targetable target) { this(path, type); targetId = target.getId(); targetType = target.getTargetType(); hasEverUnloaded = (type == MoveStepType.UNLOAD) || - (type == MoveStepType.LAUNCH) || - (type == MoveStepType.DROP) || - (type == MoveStepType.UNDOCK) || - (type == MoveStepType.DISCONNECT); + (type == MoveStepType.LAUNCH) || + (type == MoveStepType.DROP) || + (type == MoveStepType.UNDOCK) || + (type == MoveStepType.DISCONNECT); } /** @@ -318,7 +326,9 @@ public MoveStep(MovePath path, MoveStepType type, Targetable target) { * @param additionalIntData "mineToLay" by default to retain compatibility with existing code "braceLocation" if the * move step type is BRACE */ - public MoveStep(MovePath path, MoveStepType type, int additionalIntData) { + public MoveStep(MovePath path, + MoveStepType type, + int additionalIntData) { this(path, type); if (type == MoveStepType.BRACE) { @@ -337,7 +347,9 @@ public MoveStep(MovePath path, MoveStepType type, int additionalIntData) { /** * Creates a step with an arbitrary int-to-int mapping of additional data. */ - public MoveStep(MovePath path, MoveStepType type, Map additionalIntData) { + public MoveStep(MovePath path, + MoveStepType type, + Map additionalIntData) { this(path, type); additionalData.putAll(additionalIntData); @@ -350,48 +362,67 @@ public MoveStep(MovePath path, MoveStepType type, Map addition * @param type should match one of the {@link MovePath} constants, but this is not currently checked. * @param targets vector of integers identifying the entities to launch */ - public MoveStep(MovePath path, MoveStepType type, TreeMap> targets) { + public MoveStep(MovePath path, + MoveStepType type, + TreeMap> targets) { this(path, type); launched = targets; hasEverUnloaded = (type == MoveStepType.UNLOAD) || - (type == MoveStepType.LAUNCH) || - (type == MoveStepType.DROP) || - (type == MoveStepType.UNDOCK) || - (type == MoveStepType.DISCONNECT); + (type == MoveStepType.LAUNCH) || + (type == MoveStepType.DROP) || + (type == MoveStepType.UNDOCK) || + (type == MoveStepType.DISCONNECT); } - public MoveStep(MovePath path, MoveStepType type, int recovery, int mineToLay) { + public MoveStep(MovePath path, + MoveStepType type, + int recovery, + int mineToLay) { this(path, type); recoveryUnit = recovery; this.mineToLay = mineToLay; } - public MoveStep(MovePath path, MoveStepType type, boolean noCost) { + public MoveStep(MovePath path, + MoveStepType type, + boolean noCost) { this(path, type); this.noCost = noCost; } - public MoveStep(MovePath path, MoveStepType type, boolean noCost, boolean isManeuver, int maneuverType) { + public MoveStep(MovePath path, + MoveStepType type, + boolean noCost, + boolean isManeuver, + int maneuverType) { this(path, type); this.noCost = noCost; maneuver = isManeuver; this.maneuverType = maneuverType; } - public MoveStep(MovePath path, MoveStepType type, int recovery, int mineToLay, int manType) { + public MoveStep(MovePath path, + MoveStepType type, + int recovery, + int mineToLay, + int manType) { this(path, type); recoveryUnit = recovery; this.mineToLay = mineToLay; maneuverType = manType; } - public MoveStep(MovePath path, MoveStepType type, Minefield mf) { + public MoveStep(MovePath path, + MoveStepType type, + Minefield mf) { this(path, type); this.mf = mf; } @Deprecated(since = "0.51.0", forRemoval = true) - public static MoveStep createChangeBoardMoveStep(MovePath path, Coords finalPosition, int finalBoardId) { + public static MoveStep createChangeBoardMoveStep(MovePath path, + Coords finalPosition, + int finalBoardId) { MoveStep newStep = new MoveStep(path, MoveStepType.CHANGE_BOARD); newStep.boardId = finalBoardId; newStep.position = finalPosition; @@ -404,16 +435,15 @@ public static MoveStep createChangeBoardMoveStep(MovePath path, Coords finalPosi * and the UI so both agree on the terrain rule. * * @param hex the hex to test, or null - * * @return {@code true} if a unit may dig in or fortify in the given hex */ public static boolean isFortifiableTerrain(@Nullable Hex hex) { return (hex != null) - && !hex.containsTerrain(Terrains.WATER) - && !hex.containsTerrain(Terrains.PAVEMENT) - && !hex.containsTerrain(Terrains.FORTIFIED) - && !hex.containsTerrain(Terrains.BUILDING) - && !hex.containsTerrain(Terrains.ROAD); + && !hex.containsTerrain(Terrains.WATER) + && !hex.containsTerrain(Terrains.PAVEMENT) + && !hex.containsTerrain(Terrains.FORTIFIED) + && !hex.containsTerrain(Terrains.BUILDING) + && !hex.containsTerrain(Terrains.ROAD); } @Override @@ -467,10 +497,9 @@ public void setStrafing(boolean strafing) { * Get the target of the current step. * * @param game The current {@link Game} - * * @return The Targetable that is the target of this step. For example, the enemy being charged. This - * value may be - * null + * value may be + * null */ public Targetable getTarget(Game game) { if (targetId == Entity.NONE) { @@ -503,7 +532,10 @@ public TreeMap> getLaunched() { * @param prev {@link MoveStep} Previous step. * @param cachedEntityState {@link CachedEntityState} the Cached Entity State */ - void compileMove(final Game game, final Entity entity, MoveStep prev, CachedEntityState cachedEntityState) { + void compileMove(final Game game, + final Entity entity, + MoveStep prev, + CachedEntityState cachedEntityState) { Hex destHex = game.getBoard(boardId).getHex(getPosition()); @@ -579,8 +611,8 @@ void compileMove(final Game game, final Entity entity, MoveStep prev, CachedEnti int maxElevation = entity.getElevation(); if (!isInsideTheSameBuilding) { maxElevation = (getAvailableJumpMP(entity) + - entity.getElevation() + - game.getBoard(boardId).getHex(entity.getPosition()).getLevel()) - hex.getLevel(); + entity.getElevation() + + game.getBoard(boardId).getHex(entity.getPosition()).getLevel()) - hex.getLevel(); } int building = hex.terrainLevel(Terrains.BLDG_ELEV); int depth = -hex.depth(true); @@ -592,8 +624,8 @@ void compileMove(final Game game, final Entity entity, MoveStep prev, CachedEnti // 3. Jumping onto water with WiGE move mode if (hex.containsTerrain(Terrains.WATER)) { if (hex.containsTerrain(Terrains.ICE) || - entity.getMovementMode() == EntityMovementMode.HOVER || - entity.getMovementMode() == EntityMovementMode.WIGE) { + entity.getMovementMode() == EntityMovementMode.HOVER || + entity.getMovementMode() == EntityMovementMode.WIGE) { depth = 0; } } @@ -643,9 +675,9 @@ void compileMove(final Game game, final Entity entity, MoveStep prev, CachedEnti if (bld != null) { Hex hex = game.getBoard(boardId).getHex(getPosition()); int maxElevation = (entity.getElevation() + game.getBoard(boardId) - .getHex(entity.getPosition()) - .getLevel()) - - hex.getLevel(); + .getHex(entity.getPosition()) + .getLevel()) - + hex.getLevel(); // Meks can climb up level 2 walls or fewer while everything can only climb up one level if (entity instanceof Mek) { @@ -670,9 +702,9 @@ void compileMove(final Game game, final Entity entity, MoveStep prev, CachedEnti setElevation(ClimbingHelper.getClimbDestinationElevation(hex)); } else { setElevation(entity.calcElevation(game.getBoard(boardId).getHex(prev.getPosition()), - game.getBoard(boardId).getHex(getPosition()), - elevation, - climbMode())); + game.getBoard(boardId).getHex(getPosition()), + elevation, + climbMode())); } } else if (isClimbing) { // Same as above for the no-building case. A Mek climbing onto a plain higher @@ -681,9 +713,9 @@ void compileMove(final Game game, final Entity entity, MoveStep prev, CachedEnti setElevation(ClimbingHelper.getClimbDestinationElevation(curHex)); } else { setElevation(entity.calcElevation(game.getBoard(boardId).getHex(prev.getPosition()), - game.getBoard(boardId).getHex(getPosition()), - elevation, - climbMode())); + game.getBoard(boardId).getHex(getPosition()), + elevation, + climbMode())); } } @@ -707,11 +739,11 @@ void compileMove(final Game game, final Entity entity, MoveStep prev, CachedEnti // check for water if (Game.rulesManager.getRulesMovement().cannotRunInWater(entity.getMovementMode(), cachedEntityState.hasWorkingMisc(MiscType.F_FULLY_AMPHIBIOUS)) - && !isPavementStep() - && (destHex.terrainLevel(Terrains.WATER) > 0) - && !(destHex.containsTerrain(Terrains.ICE) && (elevation >= 0)) - && - !(destHex.terrainLevel(Terrains.BRIDGE_ELEV) == elevation)) { + && !isPavementStep() + && (destHex.terrainLevel(Terrains.WATER) > 0) + && !(destHex.containsTerrain(Terrains.ICE) && (elevation >= 0)) + && + !(destHex.terrainLevel(Terrains.BRIDGE_ELEV) == elevation)) { setRunProhibited(true); } @@ -753,11 +785,11 @@ void compileMove(final Game game, final Entity entity, MoveStep prev, CachedEnti // WiGEs get bonus MP for each string of three consecutive hexes they descend. if (entity.getMovementMode() == EntityMovementMode.WIGE && - getClearance() > 0 && - game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_ADVANCED_MANEUVERS)) { + getClearance() > 0 && + game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_ADVANCED_MANEUVERS)) { if (game.getBoard(boardId).getHex(getPosition()).ceiling() < - game.getBoard(boardId).getHex(prev.getPosition()).ceiling()) { + game.getBoard(boardId).getHex(prev.getPosition()).ceiling()) { nWigeDescent = prev.getNWigeDescent() + 1; if (nWigeDescent >= 3) { wigeBonus++; @@ -776,16 +808,16 @@ void compileMove(final Game game, final Entity entity, MoveStep prev, CachedEnti * * @param game the current {@link Game} * @param entity the moving entity - * * @return true if this step is an in-place facing change that should drop the entity's hull-down state */ - private boolean losesHullDownToInPlaceTurn(final Game game, final Entity entity) { + private boolean losesHullDownToInPlaceTurn(final Game game, + final Entity entity) { boolean isTurnStep = (type == MoveStepType.TURN_LEFT) || (type == MoveStepType.TURN_RIGHT); if (!isTurnStep || !isHullDown()) { return false; } boolean isVehicleHullDown = (entity instanceof Tank) - || ((entity instanceof QuadVee) && (entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)); + || ((entity instanceof QuadVee) && (entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)); if (!isVehicleHullDown) { return false; } @@ -800,7 +832,10 @@ private boolean losesHullDownToInPlaceTurn(final Game game, final Entity entity) * @param entity the Entity taking this step. * @param prev the previous step in the path. */ - protected void compile(final Game game, final Entity entity, MoveStep prev, CachedEntityState cachedEntityState) { + protected void compile(final Game game, + final Entity entity, + MoveStep prev, + CachedEntityState cachedEntityState) { // set up the current move step using the state of the previous step copy(game, prev); @@ -822,12 +857,12 @@ protected void compile(final Game game, final Entity entity, MoveStep prev, Cach // hull-down in a fortified ("infantry-built") hex also forfeits cover when it changes facing in place, // since RAW requires it to exit, turn, then re-enter rather than turning within the hex (TO:AR p.19). if ((entity instanceof Tank || - (entity instanceof QuadVee && entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)) && - (distance > 0)) { + (entity instanceof QuadVee && entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)) && + (distance > 0)) { setHullDown(false); } else if (losesHullDownToInPlaceTurn(game, entity)) { LOGGER.debug("[HullDown] {}: hull-down lost - a vehicle cannot change facing within a fortified hex; " - + "it must exit, turn, then re-enter (TO:AR p.19)", entity.getDisplayName()); + + "it must exit, turn, then re-enter (TO:AR p.19)", entity.getDisplayName()); setHullDown(false); } @@ -836,12 +871,12 @@ protected void compile(final Game game, final Entity entity, MoveStep prev, Cach // Check for a stacking violation. final Entity violation = Compute.stackingViolation(game, - entity, - getElevation(), - getPosition(), boardId, - null, - climbMode, - true); + entity, + getElevation(), + getPosition(), boardId, + null, + climbMode, + true); if ((violation != null) && (getType() != MoveStepType.CHARGE) && (getType() != MoveStepType.DFA)) { setStackingViolation(true); } @@ -851,10 +886,10 @@ protected void compile(final Game game, final Entity entity, MoveStep prev, Cach if (isClimbing) { LOGGER.debug("[CLIMB-TRACE] compile FINAL: type={}, movementType={}, mp={}, mpUsed={}, " + - "elevation={}, position={}, isClimbing={}, isStackingViolation={}, terrainInvalid={}, " + - "isLegalEndPos={}", - type, movementType, mp, mpUsed, elevation, position, - isClimbing, isStackingViolation, terrainInvalid, isLegalEndPos()); + "elevation={}, position={}, isClimbing={}, isStackingViolation={}, terrainInvalid={}, " + + "isLegalEndPos={}", + type, movementType, mp, mpUsed, elevation, position, + isClimbing, isStackingViolation, terrainInvalid, isLegalEndPos()); } } @@ -864,10 +899,11 @@ protected void compile(final Game game, final Entity entity, MoveStep prev, Cach * "F" (forward move) first move step otherwise. * * @param prev The previous move step in the path, if any - * * @return The previous move step if it is not null, a fake forward move step otherwise */ - private MoveStep evaluateFirstStep(Game game, Entity entity, MoveStep prev) { + private MoveStep evaluateFirstStep(Game game, + Entity entity, + MoveStep prev) { if (prev == null) { setFirstStep(); return createFakeFirstStep(game, entity); @@ -876,17 +912,21 @@ private MoveStep evaluateFirstStep(Game game, Entity entity, MoveStep prev) { // A climb mode change is only meta info and does not count as an action setFirstStep(); + } else if (prev.isFirstStep() && (prev.getType() == MoveStepType.DEPLOY)) { + //Walk-on deployment + setFirstStep(); } else if (prev.isFirstStep() - && prev.isTurning - && entity instanceof ConvInfantry infantry - && !infantry.hasActiveFieldArtillery()) { + && prev.isTurning + && entity instanceof ConvInfantry infantry + && !infantry.hasActiveFieldArtillery()) { // For CI, turning is only a graphical distinction unless they are field artillery setFirstStep(); } return prev; } - private MoveStep createFakeFirstStep(Game game, Entity entity) { + private MoveStep createFakeFirstStep(Game game, + Entity entity) { MoveStep prev = new MoveStep(null, MoveStepType.FORWARDS); prev.setFromEntity(entity, game); prev.isCarefulPath = isCareful(); @@ -919,7 +959,8 @@ protected void setAltitude(int alt) { * @param game The current {@link Game} * @param prev Previous {@link MoveStep} */ - public void copy(final Game game, MoveStep prev) { + public void copy(final Game game, + MoveStep prev) { if (prev == null) { setFromEntity(getEntity(), game); return; @@ -968,7 +1009,8 @@ public void copy(final Game game, MoveStep prev) { * * @param entity Set local {@link Entity} with passed in one. */ - public void setFromEntity(Entity entity, Game game) { + public void setFromEntity(Entity entity, + Game game) { this.entity = entity; position = entity.getPosition(); boardId = entity.getBoardId(); @@ -994,7 +1036,7 @@ public void setFromEntity(Entity entity, Game game) { movementMode = entity.getMovementMode(); if (isClimbing) { LOGGER.debug("setFromEntity: climbing entity {} at elevation={}, position={}, isClimbing={}", - entity.getDisplayName(), elevation, position, isClimbing); + entity.getDisplayName(), elevation, position, isClimbing); } isRolled = false; @@ -1011,7 +1053,7 @@ public void setFromEntity(Entity entity, Game game) { int[] tempMv = entity.getVectors(); mv = new int[] { 0, 0, 0, 0, 0, 0 }; - System.arraycopy(tempMv, 0, mv, 0, 6); + mv = Arrays.copyOf(tempMv, 6); // if ASF get velocity if (entity.isAero()) { @@ -1038,8 +1080,8 @@ public void setFromEntity(Entity entity, Game game) { // Cannot run while using Mek tracks if (entity instanceof Mek && - entity.getMovementMode() == EntityMovementMode.TRACKED && - !(entity instanceof QuadVee)) { + entity.getMovementMode() == EntityMovementMode.TRACKED && + !(entity instanceof QuadVee)) { isRunProhibited = true; } @@ -1068,12 +1110,12 @@ public void setFromEntity(Entity entity, Game game) { } // if entity already moved into water it can't run now if (curHex.containsTerrain(Terrains.WATER) && - (entity.getElevation() < 0) && - (distance > 0) && - (nMove != EntityMovementMode.NAVAL) && - (nMove != EntityMovementMode.HYDROFOIL) && - (nMove != EntityMovementMode.SUBMARINE) && - (nMove != EntityMovementMode.INF_UMU)) { + (entity.getElevation() < 0) && + (distance > 0) && + (nMove != EntityMovementMode.NAVAL) && + (nMove != EntityMovementMode.HYDROFOIL) && + (nMove != EntityMovementMode.SUBMARINE) && + (nMove != EntityMovementMode.INF_UMU)) { if (Game.rulesManager.getRulesMovement().cannotRunInWater(nMove, false)) { isRunProhibited = true; } @@ -1232,9 +1274,8 @@ public boolean isStrafingStep() { * Determine if this is a legal step as part of the supplied MovePath. * * @param path A MovePath that contains this step. - * * @return true if the step is legal. false - * otherwise. + * otherwise. */ public boolean isLegal(MovePath path) { // A step is legal if it's static movement type is not illegal, and it is either a valid end position, or not @@ -1254,10 +1295,10 @@ public EntityMovementType getMovementType(boolean isLastStep) { if (isLastStep && !isLegalEndPos()) { if (isClimbing) { LOGGER.debug("[CLIMB-TRACE] getMovementType: isLastStep={}, isLegalEndPos=false, " + - "overriding {} to MOVE_ILLEGAL, isStackingViolation={}, terrainInvalid={}, " + - "isJumping={}, distance={}, hasEverUnloaded={}, position={}, elevation={}", - isLastStep, movementType, isStackingViolation, terrainInvalid, - isJumping(), distance, hasEverUnloaded, position, elevation); + "overriding {} to MOVE_ILLEGAL, isStackingViolation={}, terrainInvalid={}, " + + "isJumping={}, distance={}, hasEverUnloaded={}, position={}, elevation={}", + isLastStep, movementType, isStackingViolation, terrainInvalid, + isJumping(), distance, hasEverUnloaded, position, elevation); } moveType = EntityMovementType.MOVE_ILLEGAL; } @@ -1279,8 +1320,8 @@ public EntityMovementMode getMovementMode() { * Check to see if this step's position is a valid end of a path. * * @return true if this step's position is a legal end of a - * path. If the step is not legal for an end of a path, then - * false is returned. + * path. If the step is not legal for an end of a path, then + * false is returned. */ public boolean isLegalEndPos() { // Can't be a stacking violation. @@ -1294,22 +1335,22 @@ public boolean isLegalEndPos() { // Can't be into invalid terrain. if (isClimbing) { LOGGER.debug("[CLIMB-TRACE] isLegalEndPos: BLOCKED by terrainInvalid, pos={}, elevation={}", - position, - elevation); + position, + elevation); } legal = false; } else if (isJumping() && (distance == 0)) { // Can't jump zero hexes. legal = false; } else if (hasEverUnloaded && - (type != MoveStepType.UNLOAD) && - (type != MoveStepType.LAUNCH) && - (type != MoveStepType.DROP) && - (type != MoveStepType.UNDOCK) && - (type != MoveStepType.DISCONNECT) && - (type != MoveStepType.CHAFF) && - (type != MoveStepType.DROP_CARGO) && - (getAltitude() == 0)) { + (type != MoveStepType.UNLOAD) && + (type != MoveStepType.LAUNCH) && + (type != MoveStepType.DROP) && + (type != MoveStepType.UNDOCK) && + (type != MoveStepType.DISCONNECT) && + (type != MoveStepType.CHAFF) && + (type != MoveStepType.DROP_CARGO) && + (getAltitude() == 0)) { // Can't be after unloading BA/inf legal = false; } @@ -1322,10 +1363,9 @@ public boolean isLegalEndPos() { * {@link #isEndPos(MovePath)}, and {@link MovePath#addStep(MoveStep)} for additional information. * * @param isEnd the boolean flag that specifies that this step's position is the end of a path. - * * @return true if the path needs to keep updating the steps. - * false if the update of the path is complete. - *

+ * false if the update of the path is complete. + *

*/ public boolean setEndPos(boolean isEnd) { boolean isEndPos = true; @@ -1342,8 +1382,8 @@ public boolean setEndPos(boolean isEnd) { Hex pos = getGame().getBoard(boardId).getHex(position); if (getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_PSR_JUMP_HEAVY_WOODS)) { if (!isEnd && - isJumping() && - (pos.containsTerrain(Terrains.WOODS, 2) || pos.containsTerrain(Terrains.WOODS, 3))) { + isJumping() && + (pos.containsTerrain(Terrains.WOODS, 2) || pos.containsTerrain(Terrains.WOODS, 3))) { danger = false; pastDanger = false; } @@ -1356,7 +1396,6 @@ public boolean setEndPos(boolean isEnd) { * A step is in an end position if it is the last legal step, or is an illegal step past the last legal step. * * @param path {@link MovePath} to check - * * @return true if a step is considered to be in an end position for the given MovePath. */ public boolean isEndPos(MovePath path) { @@ -1586,12 +1625,13 @@ protected void setStackingViolation(boolean isStackingViolation) { * @param game the current game * @param entity the moving entity * @param curPos the position of the entity when this step begins, or null if unknown - * * @return {@code true} if this BUILD_BRIDGE step is legal: the Bridge-Building Engineers game option is active, the - * unit is an engineer platoon with its bridge kit and enough remaining budget for the chosen bridge type, and - * the step's target hex is adjacent and a valid bridge site. TO:AUE p.152. + * unit is an engineer platoon with its bridge kit and enough remaining budget for the chosen bridge type, and + * the step's target hex is adjacent and a valid bridge site. TO:AUE p.152. */ - private boolean isValidBridgeBuildStep(Game game, Entity entity, @Nullable Coords curPos) { + private boolean isValidBridgeBuildStep(Game game, + Entity entity, + @Nullable Coords curPos) { // Failures are logged at DEBUG: a rejected BUILD_BRIDGE step silently becomes an illegal move, so the log // is the only way to see why a declared build was refused if (!game.getOptions().booleanOption(OptionsConstants.ADVANCED_BRIDGE_BUILDING_ENGINEERS)) { @@ -1599,15 +1639,15 @@ private boolean isValidBridgeBuildStep(Game game, Entity entity, @Nullable Coord return false; } if (!(entity instanceof ConvInfantry convInfantry) || !convInfantry.canStartBridgeBuild() - || !convInfantry.canAffordBridge(getBridgeType())) { + || !convInfantry.canAffordBridge(getBridgeType())) { LOGGER.debug("[BuildBridge] step rejected for {}: not an eligible engineer platoon " - + "(specialization, kit, budget or an active build)", entity.getShortName()); + + "(specialization, kit, budget or an active build)", entity.getShortName()); return false; } Coords target = getBridgeTargetCoords(); if ((target == null) || (curPos == null) || (curPos.distance(target) != 1)) { LOGGER.debug("[BuildBridge] step rejected for {}: target {} is not adjacent to {}", - entity.getShortName(), target, curPos); + entity.getShortName(), target, curPos); return false; } Board board = game.getBoard(boardId); @@ -1619,8 +1659,8 @@ private boolean isValidBridgeBuildStep(Game game, Entity entity, @Nullable Coord boolean isRepairSite = repairAllowed && BridgeConstruction.isBridgeRepairSite(board, target, exits); if (!isFreshSite && !isRepairSite) { LOGGER.debug("[BuildBridge] step rejected for {}: {} with exits bitmask {} is neither a valid bridge site " - + "nor a repairable gap (repair option {})", entity.getShortName(), target, exits, - repairAllowed ? "on" : "off"); + + "nor a repairable gap (repair option {})", entity.getShortName(), target, exits, + repairAllowed ? "on" : "off"); return false; } return true; @@ -1629,26 +1669,25 @@ private boolean isValidBridgeBuildStep(Game game, Entity entity, @Nullable Coord /** * @param hex the hex to check, or null (returns {@code false}) * @param assumedElevation the elevation of the unit in that hex - * * @return {@code true} if a unit at the given elevation in the given hex stands on top of a bridge deck. Such a - * unit is on the bridge, not in the terrain below it, so the underlying terrain's movement restrictions do - * not apply to it (TO:AR p.115). + * unit is on the bridge, not in the terrain below it, so the underlying terrain's movement restrictions do + * not apply to it (TO:AR p.115). */ - private static boolean isOnBridgeDeck(@Nullable Hex hex, int assumedElevation) { + private static boolean isOnBridgeDeck(@Nullable Hex hex, + int assumedElevation) { return (hex != null) && hex.containsTerrain(Terrains.BRIDGE) - && (assumedElevation == hex.terrainLevel(Terrains.BRIDGE_ELEV)); + && (assumedElevation == hex.terrainLevel(Terrains.BRIDGE_ELEV)); } /** * @param hex the hex to check, or null - * * @return {@code true} if the hex holds a bridge that a ground unit must use the bridge to cross - one over water, - * or one whose deck is raised above the hex. A bridge flush with dry ground acts as a road and is not - * constrained to its exits, so it returns {@code false}. + * or one whose deck is raised above the hex. A bridge flush with dry ground acts as a road and is not + * constrained to its exits, so it returns {@code false}. */ private static boolean isRealBridgeSpan(@Nullable Hex hex) { return (hex != null) && hex.containsTerrain(Terrains.BRIDGE) - && ((hex.terrainLevel(Terrains.WATER) > 0) || (hex.terrainLevel(Terrains.BRIDGE_ELEV) > 0)); + && ((hex.terrainLevel(Terrains.WATER) > 0) || (hex.terrainLevel(Terrains.BRIDGE_ELEV) > 0)); } /** @@ -1657,18 +1696,20 @@ private static boolean isRealBridgeSpan(@Nullable Hex hex) { * @param elevation the entity's elevation in {@code bridgeHex} * @param hexPos the coordinates of {@code bridgeHex} * @param otherPos the hex the entity is moving to or from across the bridge edge - * * @return {@code true} if the entity would cross the edge between {@code hexPos} and {@code otherPos} while - * standing on a real bridge deck it must use (one over water or raised above the hex) at a hexside that is - * not one of the bridge's exits - i.e. boarding or leaving the bridge over its side rather than at an end - * (TO:AR p.115). A unit that could traverse the terrain under the bridge is not bound to its exits. + * standing on a real bridge deck it must use (one over water or raised above the hex) at a hexside that is + * not one of the bridge's exits - i.e. boarding or leaving the bridge over its side rather than at an end + * (TO:AR p.115). A unit that could traverse the terrain under the bridge is not bound to its exits. */ - private boolean crossesBridgeDeckOffExit(Entity entity, @Nullable Hex bridgeHex, int elevation, Coords hexPos, - Coords otherPos) { + private boolean crossesBridgeDeckOffExit(Entity entity, + @Nullable Hex bridgeHex, + int elevation, + Coords hexPos, + Coords otherPos) { return isRealBridgeSpan(bridgeHex) - && (elevation == bridgeHex.terrainLevel(Terrains.BRIDGE_ELEV)) - && entity.isLocationProhibited(hexPos, boardId, elevation) - && !bridgeHex.containsTerrainExit(Terrains.BRIDGE, hexPos.direction(otherPos)); + && (elevation == bridgeHex.terrainLevel(Terrains.BRIDGE_ELEV)) + && entity.isLocationProhibited(hexPos, boardId, elevation) + && !bridgeHex.containsTerrainExit(Terrains.BRIDGE, hexPos.direction(otherPos)); } /** @@ -1680,8 +1721,10 @@ private boolean crossesBridgeDeckOffExit(Entity entity, @Nullable Hex bridgeHex, * @param entity The {@link Entity} taking this step. * @param prev The {@link MoveStep} previous step in the path. */ - private void compileIllegal(final Game game, final Entity entity, final MoveStep prev, - CachedEntityState cachedEntityState) { + private void compileIllegal(final Game game, + final Entity entity, + final MoveStep prev, + CachedEntityState cachedEntityState) { final MoveStepType stepType = getType(); final boolean isInfantry = entity instanceof Infantry; final boolean isTank = entity instanceof Tank; @@ -1705,9 +1748,9 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // I am going to put in a whole separate section for Aerospace and just return from it only if Aerospace are // airborne, otherwise they should move like other units if (type == MoveStepType.HOVER && - entity instanceof LandAirMek && - entity.getMovementMode() == EntityMovementMode.WIGE && - entity.getAltitude() <= 3) { + entity instanceof LandAirMek && + entity.getMovementMode() == EntityMovementMode.WIGE && + entity.getAltitude() <= 3) { if (mpUsed <= cachedEntityState.getWalkMP()) { movementType = EntityMovementType.MOVE_VTOL_WALK; } else if (mpUsed <= cachedEntityState.getRunMP()) { @@ -1763,8 +1806,8 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // check the fuel requirements if (game.getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUEL_CONSUMPTION) && - entity.hasEngine() && - a.requiresFuel()) { + entity.hasEngine() && + a.requiresFuel()) { int fuelUsed = mpUsed + Math.max(mpUsed - cachedEntityState.getWalkMP(), 0); if (fuelUsed > a.getCurrentFuel()) { return; @@ -1775,19 +1818,19 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep if (game.getBoard(boardId).isSpace()) { // space stations can only turn and launch spacecraft if ((entity instanceof SpaceStation) && - !((type == MoveStepType.TURN_LEFT) || - (type == MoveStepType.TURN_RIGHT) || - (type == MoveStepType.LAUNCH) || - (type == MoveStepType.UNDOCK))) { + !((type == MoveStepType.TURN_LEFT) || + (type == MoveStepType.TURN_RIGHT) || + (type == MoveStepType.LAUNCH) || + (type == MoveStepType.UNDOCK))) { return; } // unless velocity is zero ASFs must move forward one hex before // making turns in space if (!game.useVectorMove() && - (distance == 0) && - (velocity != 0) && - ((type == MoveStepType.TURN_LEFT) || (type == MoveStepType.TURN_RIGHT))) { + (distance == 0) && + (velocity != 0) && + ((type == MoveStepType.TURN_LEFT) || (type == MoveStepType.TURN_RIGHT))) { return; } @@ -1804,34 +1847,34 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // Jump ships cannot change velocity and use attitude jets in the same turn. if ((a instanceof Jumpship) && - ((Jumpship) a).hasStationKeepingDrive() && - (prev.getMovementType(false) == EntityMovementType.MOVE_OVER_THRUST) && - ((type == MoveStepType.TURN_LEFT) || (type == MoveStepType.TURN_RIGHT))) { + ((Jumpship) a).hasStationKeepingDrive() && + (prev.getMovementType(false) == EntityMovementType.MOVE_OVER_THRUST) && + ((type == MoveStepType.TURN_LEFT) || (type == MoveStepType.TURN_RIGHT))) { return; } } // atmosphere has its own rules about turning if (useAeroAtmosphere(game, entity) && - ((type == MoveStepType.TURN_LEFT) || (type == MoveStepType.TURN_RIGHT)) && - !prev.canAeroTurn(game)) { + ((type == MoveStepType.TURN_LEFT) || (type == MoveStepType.TURN_RIGHT)) && + !prev.canAeroTurn(game)) { return; } // aerodynes cannot fly backwards in atmosphere if (useAeroAtmosphere(game, entity) && - ((type == MoveStepType.BACKWARDS) || - (type == MoveStepType.LATERAL_LEFT_BACKWARDS) || - (type == MoveStepType.LATERAL_RIGHT_BACKWARDS))) { + ((type == MoveStepType.BACKWARDS) || + (type == MoveStepType.LATERAL_LEFT_BACKWARDS) || + (type == MoveStepType.LATERAL_RIGHT_BACKWARDS))) { return; } // spheroids in atmosphere can move a max of 1 hex on the low atmosphere map and 8 hexes on the ground // map, regardless of any other considerations unless they're out of control, in which case, well... if (useSpheroidAtmosphere(game, entity) && - (((IAero) entity).isOutControlTotal() || - (!game.getBoard(boardId).isGround() && (this.getDistance() > 1) || - (game.getBoard(boardId).isGround() && (getDistance() > 8))))) { + (((IAero) entity).isOutControlTotal() || + (!game.getBoard(boardId).isGround() && (this.getDistance() > 1) || + (game.getBoard(boardId).isGround() && (getDistance() > 8))))) { return; } @@ -1851,13 +1894,13 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // check for thruster damage if ((type == MoveStepType.TURN_LEFT) && - (a.getRightThrustHits() > 2) && - !useSpheroidAtmosphere(game, entity)) { + (a.getRightThrustHits() > 2) && + !useSpheroidAtmosphere(game, entity)) { return; } if ((type == MoveStepType.TURN_RIGHT) && - (a.getLeftThrustHits() > 2) && - !useSpheroidAtmosphere(game, entity)) { + (a.getLeftThrustHits() > 2) && + !useSpheroidAtmosphere(game, entity)) { return; } @@ -1883,7 +1926,7 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // no moves after landing if (!isFirstStep() && ((prev.getType() == MoveStepType.LAND) || (prev.getType() - == MoveStepType.VERTICAL_LAND))) { + == MoveStepType.VERTICAL_LAND))) { return; } @@ -1906,9 +1949,9 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // when Aerospace are flying on the ground map sheet we need an additional check because velocityLeft // is only decremented at intervals of 16 hexes if (useAeroAtmosphere(game, entity) && - game.getBoard(boardId).isGround() && - (getVelocityLeft() == 0) && - (getNMoved() > 0)) { + game.getBoard(boardId).isGround() && + (getVelocityLeft() == 0) && + (getNMoved() > 0)) { return; } if (getMpUsed() <= tmpSafeTh) { @@ -1932,8 +1975,10 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep Hex adjacentHex = game.getHex(adjacentCoords, boardId); boolean hasLOS = LosEffects.calculateLOS(game, - entity, - new FloorTarget(curPos, game.getBoard(boardId), getElevation())).canSee(); + entity, + new FloorTarget(curPos, + game.getBoard(boardId), + getElevation())).canSee(); if (adjacentHex.ceiling() >= getElevation() + curHex.getLevel() || !hasLOS) { return; // can't enter the building from this direction @@ -1950,9 +1995,9 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep if (prev.isClimbing) { isClimbing = true; if ((stepType != MoveStepType.FORWARDS) - && (stepType != MoveStepType.CLIMB_MODE_ON) - && (stepType != MoveStepType.CLIMB_MODE_OFF) - && (stepType != MoveStepType.DOWN)) { + && (stepType != MoveStepType.CLIMB_MODE_ON) + && (stepType != MoveStepType.CLIMB_MODE_OFF) + && (stepType != MoveStepType.DOWN)) { LOGGER.debug("[CLIMB-TRACE] Blocked step type {} while climbing - set MOVE_ILLEGAL", stepType); movementType = EntityMovementType.MOVE_ILLEGAL; return; @@ -1963,16 +2008,16 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // Log all facing changes for debugging if ((stepType == MoveStepType.TURN_LEFT) || (stepType == MoveStepType.TURN_RIGHT)) { LOGGER.debug("[FACING-TRACE] Facing change: type={}, prev.isClimbing={}, " + - "entity.isClimbing={}, position={}, elevation={}, prev.facing={}", - stepType, prev.isClimbing, entity.isClimbing(), curPos, elevation, prev.getFacing()); + "entity.isClimbing={}, position={}, elevation={}, prev.facing={}", + stepType, prev.isClimbing, entity.isClimbing(), curPos, elevation, prev.getFacing()); } // A platoon actively raising or dismantling a bridge may take no other action at all (TO:AUE p.152): it can // only keep working (no step) or declare one of the bridge actions (pause/cancel/abandon/resume) - it may not // move, and not even turn in place. A *paused* build does not lock the platoon: it is freed to move and fight. if ((entity instanceof ConvInfantry bridgeWorker) && bridgeWorker.isBusyWithBridge() - && (type != MoveStepType.CANCEL_BRIDGE) && (type != MoveStepType.RESUME_BRIDGE) - && (type != MoveStepType.PAUSE_BRIDGE) && (type != MoveStepType.ABANDON_BRIDGE)) { + && (type != MoveStepType.CANCEL_BRIDGE) && (type != MoveStepType.RESUME_BRIDGE) + && (type != MoveStepType.PAUSE_BRIDGE) && (type != MoveStepType.ABANDON_BRIDGE)) { movementType = EntityMovementType.MOVE_ILLEGAL; return; } @@ -1990,7 +2035,7 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // change facing (TacOps). The vehicle drives into the rubble hex and then clears it, so this is the final // step of the move (after any movement into the hex), not necessarily the first step. String illegalReason = BulldozerRules.clearRubbleIllegalReason(entity, - game.getBoard(boardId).getHex(curPos), game); + game.getBoard(boardId).getHex(curPos), game); if (illegalReason != null) { LOGGER.debug("[Bulldozer] {}: {} (at {})", entity.getDisplayName(), illegalReason, curPos); return; @@ -2005,7 +2050,7 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep boolean isMekFortify = (type == MoveStepType.FORTIFY) && (entity instanceof Mek); if ((!isInfantry && !isTank && !isMekFortify) || !isFirstStep()) { LOGGER.debug("[Fortify] {}: {} illegal - only infantry, vehicles, or fieldworks-capable Meks, and " - + "only as the first/sole action", entity.getDisplayName(), type); + + "only as the first/sole action", entity.getDisplayName(), type); return; // can't dig in } @@ -2013,7 +2058,7 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // vibro-shovel or equivalent (TO:AUE p.153). Plain self digging-in (DIG_IN) does not. if ((type == MoveStepType.FORTIFY) && !entity.hasWorkingMisc(MiscType.F_TRENCH_CAPABLE)) { LOGGER.debug("[Fortify] {}: fortify illegal - no fieldworks-capable equipment (F_TRENCH_CAPABLE)", - entity.getDisplayName()); + entity.getDisplayName()); return; } @@ -2021,7 +2066,7 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep Infantry inf = (Infantry) entity; if ((inf.getDugIn() != Infantry.DUG_IN_NONE) && (inf.getDugIn() != Infantry.DUG_IN_COMPLETE)) { LOGGER.debug("[Fortify] {}: {} illegal - already dug in (stage {})", - entity.getDisplayName(), type, inf.getDugIn()); + entity.getDisplayName(), type, inf.getDugIn()); return; // Already dug in } } @@ -2030,10 +2075,10 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // or road hexes (TO:AR p.106 / TO:AUE p.153). An already-fortified hex is excluded too (no gain). if (!isFortifiableTerrain(game.getBoard(boardId).getHex(curPos))) { LOGGER.debug( - "[Fortify] {}: {} illegal - terrain at {} is water/pavement/building/road or already fortified", - entity.getDisplayName(), - type, - curPos); + "[Fortify] {}: {} illegal - terrain at {} is water/pavement/building/road or already fortified", + entity.getDisplayName(), + type, + curPos); return; } isDiggingIn = true; @@ -2062,7 +2107,7 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep return; } boolean canResume = convInfantry.isDismantlingBridge() - || (convInfantry.isBridgePaused() && convInfantry.isAdjacentToBridgeSite()); + || (convInfantry.isBridgePaused() && convInfantry.isAdjacentToBridgeSite()); if (!canResume) { return; } @@ -2070,7 +2115,7 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep } else if (type == MoveStepType.ABANDON_BRIDGE) { // A platoon may abandon any bridge work in progress (building, paused, or dismantling) - instant, no refund. if (!isFirstStep() || !(entity instanceof ConvInfantry convInfantry) - || !convInfantry.hasBridgeInProgress()) { + || !convInfantry.hasBridgeInProgress()) { return; } movementType = EntityMovementType.MOVE_NONE; @@ -2108,8 +2153,8 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // WIGEs can take off on their first step... if ((type == MoveStepType.UP) && - (entity.getMovementMode() == EntityMovementMode.WIGE) && - (prev.getClearance() == 0)) { + (entity.getMovementMode() == EntityMovementMode.WIGE) && + (prev.getClearance() == 0)) { if (firstStep && (cachedEntityState.getRunMP() >= mp)) { movementType = EntityMovementType.MOVE_VTOL_WALK; } else { @@ -2120,10 +2165,10 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // WIGEs need to be able to land too, or even descend if (entity.getMovementMode() == EntityMovementMode.WIGE && - type == MoveStepType.DOWN && - getClearance() < prev.getClearance()) { // landing + type == MoveStepType.DOWN && + getClearance() < prev.getClearance()) { // landing if (prev.getMovementType(false) == EntityMovementType.MOVE_VTOL_RUN || - prev.getMovementType(false) == EntityMovementType.MOVE_VTOL_SPRINT) { + prev.getMovementType(false) == EntityMovementType.MOVE_VTOL_SPRINT) { movementType = prev.getMovementType(false); } else { movementType = EntityMovementType.MOVE_VTOL_WALK; @@ -2139,6 +2184,10 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep if ((type == MoveStepType.CLIMB_MODE_ON) || (type == MoveStepType.CLIMB_MODE_OFF)) { movementType = prev.movementType; } + if (type == MoveStepType.DEPLOY) { + // Deployment + movementType = EntityMovementType.MOVE_NONE; + } // check for ejection (always legal?) if (type == MoveStepType.EJECT) { movementType = EntityMovementType.MOVE_NONE; @@ -2164,9 +2213,9 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // check for evasion if (type == MoveStepType.EVADE) { if (entity.hasHipCrit() || - (entity.getMovementMode() == EntityMovementMode.WIGE && - (entity instanceof LandAirMek || entity instanceof ProtoMek) && - getClearance() > 0)) { + (entity.getMovementMode() == EntityMovementMode.WIGE && + (entity instanceof LandAirMek || entity instanceof ProtoMek) && + getClearance() > 0)) { movementType = EntityMovementType.MOVE_ILLEGAL; return; } @@ -2191,28 +2240,28 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // check for valid jump mp if (isJumping() && - (getMpUsed() <= getAvailableJumpMP(entity)) && - !isProne() && - !isHullDown() && - !((entity instanceof ProtoMek) && - (entity.getInternal(ProtoMek.LOC_LEG) == IArmorState.ARMOR_DESTROYED)) && - (!entity.isStuck() || entity.canUnstickByJumping())) { + (getMpUsed() <= getAvailableJumpMP(entity)) && + !isProne() && + !isHullDown() && + !((entity instanceof ProtoMek) && + (entity.getInternal(ProtoMek.LOC_LEG) == IArmorState.ARMOR_DESTROYED)) && + (!entity.isStuck() || entity.canUnstickByJumping())) { movementType = EntityMovementType.MOVE_JUMP; } // legged ProtoMeks may make one facing change if (isFirstStep() && - (entity instanceof ProtoMek) && - (entity.getInternal(ProtoMek.LOC_LEG) == IArmorState.ARMOR_DESTROYED) && - ((stepType == MoveStepType.TURN_LEFT) || (stepType == MoveStepType.TURN_RIGHT)) && - !entity.isStuck()) { + (entity instanceof ProtoMek) && + (entity.getInternal(ProtoMek.LOC_LEG) == IArmorState.ARMOR_DESTROYED) && + ((stepType == MoveStepType.TURN_LEFT) || (stepType == MoveStepType.TURN_RIGHT)) && + !entity.isStuck()) { movementType = EntityMovementType.MOVE_WALK; } // Infantry that is first stepping and turning is legal if (isInfantry && - ((stepType == MoveStepType.TURN_LEFT) || (stepType == MoveStepType.TURN_RIGHT)) && - isFirstStep()) { + ((stepType == MoveStepType.TURN_LEFT) || (stepType == MoveStepType.TURN_RIGHT)) && + isFirstStep()) { if (isJumping()) { movementType = EntityMovementType.MOVE_JUMP; } else { @@ -2318,54 +2367,54 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep } if ((getEntity().getMovementMode() == EntityMovementMode.INF_UMU) && - (currHex.containsTerrain(Terrains.WATER) && - lastHex.containsTerrain(Terrains.WATER) && - (entity.relHeight() < currHex.getLevel()))) { + (currHex.containsTerrain(Terrains.WATER) && + lastHex.containsTerrain(Terrains.WATER) && + (entity.relHeight() < currHex.getLevel()))) { tmpWalkMP = entity.getActiveUMUCount(); } if ((getEntity().getMovementMode() == EntityMovementMode.BIPED_SWIM) || - (getEntity().getMovementMode() == EntityMovementMode.QUAD_SWIM) || - ((getEntity() instanceof Infantry && - getEntity().getMovementMode().isSubmarine() && - (currHex.terrainLevel(Terrains.WATER) > 0)))) { + (getEntity().getMovementMode() == EntityMovementMode.QUAD_SWIM) || + ((getEntity() instanceof Infantry && + getEntity().getMovementMode().isSubmarine() && + (currHex.terrainLevel(Terrains.WATER) > 0)))) { tmpWalkMP = entity.getActiveUMUCount(); } // VTOLs use jump MP when airborne (includes powered flight infantry whose getMovementMode() returns VTOL) if ((getEntity().getMovementMode() == EntityMovementMode.VTOL) && - getClearance() > 0 && - !(getEntity() instanceof VTOL)) { + getClearance() > 0 && + !(getEntity() instanceof VTOL)) { tmpWalkMP = entity.getJumpMP(); } // check for valid walk/run mp; BRACE is a special case for ProtoMeks // 0 MP infantry with fast movement have runMPMax > 0 even when tmpWalkMP is 0 if (!isJumping() && - !entity.isStuck() && - ((tmpWalkMP > 0) || (runMPMax > 0)) && - ((getMp() > 0) || (stepType == MoveStepType.BRACE))) { + !entity.isStuck() && + ((tmpWalkMP > 0) || (runMPMax > 0)) && + ((getMp() > 0) || (stepType == MoveStepType.BRACE))) { // Prone meks can only spend MP to turn or get up if ((stepType != MoveStepType.TURN_LEFT) && - (stepType != MoveStepType.TURN_RIGHT) && - (stepType != MoveStepType.GET_UP) && - (stepType != MoveStepType.LOAD) && - (stepType != MoveStepType.CAREFUL_STAND) && - (stepType != MoveStepType.HULL_DOWN) && - (stepType != MoveStepType.GO_PRONE) && - (stepType != MoveStepType.DROP_CARGO) && - !(entity instanceof Tank) - // Tanks can drive out of hull-down - && - (isProne() || isHullDown())) { + (stepType != MoveStepType.TURN_RIGHT) && + (stepType != MoveStepType.GET_UP) && + (stepType != MoveStepType.LOAD) && + (stepType != MoveStepType.CAREFUL_STAND) && + (stepType != MoveStepType.HULL_DOWN) && + (stepType != MoveStepType.GO_PRONE) && + (stepType != MoveStepType.DROP_CARGO) && + !(entity instanceof Tank) + // Tanks can drive out of hull-down + && + (isProne() || isHullDown())) { movementType = EntityMovementType.MOVE_ILLEGAL; return; } // WiGEs that land is finished with movement if (entity.getMovementMode() == EntityMovementMode.WIGE && - prev.getType() == MoveStepType.DOWN && - getClearance() == 0) { + prev.getType() == MoveStepType.DOWN && + getClearance() == 0) { movementType = EntityMovementType.MOVE_ILLEGAL; return; } @@ -2378,11 +2427,11 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep if (isClimbing) { movementType = EntityMovementType.MOVE_WALK; LOGGER.debug("[CLIMB-TRACE] compileIllegal: isClimbing=true, set MOVE_WALK, mpUsed={}, stepType={}", - getMpUsed(), stepType); + getMpUsed(), stepType); } else if (getMpUsed() <= tmpWalkMP) { // VTOL includes powered flight infantry whose getMovementMode() returns VTOL boolean isVTOLMovement = (getEntity().getMovementMode() == EntityMovementMode.VTOL || - getEntity().getMovementMode() == EntityMovementMode.WIGE) && getClearance() > 0; + getEntity().getMovementMode() == EntityMovementMode.WIGE) && getClearance() > 0; if (isVTOLMovement) { movementType = EntityMovementType.MOVE_VTOL_WALK; @@ -2395,8 +2444,8 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // tank on a road can move 5/7, **not** 5/8. } } else if ((entity instanceof Infantry) && - (curPos.distance(entity.getPosition()) == 1) && - (lastPos.equals(entity.getPosition()))) { + (curPos.distance(entity.getPosition()) == 1) && + (lastPos.equals(entity.getPosition()))) { // This ensures that Infantry always get their minimum 1 hex movement when TO fast infantry movement // is on. A MovePath that consists of a single step from one hex to the next should always be a walk, // since it's covered under the infantry's 1 free movement @@ -2418,7 +2467,7 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep if (isClimbing) { LOGGER.info("compileIllegal: climbing step classified as RUN! " + "mpUsed={}, walkMP={}, runMPMax={}, isRunProhibited={}, isRunAllowed={}", - getMpUsed(), tmpWalkMP, runMPMax, isRunProhibited, isRunAllowed()); + getMpUsed(), tmpWalkMP, runMPMax, isRunProhibited, isRunAllowed()); } if (getMpUsed() > runMPNoBoost) { @@ -2436,7 +2485,7 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // VTOL includes powered flight infantry whose getMovementMode() returns VTOL boolean isVTOLRun = (entity.getMovementMode() == EntityMovementMode.VTOL || - entity.getMovementMode() == EntityMovementMode.WIGE) && getClearance() > 0; + entity.getMovementMode() == EntityMovementMode.WIGE) && getClearance() > 0; if (isVTOLRun) { movementType = EntityMovementType.MOVE_VTOL_RUN; @@ -2460,7 +2509,7 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // VTOL includes powered flight infantry whose getMovementMode() returns VTOL boolean isVTOLSprint = entity.getMovementMode() == EntityMovementMode.VTOL || - (entity.getMovementMode() == EntityMovementMode.WIGE && getClearance() > 0); + (entity.getMovementMode() == EntityMovementMode.WIGE && getClearance() > 0); if (isVTOLSprint) { movementType = EntityMovementType.MOVE_VTOL_SPRINT; @@ -2485,22 +2534,22 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // Stop to flank or cruise to overdrive is permitted with a driving check // ("gunning it"). if (game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_ACCELERATION) && - movementType == EntityMovementType.MOVE_SPRINT && - (entity instanceof Tank || - (entity instanceof QuadVee && entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)) && - (entity.movedLastRound == EntityMovementType.MOVE_NONE || - entity.movedLastRound == EntityMovementType.MOVE_SKID || - entity.movedLastRound == EntityMovementType.MOVE_JUMP)) { + movementType == EntityMovementType.MOVE_SPRINT && + (entity instanceof Tank || + (entity instanceof QuadVee && entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)) && + (entity.movedLastRound == EntityMovementType.MOVE_NONE || + entity.movedLastRound == EntityMovementType.MOVE_SKID || + entity.movedLastRound == EntityMovementType.MOVE_JUMP)) { movementType = EntityMovementType.MOVE_ILLEGAL; } // 0 MP infantry units can move 1 hex if (isInfantry && - (cachedEntityState.getWalkMP() == 0) && - getEntity().getPosition().equals(prev.getPosition()) && - (prev.getElevation() == entity.getElevation()) && - (getEntity().getPosition().distance(getPosition()) <= 1) && - (Math.abs(entity.getElevation() - getElevation()) <= entity.getMaxElevationChange()) && - (movementType != EntityMovementType.MOVE_JUMP)) { + (cachedEntityState.getWalkMP() == 0) && + getEntity().getPosition().equals(prev.getPosition()) && + (prev.getElevation() == entity.getElevation()) && + (getEntity().getPosition().distance(getPosition()) <= 1) && + (Math.abs(entity.getElevation() - getElevation()) <= entity.getMaxElevationChange()) && + (movementType != EntityMovementType.MOVE_JUMP)) { movementType = EntityMovementType.MOVE_WALK; } @@ -2511,8 +2560,8 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // Mechanical Jump Boosters don't allow facing changes if (isJumping() && - isUsingMekJumpBooster && - ((stepType == MoveStepType.TURN_LEFT) || (stepType == MoveStepType.TURN_RIGHT))) { + isUsingMekJumpBooster && + ((stepType == MoveStepType.TURN_LEFT) || (stepType == MoveStepType.TURN_RIGHT))) { movementType = EntityMovementType.MOVE_ILLEGAL; } @@ -2524,26 +2573,26 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep if ((movementType == EntityMovementType.MOVE_WALK) && (prev.movementType == EntityMovementType.MOVE_RUN)) { movementType = EntityMovementType.MOVE_RUN; } else if ((movementType == EntityMovementType.MOVE_VTOL_WALK) && - (prev.movementType == EntityMovementType.MOVE_VTOL_RUN)) { + (prev.movementType == EntityMovementType.MOVE_VTOL_RUN)) { movementType = EntityMovementType.MOVE_VTOL_RUN; } else if (((movementType == EntityMovementType.MOVE_WALK) || (movementType == EntityMovementType.MOVE_RUN)) && - (prev.movementType == EntityMovementType.MOVE_SPRINT)) { + (prev.movementType == EntityMovementType.MOVE_SPRINT)) { movementType = EntityMovementType.MOVE_SPRINT; } else if (((movementType == EntityMovementType.MOVE_VTOL_WALK) || - (movementType == EntityMovementType.MOVE_VTOL_RUN)) && - (prev.movementType == EntityMovementType.MOVE_VTOL_SPRINT)) { + (movementType == EntityMovementType.MOVE_VTOL_RUN)) && + (prev.movementType == EntityMovementType.MOVE_VTOL_SPRINT)) { movementType = EntityMovementType.MOVE_VTOL_SPRINT; } if (entity.isGyroDestroyed() && - !((entity instanceof LandAirMek) && (entity.getConversionMode() == LandAirMek.CONV_MODE_FIGHTER))) { + !((entity instanceof LandAirMek) && (entity.getConversionMode() == LandAirMek.CONV_MODE_FIGHTER))) { // A prone `Mek with a destroyed gyro can only change a single hex side, or eject if (entity.isProne()) { if (((stepType != MoveStepType.TURN_LEFT && stepType != MoveStepType.TURN_RIGHT) || getMpUsed() > 1) && - stepType != MoveStepType.EJECT) { + stepType != MoveStepType.EJECT) { if (isClimbing) { LOGGER.debug("[CLIMB-TRACE] gyro destroyed (prone): set MOVE_ILLEGAL, stepType={}", - stepType); + stepType); } movementType = EntityMovementType.MOVE_ILLEGAL; } @@ -2555,14 +2604,14 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // be allowed. if (getMp() > 0) { boolean isTracked = entity.getMovementMode() == EntityMovementMode.TRACKED || - entity.getMovementMode() == EntityMovementMode.WHEELED; + entity.getMovementMode() == EntityMovementMode.WHEELED; if (entity instanceof QuadVee) { // We are in `Mek/non-tracked mode if the end mode is vee, and we are converting of the end // mode is `Mek, and we are not converting. if (isTracked == entity.isConvertingNow() && stepType != MoveStepType.CONVERT_MODE) { if (isClimbing) { LOGGER.debug("[CLIMB-TRACE] gyro destroyed (QuadVee): set MOVE_ILLEGAL, stepType={}", - stepType); + stepType); } movementType = EntityMovementType.MOVE_ILLEGAL; } @@ -2571,8 +2620,8 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // the end mode. if (isClimbing) { LOGGER.debug("[CLIMB-TRACE] gyro destroyed (non-tracked Mek): set MOVE_ILLEGAL, " - + "stepType={}, mp={}, mpUsed={}", - stepType, getMp(), getMpUsed()); + + "stepType={}, mp={}, mpUsed={}", + stepType, getMp(), getMpUsed()); } movementType = EntityMovementType.MOVE_ILLEGAL; } @@ -2582,10 +2631,10 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // Meks with no arms and a missing leg cannot attempt to stand if (((stepType == MoveStepType.GET_UP) || (stepType == MoveStepType.CAREFUL_STAND)) && - (entity instanceof Mek) && - entity.isLocationBad(Mek.LOC_LEFT_ARM) && - entity.isLocationBad(Mek.LOC_RIGHT_ARM) && - (entity.isLocationBad(Mek.LOC_RIGHT_LEG) || entity.isLocationBad(Mek.LOC_LEFT_LEG))) { + (entity instanceof Mek) && + entity.isLocationBad(Mek.LOC_LEFT_ARM) && + entity.isLocationBad(Mek.LOC_RIGHT_ARM) && + (entity.isLocationBad(Mek.LOC_RIGHT_LEG) || entity.isLocationBad(Mek.LOC_LEFT_LEG))) { LOGGER.debug("[STAND-TRACE] {} blocked: no arms + missing leg", stepType); movementType = EntityMovementType.MOVE_ILLEGAL; return; @@ -2594,9 +2643,9 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep // Meks with 1 MP are allowed to get up, except // if they've used that 1MP up already if ((MoveStepType.GET_UP == stepType) && - (1 == cachedEntityState.getRunMP()) && - (entity.mpUsed < 1) && - !entity.isStuck()) { + (1 == cachedEntityState.getRunMP()) && + (entity.mpUsed < 1) && + !entity.isStuck()) { LOGGER.debug("[STAND-TRACE] GET_UP with 1 MP, set MOVE_RUN"); movementType = EntityMovementType.MOVE_RUN; } @@ -2608,10 +2657,10 @@ private void compileIllegal(final Game game, final Entity entity, final MoveStep if ((stepType == MoveStepType.GET_UP) || (stepType == MoveStepType.CAREFUL_STAND)) { LOGGER.debug("[STAND-TRACE] {} after checks: movementType={}, isProne={}, " + - "isClimbing={}, entity.isClimbing={}, climbMode={}, elevation={}, " + - "entity.elevation={}, entity.position={}, entity.mpUsed={}", - stepType, movementType, isProne(), isClimbing, entity.isClimbing(), - climbMode, elevation, entity.getElevation(), entity.getPosition(), entity.mpUsed); + "isClimbing={}, entity.isClimbing={}, climbMode={}, elevation={}, " + + "entity.elevation={}, entity.position={}, entity.mpUsed={}", + stepType, movementType, isProne(), isClimbing, entity.isClimbing(), + climbMode, elevation, entity.getElevation(), entity.getPosition(), entity.mpUsed); } if (isFirstStep() && ((stepType == MoveStepType.TAKEOFF) || (stepType == MoveStepType.VERTICAL_TAKE_OFF))) { @@ -2620,38 +2669,38 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), // VTOLs with a damaged flight stabiliser can't flank if ((entity instanceof VTOL) && - (movementType == EntityMovementType.MOVE_VTOL_RUN || - movementType == EntityMovementType.MOVE_VTOL_SPRINT) && - ((VTOL) entity).isStabiliserHit(VTOL.LOC_ROTOR)) { + (movementType == EntityMovementType.MOVE_VTOL_RUN || + movementType == EntityMovementType.MOVE_VTOL_SPRINT) && + ((VTOL) entity).isStabiliserHit(VTOL.LOC_ROTOR)) { movementType = EntityMovementType.MOVE_ILLEGAL; } // check for UMU infantry on land if ((entity.getMovementMode() == EntityMovementMode.INF_UMU) && - !game.getBoard(boardId).getHex(curPos).containsTerrain(Terrains.WATER) && - (movementType == EntityMovementType.MOVE_RUN)) { + !game.getBoard(boardId).getHex(curPos).containsTerrain(Terrains.WATER) && + (movementType == EntityMovementType.MOVE_RUN)) { movementType = EntityMovementType.MOVE_ILLEGAL; } // amnesty for the first step if (isFirstStep() && - (movementType == EntityMovementType.MOVE_ILLEGAL) && - (cachedEntityState.getWalkMP() > 0) && - !entity.isProne() && - !entity.isHullDown() && - !entity.isStuck() && - !entity.isGyroDestroyed() && - (stepType == MoveStepType.FORWARDS)) { + (movementType == EntityMovementType.MOVE_ILLEGAL) && + (cachedEntityState.getWalkMP() > 0) && + !entity.isProne() && + !entity.isHullDown() && + !entity.isStuck() && + !entity.isGyroDestroyed() && + (stepType == MoveStepType.FORWARDS)) { movementType = EntityMovementType.MOVE_RUN; } // Bimodal LAMs cannot spend MP when converting to fighter mode on the ground. if (entity instanceof LandAirMek && - ((LandAirMek) entity).getLAMType() == LandAirMek.LAM_BIMODAL && - entity.getConversionMode() == LandAirMek.CONV_MODE_MEK && - movementMode == EntityMovementMode.AERODYNE && - altitude == 0 && - mp > 0) { + ((LandAirMek) entity).getLAMType() == LandAirMek.LAM_BIMODAL && + entity.getConversionMode() == LandAirMek.CONV_MODE_MEK && + movementMode == EntityMovementMode.AERODYNE && + altitude == 0 && + mp > 0) { movementType = EntityMovementType.MOVE_ILLEGAL; } @@ -2675,8 +2724,8 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), // Prone Meks are able to unload, if they have the MP. if ((getMpUsed() <= cachedEntityState.getRunMP()) && - (entity.isProne() || entity.isHullDown()) && - (movementType == EntityMovementType.MOVE_ILLEGAL)) { + (entity.isProne() || entity.isHullDown()) && + (movementType == EntityMovementType.MOVE_ILLEGAL)) { movementType = EntityMovementType.MOVE_RUN; if (getMpUsed() <= cachedEntityState.getWalkMP()) { movementType = EntityMovementType.MOVE_WALK; @@ -2704,7 +2753,7 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), } } if ((null != Compute.stackingViolation(game, other, curPos, entity, climbMode, true)) || - other.isLocationProhibited(curPos, unloadElevation)) { + other.isLocationProhibited(curPos, unloadElevation)) { movementType = EntityMovementType.MOVE_ILLEGAL; } } else { @@ -2750,7 +2799,7 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), Targetable target = getTarget(game); if (target instanceof Entity other) { if ((null != Compute.stackingViolation(game, other, curPos, entity, climbMode, true)) || - other.isLocationProhibited(curPos, getElevation())) { + other.isLocationProhibited(curPos, getElevation())) { movementType = EntityMovementType.MOVE_ILLEGAL; } } else { @@ -2775,11 +2824,11 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), // Can't run or jump if unjamming a RAC. if (isUnjammingRAC && - ((movementType == EntityMovementType.MOVE_RUN) || - (movementType == EntityMovementType.MOVE_SPRINT) || - (movementType == EntityMovementType.MOVE_VTOL_RUN || - (movementType == EntityMovementType.MOVE_VTOL_SPRINT)) || - isJumping())) { + ((movementType == EntityMovementType.MOVE_RUN) || + (movementType == EntityMovementType.MOVE_SPRINT) || + (movementType == EntityMovementType.MOVE_VTOL_RUN || + (movementType == EntityMovementType.MOVE_VTOL_SPRINT)) || + isJumping())) { movementType = EntityMovementType.MOVE_ILLEGAL; } @@ -2794,15 +2843,15 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), movementType = EntityMovementType.MOVE_ILLEGAL; } if (entity instanceof Tank || - (entity instanceof QuadVee && - ((entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE) != entity.isConvertingNow()))) { + (entity instanceof QuadVee && + ((entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE) != entity.isConvertingNow()))) { // Tanks and QuadVees ending movement in vehicle mode require a fortified hex. if (!(game.getBoard(boardId).getHex(curPos).containsTerrain(Terrains.FORTIFIED))) { movementType = EntityMovementType.MOVE_ILLEGAL; } else if ((entity instanceof Tank tank) && tank.isLargeVehicleForHullDown()) { // Large Vehicles cannot use infantry-built (fortified) hexes for cover (TO:AR p.19). LOGGER.debug("[HullDown] {}: HULL_DOWN step illegal - Large Vehicles cannot use infantry-built " - + "(fortified) hexes for cover", entity.getDisplayName()); + + "(fortified) hexes for cover", entity.getDisplayName()); movementType = EntityMovementType.MOVE_ILLEGAL; } } else if (entity.isGyroDestroyed()) { @@ -2819,28 +2868,28 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), // do not allow to move onto a bridge if there's no exit in lastPos's // direction, unless jumping or already at/above bridge level if (!isFirstStep() && - !curPos.equals(lastPos) && - climbMode && - entity.getMovementMode() != EntityMovementMode.VTOL && - (entity.getMovementMode() != EntityMovementMode.WIGE || getClearance() == 0) && - (movementType != EntityMovementType.MOVE_JUMP) && - game.getBoard(boardId).getHex(curPos).containsTerrain(Terrains.BRIDGE) && - !game.getBoard(boardId).getHex(curPos).containsTerrainExit(Terrains.BRIDGE, curPos.direction(lastPos)) && - (getElevation() < game.getBoard(boardId).getHex(curPos).terrainLevel(Terrains.BRIDGE_ELEV)) && - (getElevation() + entity.getHeight() >= - game.getBoard(boardId).getHex(curPos).terrainLevel(Terrains.BRIDGE_ELEV))) { + !curPos.equals(lastPos) && + climbMode && + entity.getMovementMode() != EntityMovementMode.VTOL && + (entity.getMovementMode() != EntityMovementMode.WIGE || getClearance() == 0) && + (movementType != EntityMovementType.MOVE_JUMP) && + game.getBoard(boardId).getHex(curPos).containsTerrain(Terrains.BRIDGE) && + !game.getBoard(boardId).getHex(curPos).containsTerrainExit(Terrains.BRIDGE, curPos.direction(lastPos)) && + (getElevation() < game.getBoard(boardId).getHex(curPos).terrainLevel(Terrains.BRIDGE_ELEV)) && + (getElevation() + entity.getHeight() >= + game.getBoard(boardId).getHex(curPos).terrainLevel(Terrains.BRIDGE_ELEV))) { movementType = EntityMovementType.MOVE_ILLEGAL; } // Walking under a bridge: check if entity fits (TO:AR 115) // If entity is under the bridge and can't fit, move is illegal (unless jumping) if (!isFirstStep() && - !curPos.equals(lastPos) && - (movementType != EntityMovementType.MOVE_JUMP) && - game.getBoard(boardId).getHex(curPos).containsTerrain(Terrains.BRIDGE) && - (getElevation() < game.getBoard(boardId).getHex(curPos).terrainLevel(Terrains.BRIDGE_ELEV)) && - (getElevation() + entity.getHeight() >= - game.getBoard(boardId).getHex(curPos).terrainLevel(Terrains.BRIDGE_ELEV))) { + !curPos.equals(lastPos) && + (movementType != EntityMovementType.MOVE_JUMP) && + game.getBoard(boardId).getHex(curPos).containsTerrain(Terrains.BRIDGE) && + (getElevation() < game.getBoard(boardId).getHex(curPos).terrainLevel(Terrains.BRIDGE_ELEV)) && + (getElevation() + entity.getHeight() >= + game.getBoard(boardId).getHex(curPos).terrainLevel(Terrains.BRIDGE_ELEV))) { movementType = EntityMovementType.MOVE_ILLEGAL; } @@ -2849,18 +2898,18 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), // level - including engineer bridges over water (TO:AUE p.152) - which the climb-from-below check above misses. // Bridges flush with dry ground (a bridge acting as a road) are exempt, preserving road-segment movement. if (!isFirstStep() && - !curPos.equals(lastPos) && - (movementType != EntityMovementType.MOVE_JUMP) && - (entity.getMovementMode() != EntityMovementMode.VTOL) && - ((entity.getMovementMode() != EntityMovementMode.WIGE) || (getClearance() == 0))) { + !curPos.equals(lastPos) && + (movementType != EntityMovementType.MOVE_JUMP) && + (entity.getMovementMode() != EntityMovementMode.VTOL) && + ((entity.getMovementMode() != EntityMovementMode.WIGE) || (getClearance() == 0))) { Hex bridgeDestHex = game.getBoard(boardId).getHex(curPos); Hex bridgeSrcHex = game.getBoard(boardId).getHex(lastPos); // Only units that cannot traverse the terrain under the bridge are bound to use its exits; a hovercraft // or naval unit crossing the water is not on the bridge and moves freely. boolean leavingBridgeDeckOffExit = crossesBridgeDeckOffExit(entity, bridgeSrcHex, prev.getElevation(), - lastPos, curPos); + lastPos, curPos); boolean enteringBridgeDeckOffExit = crossesBridgeDeckOffExit(entity, bridgeDestHex, getElevation(), - curPos, lastPos); + curPos, lastPos); if (leavingBridgeDeckOffExit || enteringBridgeDeckOffExit) { movementType = EntityMovementType.MOVE_ILLEGAL; } @@ -2868,9 +2917,9 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), // super heavy meks can't climb on buildings if ((entity instanceof Mek mek) && - mek.isSuperHeavy() && - climbMode && - game.getBoard(boardId).getHex(curPos).containsTerrain(Terrains.BUILDING)) { + mek.isSuperHeavy() && + climbMode && + game.getBoard(boardId).getHex(curPos).containsTerrain(Terrains.BUILDING)) { movementType = EntityMovementType.MOVE_ILLEGAL; } @@ -2886,7 +2935,7 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), Hex prevHex = game.getBoard(boardId).getHex(lastPos); // Check if we're climbing from outside/below onto the building top if (!prevHex.containsTerrain(Terrains.BUILDING) || - prevHex.terrainLevel(Terrains.BLDG_ELEV) < curHex.terrainLevel(Terrains.BLDG_ELEV)) { + prevHex.terrainLevel(Terrains.BLDG_ELEV) < curHex.terrainLevel(Terrains.BLDG_ELEV)) { int prevAbsoluteElev = prevHex.getLevel() + prev.getElevation(); int curAbsoluteElev = curHex.getLevel() + getElevation(); int elevChange = curAbsoluteElev - prevAbsoluteElev; @@ -2898,11 +2947,11 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), } boolean climbingEnabled = game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING); + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING); boolean canTacOpsClimb = climbingEnabled - && (entity instanceof Mek) - && ClimbingHelper.canClimb(entity, prev.isProne()) - && (elevChange > 0); + && (entity instanceof Mek) + && ClimbingHelper.canClimb(entity, prev.isProne()) + && (elevChange > 0); if ((elevChange > maxAllowed) && !canTacOpsClimb) { movementType = EntityMovementType.MOVE_ILLEGAL; } @@ -2922,10 +2971,10 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), movementType = prev.movementType; if (entity instanceof BattleArmor && - !(isFirstStep() || - (prev.movementType == EntityMovementType.MOVE_JUMP) || - (prev.movementType == EntityMovementType.MOVE_VTOL_RUN) || - (prev.movementType == EntityMovementType.MOVE_VTOL_WALK))) { + !(isFirstStep() || + (prev.movementType == EntityMovementType.MOVE_JUMP) || + (prev.movementType == EntityMovementType.MOVE_VTOL_RUN) || + (prev.movementType == EntityMovementType.MOVE_VTOL_WALK))) { movementType = EntityMovementType.MOVE_ILLEGAL; } } @@ -2948,9 +2997,9 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), // Bootlegger requires three hexes straight and is illegal for tracked, WiGE, or // naval. if (prev.nStraight < 3 || - (entity.getMovementMode() != EntityMovementMode.WHEELED && - entity.getMovementMode() != EntityMovementMode.HOVER && - entity.getMovementMode() != EntityMovementMode.VTOL)) { + (entity.getMovementMode() != EntityMovementMode.WHEELED && + entity.getMovementMode() != EntityMovementMode.HOVER && + entity.getMovementMode() != EntityMovementMode.VTOL)) { movementType = EntityMovementType.MOVE_ILLEGAL; } else { danger = true; @@ -2968,24 +3017,24 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), // A DOWN step on a climbing/dangling Mek (TO:AR p.20) likewise stays in the same hex, // moving down the cliff face — isMovementPossible would reject the in-air elevation. boolean isInPlaceAction = (stepType == MoveStepType.GET_UP) - || (stepType == MoveStepType.CAREFUL_STAND) - || (stepType == MoveStepType.GO_PRONE) - || (stepType == MoveStepType.HULL_DOWN) - || (isClimbing && (stepType == MoveStepType.DOWN)); + || (stepType == MoveStepType.CAREFUL_STAND) + || (stepType == MoveStepType.GO_PRONE) + || (stepType == MoveStepType.HULL_DOWN) + || (isClimbing && (stepType == MoveStepType.DOWN)); boolean movementPossible = isInPlaceAction - || isMovementPossible(game, lastPos, prev.getElevation(), cachedEntityState); + || isMovementPossible(game, lastPos, prev.getElevation(), cachedEntityState); if (!movementPossible || - (isUnloaded && !(type == MoveStepType.CHAFF || type == MoveStepType.UNLOAD)) + (isUnloaded && !(type == MoveStepType.CHAFF || type == MoveStepType.UNLOAD)) ) { if (isClimbing) { LOGGER.info("compileIllegal: climbing step overridden to MOVE_ILLEGAL! " + "movementPossible={}, movementType was={}, prevEl={}", - movementPossible, movementType, prev.getElevation()); + movementPossible, movementType, prev.getElevation()); } if ((stepType == MoveStepType.GET_UP) || (stepType == MoveStepType.CAREFUL_STAND)) { LOGGER.debug("[STAND-TRACE] {} overridden to MOVE_ILLEGAL by isMovementPossible! " + - "movementPossible={}, prevEl={}, lastPos={}, prev.movementType={}", - stepType, movementPossible, prev.getElevation(), lastPos, prev.movementType); + "movementPossible={}, prevEl={}, lastPos={}, prev.movementType={}", + stepType, movementPossible, prev.getElevation(), lastPos, prev.movementType); } movementType = EntityMovementType.MOVE_ILLEGAL; } @@ -2994,7 +3043,7 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), if (EntityMovementType.MOVE_ILLEGAL == prev.movementType) { if ((stepType == MoveStepType.GET_UP) || (stepType == MoveStepType.CAREFUL_STAND)) { LOGGER.debug("[STAND-TRACE] {} overridden to MOVE_ILLEGAL because prev step was ILLEGAL! " + - "prev.type={}", stepType, prev.type); + "prev.type={}", stepType, prev.type); } movementType = EntityMovementType.MOVE_ILLEGAL; } @@ -3063,10 +3112,10 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), // Vehicles carrying mechanized BA can't jump, VTOL, or WiGE if ((entity instanceof Tank) && !entity.getExternalUnits().isEmpty()) { if ((movementType == EntityMovementType.MOVE_JUMP) || - (movementType == EntityMovementType.MOVE_VTOL_WALK) || - (movementType == EntityMovementType.MOVE_VTOL_RUN) || - (movementType == EntityMovementType.MOVE_VTOL_SPRINT) || - ((entity.getMovementMode() == EntityMovementMode.WIGE) && getClearance() > 0)) { + (movementType == EntityMovementType.MOVE_VTOL_WALK) || + (movementType == EntityMovementType.MOVE_VTOL_RUN) || + (movementType == EntityMovementType.MOVE_VTOL_SPRINT) || + ((entity.getMovementMode() == EntityMovementMode.WIGE) && getClearance() > 0)) { movementType = EntityMovementType.MOVE_ILLEGAL; } } @@ -3076,7 +3125,8 @@ stepType, movementType, isProne(), isClimbing, entity.isClimbing(), * If the entity has both, choose the one with the lower risk, or Supercharger if they are even Require a PSR if it * has not been done in an earlier part of the move */ - private void UseEitherMASCOrSupercharger(boolean hasMASCBeenUsed, boolean hasSuperchargerBeenUsed) { + private void UseEitherMASCOrSupercharger(boolean hasMASCBeenUsed, + boolean hasSuperchargerBeenUsed) { MPBoosters mpBoosters = entity.getArmedMPBoosters(); if (mpBoosters.isJetBooster() && !hasMASCBeenUsed) { setUsingMASC(true); @@ -3096,7 +3146,8 @@ private void UseEitherMASCOrSupercharger(boolean hasMASCBeenUsed, boolean hasSup /** * Require a PSR if it has not been done in an earlier part of the move */ - private void UseBothMASCAndSupercharger(boolean hasMASCBeenUsed, boolean hasSuperchargerBeenUsed) { + private void UseBothMASCAndSupercharger(boolean hasMASCBeenUsed, + boolean hasSuperchargerBeenUsed) { if (!hasMASCBeenUsed) { setUsingMASC(true); setTargetNumberMASC(entity.getMASCTarget()); @@ -3114,7 +3165,9 @@ public int getHeat() { /** * Amount of movement points required to move from start to dest */ - protected void calcMovementCostFor(Game game, MoveStep prevStep, CachedEntityState cachedEntityState) { + protected void calcMovementCostFor(Game game, + MoveStep prevStep, + CachedEntityState cachedEntityState) { final Coords prev = prevStep.getPosition(); final int prevEl = prevStep.getElevation(); final EntityMovementMode moveMode = getEntity().getMovementMode(); @@ -3127,15 +3180,15 @@ protected void calcMovementCostFor(Game game, MoveStep prevStep, CachedEntitySta final boolean isProto = getEntity() instanceof ProtoMek; final boolean isMek = getEntity() instanceof Mek; final boolean isAmphibious = cachedEntityState.hasWorkingMisc(MiscType.F_FULLY_AMPHIBIOUS) || - cachedEntityState.hasWorkingMisc(MiscType.F_LIMITED_AMPHIBIOUS); + cachedEntityState.hasWorkingMisc(MiscType.F_LIMITED_AMPHIBIOUS); final boolean isFogSpecialist = en.getCrew() - .getOptions() - .stringOption(OptionsConstants.MISC_ENV_SPECIALIST) - .equals(Crew.ENVIRONMENT_SPECIALIST_FOG); + .getOptions() + .stringOption(OptionsConstants.MISC_ENV_SPECIALIST) + .equals(Crew.ENVIRONMENT_SPECIALIST_FOG); final boolean isLightSpecialist = en.getCrew() - .getOptions() - .stringOption(OptionsConstants.MISC_ENV_SPECIALIST) - .equals(Crew.ENVIRONMENT_SPECIALIST_LIGHT); + .getOptions() + .stringOption(OptionsConstants.MISC_ENV_SPECIALIST) + .equals(Crew.ENVIRONMENT_SPECIALIST_LIGHT); int nSrcEl = srcHex.getLevel() + prevEl; // Use the step's actual resolved elevation. MoveStep.compile sets it correctly via // ClimbingHelper.getClimbDestinationElevation for continuation climbs (top of the @@ -3152,18 +3205,18 @@ protected void calcMovementCostFor(Game game, MoveStep prevStep, CachedEntitySta // 0 MP infantry units can move 1 hex if (isInfantry && - (cachedEntityState.getWalkMP() == 0) && - !moveMode.isSubmarine() && - !moveMode.isVTOL() && - getEntity().getPosition().equals(prev) && - (getEntity().getPosition().distance(getPosition()) == 1) && - (!isJumping())) { + (cachedEntityState.getWalkMP() == 0) && + !moveMode.isSubmarine() && + !moveMode.isVTOL() && + getEntity().getPosition().equals(prev) && + (getEntity().getPosition().distance(getPosition()) == 1) && + (!isJumping())) { mp = 0; return; } boolean applyNightPen = !game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_NO_NIGHT_MOVE_PEN); + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_NO_NIGHT_MOVE_PEN); // VTOL (includes powered flight infantry), jumping are exempt from careful movement penalties boolean carefulExempt = (moveMode == EntityMovementMode.VTOL) || isJumping(); @@ -3263,8 +3316,8 @@ protected void calcMovementCostFor(Game game, MoveStep prevStep, CachedEntitySta if (!isPavementStep()) { if ((moveMode != EntityMovementMode.BIPED_SWIM) && - (moveMode != EntityMovementMode.QUAD_SWIM) && - getClearance() == 0) { + (moveMode != EntityMovementMode.QUAD_SWIM) && + getClearance() == 0) { mp += destHex.movementCost(getEntity()); } @@ -3286,14 +3339,14 @@ protected void calcMovementCostFor(Game game, MoveStep prevStep, CachedEntitySta // non-hovers, non-naval and non-VTOLs check for water depth and // are affected by swamp if ((moveMode != EntityMovementMode.HOVER) && - (moveMode != EntityMovementMode.NAVAL) && - (moveMode != EntityMovementMode.HYDROFOIL) && - (moveMode != EntityMovementMode.SUBMARINE) && - (moveMode != EntityMovementMode.INF_UMU) && - (moveMode != EntityMovementMode.VTOL) && - (moveMode != EntityMovementMode.BIPED_SWIM) && - (moveMode != EntityMovementMode.QUAD_SWIM) && - (moveMode != EntityMovementMode.WIGE)) { + (moveMode != EntityMovementMode.NAVAL) && + (moveMode != EntityMovementMode.HYDROFOIL) && + (moveMode != EntityMovementMode.SUBMARINE) && + (moveMode != EntityMovementMode.INF_UMU) && + (moveMode != EntityMovementMode.VTOL) && + (moveMode != EntityMovementMode.BIPED_SWIM) && + (moveMode != EntityMovementMode.QUAD_SWIM) && + (moveMode != EntityMovementMode.WIGE)) { // Water entry MP only applies when the unit is actually IN the water column at // the destination — i.e., its destination elevation is at or below the hex // surface. A Mek crossing a bridge over water (nDestEl above the hex level) is @@ -3302,19 +3355,20 @@ protected void calcMovementCostFor(Game game, MoveStep prevStep, CachedEntitySta int waterDepth = destHex.terrainLevel(Terrains.WATER); // no additional cost when moving on surface of ice. boolean isOnIceSurface = destHex.containsTerrain(Terrains.ICE) - && (nDestEl == destHex.getLevel()); + && (nDestEl == destHex.getLevel()); if (inWaterColumn && !isOnIceSurface && (waterDepth > 0) && !isAmphibious) { // "To be considered underwater, a unit must be completely submerged... 'Mechs must be in at // least Depth 2 water, or at least Depth 1 if prone" (TW p.56). Everything shorter than a Mek // is already completely under in Depth 1, so it pays the underwater cost there. Anything only // partly submerged is wading, and pays the lower Depth 1 cost from the Movement Costs Table. boolean isFullySubmerged = (entity instanceof Mek) - ? EnvironmentalSealingRules.isMekCompletelySubmerged(isProne(), waterDepth) - : (waterDepth > 0); + ? EnvironmentalSealingRules.isMekCompletelySubmerged(isProne(), + waterDepth) + : (waterDepth > 0); if (!isFullySubmerged) { mp++; } else if (getEntity().hasAbility(OptionsConstants.PILOT_TM_FROGMAN) - && ((entity instanceof Mek) || (entity instanceof ProtoMek))) { + && ((entity instanceof Mek) || (entity instanceof ProtoMek))) { mp += 2; } else { mp += Game.rulesManager.getRulesMovement().getUnderwaterMPCost(); @@ -3332,17 +3386,19 @@ protected void calcMovementCostFor(Game game, MoveStep prevStep, CachedEntitySta if ((nSrcEl != nDestEl) && (moveMode != EntityMovementMode.WIGE)) { int deltaElevation = Math.abs(nSrcEl - nDestEl); if (isMek && (deltaElevation > Game.rulesManager.getRulesTerrain().getMaxElevationChangeAllowed(srcHex, - destHex, getEntity().getMaxElevationChange()))) { + destHex, + getEntity().getMaxElevationChange()))) { LOGGER.debug("calcMovementCostFor elevation: prevEl={}, elevation={}, " + - "srcHex.level={}, destHex.level={}, nSrcEl={}, nDestEl={}, deltaElevation={}", - prevEl, elevation, srcHex.getLevel(), destHex.getLevel(), - nSrcEl, nDestEl, deltaElevation); + "srcHex.level={}, destHex.level={}, nSrcEl={}, nDestEl={}, deltaElevation={}", + prevEl, elevation, srcHex.getLevel(), destHex.getLevel(), + nSrcEl, nDestEl, deltaElevation); } if (game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_LEAPING) && - isMek && - (deltaElevation > Game.rulesManager.getRulesTerrain().getMaxElevationChangeAllowed(srcHex, - destHex, getEntity().getMaxElevationChange())) && - (nDestEl < nSrcEl)) { + isMek && + (deltaElevation > Game.rulesManager.getRulesTerrain().getMaxElevationChangeAllowed(srcHex, + destHex, + getEntity().getMaxElevationChange())) && + (nDestEl < nSrcEl)) { // leaping (moving down more than 2 hexes) always costs 4 mp // regardless of anything else mp = 4; @@ -3353,26 +3409,27 @@ destHex, getEntity().getMaxElevationChange())) && // Also applies when continuing a multi-turn climb (entity already climbing) // even if remaining elevation is within normal movement limits. boolean isNewClimb = (deltaElevation > entity.getMaxElevationChange()) - && (nDestEl > nSrcEl); + && (nDestEl > nSrcEl); boolean isContinuedClimb = isClimbing && (nDestEl > nSrcEl); // Climbing only applies to walking movement, not jumping or VTOL boolean isWalkingMovement = (movementType != EntityMovementType.MOVE_JUMP) - && (movementType != EntityMovementType.MOVE_VTOL_WALK) - && (movementType != EntityMovementType.MOVE_VTOL_RUN) - && (movementType != EntityMovementType.MOVE_VTOL_SPRINT); + && (movementType != EntityMovementType.MOVE_VTOL_WALK) + && (movementType != EntityMovementType.MOVE_VTOL_RUN) + && (movementType != EntityMovementType.MOVE_VTOL_SPRINT); boolean isClimbingMove = isMek - && climbMode - && isWalkingMovement - && (isNewClimb || isContinuedClimb) - && game.getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING) - && ClimbingHelper.canClimb(entity, prevStep.isProne()); + && climbMode + && isWalkingMovement + && (isNewClimb || isContinuedClimb) + && game.getOptions() + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING) + && ClimbingHelper.canClimb(entity, prevStep.isProne()); if (isClimbingMove) { int climbCostPerLevel = ClimbingHelper.getClimbingMPCostPerLevel((Mek) entity); // Use player-chosen levels if set, otherwise full elevation delta int chosenLevels = entity.getClimbingLevelsChosen(); int levelsToCharge = (chosenLevels > 0) - ? Math.min(chosenLevels, deltaElevation) - : deltaElevation; + ? Math.min(chosenLevels, deltaElevation) + : deltaElevation; mp += levelsToCharge * climbCostPerLevel; isClimbing = true; climbingTotalLevels = deltaElevation; @@ -3381,29 +3438,29 @@ destHex, getEntity().getMaxElevationChange())) && isRunProhibited = true; movementType = EntityMovementType.MOVE_WALK; LOGGER.debug("calcMovementCostFor: climbing {} of {} levels at {} MP/level = {} MP, " + - "chosenLevels={}, movementType forced to MOVE_WALK", - levelsToCharge, deltaElevation, climbCostPerLevel, levelsToCharge * climbCostPerLevel, - chosenLevels); + "chosenLevels={}, movementType forced to MOVE_WALK", + levelsToCharge, deltaElevation, climbCostPerLevel, levelsToCharge * climbCostPerLevel, + chosenLevels); return; } // Mountain Troops only expend 1 MP per 2 levels moved up or down (TO:AUE p.153). // This stacks with the Mountaineer ability (PILOT_TM_MOUNTAINEER) which reduces // elevation cost by 1 MP. Combined, a 1-level change can cost 0 MP elevation. boolean isMountainTroop = entity instanceof ConvInfantry convInfantry - && convInfantry.hasSpecialization(ConvInfantry.MOUNTAIN_TROOPS); + && convInfantry.hasSpecialization(ConvInfantry.MOUNTAIN_TROOPS); if (isMountainTroop) { deltaElevation = (int) Math.ceil(deltaElevation / 2.0); } else if ((isInfantry && - !((getMovementType(false) == EntityMovementType.MOVE_VTOL_WALK) || - (getMovementType(false) == EntityMovementType.MOVE_VTOL_RUN))) || - ((moveMode == EntityMovementMode.TRACKED) || + !((getMovementType(false) == EntityMovementType.MOVE_VTOL_WALK) || + (getMovementType(false) == EntityMovementType.MOVE_VTOL_RUN))) || + ((moveMode == EntityMovementMode.TRACKED) || (moveMode == EntityMovementMode.WHEELED) || (moveMode == EntityMovementMode.HOVER))) { // non-flying Infantry and ground vehicles are charged double. deltaElevation *= 2; } int elevationCost = Game.rulesManager.getRulesTerrain().getRoadElevationCostDifference(srcHex, destHex, - deltaElevation); + deltaElevation); if (entity.hasAbility(OptionsConstants.PILOT_TM_MOUNTAINEER)) { mp += Math.max(0, deltaElevation - 1 - elevationCost); } else { @@ -3419,17 +3476,17 @@ destHex, getEntity().getMaxElevationChange())) && // See // http://bg.battletech.com/forums/index.php?topic=51081.msg1297747#msg1297747 if (entity.getMovementMode() == EntityMovementMode.WIGE && - distance > 0 && - (getClearance() > 1 || - (destHex.containsTerrain(Terrains.BLDG_ELEV) && destHex.ceiling() > srcHex.ceiling()))) { + distance > 0 && + (getClearance() > 1 || + (destHex.containsTerrain(Terrains.BLDG_ELEV) && destHex.ceiling() > srcHex.ceiling()))) { mp += 2; } // WIGEs spend one extra MP to ascend a sheer cliff, TO p.39 if (entity.getMovementMode() == EntityMovementMode.WIGE && - distance > 0 && - destHex.hasCliffTopTowards(srcHex) && - nDestEl > nSrcEl) { + distance > 0 && + destHex.hasCliffTopTowards(srcHex) && + nDestEl > nSrcEl) { mp += 1; } @@ -3476,7 +3533,10 @@ destHex, getEntity().getMaxElevationChange())) && * This function does not comment on whether an overall movement path is possible, just whether the current * step is possible. */ - public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntityState cachedEntityState) { + public boolean isMovementPossible(Game game, + Coords src, + int srcEl, + CachedEntityState cachedEntityState) { final Hex srcHex = game.getBoard(boardId).getHex(src); final Coords dest = getPosition(); final Hex destHex = game.getBoard(boardId).getHex(dest); @@ -3500,21 +3560,21 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // Assault dropping units cannot move if ((entity.isAssaultDropInProgress() || entity.isDropping()) && - !((entity instanceof LandAirMek) && - (entity.getMovementMode() == EntityMovementMode.WIGE) && - (entity.getAltitude() <= 3))) { + !((entity instanceof LandAirMek) && + (entity.getMovementMode() == EntityMovementMode.WIGE) && + (entity.getAltitude() <= 3))) { return false; } // If we're a tank and immobile, check if we try to unjam // or eject and the crew is not unconscious if ((entity instanceof Tank) && - !entity.getCrew().isUnconscious() && - ((type == MoveStepType.UNJAM_RAC) || - (type == MoveStepType.EJECT) || - (type == MoveStepType.LAUNCH_ESCAPE_POD) || - (type == MoveStepType.ABANDON) || - (type == MoveStepType.SEARCHLIGHT))) { + !entity.getCrew().isUnconscious() && + ((type == MoveStepType.UNJAM_RAC) || + (type == MoveStepType.EJECT) || + (type == MoveStepType.LAUNCH_ESCAPE_POD) || + (type == MoveStepType.ABANDON) || + (type == MoveStepType.SEARCHLIGHT))) { return true; } @@ -3547,7 +3607,7 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // unless it is the movement phase and the plan is to activate then // if we're in this method, we're implicitly in the movement phase if (entity.isHidden() || - (!entity.getHiddenActivationPhase().isUnknown() && !entity.getHiddenActivationPhase().isMovement())) { + (!entity.getHiddenActivationPhase().isUnknown() && !entity.getHiddenActivationPhase().isMovement())) { return false; } @@ -3571,12 +3631,12 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // Exception: TacOps Climbing allows climbing the outside of a building to the roof, // so use the full building elevation when climbing is enabled. boolean tacOpsClimbingAvailable = game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING) - && climbMode - && ClimbingHelper.canClimb(entity, isProne()); + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING) + && climbMode + && ClimbingHelper.canClimb(entity, isProne()); if (bld != null && getEntity().getElevation() == 0 && climbMode - && !destHex.containsTerrain(Terrains.BRIDGE) - && !tacOpsClimbingAvailable) { + && !destHex.containsTerrain(Terrains.BRIDGE) + && !tacOpsClimbingAvailable) { destAlt = destHex.floor(); } else { destAlt = elevation + destHex.getLevel(); @@ -3586,16 +3646,16 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // ProtoMeks that are jumping can't change the level inside a building, // they can only jump onto a building or out of it if (src.equals(dest) && - (srcAlt != destAlt) && - (entity instanceof ProtoMek) && - (getMovementType(false) == EntityMovementType.MOVE_JUMP)) { + (srcAlt != destAlt) && + (entity instanceof ProtoMek) && + (getMovementType(false) == EntityMovementType.MOVE_JUMP)) { return false; } Hex hex = game.getBoard(boardId).getHex(getPosition()); int maxElevation = (2 + entity.getElevation() + game.getBoard(boardId) - .getHex(entity.getPosition()) - .getLevel()) - - hex.getLevel(); + .getHex(entity.getPosition()) + .getLevel()) - + hex.getLevel(); if ((bld.getBuildingType() == BuildingType.WALL) && (maxElevation < hex.terrainLevel(Terrains.BLDG_ELEV))) { return false; @@ -3603,34 +3663,35 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // only infantry can enter an armored building if ((elevation < hex.terrainLevel(Terrains.BLDG_ELEV)) && - (bld.getArmor(dest) > 0) && - !(entity instanceof Infantry)) { + (bld.getArmor(dest) > 0) && + !(entity instanceof Infantry)) { return false; } // only infantry can enter a gun emplacement if ((elevation < hex.terrainLevel(Terrains.BLDG_ELEV)) && - (bld.getBldgClass() == IBuilding.GUN_EMPLACEMENT) && - !(entity instanceof Infantry)) { + (bld.getBldgClass() == IBuilding.GUN_EMPLACEMENT) && + !(entity instanceof Infantry)) { return false; } } // Can we backup up hills? boolean bBackwardsElevationChange = - Game.rulesManager.getRulesMovement() - .enableBackwardsElevationChange(game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_WALK_BACKWARDS), entity); + Game.rulesManager.getRulesMovement() + .enableBackwardsElevationChange(game.getOptions() + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_WALK_BACKWARDS), + entity); if (!(entity instanceof VTOL) && - isThisStepBackwards() && - !(isJumping() && isUsingMekJumpBooster) && - (((destAlt != srcAlt) - && - !bBackwardsElevationChange) - || - (bBackwardsElevationChange - && - (Math.abs(destAlt - srcAlt) > 1)))) { + isThisStepBackwards() && + !(isJumping() && isUsingMekJumpBooster) && + (((destAlt != srcAlt) + && + !bBackwardsElevationChange) + || + (bBackwardsElevationChange + && + (Math.abs(destAlt - srcAlt) > 1)))) { return false; } @@ -3706,11 +3767,11 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity } } if (bDumping && - ((movementType == EntityMovementType.MOVE_RUN) || - (movementType == EntityMovementType.MOVE_SPRINT) || - (movementType == EntityMovementType.MOVE_VTOL_RUN) || - (movementType == EntityMovementType.MOVE_VTOL_SPRINT) || - (movementType == EntityMovementType.MOVE_JUMP))) { + ((movementType == EntityMovementType.MOVE_RUN) || + (movementType == EntityMovementType.MOVE_SPRINT) || + (movementType == EntityMovementType.MOVE_VTOL_RUN) || + (movementType == EntityMovementType.MOVE_VTOL_SPRINT) || + (movementType == EntityMovementType.MOVE_JUMP))) { return false; } @@ -3729,18 +3790,19 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // doesn't return VTOL (e.g., if crew/abilities aren't accessible during validation) boolean hasPoweredFlight = (entity instanceof ConvInfantry infantry) && infantry.hasVTOLMovementCapability(); boolean isVTOLFlight = (nMove == EntityMovementMode.VTOL) || - hasPoweredFlight || - (movementType == EntityMovementType.MOVE_VTOL_WALK) || - (movementType == EntityMovementType.MOVE_VTOL_RUN) || - (movementType == EntityMovementType.MOVE_VTOL_SPRINT); - + hasPoweredFlight || + (movementType == EntityMovementType.MOVE_VTOL_WALK) || + (movementType == EntityMovementType.MOVE_VTOL_RUN) || + (movementType == EntityMovementType.MOVE_VTOL_SPRINT); + if ((movementType != EntityMovementType.MOVE_JUMP) && !isVTOLFlight) { int maxDown = Game.rulesManager.getRulesTerrain().getMaxElevationChangeAllowed(srcHex, destHex, - entity.getMaxElevationDown(srcAlt)); + entity.getMaxElevationDown( + srcAlt)); if (movementMode == EntityMovementMode.WIGE && - (srcEl == 0 || - (srcHex.containsTerrain(Terrains.BLDG_ELEV) && - (srcHex.terrainLevel(Terrains.BLDG_ELEV) >= srcEl)))) { + (srcEl == 0 || + (srcHex.containsTerrain(Terrains.BLDG_ELEV) && + (srcHex.terrainLevel(Terrains.BLDG_ELEV) >= srcEl)))) { maxDown = entity.getMaxElevationChange(); } // TacOps Climbing (TO:AR p.20): Meks with functional arms can climb @@ -3748,7 +3810,7 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // mode is enabled. Climb mode OFF ("Move Thru") means normal movement // restrictions apply — cannot scale cliffs without climb mode. boolean climbingEnabled = game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING); + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING); boolean canUseClimbing = climbingEnabled && climbMode && ClimbingHelper.canClimb(entity, isProne()); // Edge descent (TO:AR p.20): Meks with at least one functional climbing arm // stepping off a 3+ level edge with climb mode on can climb-down (1 arm) or @@ -3758,12 +3820,12 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // strip it before the server saw the path. Use canClimb (1 arm) not canDangle // (2 arms) so one-arm climb-down still works. boolean canEdgeDescend = climbingEnabled && climbMode - && ClimbingHelper.canClimb(entity, isProne()); + && ClimbingHelper.canClimb(entity, isProne()); int elevationUp = (destAlt - srcAlt); int elevationDown = (srcAlt - destAlt); if (((elevationDown > 0) && (elevationDown > maxDown)) || - ((elevationUp > 0) && (elevationUp > entity.getMaxElevationChange()))) { + ((elevationUp > 0) && (elevationUp > entity.getMaxElevationChange()))) { // Allow climbing UP if the option is enabled and entity can climb; // allow climbing DOWN (edge descent) if the entity can dangle. boolean allowUp = canUseClimbing && (elevationUp > 0); @@ -3773,10 +3835,10 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity } if (allowUp) { LOGGER.debug("isValidStep: allowing climbing for {} levels up (TacOps Climbing)", - elevationUp); + elevationUp); } else { LOGGER.debug("isValidStep: allowing edge descent for {} levels down (TacOps Climbing)", - elevationDown); + elevationDown); } } } @@ -3785,18 +3847,18 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // Roads over cliffs cancel the cliff effects for units that move on roads boolean vehicleAffectedByCliff = entity instanceof Tank && !entity.isAirborneVTOLorWIGE(); boolean quadVeeVehicleMode = entity instanceof QuadVee && - entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE; + entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE; int stepHeight = destAlt - srcAlt; // Cliffs should only exist towards 1 or 2 level drops, check just to make sure // Everything that does not have a 1 or 2 level drop shouldn't be handled as a // cliff boolean isUpCliff = !src.equals(dest) && - destHex.hasCliffTopTowards(srcHex) && - (stepHeight == 1 || stepHeight == 2); + destHex.hasCliffTopTowards(srcHex) && + (stepHeight == 1 || stepHeight == 2); boolean isDownCliff = !src.equals(dest) && - srcHex.hasCliffTopTowards(destHex) && - (stepHeight == -1 || stepHeight == -2); - + srcHex.hasCliffTopTowards(destHex) && + (stepHeight == -1 || stepHeight == -2); + // For vehicles exc. VTOL, WIGE, upward Sheer Cliffs is forbidden // QuadVees in vehicle mode drive as vehicles, IO p.133 if ((vehicleAffectedByCliff || quadVeeVehicleMode) && isUpCliff && !isPavementStep) { @@ -3823,30 +3885,31 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // Units moving backwards may not change elevation levels. if (((type == MoveStepType.BACKWARDS) || - (type == MoveStepType.LATERAL_LEFT_BACKWARDS) || - (type == MoveStepType.LATERAL_RIGHT_BACKWARDS)) && - (destAlt != srcAlt) && - !(entity instanceof VTOL) && - !(isJumping() && isUsingMekJumpBooster)) { + (type == MoveStepType.LATERAL_LEFT_BACKWARDS) || + (type == MoveStepType.LATERAL_RIGHT_BACKWARDS)) && + (destAlt != srcAlt) && + !(entity instanceof VTOL) && + !(isJumping() && isUsingMekJumpBooster)) { // Generally forbidden without TacOps Expanded Backward Movement p.22 if (!Game.rulesManager.getRulesMovement() - .enableBackwardsElevationChange(game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_WALK_BACKWARDS), entity)) { + .enableBackwardsElevationChange(game.getOptions() + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_WALK_BACKWARDS), + entity)) { return false; } // Even with Expanded Backward Movement, ... // May not move across a cliff (up) moving backwards at all if (destHex.containsTerrain(Terrains.CLIFF_TOP) && - destHex.getTerrain(Terrains.CLIFF_TOP).hasExitsSpecified() && - ((destHex.getTerrain(Terrains.CLIFF_TOP).getExits() & (1 << dest.direction(src))) != 0) && - (!src.equals(dest))) { + destHex.getTerrain(Terrains.CLIFF_TOP).hasExitsSpecified() && + ((destHex.getTerrain(Terrains.CLIFF_TOP).getExits() & (1 << dest.direction(src))) != 0) && + (!src.equals(dest))) { return false; } // May not move across a cliff (down) moving backwards at all if (srcHex.containsTerrain(Terrains.CLIFF_TOP) && - srcHex.getTerrain(Terrains.CLIFF_TOP).hasExitsSpecified() && - ((srcHex.getTerrain(Terrains.CLIFF_TOP).getExits() & (1 << src.direction(dest))) != 0) && - (!src.equals(dest))) { + srcHex.getTerrain(Terrains.CLIFF_TOP).hasExitsSpecified() && + ((srcHex.getTerrain(Terrains.CLIFF_TOP).getExits() & (1 << src.direction(dest))) != 0) && + (!src.equals(dest))) { return false; } if (Math.abs(destAlt - srcAlt) > 1) { @@ -3862,16 +3925,16 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // Can't run into water unless hovering, naval, first step, using a // bridge, fly, or if the rules say meks can. if (((movementType == EntityMovementType.MOVE_RUN) || - (movementType == EntityMovementType.MOVE_SPRINT) || - (movementType == EntityMovementType.MOVE_VTOL_RUN) || - (movementType == EntityMovementType.MOVE_VTOL_SPRINT)) && - Game.rulesManager.getRulesMovement().cannotRunInWater(nMove, - cachedEntityState.hasWorkingMisc(MiscType.F_FULLY_AMPHIBIOUS)) && - (destHex.terrainLevel(Terrains.WATER) > 0) && - !(destHex.containsTerrain(Terrains.ICE) && (elevation >= 0)) && - !dest.equals(entity.getPosition()) && - !isFirstStep() && - !isPavementStep()) { + (movementType == EntityMovementType.MOVE_SPRINT) || + (movementType == EntityMovementType.MOVE_VTOL_RUN) || + (movementType == EntityMovementType.MOVE_VTOL_SPRINT)) && + Game.rulesManager.getRulesMovement().cannotRunInWater(nMove, + cachedEntityState.hasWorkingMisc(MiscType.F_FULLY_AMPHIBIOUS)) && + (destHex.terrainLevel(Terrains.WATER) > 0) && + !(destHex.containsTerrain(Terrains.ICE) && (elevation >= 0)) && + !dest.equals(entity.getPosition()) && + !isFirstStep() && + !isPavementStep()) { return false; } @@ -3880,7 +3943,7 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity if (!isJumping() && (type != MoveStepType.CHARGE) && (type != MoveStepType.DFA)) { // can't move a mek into a hex with an enemy mek if ((entity instanceof Mek) && - Compute.isEnemyIn(game, entity, dest, true, true, getElevation(), true) + Compute.isEnemyIn(game, entity, dest, true, true, getElevation(), true) ) { return false; } @@ -3888,12 +3951,12 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // Can't move out of a hex with an enemy unit unless we started // there, BUT we're allowed to turn, unload/Disconnect, or go prone. if (Compute.isEnemyIn(game, entity, src, false, entity instanceof Mek, srcEl, true) && - !src.equals(entity.getPosition()) && - (type != MoveStepType.TURN_LEFT) && - (type != MoveStepType.TURN_RIGHT) && - (type != MoveStepType.UNLOAD) && - (type != MoveStepType.DISCONNECT) && - (type != MoveStepType.GO_PRONE)) { + !src.equals(entity.getPosition()) && + (type != MoveStepType.TURN_LEFT) && + (type != MoveStepType.TURN_RIGHT) && + (type != MoveStepType.UNLOAD) && + (type != MoveStepType.DISCONNECT) && + (type != MoveStepType.GO_PRONE)) { return false; } @@ -3907,7 +3970,7 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // TW p.57 if ((inHex instanceof LargeSupportTank) - || ((inHex instanceof Dropship dropship) && dropship.isAeroLandedOnGroundMap())) { + || ((inHex instanceof Dropship dropship) && dropship.isAeroLandedOnGroundMap())) { if (getElevation() <= inHex.height()) { return false; } @@ -3924,11 +3987,11 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // can't jump over too-high terrain if ((movementType == EntityMovementType.MOVE_JUMP) && - (destAlt > - (entity.getElevation() + - game.getBoard(boardId).getHex(entity.getPosition()).getLevel() + - getAvailableJumpMP(entity) + - (type == MoveStepType.DFA ? 1 : 0)))) { + (destAlt > + (entity.getElevation() + + game.getBoard(boardId).getHex(entity.getPosition()).getLevel() + + getAvailableJumpMP(entity) + + (type == MoveStepType.DFA ? 1 : 0)))) { return false; } @@ -3941,24 +4004,24 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // approach roads, such as those raised by Bridge-Building Engineers (TO:AUE p.152), where the step onto the // bridge does not qualify as a pavement step. if (entity.isLocationProhibited(dest, boardId, getElevation()) - && !isOnBridgeDeck(game.getBoard(boardId).getHex(dest), getElevation()) - // Units in prohibited terran should still be able to unload/disconnect - && - (type != MoveStepType.UNLOAD) && - (type != MoveStepType.DISCONNECT) - // Should allow vertical takeoffs - && - (type != MoveStepType.VERTICAL_TAKE_OFF) - // QuadVees can convert to vehicle mode even if they cannot enter the terrain - && - (type != MoveStepType.CONVERT_MODE) && - (!isPavementStep() || - (nMove == EntityMovementMode.NAVAL) || - (nMove == EntityMovementMode.HYDROFOIL) || - (nMove == EntityMovementMode.SUBMARINE)) && - (movementType != EntityMovementType.MOVE_VTOL_WALK) && - (movementType != EntityMovementType.MOVE_VTOL_RUN) && - (movementType != EntityMovementType.MOVE_VTOL_SPRINT)) { + && !isOnBridgeDeck(game.getBoard(boardId).getHex(dest), getElevation()) + // Units in prohibited terran should still be able to unload/disconnect + && + (type != MoveStepType.UNLOAD) && + (type != MoveStepType.DISCONNECT) + // Should allow vertical takeoffs + && + (type != MoveStepType.VERTICAL_TAKE_OFF) + // QuadVees can convert to vehicle mode even if they cannot enter the terrain + && + (type != MoveStepType.CONVERT_MODE) && + (!isPavementStep() || + (nMove == EntityMovementMode.NAVAL) || + (nMove == EntityMovementMode.HYDROFOIL) || + (nMove == EntityMovementMode.SUBMARINE)) && + (movementType != EntityMovementType.MOVE_VTOL_WALK) && + (movementType != EntityMovementType.MOVE_VTOL_RUN) && + (movementType != EntityMovementType.MOVE_VTOL_SPRINT)) { // We're allowed to pass *over* invalid // terrain, but we can't end there. @@ -3973,12 +4036,12 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // We need extra checking for DropShips, due to secondary positions // if the DropShip is taking off, MoveType will be safe thrust if ((entity instanceof Dropship) && - !entity.isAirborne() && - isPavementStep() && - entity.isLocationProhibited(dest, boardId, getElevation()) && - (movementType != EntityMovementType.MOVE_SAFE_THRUST) && - (type != MoveStepType.LOAD) && - (type != MoveStepType.UNLOAD)) { + !entity.isAirborne() && + isPavementStep() && + entity.isLocationProhibited(dest, boardId, getElevation()) && + (movementType != EntityMovementType.MOVE_SAFE_THRUST) && + (type != MoveStepType.LOAD) && + (type != MoveStepType.UNLOAD)) { for (int dir = 0; dir < 6; dir++) { Coords secondaryCoords = dest.translated(dir); Hex secondaryHex = game.getBoard(boardId).getHex(secondaryCoords); @@ -3991,10 +4054,10 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // If we're a land train with mixed motive types, use the most restrictive type // to determine terrain restrictions if (!entity.getAllTowedUnits().isEmpty() && - (type != MoveStepType.LOAD && - type != MoveStepType.UNLOAD && - type != MoveStepType.TOW && - type != MoveStepType.DISCONNECT)) { + (type != MoveStepType.LOAD && + type != MoveStepType.UNLOAD && + type != MoveStepType.TOW && + type != MoveStepType.DISCONNECT)) { boolean prohibitedByTrailer; // Add up the trailers for (int id : entity.getAllTowedUnits()) { @@ -4014,9 +4077,9 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // Jumping into a building hex below the roof ends the move // Applies also to VTOL movement (includes powered flight infantry whose getMovementMode() returns VTOL) if (!(src.equals(dest)) && - (src != entity.getPosition()) && - (isJumping() || (entity.getMovementMode() == EntityMovementMode.VTOL)) && - (srcEl < srcHex.terrainLevel(Terrains.BLDG_ELEV))) { + (src != entity.getPosition()) && + (isJumping() || (entity.getMovementMode() == EntityMovementMode.VTOL)) && + (srcEl < srcHex.terrainLevel(Terrains.BLDG_ELEV))) { return false; } @@ -4024,37 +4087,37 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity Coords startingPosition = getEntity().getPosition(); Hex startingHex = game.getHexOf(getEntity()); if (!destHex.getCoords().equals(startingPosition) && - isJumping() && - startingHex.containsTerrain(Terrains.BUILDING) && - destHex.containsTerrain(Terrains.BUILDING) && - srcEl < srcHex.terrainLevel(Terrains.BLDG_ELEV) && - (game.getBoard(getEntity()) - .getBuildingAt(startingPosition) - .equals(game.getBoard(getEntity()).getBuildingAt(getPosition())))) { + isJumping() && + startingHex.containsTerrain(Terrains.BUILDING) && + destHex.containsTerrain(Terrains.BUILDING) && + srcEl < srcHex.terrainLevel(Terrains.BLDG_ELEV) && + (game.getBoard(getEntity()) + .getBuildingAt(startingPosition) + .equals(game.getBoard(getEntity()).getBuildingAt(getPosition())))) { return false; } // If we are *in* restricted terrain, we can only leave via roads. if ((movementType != EntityMovementType.MOVE_JUMP) && - (movementType != EntityMovementType.MOVE_VTOL_WALK) && - (movementType != EntityMovementType.MOVE_VTOL_RUN) && - (movementType != EntityMovementType.MOVE_VTOL_SPRINT) - // Units in prohibited terran should still be able to unload/disconnect - && - (type != MoveStepType.UNLOAD) && - (type != MoveStepType.DISCONNECT) - // Should allow vertical takeoffs - && - (type != MoveStepType.VERTICAL_TAKE_OFF) - // QuadVees can still convert to vehicle mode in prohibited terrain, but cannot - // leave - && - (type != MoveStepType.CONVERT_MODE) && - entity.isLocationProhibited(src, boardId, srcEl) && - // Standing on a bridge deck is not standing in the prohibited terrain below it (TO:AR p.115), so - // a unit on a bridge without approach roads may still leave it - !isOnBridgeDeck(game.getBoard(boardId).getHex(src), srcEl) && - !isPavementStep()) { + (movementType != EntityMovementType.MOVE_VTOL_WALK) && + (movementType != EntityMovementType.MOVE_VTOL_RUN) && + (movementType != EntityMovementType.MOVE_VTOL_SPRINT) + // Units in prohibited terran should still be able to unload/disconnect + && + (type != MoveStepType.UNLOAD) && + (type != MoveStepType.DISCONNECT) + // Should allow vertical takeoffs + && + (type != MoveStepType.VERTICAL_TAKE_OFF) + // QuadVees can still convert to vehicle mode in prohibited terrain, but cannot + // leave + && + (type != MoveStepType.CONVERT_MODE) && + entity.isLocationProhibited(src, boardId, srcEl) && + // Standing on a bridge deck is not standing in the prohibited terrain below it (TO:AR p.115), so + // a unit on a bridge without approach roads may still leave it + !isOnBridgeDeck(game.getBoard(boardId).getHex(src), srcEl) && + !isPavementStep()) { return false; } if (type == MoveStepType.UP) { @@ -4073,26 +4136,26 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity } if (entity instanceof VTOL) { if ((type == MoveStepType.BACKWARDS) || - (type == MoveStepType.FORWARDS) || - (type == MoveStepType.LATERAL_LEFT) || - (type == MoveStepType.LATERAL_LEFT_BACKWARDS) || - (type == MoveStepType.LATERAL_RIGHT) || - (type == MoveStepType.LATERAL_RIGHT_BACKWARDS) || - (type == MoveStepType.TURN_LEFT) || - (type == MoveStepType.TURN_RIGHT)) { + (type == MoveStepType.FORWARDS) || + (type == MoveStepType.LATERAL_LEFT) || + (type == MoveStepType.LATERAL_LEFT_BACKWARDS) || + (type == MoveStepType.LATERAL_RIGHT) || + (type == MoveStepType.LATERAL_RIGHT_BACKWARDS) || + (type == MoveStepType.TURN_LEFT) || + (type == MoveStepType.TURN_RIGHT)) { if (getClearance() == 0) {// can't move on the ground. return false; } } } if ((entity instanceof VTOL || entity.getMovementMode() == EntityMovementMode.WIGE) && - getClearance() > 0 && - ((type == MoveStepType.BACKWARDS) || - (type == MoveStepType.FORWARDS) || - (type == MoveStepType.LATERAL_LEFT) || - (type == MoveStepType.LATERAL_LEFT_BACKWARDS) || - (type == MoveStepType.LATERAL_RIGHT) || - (type == MoveStepType.LATERAL_RIGHT_BACKWARDS))) { + getClearance() > 0 && + ((type == MoveStepType.BACKWARDS) || + (type == MoveStepType.FORWARDS) || + (type == MoveStepType.LATERAL_LEFT) || + (type == MoveStepType.LATERAL_LEFT_BACKWARDS) || + (type == MoveStepType.LATERAL_RIGHT) || + (type == MoveStepType.LATERAL_RIGHT_BACKWARDS))) { // It's possible to fly under a bridge. if (destHex.containsTerrain(Terrains.BRIDGE_ELEV)) { if (elevation == destHex.terrainLevel(Terrains.BRIDGE_ELEV)) { @@ -4116,10 +4179,10 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity // check the elevation is valid for the type of entity and hex if ((type != MoveStepType.DFA) && !entity.isElevationValid(elevation, destHex)) { LOGGER.debug("[CLIMB-TRACE] isMovementPossible: elevation NOT valid! elevation={}, " + - "destHex={}, destHex.level={}, destHex.ceiling={}, destHex.floor={}, " + - "isClimbing={}, entity={}", - elevation, dest, destHex.getLevel(), destHex.ceiling(), destHex.floor(), - isClimbing, entity.getDisplayName()); + "destHex={}, destHex.level={}, destHex.ceiling={}, destHex.floor={}, " + + "isClimbing={}, entity={}", + elevation, dest, destHex.getLevel(), destHex.ceiling(), destHex.floor(), + isClimbing, entity.getDisplayName()); if (isJumping()) { terrainInvalid = true; } else { @@ -4140,10 +4203,12 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity * @param srcHex the hex the unit occupies * @param src the coordinates of that hex * @param srcEl the unit's current elevation in that hex - * * @return {@code true} if the elevator step is possible */ - private boolean isElevatorMovementPossible(Game game, Hex srcHex, Coords src, int srcEl) { + private boolean isElevatorMovementPossible(Game game, + Hex srcHex, + Coords src, + int srcEl) { if (!srcHex.containsTerrain(Terrains.INDUSTRIAL_ELEVATOR)) { LOGGER.debug("[IndustrialElevator] Step impossible at {}: no elevator terrain in hex", src); return false; @@ -4151,7 +4216,7 @@ private boolean isElevatorMovementPossible(Game game, Hex srcHex, Coords src, in IndustrialElevator elevator = game.getIndustrialElevator(BoardLocation.of(src, boardId)); if (elevator == null) { LOGGER.debug("[IndustrialElevator] Step impossible at {}: elevator terrain present but no elevator " - + "registered with the game", src); + + "registered with the game", src); return false; } if (!elevator.isFunctional()) { @@ -4161,18 +4226,18 @@ private boolean isElevatorMovementPossible(Game game, Hex srcHex, Coords src, in // An over-capacity elevator will not move in either direction (TO:AR) if (elevator.getCurrentLoad(game) > elevator.getCapacityTons()) { LOGGER.debug("[IndustrialElevator] Step impossible at {}: overloaded ({}t load, {}t capacity)", - src, elevator.getCurrentLoad(game), elevator.getCapacityTons()); + src, elevator.getCurrentLoad(game), elevator.getCapacityTons()); return false; } if (elevator.getPlatformLevel() != srcEl) { LOGGER.debug("[IndustrialElevator] Step impossible at {}: platform at level {}, unit at level {}", - src, elevator.getPlatformLevel(), srcEl); + src, elevator.getPlatformLevel(), srcEl); return false; } int targetElevation = (type == MoveStepType.ELEVATOR_ASCEND) ? srcEl + 1 : srcEl - 1; if ((targetElevation > elevator.getShaftTop()) || (targetElevation < elevator.getShaftBottom())) { LOGGER.debug("[IndustrialElevator] Step impossible at {}: target level {} outside shaft [{}, {}]", - src, targetElevation, elevator.getShaftBottom(), elevator.getShaftTop()); + src, targetElevation, elevator.getShaftBottom(), elevator.getShaftTop()); return false; } return true; @@ -4276,7 +4341,9 @@ public int getVelocityLeft() { return velocityLeft; } - public int asfTurnCost(Game game, MoveStepType direction, Entity entity) { + public int asfTurnCost(Game game, + MoveStepType direction, + Entity entity) { // jump ships (but not space stations and warships) never pay if ((entity instanceof Jumpship) && !(entity instanceof Warship) && !(entity instanceof SpaceStation)) { @@ -4535,7 +4602,7 @@ public Minefield getMinefield() { /** * @return The hex a BUILD_BRIDGE step raises its bridge in, from the step's additional data, or null if the step - * does not carry target coordinates. + * does not carry target coordinates. */ public @Nullable Coords getBridgeTargetCoords() { Integer targetX = additionalData.get(BRIDGE_TARGET_X_KEY); @@ -4555,7 +4622,7 @@ public int getBridgeExits() { /** * @return The bridge type of a BUILD_BRIDGE step, {@link ConvInfantry#BRIDGE_TYPE_LIGHT} or - * {@link ConvInfantry#BRIDGE_TYPE_MEDIUM}. + * {@link ConvInfantry#BRIDGE_TYPE_MEDIUM}. */ public int getBridgeType() { return additionalData.getOrDefault(BRIDGE_TYPE_KEY, ConvInfantry.BRIDGE_TYPE_LIGHT); @@ -4571,7 +4638,8 @@ public Map getAdditionalData() { /** * Should we treat this movement as if it is occurring for an aerodyne unit flying in atmosphere? */ - boolean useAeroAtmosphere(Game game, Entity en) { + boolean useAeroAtmosphere(Game game, + Entity en) { if (!en.isAero()) { return false; } @@ -4590,7 +4658,8 @@ boolean useAeroAtmosphere(Game game, Entity en) { /** * Should we treat this movement as if it is occurring for a spheroid unit flying in atmosphere? */ - public boolean useSpheroidAtmosphere(Game game, Entity en) { + public boolean useSpheroidAtmosphere(Game game, + Entity en) { return Compute.useSpheroidAtmosphere(game, en); } @@ -4635,13 +4704,13 @@ public boolean canUseSprint(Game game) { return false; } if (entity instanceof Tank || - (entity instanceof QuadVee && entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)) { + (entity instanceof QuadVee && entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)) { return game.getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_ADVANCED_MANEUVERS); + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_ADVANCED_MANEUVERS); } if (entity instanceof LandAirMek) { return entity.getConversionMode() == LandAirMek.CONV_MODE_MEK || - (entity.getConversionMode() == LandAirMek.CONV_MODE_AIR_MEK && getClearance() <= 0); + (entity.getConversionMode() == LandAirMek.CONV_MODE_AIR_MEK && getClearance() <= 0); } return entity instanceof Mek; } diff --git a/megamek/src/megamek/common/moves/PhasePassSelector.java b/megamek/src/megamek/common/moves/PhasePassSelector.java index b689ef7ed06..47506ed6527 100644 --- a/megamek/src/megamek/common/moves/PhasePassSelector.java +++ b/megamek/src/megamek/common/moves/PhasePassSelector.java @@ -79,6 +79,7 @@ class PhasePassSelector { private static final PhasePass BOOTLEGGER_STEP = new BootleggerStep(); private static final PhasePass BRACE_STEP = new BraceStep(); private static final PhasePass DEFAULT_STEP = new DefaultStep(); + private static final PhasePass DEPLOY_STEP = new DeployStep(); /** * Returns the correct phase pass for {@link MoveStep} compilation based on the move step type. @@ -124,6 +125,7 @@ static PhasePass getPhasePass(MoveStepType moveStepType) { case CONVERT_MODE -> CONVERT_MODE_STEP; case BOOTLEGGER -> BOOTLEGGER_STEP; case BRACE -> BRACE_STEP; + case DEPLOY -> DEPLOY_STEP; default -> DEFAULT_STEP; }; } diff --git a/megamek/src/megamek/common/options/GameOptions.java b/megamek/src/megamek/common/options/GameOptions.java index a40d94ec08a..bea885468fa 100755 --- a/megamek/src/megamek/common/options/GameOptions.java +++ b/megamek/src/megamek/common/options/GameOptions.java @@ -92,6 +92,7 @@ public synchronized void initialize() { addOption(base, OptionsConstants.BASE_AUTO_AMS, true); addOption(base, OptionsConstants.BASE_RANDOM_BASEMENTS, true); addOption(base, OptionsConstants.BASE_BREEZE, false); + addOption(base, OptionsConstants.BASE_WALK_ON_DEPLOYMENT, false); IBasicOptionGroup gameMaster = addGroup("gameMaster"); addOption(gameMaster, OptionsConstants.GAME_MASTER_ALLOW, true); diff --git a/megamek/src/megamek/common/options/OptionsConstants.java b/megamek/src/megamek/common/options/OptionsConstants.java index 06be0fe2890..285992a6a97 100644 --- a/megamek/src/megamek/common/options/OptionsConstants.java +++ b/megamek/src/megamek/common/options/OptionsConstants.java @@ -340,6 +340,7 @@ public class OptionsConstants { public static final String BASE_GM_CONTROLS_DONE_REPORT_PHASE = "gm_controls_done_report_phase"; public static final String BASE_HIDE_UNOFFICIAL = "hide_unofficial"; public static final String BASE_HIDE_LEGACY = "hide_legacy"; + public static final String BASE_WALK_ON_DEPLOYMENT = "walk_on_deployment"; public static final String VICTORY_SKIP_FORCED_VICTORY = "skip_forced_victory"; public static final String VICTORY_CHECK_VICTORY = "check_victory"; diff --git a/megamek/src/megamek/common/rules/RulesGame.java b/megamek/src/megamek/common/rules/RulesGame.java index a2271c0c7e1..c08686c1917 100644 --- a/megamek/src/megamek/common/rules/RulesGame.java +++ b/megamek/src/megamek/common/rules/RulesGame.java @@ -34,7 +34,9 @@ import megamek.client.ui.clientGUI.calculationReport.CalculationReport; +import megamek.common.Player; import megamek.common.annotations.Nullable; +import megamek.common.board.Board; import megamek.common.enums.GamePhase; import megamek.common.equipment.AmmoType; import megamek.common.equipment.MiscType; @@ -53,59 +55,72 @@ public abstract class RulesGame { /** * Is an entity eligible for a phase + * * @param entity the unit being considered - * @param phase what phase it is in + * @param phase what phase it is in * @return is it eligible */ - public abstract boolean eligibleForPhase(Entity entity, @Nullable GamePhase phase); + public abstract boolean eligibleForPhase(Entity entity, + @Nullable GamePhase phase); /** * Return the number of units to move. * * @param num_normal_turns array of normal turns - * @param index the current index - * @param min the minimum value - * @param frontLoadOption true if front load option is enabled + * @param index the current index + * @param min the minimum value + * @param frontLoadOption true if front load option is enabled * @return the initiative order */ - public abstract int getInitiativeOrder(int[] num_normal_turns, int index, int min, boolean frontLoadOption); + public abstract int getInitiativeOrder(int[] num_normal_turns, + int index, + int min, + boolean frontLoadOption); /** * Is there a BV bump for tag? - * - * @param entity The entity being considered - * @param bvReport the report + * + * @param entity The entity being considered + * @param bvReport the report * @param adjustedBV the adjusted BV so far - * @param tagCount how many tags in the force - * @param hasGuided does it have guided? (default false) + * @param tagCount how many tags in the force + * @param hasGuided does it have guided? (default false) * @return adjusted BV value with bump if applicable */ - public abstract double tagBVBump(Entity entity, CalculationReport bvReport, double adjustedBV, - long tagCount, boolean hasGuided); + public abstract double tagBVBump(Entity entity, + CalculationReport bvReport, + double adjustedBV, + long tagCount, + boolean hasGuided); /** * Allow minefields or not + * * @param toMinefields OptionsConstants.ADVANCED_MINEFIELDS - * @return + * @return Allow in core or TO */ public abstract boolean allowMinefields(boolean toMinefields); /** * Helped function for tagBVBump to get the equipment descriptor for a mounted item. + * * @param mounted * @param entity * @return */ - public String equipmentDescriptor(Mounted mounted, Entity entity) { + public String equipmentDescriptor(Mounted mounted, + Entity entity) { if (mounted.getType() instanceof WeaponType) { String descriptor = mounted.getType().getShortName() + - " (" + - entity.getLocationAbbr(mounted.getLocation()) + - ")"; + " (" + + entity.getLocationAbbr(mounted.getLocation()) + + ")"; if (mounted.isMekTurretMounted()) { descriptor += " (T)"; } - if (mounted.isRearMounted() || (mounted.getType().hasFlag(WeaponType.F_VGL) && (mounted.getFacing() >= 2) && (mounted.getFacing() <= 4))) { + if (mounted.isRearMounted() || (mounted.getType().hasFlag(WeaponType.F_VGL) + && (mounted.getFacing() >= 2) + && (mounted.getFacing() <= 4))) { descriptor += " (R)"; } return descriptor; @@ -118,4 +133,92 @@ public String equipmentDescriptor(Mounted mounted, Entity entity) { return mounted.getType().getShortName(); } } + + /** + * Is walk on deployment supported? + * + * @return Can they walk ok + */ + public abstract boolean isWalkOnDeployment(); + + /** + * For use by TW to enable walk on deployment. Not used by Core + * + * @param walkOn should walk-on deployment be enabled + */ + public abstract void setWalkOnDeployment(boolean walkOn); + + /** + * Walk-on deployment. Skip deployment for eligible units Note: Bot does not get Walk-on deployment for now. + * TODO bot walk-on deployment when enabled will remove the isBot check. + * + * @param entity The mek under consideration + * @return true if they can walk on, false if they cannot + */ + public boolean canWalkOnThisRound(Entity entity) { + if (!isWalkOnDeployment() || entity.getGame() == null) { + return false; + } + + int deploymentRound = entity.getDeployRound(); + int currentRound = entity.getGame().getCurrentRound(); + int startingPos = entity.getStartingPos(); + return (!entity.isDeployed() && deploymentRound >= 0 + && deploymentRound <= currentRound + && restrictDeploymentWidth(entity.getOwner(), startingPos)); + } + + /** + * Should the entity be included in movement / initiative? + * + * @param phase what phase is it + * @param entity what entity is it + * @return True by default + */ + public boolean includeInMovement(GamePhase phase, + Entity entity) { + return true; + } + + /** + * This gets the deployment width. It is called from both Player and Entity + * + * @param player What player's deployment area (used for bot check) + * @param deploymentArea what deployment area + * @param deploymentWidth what deployment width + * @return the updated deployment width (1 for walk-on) + */ + public int getDeploymentWidth(Player player, + int deploymentArea, + int deploymentWidth) { + if (isWalkOnDeployment()) { + int width = 1; + if (!restrictDeploymentWidth(player, deploymentArea)) { + width = deploymentWidth; + } + return width; + } + return deploymentWidth; + } + + /** + * Checks if it should restrict the deployment width. + * + * @param player The player to check + * @param deploymentArea the deployment area + * @return true if it should restrict the deployment width, false otherwise + */ + public boolean restrictDeploymentWidth(@Nullable Player player, + int deploymentArea) { + if ((player != null) && player.isBot()) { + return false; + } + if (isWalkOnDeployment() && + (deploymentArea != Board.START_CENTER && + deploymentArea != Board.START_ANY && + deploymentArea < Board.NUM_ZONES)) { + return true; + } + return false; + } } diff --git a/megamek/src/megamek/common/rules/core/CoreRulesGame.java b/megamek/src/megamek/common/rules/core/CoreRulesGame.java index 607bc0b8632..0ba20a08883 100644 --- a/megamek/src/megamek/common/rules/core/CoreRulesGame.java +++ b/megamek/src/megamek/common/rules/core/CoreRulesGame.java @@ -33,6 +33,10 @@ */ +import static megamek.client.ui.clientGUI.calculationReport.CalculationReport.formatForReport; + +import java.util.EnumSet; + import megamek.client.ui.clientGUI.calculationReport.CalculationReport; import megamek.common.annotations.Nullable; import megamek.common.enums.GamePhase; @@ -43,16 +47,14 @@ import megamek.common.rules.RulesGame; import megamek.common.units.Entity; import megamek.common.units.IBomber; - -import java.util.EnumSet; - -import static megamek.client.ui.clientGUI.calculationReport.CalculationReport.formatForReport; +import megamek.logging.MMLogger; public class CoreRulesGame extends RulesGame { + private static final MMLogger logger = MMLogger.create(CoreRulesGame.class); + /** - * {@inheritDoc} - * Ammo dumping is not in Core + * {@inheritDoc} Ammo dumping is not in Core */ @Override public boolean ammoDumping() { @@ -60,13 +62,12 @@ public boolean ammoDumping() { } /** - * {@inheritDoc} - * Immobile not eligible in movement Core p.49 - * RAC Unjamming does not prevent usage (only limits movement) Core p.183 - * Finding a club can use in physical phase Core p.79 + * {@inheritDoc} Immobile not eligible in movement Core p.49 RAC Unjamming does not prevent usage (only limits + * movement) Core p.183 Finding a club can use in physical phase Core p.79 */ @Override - public boolean eligibleForPhase(Entity entity, @Nullable GamePhase phase) { + public boolean eligibleForPhase(Entity entity, + @Nullable GamePhase phase) { if (phase != null) { if (entity.isImmobile() && phase.isMovement()) { return false; @@ -79,31 +80,35 @@ public boolean eligibleForPhase(Entity entity, @Nullable GamePhase phase) { } /** - * {@inheritDoc} - * Front loaded initiative Core p.41 + * {@inheritDoc} Front loaded initiative Core p.41 */ @Override - public int getInitiativeOrder(int[] num_turns, int index, int min, boolean frontLoadOption) { + public int getInitiativeOrder(int[] num_turns, + int index, + int min, + boolean frontLoadOption) { return ((int) Math.ceil(((double) num_turns[index]) / (double) min)); } /** - * TAG can increase BV when homing arrow IV is present - * {@inheritDoc} - * - * Core rules errata v0.1 - * https://www.battletech.com/forums/index.php/topic,91365.0.html + * TAG can increase BV when homing arrow IV is present {@inheritDoc} + *

+ * Core rules errata v0.1 https://www.battletech.com/forums/index.php/topic,91365.0.html */ @Override - public double tagBVBump(Entity entity, CalculationReport bvReport, double adjustedBV, - long tagCount, boolean hasGuided) { + public double tagBVBump(Entity entity, + CalculationReport bvReport, + double adjustedBV, + long tagCount, + boolean hasGuided) { for (Entity otherEntity : entity.getGame().getEntitiesVector()) { if ((otherEntity.getOwner() == null) || otherEntity.getOwner().isEnemyOf(entity.getOwner())) { continue; } for (Mounted mounted : otherEntity.getWeaponList()) { boolean foundHoming = false; - if (mounted.getType().hasFlag(WeaponTypeFlag.F_ARROW_IV) || mounted.getType().hasFlag(WeaponTypeFlag.F_ARTILLERY)) { + if (mounted.getType().hasFlag(WeaponTypeFlag.F_ARROW_IV) || mounted.getType() + .hasFlag(WeaponTypeFlag.F_ARTILLERY)) { for (Mounted mountedAmmo : otherEntity.getAmmo()) { AmmoType ammoType = (AmmoType) mountedAmmo.getType(); EnumSet munitionType = ammoType.getMunitionType(); @@ -130,7 +135,7 @@ public double tagBVBump(Entity entity, CalculationReport bvReport, double adjust } } } - + if (otherEntity instanceof IBomber asBomber) { BombType bomb = BombType.createBombByType(BombType.BombTypeEnum.HOMING); if (bomb != null) { @@ -155,9 +160,39 @@ public double tagBVBump(Entity entity, CalculationReport bvReport, double adjust } /** - * {@inheritDoc} - * Core rules allows minefields + * {@inheritDoc} Core rules allows minefields + */ + @Override + public boolean allowMinefields(boolean toMinefields) { + return true; + } + + /** + * {@inheritDoc} Core has walk on deployment by default + * + * @return true + */ + @Override + public boolean isWalkOnDeployment() { + return true; + } + + /** + * {@inheritDoc} Core omits immobile units from movement/initiative + * + * @return false if it should not include them */ @Override - public boolean allowMinefields(boolean toMinefields) { return true; } + public boolean includeInMovement(GamePhase phase, + Entity entity) { + return !((phase.isMovement() || phase.isInitiative()) && entity.isImmobile()); + } + + /** + * {@inheritDoc} This is not used by core at all. + */ + @Override + public void setWalkOnDeployment(final boolean walkOn) { + logger.debug("Setting walk on deployment should never occur when running core."); + } } diff --git a/megamek/src/megamek/common/rules/totalwarfare/TWRulesGame.java b/megamek/src/megamek/common/rules/totalwarfare/TWRulesGame.java index 4778d245937..e6769fc401c 100644 --- a/megamek/src/megamek/common/rules/totalwarfare/TWRulesGame.java +++ b/megamek/src/megamek/common/rules/totalwarfare/TWRulesGame.java @@ -33,9 +33,12 @@ */ +import static megamek.client.ui.clientGUI.calculationReport.CalculationReport.formatForReport; + +import java.util.EnumSet; + import megamek.client.ui.clientGUI.calculationReport.CalculationReport; import megamek.common.annotations.Nullable; -import megamek.common.battleValue.BVCalculator; import megamek.common.enums.GamePhase; import megamek.common.equipment.AmmoType; import megamek.common.equipment.Mounted; @@ -44,25 +47,25 @@ import megamek.common.units.Entity; import megamek.common.units.IBomber; -import java.util.EnumSet; - -import static megamek.client.ui.clientGUI.calculationReport.CalculationReport.formatForReport; - public class TWRulesGame extends RulesGame { + private boolean walkOn = false; + /** * Ammo dumping is allowed * * @return true if ammo dumping is allowed */ @Override - public boolean ammoDumping() { return true; } + public boolean ammoDumping() {return true;} /** * Is an entity eligible for a phase + * * @param entity the unit being considered - * @param phase what phase it is in + * @param phase what phase it is in + * * @return is it eligible */ @Override @@ -74,13 +77,13 @@ public boolean eligibleForPhase(Entity entity, @Nullable GamePhase phase) { } /** - * Return the number of units to move. - * Only do front-loaded init if the option is selected + * Return the number of units to move. Only do front-loaded init if the option is selected * - * @param num_turns array of normal turns - * @param index the current index - * @param min the minimum value + * @param num_turns array of normal turns + * @param index the current index + * @param min the minimum value * @param frontLoadOption true if front load option is enabled + * * @return the initiative order */ @Override @@ -90,8 +93,7 @@ public int getInitiativeOrder(int[] num_turns, int index, int min, boolean front } /** - * TAG can increase BV when Semi-guided or homing arrow IV is present - * {@inheritDoc} + * TAG can increase BV when Semi-guided or homing arrow IV is present {@inheritDoc} */ @Override public double tagBVBump(Entity entity, CalculationReport bvReport, double adjustedBV, @@ -144,9 +146,26 @@ public double tagBVBump(Entity entity, CalculationReport bvReport, double adjust } /** - * {@inheritDoc} - * Allow only if TO Minefields is enabled + * {@inheritDoc} Allow only if TO Minefields is enabled */ @Override - public boolean allowMinefields(boolean toMinefields) { return toMinefields; } + public boolean allowMinefields(boolean toMinefields) {return toMinefields;} + + /** + * {@inheritDoc} TW will allow walk-on deployment with the right option + */ + @Override + public void setWalkOnDeployment(final boolean walkOn) { + this.walkOn = walkOn; + } + + /** + * {@inheritDoc} Should we allow Walk On Deployment + * + * @return read the variable and return it (default False) + */ + @Override + public boolean isWalkOnDeployment() { + return walkOn; + } } diff --git a/megamek/src/megamek/common/units/Dropship.java b/megamek/src/megamek/common/units/Dropship.java index de837e9ce39..3cfc26e8abc 100644 --- a/megamek/src/megamek/common/units/Dropship.java +++ b/megamek/src/megamek/common/units/Dropship.java @@ -74,7 +74,9 @@ public class Dropship extends SmallCraft { @Serial private static final long serialVersionUID = 1528728632696989565L; - /** A DropShip is far too large to be hidden (hidden units, TW pg 259). */ + /** + * A DropShip is far too large to be hidden (hidden units, TW pg 259). + */ @Override public boolean canHide() { return false; @@ -91,7 +93,8 @@ public boolean canHide() { * @param turns - integer specifying the number of end phases that the effects last through Technically, about 1.5 * turns elapse per the rules for ASEW missiles in TO */ - public void setASEWAffected(int arc, int turns) { + public void setASEWAffected(int arc, + int turns) { if (arc < asewAffectedTurns.length) { asewAffectedTurns[arc] = turns; } @@ -117,7 +120,7 @@ public int getASEWAffected(int arc) { public static final int COLLAR_NO_BOOM = 2; private static final String[] COLLAR_NAMES = { - "KF-Boom", "Prototype KF-Boom", "No Boom" + "KF-Boom", "Prototype KF-Boom", "No Boom" }; // Likewise, you can have a prototype or standard K-F Boom @@ -183,13 +186,15 @@ public static String getCollarName(int type) { public static TechAdvancement getCollarTA() { return new TechAdvancement(TechBase.ALL).setAdvancement(2458, 2470, 2500) - .setPrototypeFactions(Faction.TH).setProductionFactions(Faction.TH).setTechRating( - TechRating.C) - .setAvailability(AvailabilityValue.C, - AvailabilityValue.C, - AvailabilityValue.C, - AvailabilityValue.C) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + .setPrototypeFactions(Faction.TH) + .setProductionFactions(Faction.TH) + .setTechRating( + TechRating.C) + .setAvailability(AvailabilityValue.C, + AvailabilityValue.C, + AvailabilityValue.C, + AvailabilityValue.C) + .setStaticTechLevel(SimpleTechLevel.STANDARD); } //KF Boom Stuff @@ -216,7 +221,9 @@ public String getCritDamageString() { } @Override - public boolean isLocationProhibited(Coords c, int testBoardId, int currElevation) { + public boolean isLocationProhibited(Coords c, + int testBoardId, + int currElevation) { if (!game.hasBoardLocation(c, testBoardId)) { return true; } @@ -289,7 +296,7 @@ public boolean isLocationProhibited(Coords c, int testBoardId, int currElevation // TODO: get updated ruling; this code causes a hex with one single lower- or higher-level neighbor // to be disqualified, but it would seem that a single lower-level neighbor should be fine. if ((elevDifference > 1) || (elevations.get(elevationsKeys[0]) < elevMinCount) - || (elevations.get(elevationsKeys[1]) < elevMinCount)) { + || (elevations.get(elevationsKeys[1]) < elevMinCount)) { return true; } @@ -409,22 +416,22 @@ public double primitiveFuelFactor() { } protected static final TechAdvancement TA_DROPSHIP = new TechAdvancement(TechBase.ALL) - .setAdvancement(ITechnology.DATE_NONE, 2470, 2490).setISApproximate(false, true, false) - .setProductionFactions(Faction.TH).setTechRating(TechRating.D) - .setAvailability(AvailabilityValue.D, - AvailabilityValue.E, - AvailabilityValue.D, - AvailabilityValue.D) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + .setAdvancement(ITechnology.DATE_NONE, 2470, 2490).setISApproximate(false, true, false) + .setProductionFactions(Faction.TH).setTechRating(TechRating.D) + .setAvailability(AvailabilityValue.D, + AvailabilityValue.E, + AvailabilityValue.D, + AvailabilityValue.D) + .setStaticTechLevel(SimpleTechLevel.STANDARD); protected static final TechAdvancement TA_DROPSHIP_PRIMITIVE = new TechAdvancement(TechBase.IS) - .setISAdvancement(ITechnology.DATE_ES, 2200, ITechnology.DATE_NONE, 2500) - .setISApproximate(false, true, false, false) - .setProductionFactions(Faction.TA).setTechRating(TechRating.D) - .setAvailability(AvailabilityValue.D, - AvailabilityValue.X, - AvailabilityValue.X, - AvailabilityValue.X) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + .setISAdvancement(ITechnology.DATE_ES, 2200, ITechnology.DATE_NONE, 2500) + .setISApproximate(false, true, false, false) + .setProductionFactions(Faction.TA).setTechRating(TechRating.D) + .setAvailability(AvailabilityValue.D, + AvailabilityValue.X, + AvailabilityValue.X, + AvailabilityValue.X) + .setStaticTechLevel(SimpleTechLevel.STANDARD); @Override public TechAdvancement getConstructionTechAdvancement() { @@ -440,7 +447,8 @@ protected void addSystemTechAdvancement(CompositeTechLevel techLevel) { } @Override - public double getCost(CalculationReport calcReport, boolean ignoreAmmo) { + public double getCost(CalculationReport calcReport, + boolean ignoreAmmo) { return DropShipCostCalculator.calculateCost(this, calcReport, ignoreAmmo); } @@ -453,7 +461,8 @@ public double getPriceMultiplier() { * need to check bay location before loading ammo */ @Override - public boolean loadWeapon(WeaponMounted mounted, AmmoMounted mountedAmmo) { + public boolean loadWeapon(WeaponMounted mounted, + AmmoMounted mountedAmmo) { boolean success = false; WeaponType weaponType = mounted.getType(); AmmoType ammoType = mountedAmmo.getType(); @@ -469,9 +478,9 @@ public boolean loadWeapon(WeaponMounted mounted, AmmoMounted mountedAmmo) { } if (mountedAmmo.isAmmoUsable() - && !weaponType.hasFlag(WeaponType.F_ONE_SHOT) - && (ammoType.getAmmoType() == weaponType.getAmmoType()) - && (ammoType.getRackSize() == weaponType.getRackSize())) { + && !weaponType.hasFlag(WeaponType.F_ONE_SHOT) + && (ammoType.getAmmoType() == weaponType.getAmmoType()) + && (ammoType.getRackSize() == weaponType.getRackSize())) { mounted.setLinked(mountedAmmo); success = true; } @@ -506,7 +515,7 @@ public boolean hasActiveECM() { * What's the range of the ECM equipment? * * @return the int range of this unit's ECM. This value will be - * Entity.NONE if no ECM is active. + * Entity.NONE if no ECM is active. */ @Override public int getECMRange() { @@ -565,6 +574,10 @@ public void setPosition(Coords position) { } super.setPosition(position, false); + if (position == null) { + // Used for when clearing deployment positions. We need to clear the secondary positions as well. + secondaryPositions.clear(); + } if ((getAltitude() == 0) && (null != game) && !isSpaceborne() && (position != null)) { secondaryPositions.put(0, position); secondaryPositions.put(1, position.translated(getFacing())); @@ -642,7 +655,8 @@ public boolean usesWeaponBays() { } @Override - public HitData rollHitLocation(int table, int side) { + public HitData rollHitLocation(int table, + int side) { if ((table == ToHitData.HIT_KICK) || (table == ToHitData.HIT_PUNCH)) { // we don't really have any good rules on how to apply this, // I have a rules question posted about it: @@ -752,7 +766,7 @@ public boolean isValidSecondaryFacing(int dir) { int rotate = dir - getFacing(); if (canChangeSecondaryFacing()) { return (rotate == 0) || (rotate == 1) || (rotate == -1) - || (rotate == -5) || (rotate == 5); + || (rotate == -5) || (rotate == 5); } return rotate == 0; } @@ -804,7 +818,7 @@ public void postProcessFacingChange() { public boolean canLandVertically() { PlanetaryConditions conditions = game.getPlanetaryConditions(); return isSpheroid() - || conditions.getAtmosphere().isLighterThan(Atmosphere.THIN); + || conditions.getAtmosphere().isLighterThan(Atmosphere.THIN); } /** @@ -818,7 +832,7 @@ public boolean canTakeOffVertically() { } PlanetaryConditions conditions = game.getPlanetaryConditions(); boolean spheroidOrLessThanThin = isSpheroid() - || conditions.getAtmosphere().isLighterThan(Atmosphere.THIN); + || conditions.getAtmosphere().isLighterThan(Atmosphere.THIN); return spheroidOrLessThanThin && (getCurrentThrust() > 2); } diff --git a/megamek/src/megamek/common/units/Entity.java b/megamek/src/megamek/common/units/Entity.java index 9803305bbdd..e7550ad6cc9 100644 --- a/megamek/src/megamek/common/units/Entity.java +++ b/megamek/src/megamek/common/units/Entity.java @@ -39,6 +39,7 @@ import java.awt.Image; import java.io.Serial; import java.util.*; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -273,6 +274,7 @@ public enum InvalidSourceBuildReason { public static final int DMG_HEAVY = 3; public static final int DMG_CRIPPLED = 4; + public static final int DEPLOY_ROUND_PRE_GAME = -1; protected transient Game game; protected transient IGame iGame; @@ -1244,7 +1246,6 @@ public void setId(int id) { * Taharqa: I am changing externalId to a string, so I can use UUIDs in MHQ. * * @return the ID settable by external sources (such as mm.net) - * * @throws NumberFormatException if the stored ID is not an integer * @see Entity#externalId */ @@ -1260,7 +1261,6 @@ public String getExternalIdAsString() { * This sets the external ID. * * @param externalId the new external ID for this Entity. - * * @see Entity#externalId */ public void setExternalIdAsString(String externalId) { @@ -1658,16 +1658,16 @@ private void addEquipmentTechAdvancement(CompositeTechLevel techLevel) { // ProtoMek EI is built-in per IO:AE p.69 -- only count toward tech level // in Full Tracking mode (Off and Pilot Only = Standard tech) if (isProtoMek() - && (mounted.getType() instanceof MiscType) - && mounted.getType().hasFlag(MiscType.F_EI_INTERFACE) - && !isNeuralInterfaceFullTracking()) { + && (mounted.getType() instanceof MiscType) + && mounted.getType().hasFlag(MiscType.F_EI_INTERFACE) + && !isNeuralInterfaceFullTracking()) { continue; } techLevel.addComponent(mounted.getType()); if (mounted.isArmored()) { techLevel.addComponent(TA_ARMORED_COMPONENT, - Messages.getString("CompositeTechLevel.component.armoredComponent")); + Messages.getString("CompositeTechLevel.component.armoredComponent")); } } } @@ -1679,10 +1679,10 @@ private void addEquipmentTechAdvancement(CompositeTechLevel techLevel) { * * @param techFaction The faction to evaluate faction-specific dates for * @param evaluationYear The year to evaluate each component's variable tech level in - * * @return A composite tech level that knows every component that went into it */ - public RecordingCompositeTechLevel recordedTechLevel(Faction techFaction, int evaluationYear) { + public RecordingCompositeTechLevel recordedTechLevel(Faction techFaction, + int evaluationYear) { RecordingCompositeTechLevel recorded = new RecordingCompositeTechLevel(this, techFaction, evaluationYear); addSystemTechAdvancement(recorded); addEquipmentTechAdvancement(recorded); @@ -1690,77 +1690,102 @@ public RecordingCompositeTechLevel recordedTechLevel(Faction techFaction, int ev } protected static final TechAdvancement TA_OMNI = new TechAdvancement(TechBase.ALL).setISAdvancement(DATE_NONE, - DATE_NONE, - 3052) - .setClanAdvancement(2854, 2856, 2864) - .setClanApproximate(true) - .setPrototypeFactions(Faction.CCY, Faction.CSF) - .setProductionFactions(Faction.CCY, Faction.DC) - .setTechRating(TechRating.E) - .setAvailability(AvailabilityValue.X, AvailabilityValue.E, AvailabilityValue.E, AvailabilityValue.D) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + DATE_NONE, + 3052) + .setClanAdvancement(2854, + 2856, + 2864) + .setClanApproximate(true) + .setPrototypeFactions(Faction.CCY, + Faction.CSF) + .setProductionFactions(Faction.CCY, + Faction.DC) + .setTechRating(TechRating.E) + .setAvailability(AvailabilityValue.X, + AvailabilityValue.E, + AvailabilityValue.E, + AvailabilityValue.D) + .setStaticTechLevel( + SimpleTechLevel.STANDARD); // This is not in the rules anywhere, but is implied by the existence of the // Badger and Bandit // tanks used by Wolf's Dragoons and sold to the merc market as early as 3008. private static final TechAdvancement TA_OMNI_VEHICLE = new TechAdvancement(TechBase.ALL).setISAdvancement(3008, - DATE_NONE, - 3052) - .setISApproximate(true) - .setClanAdvancement(2854, 2856, 2864) - .setClanApproximate(true) - .setPrototypeFactions(Faction.CCY, Faction.CSF, Faction.MERC) - .setProductionFactions(Faction.CCY, Faction.DC) - .setTechRating(TechRating.E) - .setAvailability(AvailabilityValue.X, AvailabilityValue.E, AvailabilityValue.E, AvailabilityValue.D) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + DATE_NONE, + 3052) + .setISApproximate(true) + .setClanAdvancement(2854, + 2856, + 2864) + .setClanApproximate(true) + .setPrototypeFactions( + Faction.CCY, + Faction.CSF, + Faction.MERC) + .setProductionFactions( + Faction.CCY, + Faction.DC) + .setTechRating(TechRating.E) + .setAvailability( + AvailabilityValue.X, + AvailabilityValue.E, + AvailabilityValue.E, + AvailabilityValue.D) + .setStaticTechLevel( + SimpleTechLevel.STANDARD); // Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS protected static final TechAdvancement TA_PATCHWORK_ARMOR = new TechAdvancement(TechBase.ALL).setAdvancement( - DATE_PS, - 3080, - DATE_NONE) - .setApproximate(false, true, false) - .setTechRating(TechRating.A) - .setAvailability(AvailabilityValue.E, - AvailabilityValue.D, - AvailabilityValue.E, - AvailabilityValue.E) - .setStaticTechLevel(SimpleTechLevel.ADVANCED); + DATE_PS, + 3080, + DATE_NONE) + .setApproximate(false, + true, + false) + .setTechRating( + TechRating.A) + .setAvailability( + AvailabilityValue.E, + AvailabilityValue.D, + AvailabilityValue.E, + AvailabilityValue.E) + .setStaticTechLevel( + SimpleTechLevel.ADVANCED); // Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS protected static final TechAdvancement TA_MIXED_TECH = new TechAdvancement(TechBase.ALL) - .setISAdvancement(DATE_NONE, 3050, 3082) - .setClanAdvancement(DATE_NONE, 2820, 3082) - .setApproximate(false, true, true, false, false) - .setPrototypeFactions(Faction.CLAN, Faction.DC, Faction.FS, Faction.LC) - .setTechRating(TechRating.A) - .setAvailability(AvailabilityValue.X, - AvailabilityValue.X, - AvailabilityValue.E, - AvailabilityValue.D) - .setStaticTechLevel(SimpleTechLevel.STANDARD); + .setISAdvancement(DATE_NONE, 3050, 3082) + .setClanAdvancement(DATE_NONE, 2820, 3082) + .setApproximate(false, true, true, false, false) + .setPrototypeFactions(Faction.CLAN, Faction.DC, Faction.FS, Faction.LC) + .setTechRating(TechRating.A) + .setAvailability(AvailabilityValue.X, + AvailabilityValue.X, + AvailabilityValue.E, + AvailabilityValue.D) + .setStaticTechLevel(SimpleTechLevel.STANDARD); // Tech Progression tweaked to combine IntOps with TRO Prototypes/3145 NTNU RS protected static final TechAdvancement TA_ARMORED_COMPONENT = new TechAdvancement(TechBase.ALL) - .setISAdvancement( - 3061, - 3082) - .setISApproximate(false, - true, - false, - false, - false) - .setClanAdvancement(3061, 3082) - .setClanApproximate(false, - true, - false, - false, - false) - .setPrototypeFactions(Faction.CSF, Faction.FW) - .setProductionFactions(Faction.CJF, Faction.FW) - .setTechRating(TechRating.E) - .setAvailability(AvailabilityValue.X, - AvailabilityValue.X, - AvailabilityValue.F, - AvailabilityValue.E) - .setStaticTechLevel(SimpleTechLevel.ADVANCED); + .setISAdvancement( + 3061, + 3082) + .setISApproximate(false, + true, + false, + false, + false) + .setClanAdvancement(3061, 3082) + .setClanApproximate(false, + true, + false, + false, + false) + .setPrototypeFactions(Faction.CSF, Faction.FW) + .setProductionFactions(Faction.CJF, Faction.FW) + .setTechRating(TechRating.E) + .setAvailability(AvailabilityValue.X, + AvailabilityValue.X, + AvailabilityValue.F, + AvailabilityValue.E) + .setStaticTechLevel(SimpleTechLevel.ADVANCED); public static TechAdvancement getOmniAdvancement() { return getOmniAdvancement(null); @@ -1798,17 +1823,17 @@ protected void addSystemTechAdvancement(CompositeTechLevel techLevel) { } if (hasPatchworkArmor()) { techLevel.addComponent(TA_PATCHWORK_ARMOR, - Messages.getString("CompositeTechLevel.component.patchworkArmor")); + Messages.getString("CompositeTechLevel.component.patchworkArmor")); for (int loc = 0; loc < locations(); loc++) { ArmorType locationArmor = ArmorType.forEntity(this, loc); techLevel.addComponent(locationArmor.getTechAdvancement(), - Messages.getString("CompositeTechLevel.component.armorInLocation", - getLocationAbbr(loc), locationArmor.getName())); + Messages.getString("CompositeTechLevel.component.armorInLocation", + getLocationAbbr(loc), locationArmor.getName())); } } else { ArmorType armor = ArmorType.forEntity(this); techLevel.addComponent(armor.getTechAdvancement(), - Messages.getString("CompositeTechLevel.component.armorNamed", armor.getName())); + Messages.getString("CompositeTechLevel.component.armorNamed", armor.getName())); } if (isMixedTech()) { techLevel.addComponent(TA_MIXED_TECH, Messages.getString("CompositeTechLevel.component.mixedTech")); @@ -1817,11 +1842,11 @@ protected void addSystemTechAdvancement(CompositeTechLevel techLevel) { // Unit types that have no internal structure type (battle armor, infantry) still contribute a blank // advancement here, so name it generically rather than let the lookup report "UNKNOWN". String structureName = (structureType == EquipmentType.T_STRUCTURE_UNKNOWN) - ? Messages.getString("CompositeTechLevel.component.internalStructure") - : Messages.getString("CompositeTechLevel.component.internalStructureNamed", - EquipmentType.getStructureTypeName(structureType, isClanStructure)); + ? Messages.getString("CompositeTechLevel.component.internalStructure") + : Messages.getString("CompositeTechLevel.component.internalStructureNamed", + EquipmentType.getStructureTypeName(structureType, isClanStructure)); techLevel.addComponent(EquipmentType.getStructureTechAdvancement(structureType, isClanStructure), - structureName); + structureName); } public int getRecoveryTurn() { @@ -1855,7 +1880,7 @@ public void performManualStartup() { setManualShutdown(false); // Can't start up if a taser shutdown, TSEMP shutdown, or EMP mine shutdown if ((getTaserShutdownRounds() == 0) && (getTsempEffect() != MMConstants.TSEMP_EFFECT_SHUTDOWN) - && (getEMPShutdownRounds() == 0)) { + && (getEMPShutdownRounds() == 0)) { setShutDown(false); setStartupThisPhase(true); } @@ -1865,15 +1890,14 @@ public void performManualStartup() { * Checks if this is a clan unit. This is determined by tech level. * * @return true if this unit is a clan unit. - * * @see Entity#setTechLevel(int) */ @Override public boolean isClan() { return (IntStream.of(TechConstants.T_CLAN_TW, - TechConstants.T_CLAN_ADVANCED, - TechConstants.T_CLAN_EXPERIMENTAL, - TechConstants.T_CLAN_UNOFFICIAL).anyMatch(i -> (techLevel == i))); + TechConstants.T_CLAN_ADVANCED, + TechConstants.T_CLAN_EXPERIMENTAL, + TechConstants.T_CLAN_UNOFFICIAL).anyMatch(i -> (techLevel == i))); } public boolean isClanArmor(int loc) { @@ -1886,9 +1910,9 @@ public boolean isClanArmor(int loc) { return isClan(); } return IntStream.of(TechConstants.T_CLAN_TW, - TechConstants.T_CLAN_ADVANCED, - TechConstants.T_CLAN_EXPERIMENTAL, - TechConstants.T_CLAN_UNOFFICIAL).anyMatch(i -> (getArmorTechLevel(loc) == i)); + TechConstants.T_CLAN_ADVANCED, + TechConstants.T_CLAN_EXPERIMENTAL, + TechConstants.T_CLAN_UNOFFICIAL).anyMatch(i -> (getArmorTechLevel(loc) == i)); } @Override @@ -1921,7 +1945,9 @@ public void setYear(int year) { this.year = year; } - /** @return the tonnage of the Entity, not its weight */ + /** + * @return the tonnage of the Entity, not its weight + */ public double getWeight() { return weight; } @@ -1967,7 +1993,7 @@ public void setOmni(boolean omni) { * {@link Entity} type. * * @return The location to place equipment that is not required to be assigned a location, defaulting to - * Entity.LOC_NONE for unit types that do not have such a location. + * Entity.LOC_NONE for unit types that do not have such a location. */ public int getBodyLocation() { return LOC_NONE; @@ -2034,7 +2060,7 @@ public void setCrew(Crew crew) { /** * @return The total number of crew available to supplement marines onboarding actions. Includes officers, enlisted, - * and bay personnel, but not marines/ba or passengers. + * and bay personnel, but not marines/ba or passengers. */ public int getNCrew() { return nCrew; @@ -2045,7 +2071,7 @@ public void setNCrew(int crew) { /** * @return The number of passengers on this unit. Intended for spacecraft, where we want to get the crews of - * transported units plus actual passengers assigned to quarters + * transported units plus actual passengers assigned to quarters */ public int getNPassenger() { return nPassenger; @@ -2061,7 +2087,6 @@ public void setNPassenger(int pass) { * the total passenger capacity.

* * @return the passenger capacity available for non-crew passengers - * * @author Illiani * @since 0.50.10 */ @@ -2179,8 +2204,8 @@ public void setLoadedThisTurn(boolean loaded) { * Determine if this entity participate in the current game phase. * * @return true if this entity is not shut down, is not - * destroyed, has an active crew, and was not unloaded from a transport this turn. false - * otherwise. + * destroyed, has an active crew, and was not unloaded from a transport this turn. false + * otherwise. */ public boolean isActive() { return isActive(-1); @@ -2203,7 +2228,7 @@ public boolean isActive(int turn) { */ public boolean isSelectableThisTurn() { return !done && (conveyance == Entity.NONE) && !unloadedThisTurn && !isClearingMinefield() && !isCarcass() - && !isAbandoned() && (isSpaceborneInSpaceTurn() || isNonSpaceborneInNonSpaceTurn()); + && !isAbandoned() && (isSpaceborneInSpaceTurn() || isNonSpaceborneInNonSpaceTurn()); } private boolean isSpaceborneInSpaceTurn() { @@ -2219,17 +2244,17 @@ private boolean isNonSpaceborneInNonSpaceTurn() { */ public boolean isLoadableThisTurn() { return (delta_distance == 0) && - (conveyance == Entity.NONE) && - !unloadedThisTurn && - !isClearingMinefield() && - (getTractor() == Entity.NONE); + (conveyance == Entity.NONE) && + !unloadedThisTurn && + !isClearingMinefield() && + (getTractor() == Entity.NONE); } /** * Determine if this Entity was unloaded previously this turn. * * @return true if this entity was unloaded for any reason - * during this turn. + * during this turn. */ public boolean isUnloadedThisTurn() { return unloadedThisTurn; @@ -2245,12 +2270,12 @@ public boolean wasLoadedThisTurn() { */ public boolean isTargetable() { return !destroyed && - !doomed && - deployed && - !isOffBoard() && - (conveyance == Entity.NONE) && - !captured && - (getPosition() != null); + !doomed && + deployed && + !isOffBoard() && + (conveyance == Entity.NONE) && + !captured && + (getPosition() != null); } public boolean isProne() { @@ -2349,7 +2374,6 @@ public boolean isImmobileForJump() { * unit (shutdown, damage) and - only if checkCrew is true - also the status of the crew (unconscious). * * @param checkCrew If false, ignore the fitness of the crew when determining if the entity is immobile. - * * @return True if the target is considered immobile as a target and unable to move actively. */ public boolean isImmobile(boolean checkCrew) { @@ -2369,16 +2393,16 @@ public boolean isPermanentlyImmobilized(boolean checkCrew) { if (checkCrew && ((isUncrewed()) || getCrew().isDead())) { return true; } else if ((this instanceof Mek mek) && - (mek.getOriginalMechanicalJumpBoosterMP() > 0) && - (getMechanicalJumpBoosterMP(MPCalculationSetting.PERM_IMMOBILIZED) > 0)) { + (mek.getOriginalMechanicalJumpBoosterMP() > 0) && + (getMechanicalJumpBoosterMP(MPCalculationSetting.PERM_IMMOBILIZED) > 0)) { return false; } else if (!(this instanceof Tank)) { // this is already handled in the tank specific override return ((getOriginalWalkMP() > 0) || (getOriginalRunMP() > 0) || (getOriginalJumpMP() > 0)) - // Need to make sure here that we're ignoring heat because that's not actually "permanent": - && - ((getWalkMP(MPCalculationSetting.PERM_IMMOBILIZED) == 0) && - (getRunMP(MPCalculationSetting.PERM_IMMOBILIZED) == 0) && - (getJumpMP(MPCalculationSetting.PERM_IMMOBILIZED) == 0)); + // Need to make sure here that we're ignoring heat because that's not actually "permanent": + && + ((getWalkMP(MPCalculationSetting.PERM_IMMOBILIZED) == 0) && + (getRunMP(MPCalculationSetting.PERM_IMMOBILIZED) == 0) && + (getJumpMP(MPCalculationSetting.PERM_IMMOBILIZED) == 0)); } return false; @@ -2386,7 +2410,7 @@ public boolean isPermanentlyImmobilized(boolean checkCrew) { /** * @return true if the unit is always uncrewed, like a Handheld Weapon or unarmed, unpowered trailer. Should not - * return true for remote drone OS. + * return true for remote drone OS. */ public boolean isNotCrewedEntityType() { return defaultCrewType().equals(CrewType.NONE); @@ -2394,7 +2418,7 @@ public boolean isNotCrewedEntityType() { /** * @return true if the entity's crew is null or the entity is uncrewed, like a Handheld Weapon or unarmed, unpowered - * trailer. + * trailer. */ public boolean isUncrewed() { return getCrew() == null || (getCrew().isCrewTypeNone()); @@ -2515,7 +2539,8 @@ public void setArmsFlipped(boolean armsFlipped) { * Set whether the Mek's arms are flipped to the rear. Does not fire the game event, useful for when it's called * repeatedly such as during bot turn calculations */ - public void setArmsFlipped(boolean armsFlipped, boolean fireEvent) { + public void setArmsFlipped(boolean armsFlipped, + boolean fireEvent) { this.armsFlipped = armsFlipped; if (fireEvent) { @@ -2560,7 +2585,7 @@ public BoardLocation getBoardLocation() { } BoardLocation cached = cachedBoardLocation; if ((cached == null) || (cached.boardId() != currentBoardId) - || !Objects.equals(cached.coords(), currentPosition)) { + || !Objects.equals(cached.coords(), currentPosition)) { cached = BoardLocation.of(currentPosition, currentBoardId); cachedBoardLocation = cached; } @@ -2591,7 +2616,8 @@ public void setPosition(Coords position) { * * @param position the new position. */ - public void setPosition(Coords position, boolean gameUpdate) { + public void setPosition(Coords position, + boolean gameUpdate) { HashSet oldPositions = null; if ((game != null) && gameUpdate) { oldPositions = getOccupiedCoords(); @@ -2604,7 +2630,7 @@ public void setPosition(Coords position, boolean gameUpdate) { /** * @return the coords of the second to last position on the passed through vector or the current position if too - * small + * small */ public Coords getPriorPosition() { @@ -2631,12 +2657,13 @@ public void setElevation(int elevation) { * @param next The next hex * @param assumedElevation The assumed elevation from the previous hex * @param climb Whether the entity is climbing or not - * * @return The elevation of the entity in the next hex - * * @see Entity#setElevation(int) */ - public int calcElevation(Hex current, Hex next, int assumedElevation, boolean climb) { + public int calcElevation(Hex current, + Hex next, + int assumedElevation, + boolean climb) { int retVal = assumedElevation; if (next == null) { return retVal; @@ -2647,10 +2674,10 @@ public int calcElevation(Hex current, Hex next, int assumedElevation, boolean cl // Special case for DFA attacks into water - we want to land on the bottom of the hex if (isMakingDfa() && - (assumedElevation == 0) && - next.containsTerrain(Terrains.WATER) && - !next.containsTerrain(Terrains.ICE) && - !climb) { + (assumedElevation == 0) && + next.containsTerrain(Terrains.WATER) && + !next.containsTerrain(Terrains.ICE) && + !climb) { return next.floor(); } @@ -2692,21 +2719,21 @@ public int calcElevation(Hex current, Hex next, int assumedElevation, boolean cl // the hex. retVal = nextLevel - next.getLevel(); } else if (((getMovementMode().isSubmarine() || getMovementMode().isUMUInfantry()) && - next.containsTerrain(Terrains.WATER) && - current.containsTerrain(Terrains.WATER)) || - getMovementMode().isVTOL() || - (getMovementMode().isQuadSwim() && hasUMU()) || - (getMovementMode().isBipedSwim() && hasUMU())) { + next.containsTerrain(Terrains.WATER) && + current.containsTerrain(Terrains.WATER)) || + getMovementMode().isVTOL() || + (getMovementMode().isQuadSwim() && hasUMU()) || + (getMovementMode().isBipedSwim() && hasUMU())) { retVal += current.getLevel(); retVal -= next.getLevel(); } else { // if we're a hovercraft, surface ship, WIGE or a "fully amphibious" vehicle, we // go on the water surface without adjusting elevation if ((getMovementMode() != EntityMovementMode.HOVER) && - (getMovementMode() != EntityMovementMode.NAVAL) && - (getMovementMode() != EntityMovementMode.HYDROFOIL) && - (getMovementMode() != EntityMovementMode.WIGE) && - !hasWorkingMisc(MiscType.F_FULLY_AMPHIBIOUS)) { + (getMovementMode() != EntityMovementMode.NAVAL) && + (getMovementMode() != EntityMovementMode.HYDROFOIL) && + (getMovementMode() != EntityMovementMode.WIGE) && + !hasWorkingMisc(MiscType.F_FULLY_AMPHIBIOUS)) { int prevWaterLevel = 0; if (current.containsTerrain(Terrains.WATER)) { prevWaterLevel = current.terrainLevel(Terrains.WATER); @@ -2721,8 +2748,8 @@ public int calcElevation(Hex current, Hex next, int assumedElevation, boolean cl // a Mek can only climb out onto ice in depth 2 or shallower water // Mek on the surf ace will stay on the surface if (((waterLevel == 1) && (prevWaterLevel == 1)) || - ((prevWaterLevel <= 2) && climb) || - (assumedElevation >= 0)) { + ((prevWaterLevel <= 2) && climb) || + (assumedElevation >= 0)) { retVal += waterLevel; } } @@ -2734,15 +2761,15 @@ public int calcElevation(Hex current, Hex next, int assumedElevation, boolean cl int buildingCurrent = Math.max(-current.depth(true), current.terrainLevel(Terrains.BLDG_ELEV)); int buildingNext = Math.max(-next.depth(true), next.terrainLevel(Terrains.BLDG_ELEV)); if (((assumedElevation == buildingCurrent) && (climb || isJumpingNow) && (this instanceof Mek)) || - (retVal > buildingNext)) { + (retVal > buildingNext)) { retVal = buildingNext; } else if ((buildingNext + next.getLevel()) > (buildingCurrent + current.getLevel())) { BasementType nextBasement = BasementType.getType(next.terrainLevel(Terrains.BLDG_BASEMENT_TYPE)); int collapsedBasement = next.terrainLevel(Terrains.BLDG_BASE_COLLAPSED); if (climb || isJumpingNow) { if ((buildingNext + next.getLevel()) > this.getMaxElevationChange() && - next.containsTerrain(Terrains.BUILDING) && - climb) { + next.containsTerrain(Terrains.BUILDING) && + climb) { retVal = next.getLevel(); } else { retVal = buildingNext + next.getLevel(); @@ -2759,7 +2786,7 @@ public int calcElevation(Hex current, Hex next, int assumedElevation, boolean cl if (climb || isJumpingNow) { retVal = buildingNext + next.getLevel(); } else if (!currentBasement.isUnknownOrNone() && - (assumedElevation == -currentBasement.getDepth())) { + (assumedElevation == -currentBasement.getDepth())) { retVal = -BasementType.getType(next.terrainLevel(Terrains.BLDG_BASEMENT_TYPE)).getDepth(); } else { retVal += current.getLevel(); @@ -2769,8 +2796,8 @@ public int calcElevation(Hex current, Hex next, int assumedElevation, boolean cl } if ((getMovementMode() != EntityMovementMode.NAVAL) && - (getMovementMode() != EntityMovementMode.HYDROFOIL) && - (next.containsTerrain(Terrains.BRIDGE) || current.containsTerrain(Terrains.BRIDGE))) { + (getMovementMode() != EntityMovementMode.HYDROFOIL) && + (next.containsTerrain(Terrains.BRIDGE) || current.containsTerrain(Terrains.BRIDGE))) { int bridgeElev; if (next.containsTerrain(Terrains.BRIDGE)) { bridgeElev = next.terrainLevel(Terrains.BRIDGE_ELEV); @@ -2784,11 +2811,11 @@ public int calcElevation(Hex current, Hex next, int assumedElevation, boolean cl // this, calcElevation never places the Mek on a tall bridge, so the move compiles // as a normal (non-climbing) step and the climb dialog never appears. boolean climbingOntoTallBridge = climb - && (this instanceof Mek) - && next.containsTerrain(Terrains.BRIDGE) - && ((next.getLevel() + bridgeElev) > (current.getLevel() + assumedElevation)) - && gameOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING) - && ClimbingHelper.canClimb(this); + && (this instanceof Mek) + && next.containsTerrain(Terrains.BRIDGE) + && ((next.getLevel() + bridgeElev) > (current.getLevel() + assumedElevation)) + && gameOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING) + && ClimbingHelper.canClimb(this); if ((elevDiff <= getMaxElevationChange()) || climbingOntoTallBridge) { // bridge is reachable, or a climbing Mek can scale up to it (TO:AR p.20) if (climb || !isElevationValid(retVal, next)) { @@ -2803,13 +2830,14 @@ && gameOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS return retVal; } - public int calcElevation(Hex current, Hex next) { + public int calcElevation(Hex current, + Hex next) { return calcElevation(current, next, elevation, false); } /** * @return The elevation of this Entity, relative to the current Hex's surface. When the unit is transported, - * returns the elevation of the carrier. + * returns the elevation of the carrier. */ @Override public int getElevation() { @@ -2846,7 +2874,9 @@ private int enterableBasementDepth(Hex hex) { /** * is it possible to go down, or are we landed/just above the water/treeline? assuming passed elevation. */ - public boolean canGoDown(int assumedElevation, Coords assumedPos, int boardId) { + public boolean canGoDown(int assumedElevation, + Coords assumedPos, + int boardId) { if ((game == null) || !game.hasBoardLocation(assumedPos, boardId) || game.getBoard(boardId).isSpace()) { return false; } @@ -2875,7 +2905,7 @@ public boolean canGoDown(int assumedElevation, Coords assumedPos, int boardId) { int minElev = 0; // When over a bridge, limit downward movement. Can land on a bridge. if (hex.containsTerrain(Terrains.BRIDGE_ELEV) && - (assumedElevation >= hex.terrainLevel(Terrains.BRIDGE_ELEV))) { + (assumedElevation >= hex.terrainLevel(Terrains.BRIDGE_ELEV))) { minElev = hex.terrainLevel(Terrains.BRIDGE_ELEV); } // Cannot land on woods or water @@ -2907,8 +2937,8 @@ public boolean canGoDown(int assumedElevation, Coords assumedPos, int boardId) { case INF_UMU: /* non-mechanized SCUBA infantry have a maximum depth of 2 */ if (this instanceof ConvInfantry convInfantry && - convInfantry.hasSpecialization(ConvInfantry.SCUBA) && - hex.containsTerrain(Terrains.WATER)) { + convInfantry.hasSpecialization(ConvInfantry.SCUBA) && + hex.containsTerrain(Terrains.WATER)) { minAlt = Math.max(hex.floor(), -2); } else { minAlt = hex.floor(); @@ -2936,7 +2966,9 @@ public boolean canGoDown(int assumedElevation, Coords assumedPos, int boardId) { /** * is it possible to go up, or are we at maximum altitude? assuming passed elevation. */ - public boolean canGoUp(int assumedElevation, Coords assumedPos, int boardId) { + public boolean canGoUp(int assumedElevation, + Coords assumedPos, + int boardId) { if ((game == null) || !game.hasBoardLocation(assumedPos, boardId) || game.getBoard(boardId).isSpace()) { return false; } @@ -2955,7 +2987,7 @@ public boolean canGoUp(int assumedElevation, Coords assumedPos, int boardId) { // When under a bridge, restrict upward movement // "- 1" to correct that height() reports one less than the rules (TW p.99) say if (hex.containsTerrain(Terrains.BRIDGE_ELEV) && - assumedElevation < hex.terrainLevel(Terrains.BRIDGE_ELEV)) { + assumedElevation < hex.terrainLevel(Terrains.BRIDGE_ELEV)) { maxAlt = hex.terrainLevel(Terrains.BRIDGE_ELEV) - height() - 1; } break; @@ -3005,13 +3037,14 @@ public boolean canGoUp(int assumedElevation, Coords assumedPos, int boardId) { * Check if this entity can legally occupy the requested elevation. Does not check stacking, only terrain * limitations */ - public boolean isElevationValid(int assumedElevation, Hex hex) { + public boolean isElevationValid(int assumedElevation, + Hex hex) { int assumedAlt = assumedElevation + hex.getLevel(); if (getMovementMode() == EntityMovementMode.VTOL) { if ((this instanceof Infantry) && - (hex.containsTerrain(Terrains.BUILDING) || - hex.containsTerrain(Terrains.WOODS) || - hex.containsTerrain(Terrains.JUNGLE))) { + (hex.containsTerrain(Terrains.BUILDING) || + hex.containsTerrain(Terrains.WOODS) || + hex.containsTerrain(Terrains.JUNGLE))) { // VTOL BA (sylph) can move as ground unit as well return ((assumedElevation <= 50) && (assumedAlt >= hex.floor())); } else { @@ -3024,7 +3057,7 @@ public boolean isElevationValid(int assumedElevation, Hex hex) { boolean allowed = (assumedElevation <= 50) && (assumedElevation >= 0); if (hex.containsTerrain(Terrains.BRIDGE_ELEV)) { allowed &= (assumedElevation >= hex.terrainLevel(Terrains.BRIDGE_ELEV)) || - (assumedElevation + height() + 1 <= hex.terrainLevel(Terrains.BRIDGE_ELEV)); + (assumedElevation + height() + 1 <= hex.terrainLevel(Terrains.BRIDGE_ELEV)); } if (hex.containsTerrain(Terrains.FOLIAGE_ELEV)) { allowed &= (assumedElevation > hex.terrainLevel(Terrains.FOLIAGE_ELEV) - hex.depth()); @@ -3038,17 +3071,17 @@ public boolean isElevationValid(int assumedElevation, Hex hex) { return allowed; } } else if ((getMovementMode() == EntityMovementMode.SUBMARINE) || - ((getMovementMode() == EntityMovementMode.INF_UMU) && hex.containsTerrain(Terrains.WATER)) || - ((getMovementMode() == EntityMovementMode.QUAD_SWIM) && hasUMU()) || - ((getMovementMode() == EntityMovementMode.BIPED_SWIM) && hasUMU())) { + ((getMovementMode() == EntityMovementMode.INF_UMU) && hex.containsTerrain(Terrains.WATER)) || + ((getMovementMode() == EntityMovementMode.QUAD_SWIM) && hasUMU()) || + ((getMovementMode() == EntityMovementMode.BIPED_SWIM) && hasUMU())) { if (this instanceof ConvInfantry convInfantry && - convInfantry.hasSpecialization(ConvInfantry.SCUBA) && - getMovementMode() == EntityMovementMode.INF_UMU) { + convInfantry.hasSpecialization(ConvInfantry.SCUBA) && + getMovementMode() == EntityMovementMode.INF_UMU) { return assumedAlt >= Math.max(hex.floor(), -2) && (assumedAlt <= hex.getLevel()); } return ((assumedAlt >= hex.floor()) && (assumedAlt <= hex.getLevel())); } else if ((getMovementMode() == EntityMovementMode.HYDROFOIL) || - (getMovementMode() == EntityMovementMode.NAVAL)) { + (getMovementMode() == EntityMovementMode.NAVAL)) { return assumedAlt == hex.getLevel(); } else if (getMovementMode() == EntityMovementMode.WIGE) { // WiGEs can possibly be at any location above or on the surface @@ -3056,8 +3089,8 @@ public boolean isElevationValid(int assumedElevation, Hex hex) { } else { // regular ground units if (hex.containsTerrain(Terrains.ICE) || - (((getMovementMode() == EntityMovementMode.HOVER) || - hasWorkingMisc(MiscType.F_FULLY_AMPHIBIOUS)) && hex.containsTerrain(Terrains.WATER))) { + (((getMovementMode() == EntityMovementMode.HOVER) || + hasWorkingMisc(MiscType.F_FULLY_AMPHIBIOUS)) && hex.containsTerrain(Terrains.WATER))) { // surface of ice is OK, surface of water is OK for hovers and "fully // amphibious" units if (assumedAlt == hex.getLevel()) { @@ -3066,9 +3099,9 @@ public boolean isElevationValid(int assumedElevation, Hex hex) { } // only Meks can move underwater if (hex.containsTerrain(Terrains.WATER) && - (assumedAlt < hex.getLevel()) && - !((this instanceof Mek) || (this instanceof ProtoMek)) && - !EnvironmentalSealingRules.canOperateFullySubmerged(this)) { + (assumedAlt < hex.getLevel()) && + !((this instanceof Mek) || (this instanceof ProtoMek)) && + !EnvironmentalSealingRules.canOperateFullySubmerged(this)) { return false; } // can move on the ground unless its underwater @@ -3085,7 +3118,7 @@ public boolean isElevationValid(int assumedElevation, Hex hex) { // 0 bridge on dry ground (essentially, the bridge being a road) // but: don't forget that a height 0 bridge can be over water where this is relevant return assumedElevation >= bridgeElev - || assumedElevation + height() + 1 <= bridgeElev; // Can't fit under bridge, floor is invalid + || assumedElevation + height() + 1 <= bridgeElev; // Can't fit under bridge, floor is invalid } return true; } @@ -3133,9 +3166,9 @@ public int relHeight() { */ public boolean isOnAtmosphericGroundMap() { boolean onGroundOrinAtmosphere = getGame().getBoard().isGround() || - // doesn't make sense in english, but "atmospheric" map actually - // covers maps that are within a planet's gravity well - getGame().getBoard().isLowAltitude(); + // doesn't make sense in english, but "atmospheric" map actually + // covers maps that are within a planet's gravity well + getGame().getBoard().isLowAltitude(); PlanetaryConditions conditions = getGame().getPlanetaryConditions(); return conditions.getAtmosphere().isDenserThan(Atmosphere.TRACE) && onGroundOrinAtmosphere; } @@ -3185,7 +3218,6 @@ public synchronized void setDuplicateMarker(int duplicateMarker) { * removed from the game. * * @param removedMarker The marker of the removed entity. - * * @return A value indicating whether this entity updated its duplicate marker. */ public synchronized boolean updateDuplicateMarkerAfterDelete(int removedMarker) { @@ -3233,7 +3265,6 @@ public synchronized void generateDisplayName() { * Creates a display name for the entity. * * @param duplicateMarker A number used to disambiguate two entities with the same name. - * * @return A display name for the entity. */ private String createDisplayName(int duplicateMarker) { @@ -3271,7 +3302,6 @@ public synchronized void generateShortName() { * Creates a short name for the entity. * * @param duplicateMarker A number used to disambiguate two entities with the same name. - * * @return A short name for the entity. */ private String createShortName(int duplicateMarker) { @@ -3349,7 +3379,8 @@ public void setSecondaryFacing(int sec_facing) { /** * Sets the secondary facing. Optionally does not fire a game change event (useful for bot evaluation) */ - public void setSecondaryFacing(int sec_facing, boolean fireEvent) { + public void setSecondaryFacing(int sec_facing, + boolean fireEvent) { if (game != null) { // Only allow changing secondary facing if we haven't done so in a prior phase GamePhase phase = game.getPhase(); @@ -3435,15 +3466,15 @@ public boolean canUnjamRAC() { return true; } if (((ammoType == AmmoType.AmmoTypeEnum.AC_ULTRA) || - (ammoType == AmmoType.AmmoTypeEnum.AC_ULTRA_THB) || - (ammoType == AmmoType.AmmoTypeEnum.AC) || - (ammoType == AmmoType.AmmoTypeEnum.LAC) || - (ammoType == AmmoType.AmmoTypeEnum.AC_IMP) || - (ammoType == AmmoType.AmmoTypeEnum.PAC)) && - mounted.isJammed() && - !mounted.isDestroyed() && - gameOptions().booleanOption(OptionsConstants.ADVANCED_COMBAT_UNJAM_UAC) && - game.rulesManager.getRulesWeapons().canUACsJam()) { + (ammoType == AmmoType.AmmoTypeEnum.AC_ULTRA_THB) || + (ammoType == AmmoType.AmmoTypeEnum.AC) || + (ammoType == AmmoType.AmmoTypeEnum.LAC) || + (ammoType == AmmoType.AmmoTypeEnum.AC_IMP) || + (ammoType == AmmoType.AmmoTypeEnum.PAC)) && + mounted.isJammed() && + !mounted.isDestroyed() && + gameOptions().booleanOption(OptionsConstants.ADVANCED_COMBAT_UNJAM_UAC) && + game.rulesManager.getRulesWeapons().canUACsJam()) { return true; } } @@ -3478,7 +3509,6 @@ public double maxGroundObjectTonnage() { * * @param isCarrierHullDown is the unit that's picking this up hull down, or otherwise able to pick up ground-level * objects - * * @return true if the object can be picked up, false if it cannot */ @Override @@ -3493,7 +3523,7 @@ public boolean canBePickedUp(boolean isCarrierHullDown) { public boolean canPickupCarryableObject(ICarryable carryable) { if (carryable == null || !canPickupGroundObject() || !carryable.canBePickedUp( - isHullDown())) { + isHullDown())) { return false; } boolean canPickupWithArms = carryable.getTonnage() <= maxGroundObjectTonnage(); @@ -3512,7 +3542,8 @@ public boolean canPickupCarryableObject(ICarryable carryable) { /** * Put a ground object into the given location */ - public void pickupCarryableObject(ICarryable carryable, Integer location) { + public void pickupCarryableObject(ICarryable carryable, + Integer location) { if (carriedObjects == null) { carriedObjects = new HashMap<>(); } @@ -3533,7 +3564,8 @@ public void pickupCarryableObject(ICarryable carryable, Integer location) { * Remove a specific carried object - useful for when you have the object but not its location, or when an object is * being carried in multiple locations. */ - public void dropCarriedObject(ICarryable carryable, boolean isUnload) { + public void dropCarriedObject(ICarryable carryable, + boolean isUnload) { // build list of locations to clear out List locationsToClear = new ArrayList<>(); @@ -3613,10 +3645,9 @@ public List getValidHalfWeightPickupLocations(ICarryable cargo) { * provided cargo. * * @param cargo {@link ICarryable} carryable object that needs to be picked up - * * @return Map where the key is the {@link String} name of the location or transporter, and the value is an - * {@link Integer} that is either the location on an entity, or the index of the transporter from the list of - * the entity's transports from {@link Entity#getTransports()}. + * {@link Integer} that is either the location on an entity, or the index of the transporter from the list of + * the entity's transports from {@link Entity#getTransports()}. */ // FIXME #7640: This should only return a list of transports once we are able to carry an object in multiple // transports & the MekArms transporter is split into each arm, eliminating the need for the legacy location to @@ -3638,7 +3669,7 @@ public Map getPickupLocationMap(ICarryable cargo) { // FIXME #7640: Update once we can properly specify any transporter an entity has, and properly // load into that transporter. locationMap.put(transporter.getTransporterType() + " " + getTransports().indexOf(transporter), - Integer.MAX_VALUE - getTransports().indexOf(transporter)); + Integer.MAX_VALUE - getTransports().indexOf(transporter)); } } @@ -3651,8 +3682,8 @@ public Map getPickupLocationMap(ICarryable cargo) { * Get a map of location names / transporter names and their location / index that have cargo that can be dropped. * * @return Map where the key is the {@link String} name of the location or transporter, and the value is an - * {@link Integer} that is either the location on an entity, or the index of the transporter from the list of - * the entity's transports from {@link Entity#getTransports()}. + * {@link Integer} that is either the location on an entity, or the index of the transporter from the list of + * the entity's transports from {@link Entity#getTransports()}. */ // FIXME #7640: This should only return a list of transports once we are able to carry an object in multiple transports // & the MekArms transporter is split into each arm, eliminating the need for the legacy location to be used. @@ -3666,13 +3697,13 @@ public Map getDropCargoLocationMap() { } for (Transporter transporter : getTransports()) { if (transporter instanceof ExternalCargo externalCargo - && !externalCargo.getCarryables().isEmpty()) { + && !externalCargo.getCarryables().isEmpty()) { // FIXME #7640: Update once we can properly specify any transporter an entity has, and properly load into // that transporter. locationMap.put(transporter.getTransporterType() + " " + externalCargo.getCarryables() - .getFirst() - .toString(), - Integer.MAX_VALUE - getTransports().indexOf(transporter)); + .getFirst() + .toString(), + Integer.MAX_VALUE - getTransports().indexOf(transporter)); } } @@ -3693,7 +3724,7 @@ public boolean canFireWeapon(int location) { // the weapon for (int carriedObjectLocation : getCarriedObjects().keySet()) { if (getBlockedFiringLocations().containsKey(carriedObjectLocation) && - getBlockedFiringLocations().get(carriedObjectLocation).contains(location)) { + getBlockedFiringLocations().get(carriedObjectLocation).contains(location)) { return false; } } @@ -3793,12 +3824,16 @@ public int getWalkHeat() { return 0; } - /** @return This entity's unmodified running/flank mp. */ + /** + * @return This entity's unmodified running/flank mp. + */ public int getOriginalRunMP() { return (int) Math.ceil(getOriginalWalkMP() * 1.5); } - /** @return This entity's running/flank mp modified for all in-game effects. */ + /** + * @return This entity's running/flank mp modified for all in-game effects. + */ public int getRunMP() { return getRunMP(MPCalculationSetting.STANDARD); } @@ -3819,7 +3854,7 @@ public int getRunMPWithoutMASC() { * enhancers, including the current status of such speed enhancers. * * @return A string like 9(15) if there is no current {@link Game}, or a string like 9(15) - * MASC:0(3+) if there is one. + * MASC:0(3+) if there is one. */ public String getRunMPasString() { return getRunMPasString(true); @@ -3831,9 +3866,8 @@ public String getRunMPasString() { * * @param gameState Set this to true to include information about the current state of equipment like * MASC. - * * @return A string like 9(15) if gameState is false or there is no current - * {@link Game}, or a string like 9(15) MASC:0(3+) otherwise. + * {@link Game}, or a string like 9(15) MASC:0(3+) otherwise. */ public String getRunMPasString(boolean gameState) { return Integer.toString(getRunMP()); @@ -3918,8 +3952,7 @@ public void setOriginalJumpMP(int jumpMP) { /** * @return In most cases, the same as getJumpMP(). For Meks that have either normal jump MP or mechanical booster - * jump MP or both, the bigger value is returned. - * + * jump MP or both, the bigger value is returned. * @see #getJumpMP() * @see #getMechanicalJumpBoosterMP() */ @@ -3971,7 +4004,7 @@ public int getJumpMPWithTerrain() { /** * @return The jump MP for a Mek's mechanical jump boosters, modified for typical gameplay purposes by damage, other - * equipment (shields) and other effects. Returns 0 for non-Meks. + * equipment (shields) and other effects. Returns 0 for non-Meks. */ public int getMechanicalJumpBoosterMP() { return getMechanicalJumpBoosterMP(MPCalculationSetting.STANDARD); @@ -3979,7 +4012,7 @@ public int getMechanicalJumpBoosterMP() { /** * @return The jump MP for a Mek's mechanical jump boosters, modified as given through the MPCalculationSetting. - * Returns 0 for non-Meks. + * Returns 0 for non-Meks. */ public int getMechanicalJumpBoosterMP(MPCalculationSetting mpCalculationSetting) { return 0; @@ -4002,16 +4035,17 @@ public int elevationOccupied(Hex hex) { return elevationOccupied(hex, getElevation()); } - public int elevationOccupied(Hex hex, int elevation) { + public int elevationOccupied(Hex hex, + int elevation) { if (hex == null) { return 0; } if ((movementMode == EntityMovementMode.VTOL) || (movementMode == EntityMovementMode.WIGE)) { return hex.getLevel() + elevation; } else if (((movementMode == EntityMovementMode.HOVER) || - (movementMode == EntityMovementMode.NAVAL) || - (movementMode == EntityMovementMode.HYDROFOIL) || - hex.containsTerrain(Terrains.ICE)) && hex.containsTerrain(Terrains.WATER)) { + (movementMode == EntityMovementMode.NAVAL) || + (movementMode == EntityMovementMode.HYDROFOIL) || + hex.containsTerrain(Terrains.ICE)) && hex.containsTerrain(Terrains.WATER)) { return hex.getLevel(); } else { return hex.floor(); @@ -4024,9 +4058,7 @@ public int elevationOccupied(Hex hex, int elevation) { * location need not be deadly to the unit. * * @param boardLocation The location to test - * * @return True when the location is illegal to be in for this unit, regardless of elevation - * * @see #isLocationDeadly(Coords) */ public final boolean isLocationProhibited(BoardLocation boardLocation) { @@ -4040,12 +4072,11 @@ public final boolean isLocationProhibited(BoardLocation boardLocation) { * * @param boardLocation The location to test * @param testElevation The elevation or altitude to test - * * @return True when the location is illegal to be in for this unit, regardless of elevation - * * @see #isLocationDeadly(Coords) */ - public final boolean isLocationProhibited(BoardLocation boardLocation, int testElevation) { + public final boolean isLocationProhibited(BoardLocation boardLocation, + int testElevation) { return isLocationProhibited(boardLocation.coords(), boardLocation.boardId(), testElevation); } @@ -4057,12 +4088,12 @@ public final boolean isLocationProhibited(BoardLocation boardLocation, int testE * @param testPosition The position to test * @param testBoardId The board to test * @param testElevation The elevation or altitude to test - * * @return True when the location is illegal to be in for this unit, regardless of elevation - * * @see #isLocationDeadly(Coords) */ - public boolean isLocationProhibited(Coords testPosition, int testBoardId, int testElevation) { + public boolean isLocationProhibited(Coords testPosition, + int testBoardId, + int testElevation) { if (!game.hasBoardLocation(testPosition, testBoardId)) { return true; } @@ -4104,21 +4135,20 @@ public final boolean isLocationProhibited(Coords c) { /** * @param c {@link Coords} Coordinates * @param currElevation Elevation level - * * @return true if the specified hex contains some sort of prohibited terrain if the Entity is at the specified - * elevation. Elevation generally only matters for units like WiGEs or VTOLs. - *

- * legacy - use the board location/board ID methods instead + * elevation. Elevation generally only matters for units like WiGEs or VTOLs. + *

+ * legacy - use the board location/board ID methods instead */ - public final boolean isLocationProhibited(Coords c, int currElevation) { + public final boolean isLocationProhibited(Coords c, + int currElevation) { return isLocationProhibited(c, boardId, currElevation); } /** * @return True if the given board is prohibited to this unit according to the type of unit and type of board or if - * the unit cannot survive on this board; e.g., JumpShips are prohibited from entering ground maps while BA - * are not allowed on an atmospheric map. This refers to the various doomed... methods. - * + * the unit cannot survive on this board; e.g., JumpShips are prohibited from entering ground maps while BA + * are not allowed on an atmospheric map. This refers to the various doomed... methods. * @see #doomedOnGround() * @see #doomedInAtmosphere() * @see #doomedInSpace() @@ -4129,17 +4159,16 @@ public boolean isBoardProhibited(Board board) { /** * @return True if the given board type is prohibited to this unit according to the type of unit or if the unit - * cannot survive on this board; e.g., JumpShips are prohibited from entering ground maps while BA are not - * allowed on an atmospheric map. This refers to the various doomed... methods. - * + * cannot survive on this board; e.g., JumpShips are prohibited from entering ground maps while BA are not + * allowed on an atmospheric map. This refers to the various doomed... methods. * @see #doomedOnGround() * @see #doomedInAtmosphere() * @see #doomedInSpace() */ public boolean isBoardProhibited(BoardType boardType) { return (boardType.isGround() && doomedOnGround()) || - (boardType.isLowAltitude() && doomedInAtmosphere()) || - (boardType.isSpace() && doomedInSpace()); + (boardType.isLowAltitude() && doomedInAtmosphere()) || + (boardType.isSpace() && doomedInSpace()); } // legacy use board id version @@ -4152,7 +4181,8 @@ public boolean isLocationDeadly(Coords c) { * Returns true if the specified hex exists and has terrain that is deadly to this unit. Note: Currently this is * only overridden for meks and is missing elevation information which makes it incomplete. */ - public boolean isLocationDeadly(Coords c, int boardId) { + public boolean isLocationDeadly(Coords c, + int boardId) { return false; } @@ -4199,8 +4229,8 @@ public String getLocationName(int loc) { */ public String getLocationAbbr(HitData hit) { return getLocationAbbr(hit.getLocation()) + - (hit.isRear() && hasRearArmor(hit.getLocation()) ? "R" : "") + - (((hit.getEffect() & HitData.EFFECT_CRITICAL) == HitData.EFFECT_CRITICAL) ? " (critical)" : ""); + (hit.isRear() && hasRearArmor(hit.getLocation()) ? "R" : "") + + (((hit.getEffect() & HitData.EFFECT_CRITICAL) == HitData.EFFECT_CRITICAL) ? " (critical)" : ""); } /** @@ -4237,10 +4267,10 @@ public int getLocationFromAbbr(String abbr) { * * @param locations A list of location indices * @param limit The maximum number of locations to show in full - * * @return A string formatted for display that shows the locations */ - public String joinLocationAbbr(List locations, int limit) { + public String joinLocationAbbr(List locations, + int limit) { if (locations.size() > limit) { return "*"; } else { @@ -4251,12 +4281,17 @@ public String joinLocationAbbr(List locations, int limit) { /** * Rolls the to-hit number */ - public abstract HitData rollHitLocation(int table, int side, int aimedLocation, AimingMode aimingMode, int cover); + public abstract HitData rollHitLocation(int table, + int side, + int aimedLocation, + AimingMode aimingMode, + int cover); /** * Rolls up a hit location */ - public abstract HitData rollHitLocation(int table, int side); + public abstract HitData rollHitLocation(int table, + int side); /** * Gets the location that excess damage transfers to. That is, one location inwards. @@ -4302,14 +4337,16 @@ public int getArmor(HitData hit) { /** * Returns the amount of armor in the location specified, or IArmorState.ARMOR_NA, or IArmorState.ARMOR_DESTROYED. */ - public int getArmor(int loc, boolean rear) { + public int getArmor(int loc, + boolean rear) { if (loc >= armor.length) { return IArmorState.ARMOR_NA; } return getArmorForReal(loc, rear); } - public int getArmorForReal(int loc, boolean rear) { + public int getArmorForReal(int loc, + boolean rear) { return armor[loc]; } @@ -4337,21 +4374,24 @@ public int getOArmor(HitData hit) { * @param loc the location to check. * @param rear if true inspect the rear armor, else check the front. */ - public int getOArmor(int loc, boolean rear) { + public int getOArmor(int loc, + boolean rear) { return orig_armor[loc]; } /** * Sets the amount of armor in the location specified. */ - public void setArmor(int val, HitData hit) { + public void setArmor(int val, + HitData hit) { setArmor(val, hit.getLocation(), hit.isRear()); } /** * Sets the amount of armor in the front location specified. */ - public void setArmor(int val, int loc) { + public void setArmor(int val, + int loc) { setArmor(val, loc, false); } @@ -4362,7 +4402,9 @@ public void setArmor(int val, int loc) { * @param loc the location of the armor * @param rear true iff the armor is rear mounted. */ - public void setArmor(int val, int loc, boolean rear) { + public void setArmor(int val, + int loc, + boolean rear) { armor[loc] = val; } @@ -4388,7 +4430,8 @@ public int firstArmorIndex() { /** * Initializes the armor on the unit. Sets the original and starting point of the armor to the same number. */ - public void initializeArmor(int val, int loc) { + public void initializeArmor(int val, + int loc) { orig_armor[loc] = val; setArmor(val, loc); } @@ -4473,14 +4516,16 @@ public int getOInternal(int loc) { /** * Sets the amount of armor in the location specified. */ - public void setInternal(int val, HitData hit) { + public void setInternal(int val, + HitData hit) { setInternal(val, hit.getLocation()); } /** * Sets the amount of armor in the location specified. */ - public void setInternal(int val, int loc) { + public void setInternal(int val, + int loc) { internal[loc] = val; } @@ -4488,7 +4533,8 @@ public void setInternal(int val, int loc) { * Initializes the internal structure on the unit. Sets the original and starting point of the internal structure to * the same number. */ - public void initializeInternal(int val, int loc) { + public void initializeInternal(int val, + int loc) { orig_internal[loc] = val; setInternal(val, loc); } @@ -4565,7 +4611,8 @@ public int getLocationStatus(int loc) { * @param loc the location whose exposure is to be set * @param status the status to set */ - public void setLocationStatus(int loc, int status) { + public void setLocationStatus(int loc, + int status) { setLocationStatus(loc, status, false); } @@ -4576,7 +4623,9 @@ public void setLocationStatus(int loc, int status) { * @param status the status to set * @param allowChange allow change of breached locations */ - public void setLocationStatus(int loc, int status, boolean allowChange) { + public void setLocationStatus(int loc, + int status, + boolean allowChange) { if (allowChange || (exposure[loc] > ILocationExposureStatus.BREACHED)) { // can't change BREACHED status exposure[loc] = status; } @@ -4584,7 +4633,6 @@ public void setLocationStatus(int loc, int status, boolean allowChange) { /** * @param loc the location to check. - * * @return True if the given location is a leg location; this can only be true on Meks. */ public boolean locationIsLeg(int loc) { @@ -4601,7 +4649,8 @@ public String getArmorString(int loc) { /** * Returns a string representing the armor in the location */ - public String getArmorString(int loc, boolean rear) { + public String getArmorString(int loc, + boolean rear) { return Entity.armorStringFor(getArmor(loc, rear)); } @@ -4662,64 +4711,88 @@ public int getHeatFiringModifier() { /** * Creates a new mount for this equipment and adds it in. */ - public Mounted addEquipment(EquipmentType equipmentType, int loc) throws LocationFullException { + public Mounted addEquipment(EquipmentType equipmentType, + int loc) throws LocationFullException { return addEquipment(equipmentType, loc, false); } /** * Creates a new mount for this equipment and adds it in. */ - public Mounted addEquipment(EquipmentType equipmentType, int loc, boolean rearMounted) - throws LocationFullException { + public Mounted addEquipment(EquipmentType equipmentType, + int loc, + boolean rearMounted) + throws LocationFullException { return addEquipment(equipmentType, - loc, - rearMounted, - BattleArmor.MOUNT_LOC_NONE, - false, - false, - false, - false, - false); + loc, + rearMounted, + BattleArmor.MOUNT_LOC_NONE, + false, + false, + false, + false, + false); } /** * Creates a new mount for this equipment and adds it in. */ - public Mounted addEquipment(EquipmentType equipmentType, int loc, boolean rearMounted, int baMountLoc, - boolean isArmored, boolean isTurreted) throws LocationFullException { + public Mounted addEquipment(EquipmentType equipmentType, + int loc, + boolean rearMounted, + int baMountLoc, + boolean isArmored, + boolean isTurreted) throws LocationFullException { return addEquipment(equipmentType, loc, rearMounted, baMountLoc, isArmored, isTurreted, false, false, false); } - public Mounted addEquipment(EquipmentType equipmentType, int loc, boolean rearMounted, int baMountLoc, - boolean isArmored, boolean isTurreted, boolean isSponsonTurreted) throws LocationFullException { + public Mounted addEquipment(EquipmentType equipmentType, + int loc, + boolean rearMounted, + int baMountLoc, + boolean isArmored, + boolean isTurreted, + boolean isSponsonTurreted) throws LocationFullException { return addEquipment(equipmentType, - loc, - rearMounted, - baMountLoc, - isArmored, - isTurreted, - isSponsonTurreted, - false, - false); - } - - public Mounted addEquipment(EquipmentType equipmentType, int loc, boolean rearMounted, int baMountLoc, - boolean isArmored, boolean isTurreted, boolean isSponsonTurreted, boolean isPintleTurreted) - throws LocationFullException { + loc, + rearMounted, + baMountLoc, + isArmored, + isTurreted, + isSponsonTurreted, + false, + false); + } + + public Mounted addEquipment(EquipmentType equipmentType, + int loc, + boolean rearMounted, + int baMountLoc, + boolean isArmored, + boolean isTurreted, + boolean isSponsonTurreted, + boolean isPintleTurreted) + throws LocationFullException { return addEquipment(equipmentType, - loc, - rearMounted, - baMountLoc, - isArmored, - isTurreted, - isSponsonTurreted, - isPintleTurreted, - false); - } - - public Mounted addEquipment(EquipmentType equipmentType, int loc, boolean rearMounted, int baMountLoc, - boolean isArmored, boolean isTurreted, boolean isSponsonTurreted, boolean isPintleTurreted, - boolean isOmniPodded) throws LocationFullException { + loc, + rearMounted, + baMountLoc, + isArmored, + isTurreted, + isSponsonTurreted, + isPintleTurreted, + false); + } + + public Mounted addEquipment(EquipmentType equipmentType, + int loc, + boolean rearMounted, + int baMountLoc, + boolean isArmored, + boolean isTurreted, + boolean isSponsonTurreted, + boolean isPintleTurreted, + boolean isOmniPodded) throws LocationFullException { Mounted mounted = Mounted.createMounted(this, equipmentType); mounted.setArmored(isArmored); mounted.setBaMountLoc(baMountLoc); @@ -4736,8 +4809,11 @@ public Mounted addEquipment(EquipmentType equipmentType, int loc, boolean rea * * @param equipmentType {@link EquipmentType} */ - public Mounted addEquipment(EquipmentType equipmentType, int loc, boolean rearMounted, int nAmmo) - throws LocationFullException { + public Mounted addEquipment(EquipmentType equipmentType, + int loc, + boolean rearMounted, + int nAmmo) + throws LocationFullException { Mounted mounted = Mounted.createMounted(this, equipmentType); addEquipment(mounted, loc, rearMounted, nAmmo); return mounted; @@ -4747,18 +4823,21 @@ public Mounted addEquipment(EquipmentType equipmentType, int loc, boolean rea /** * indicate whether this is a bomb mount */ - public Mounted addBomb(EquipmentType equipmentType, int loc) throws LocationFullException { + public Mounted addBomb(EquipmentType equipmentType, + int loc) throws LocationFullException { Mounted mounted = Mounted.createMounted(this, equipmentType); addBomb(mounted, loc); return mounted; } - protected void addBomb(Mounted mounted, int loc) throws LocationFullException { + protected void addBomb(Mounted mounted, + int loc) throws LocationFullException { mounted.setBombMounted(true); addEquipment(mounted, loc, false); } - public WeaponMounted addWeaponGroup(EquipmentType equipmentType, int loc) throws LocationFullException { + public WeaponMounted addWeaponGroup(EquipmentType equipmentType, + int loc) throws LocationFullException { WeaponMounted mounted = (WeaponMounted) Mounted.createMounted(this, equipmentType); addEquipment(mounted, loc, false, true); return mounted; @@ -4767,8 +4846,11 @@ public WeaponMounted addWeaponGroup(EquipmentType equipmentType, int loc) throws /** * indicate whether this is body mounted for BAs */ - public Mounted addEquipment(EquipmentType equipmentType, int loc, boolean rearMounted, int baMountLoc, - boolean dwpMounted) throws LocationFullException { + public Mounted addEquipment(EquipmentType equipmentType, + int loc, + boolean rearMounted, + int baMountLoc, + boolean dwpMounted) throws LocationFullException { Mounted mounted = Mounted.createMounted(this, equipmentType); mounted.setBaMountLoc(baMountLoc); mounted.setDWPMounted(dwpMounted); @@ -4776,8 +4858,11 @@ public Mounted addEquipment(EquipmentType equipmentType, int loc, boolean rea return mounted; } - protected void addEquipment(Mounted mounted, int loc, boolean rearMounted, int nAmmo) - throws LocationFullException { + protected void addEquipment(Mounted mounted, + int loc, + boolean rearMounted, + int nAmmo) + throws LocationFullException { if ((mounted instanceof AmmoMounted) && (nAmmo > 1)) { mounted.setByShot(true); mounted.setShotsLeft(nAmmo); @@ -4789,8 +4874,11 @@ protected void addEquipment(Mounted mounted, int loc, boolean rearMounted, in addEquipment(mounted, loc, rearMounted); } - protected void addEquipment(Mounted mounted, int loc, boolean rearMounted, boolean isWeaponGroup) - throws LocationFullException { + protected void addEquipment(Mounted mounted, + int loc, + boolean rearMounted, + boolean isWeaponGroup) + throws LocationFullException { if (mounted instanceof WeaponMounted) { mounted.setWeaponGroup(isWeaponGroup); } @@ -4806,11 +4894,11 @@ protected void addEquipment(Mounted mounted, int loc, boolean rearMounted, bo * * @param mounted The new equipment * @param loc The location; may be Entity.LOC_NONE - * * @throws LocationFullException When the location is full * @see #addEquipment(Mounted, int, boolean) */ - public final void addEquipment(Mounted mounted, int loc) throws LocationFullException { + public final void addEquipment(Mounted mounted, + int loc) throws LocationFullException { addEquipment(mounted, loc, false); } @@ -4824,10 +4912,11 @@ public final void addEquipment(Mounted mounted, int loc) throws LocationFullE * @param mounted The new equipment * @param loc The location; may be Entity.LOC_NONE * @param rearMounted True to make the equipment rear-facing - * * @throws LocationFullException When the location is full */ - public void addEquipment(Mounted mounted, int loc, boolean rearMounted) throws LocationFullException { + public void addEquipment(Mounted mounted, + int loc, + boolean rearMounted) throws LocationFullException { mounted.setLocation(loc, rearMounted); equipmentList.add(mounted); @@ -4853,8 +4942,8 @@ public void addEquipment(Mounted mounted, int loc, boolean rearMounted) throw // one-shot launchers need their single shot of ammo added. if ((mounted.getType().hasFlag(WeaponType.F_ONE_SHOT) || - (isSupportVehicle() && (mounted.getType() instanceof InfantryWeapon))) && - (AmmoType.getOneshotAmmo(mounted) != null)) { + (isSupportVehicle() && (mounted.getType() instanceof InfantryWeapon))) && + (AmmoType.getOneshotAmmo(mounted) != null)) { addOneShotAmmo(mounted); } } @@ -4868,8 +4957,8 @@ public void addEquipment(Mounted mounted, int loc, boolean rearMounted) throw miscList.add((MiscMounted) mounted); } if (!(mounted instanceof AmmoMounted) && - !(mounted instanceof MiscMounted) && - !(mounted instanceof WeaponMounted)) { + !(mounted instanceof MiscMounted) && + !(mounted instanceof WeaponMounted)) { LOGGER.error("Trying to add plain Mounted class {} on {}!", mounted, this); } } @@ -4909,7 +4998,7 @@ private void addOneShotAmmo(Mounted mounted) throws LocationFullException { m.setLinked(m2); addEquipment(m2, Entity.LOC_NONE, false); } else if ((mounted.getType() instanceof InfantryWeapon) && - ((InfantryWeapon) mounted.getType()).hasInfernoAmmo()) { + ((InfantryWeapon) mounted.getType()).hasInfernoAmmo()) { Mounted m2 = Mounted.createMounted(this, EquipmentType.get(EquipmentTypeLookup.INFANTRY_INFERNO_AMMO)); m2.setOmniPodMounted(mounted.isOmniPodMounted()); m2.setShotsLeft(0); @@ -4999,7 +5088,6 @@ public int getTotalAmmoOfType(EquipmentType et) { * Determine how much ammunition (of all munition types) remains which is compatible with the given weapon. * * @param weapon The weapon being considered - * * @return the int count of the amount of shots of all munitions available for the given weapon. */ public int getTotalMunitionsOfType(WeaponMounted weapon) { @@ -5021,7 +5109,6 @@ public int getTotalMunitionsOfType(WeaponMounted weapon) { * Returns the Rules.ARC that the weapon, specified by number, fires into. * * @param weaponNumber integer equipment number, index from equipment list - * * @return arc the specified weapon is in */ public abstract int getWeaponArc(int weaponNumber); @@ -5091,33 +5178,32 @@ public List getWeaponGroupList() { * Returns true if the given weapon is valid for the current phase. * * @param mounted The WeaponMounted to test - * * @return True if valid, else false */ public boolean isWeaponValidForPhase(@Nullable WeaponMounted mounted) { // Start reached, now we can attempt to pick a weapon. if ((mounted != null) && - (mounted.isReady()) && - (!(mounted.getType().hasFlag(WeaponType.F_AMS) && mounted.curMode().equals(Weapon.MODE_AMS_ON))) && - (!(mounted.getType().hasFlag(WeaponType.F_AMS) && mounted.curMode().equals(Weapon.MODE_AMS_OFF))) && - (!mounted.getType().hasFlag(WeaponType.F_AMS_BAY)) && - (!(mounted.hasModes() && mounted.curMode().equals("Point Defense"))) && - ((mounted.getLinked() == null) || - ((mounted.getLinked().getType() instanceof MiscType) && - mounted.getLinked().getType().hasFlag(MiscType.F_AP_MOUNT)) || - (mounted.getLinked().getUsableShotsLeft() > 0))) { + (mounted.isReady()) && + (!(mounted.getType().hasFlag(WeaponType.F_AMS) && mounted.curMode().equals(Weapon.MODE_AMS_ON))) && + (!(mounted.getType().hasFlag(WeaponType.F_AMS) && mounted.curMode().equals(Weapon.MODE_AMS_OFF))) && + (!mounted.getType().hasFlag(WeaponType.F_AMS_BAY)) && + (!(mounted.hasModes() && mounted.curMode().equals("Point Defense"))) && + ((mounted.getLinked() == null) || + ((mounted.getLinked().getType() instanceof MiscType) && + mounted.getLinked().getType().hasFlag(MiscType.F_AP_MOUNT)) || + (mounted.getLinked().getUsableShotsLeft() > 0))) { // TAG only in the correct phase... if ((mounted.getType().hasFlag(WeaponType.F_TAG) && !getGame().getPhase().isOffboard()) || - (!mounted.getType().hasFlag(WeaponType.F_TAG) && getGame().getPhase().isOffboard())) { + (!mounted.getType().hasFlag(WeaponType.F_TAG) && getGame().getPhase().isOffboard())) { return false; } // Artillery or Bearings-only missiles only in the targeting phase... if (getGame().getPhase().isTargeting() && - !(mounted.getType().hasFlag(WeaponType.F_ARTILLERY) || - mounted.isInBearingsOnlyMode() || - ((getAltitude() == 0) && (mounted.getType() instanceof CapitalMissileWeapon)))) { + !(mounted.getType().hasFlag(WeaponType.F_ARTILLERY) || + mounted.isInBearingsOnlyMode() || + ((getAltitude() == 0) && (mounted.getType() instanceof CapitalMissileWeapon)))) { return false; } // No Bearings-only missiles in the firing phase @@ -5183,20 +5269,21 @@ public void loadWeaponWithSameAmmo(WeaponMounted mounted) { /** * Tries to load the specified weapon with the specified ammo. Returns true if successful, false otherwise. */ - public boolean loadWeapon(WeaponMounted mounted, AmmoMounted mountedAmmo) { + public boolean loadWeapon(WeaponMounted mounted, + AmmoMounted mountedAmmo) { boolean success = false; WeaponType weaponType = mounted.getType(); AmmoType ammoType = mountedAmmo.getType(); if (mountedAmmo.isAmmoUsable() && - !weaponType.hasFlag(WeaponType.F_ONE_SHOT) && - (ammoType.getAmmoType() == weaponType.getAmmoType()) && - (ammoType.getRackSize() == weaponType.getRackSize())) { + !weaponType.hasFlag(WeaponType.F_ONE_SHOT) && + (ammoType.getAmmoType() == weaponType.getAmmoType()) && + (ammoType.getRackSize() == weaponType.getRackSize())) { mounted.setLinked(mountedAmmo); success = true; } else if ((weaponType.hasFlag(WeaponType.F_DOUBLE_ONE_SHOT) || - (weaponType.getAmmoType() == AmmoType.AmmoTypeEnum.INFANTRY)) && - (mountedAmmo.getLocation() == Entity.LOC_NONE)) { + (weaponType.getAmmoType() == AmmoType.AmmoTypeEnum.INFANTRY)) && + (mountedAmmo.getLocation() == Entity.LOC_NONE)) { // Make sure this ammo is in the chain, then move it to the head. for (Mounted current = mounted; current != null; current = current.getLinked()) { if (current == mountedAmmo) { @@ -5215,7 +5302,8 @@ public boolean loadWeapon(WeaponMounted mounted, AmmoMounted mountedAmmo) { /** * Tries to load the specified weapon with the specified ammo. Returns true if successful, false otherwise. */ - public boolean loadWeaponWithSameAmmo(WeaponMounted mounted, AmmoMounted mountedAmmo) { + public boolean loadWeaponWithSameAmmo(WeaponMounted mounted, + AmmoMounted mountedAmmo) { AmmoType ammoType = mountedAmmo.getType(); AmmoMounted oldMountedAmmo = mounted.getLinkedAmmo(); @@ -5263,7 +5351,6 @@ public List getAmmo() { /** * @param weapon we want to find available ammo for - * * @return an ArrayList containing _one_ Mounted ammo for each viable type */ public List getAmmo(WeaponMounted weapon) { @@ -5294,17 +5381,15 @@ public List getBombs() { * Returns a list of operable bombs with shots left and with the given flag (e.g. AmmoType.F_SPACE_BOMB) * * @param flag The AmmoType bomb flag to search for - * * @return The list of found bombs - * * @see Mounted#isOperable() */ public List getBombs(EquipmentFlag flag) { return getBombs().stream() - .filter(b -> b.getType().hasFlag(flag)) - .filter(Mounted::isOperable) - .filter(Mounted::hasUsableShotsLeft) - .toList(); + .filter(b -> b.getType().hasFlag(flag)) + .filter(Mounted::isOperable) + .filter(Mounted::hasUsableShotsLeft) + .toList(); } /** @@ -5339,10 +5424,10 @@ protected void resetBombAttacks() { for (BombMounted m : getBombs()) { // Add the space bomb attack if (!foundSpaceBomb && - gameOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_STRATOPS_SPACE_BOMB) && - m.getType().hasFlag(AmmoType.F_SPACE_BOMB) && - isBomber() && - isSpaceborne()) { + gameOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_STRATOPS_SPACE_BOMB) && + m.getType().hasFlag(AmmoType.F_SPACE_BOMB) && + isBomber() && + isSpaceborne()) { try { WeaponMounted bomb = (WeaponMounted) addEquipment(spaceBomb, m.getLocation(), false); if (hasETypeFlag(ETYPE_FIGHTER_SQUADRON)) { @@ -5356,8 +5441,8 @@ protected void resetBombAttacks() { } if (!isSpaceborne() && - m.getType().hasFlag(AmmoType.F_GROUND_BOMB) && - !((this instanceof LandAirMek) && (getConversionMode() == LandAirMek.CONV_MODE_MEK))) { + m.getType().hasFlag(AmmoType.F_GROUND_BOMB) && + !((this instanceof LandAirMek) && (getConversionMode() == LandAirMek.CONV_MODE_MEK))) { if (addedBombAttacks < 1) { try { WeaponMounted bomb = (WeaponMounted) addEquipment(diveBomb, m.getLocation(), false); @@ -5405,38 +5490,38 @@ public void removeMisc(String toRemove) { public void clearBombs() { bombList.clear(); equipmentList.removeIf(m -> (m.getType() instanceof BombType) || - (m.getType() instanceof DiveBombAttack) || - (m.getType() instanceof SpaceBombAttack) || - (m.getType() instanceof AltitudeBombAttack) || - (m.getType() instanceof ISAAAMissileWeapon) || - (m.getType() instanceof CLAAAMissileWeapon) || - (m.getType() instanceof ISASMissileWeapon) || - (m.getType() instanceof ISASEWMissileWeapon) || - (m.getType() instanceof CLASMissileWeapon) || - (m.getType() instanceof CLASEWMissileWeapon) || - (m.getType() instanceof ISLAAMissileWeapon) || - (m.getType() instanceof CLLAAMissileWeapon) || - (m.getType() instanceof BombArrowIV) - /* || m.getType() instanceof CLBombArrowIV */ || - (m.getType() instanceof ISBombTAG) || - (m.getType() instanceof BombISRL10) || - (m.getType() instanceof AlamoMissileWeapon)); + (m.getType() instanceof DiveBombAttack) || + (m.getType() instanceof SpaceBombAttack) || + (m.getType() instanceof AltitudeBombAttack) || + (m.getType() instanceof ISAAAMissileWeapon) || + (m.getType() instanceof CLAAAMissileWeapon) || + (m.getType() instanceof ISASMissileWeapon) || + (m.getType() instanceof ISASEWMissileWeapon) || + (m.getType() instanceof CLASMissileWeapon) || + (m.getType() instanceof CLASEWMissileWeapon) || + (m.getType() instanceof ISLAAMissileWeapon) || + (m.getType() instanceof CLLAAMissileWeapon) || + (m.getType() instanceof BombArrowIV) + /* || m.getType() instanceof CLBombArrowIV */ || + (m.getType() instanceof ISBombTAG) || + (m.getType() instanceof BombISRL10) || + (m.getType() instanceof AlamoMissileWeapon)); weaponList.removeIf(m -> (m.getType() instanceof DiveBombAttack) || - (m.getType() instanceof SpaceBombAttack) || - (m.getType() instanceof AltitudeBombAttack) || - (m.getType() instanceof ISAAAMissileWeapon) || - (m.getType() instanceof CLAAAMissileWeapon) || - (m.getType() instanceof ISASMissileWeapon) || - (m.getType() instanceof ISASEWMissileWeapon) || - (m.getType() instanceof CLASMissileWeapon) || - (m.getType() instanceof CLASEWMissileWeapon) || - (m.getType() instanceof ISLAAMissileWeapon) || - (m.getType() instanceof CLLAAMissileWeapon) || - (m.getType() instanceof BombArrowIV) - /* || m.getType() instanceof CLBombArrowIV */ || - (m.getType() instanceof ISBombTAG) || - (m.getType() instanceof BombISRL10) || - (m.getType() instanceof AlamoMissileWeapon)); + (m.getType() instanceof SpaceBombAttack) || + (m.getType() instanceof AltitudeBombAttack) || + (m.getType() instanceof ISAAAMissileWeapon) || + (m.getType() instanceof CLAAAMissileWeapon) || + (m.getType() instanceof ISASMissileWeapon) || + (m.getType() instanceof ISASEWMissileWeapon) || + (m.getType() instanceof CLASMissileWeapon) || + (m.getType() instanceof CLASEWMissileWeapon) || + (m.getType() instanceof ISLAAMissileWeapon) || + (m.getType() instanceof CLLAAMissileWeapon) || + (m.getType() instanceof BombArrowIV) + /* || m.getType() instanceof CLBombArrowIV */ || + (m.getType() instanceof ISBombTAG) || + (m.getType() instanceof BombISRL10) || + (m.getType() instanceof AlamoMissileWeapon)); ammoList.removeIf(m -> m.getType() instanceof BombType); } @@ -5454,7 +5539,6 @@ public List getClubs() { * Check if the entity has an arbitrary type of misc equipment * * @param flag A MiscType.F_XXX - * * @return true if at least one ready item. */ public boolean hasWorkingMisc(EquipmentFlag flag) { @@ -5466,15 +5550,15 @@ public boolean hasWorkingMisc(EquipmentFlag flag) { * * @param flag A MiscType.F_XXX * @param secondaryFlag A MiscType.S_XXX or null for don't care - * * @return true if at least one ready item. */ - public boolean hasWorkingMisc(EquipmentFlag flag, MiscTypeFlag secondaryFlag) { + public boolean hasWorkingMisc(EquipmentFlag flag, + MiscTypeFlag secondaryFlag) { for (MiscMounted miscMounted : miscList) { if (miscMounted.isReady() - && miscMounted.getType().hasFlag(flag) - && ((secondaryFlag == null) - || miscMounted.getType().hasFlag(secondaryFlag))) { + && miscMounted.getType().hasFlag(flag) + && ((secondaryFlag == null) + || miscMounted.getType().hasFlag(secondaryFlag))) { return true; } } @@ -5486,9 +5570,7 @@ public boolean hasWorkingMisc(EquipmentFlag flag, MiscTypeFlag secondaryFlag) { * that both the flags given in MiscType and the "actual" flags in MiscTypeFlag can be used. * * @param flag The MiscTypeFlag flag to look for, e.g. F_VTOL_EQUIPMENT - * * @return True when the entity has a MiscType equipment with the given flag - * * @see EquipmentType#hasFlag(EquipmentFlag) * @see MiscType * @see MiscTypeFlag @@ -5504,9 +5586,7 @@ public boolean hasMisc(EquipmentFlag flag) { * searches misc equipment and will not find weapons. * * @param internalName The internal name of the misc, e.g. EquipmentTypeLookup.BA_MYOMER_BOOSTER - * * @return True when the entity has a MiscType equipment of the given internalName - * * @see MiscType * @see EquipmentTypeLookup */ @@ -5520,9 +5600,7 @@ public boolean hasMisc(String internalName) { * name, even of weapons, can be given but this method only searches misc equipment and will not find weapons. * * @param internalName The internal name of the misc, e.g. EquipmentTypeLookup.BA_MYOMER_BOOSTER - * * @return the number of MiscType equipment of the given internalName on the unit - * * @see MiscType * @see EquipmentTypeLookup */ @@ -5540,13 +5618,12 @@ public int countMisc(String internalName) { * * @param internalName The internal name of the misc, e.g. EquipmentTypeLookup.BA_MYOMER_BOOSTER * @param location The location, e.g. Mek.LOC_LEFT_TORSO - * * @return True when the entity has a MiscType equipment of the given internalName in the given location - * * @see MiscType * @see EquipmentTypeLookup */ - public boolean hasMisc(String internalName, int location) { + public boolean hasMisc(String internalName, + int location) { return miscList.stream().filter(misc -> misc.getLocation() == location).anyMatch(misc -> misc.is(internalName)); } @@ -5559,7 +5636,6 @@ public List getMiscEquipment(EquipmentFlag flag) { * working condition. Ideally use {@link EquipmentTypeLookup} for the internal name. * * @param internalName The EquipmentType#internalName of the equipment - * * @return The equipment count on this unit */ public long countEquipment(String internalName) { @@ -5573,7 +5649,8 @@ public int countWorkingMisc(EquipmentFlag flag) { return countWorkingMisc(flag, -1); } - public int countWorkingMisc(EquipmentFlag flag, int location) { + public int countWorkingMisc(EquipmentFlag flag, + int location) { int count = 0; OUTER: for (MiscMounted m : getMisc()) { @@ -5591,13 +5668,14 @@ public int countWorkingMisc(EquipmentFlag flag, int location) { return count; } - public int countWorkingMisc(String internalName, int location) { + public int countWorkingMisc(String internalName, + int location) { int count = 0; OUTER: for (MiscMounted m : getMisc()) { if (!m.isInoperable() && - m.getType().getInternalName().equalsIgnoreCase(internalName) && - ((location == -1) || (m.getLocation() == location))) { + m.getType().getInternalName().equalsIgnoreCase(internalName) && + ((location == -1) || (m.getLocation() == location))) { if (m.hasModes()) { for (Enumeration e = m.getType().getModes(); e.hasMoreElements(); ) { if (e.nextElement().equals("On") && !m.curMode().equals("On")) { @@ -5615,7 +5693,6 @@ public int countWorkingMisc(String internalName, int location) { * Check if the entity has an arbitrary type of misc equipment * * @param name MiscType internal name - * * @return true if at least one ready item. */ public boolean hasWorkingMisc(String name) { @@ -5633,10 +5710,11 @@ public boolean hasWorkingMisc(String name) { * @param flag A MiscType.F_XXX * @param secondaryFlag A MiscType.S_XXX or null for don't care * @param location The location to check e.g. Mek.LOC_LEFT_ARM - * * @return true if at least one ready item. */ - public boolean hasWorkingMisc(EquipmentFlag flag, MiscTypeFlag secondaryFlag, int location) { + public boolean hasWorkingMisc(EquipmentFlag flag, + MiscTypeFlag secondaryFlag, + int location) { // go through the location slot by slot, because of misc equipment that // is spreadable for (int slot = 0; slot < getNumberOfCriticalSlots(location); slot++) { @@ -5726,7 +5804,6 @@ public boolean hasWorkingBackhoe() { * probably an error in the internal representation of the unit.) * * @param location The location, e.g. Mek.LOC_HEAD - * * @return A list of CriticalSlots in that location, possibly empty */ public List getCriticalSlots(int location) { @@ -5742,11 +5819,11 @@ public List getCriticalSlots(int location) { */ public boolean hasUndamagedCriticalSlots() { return IntStream.range(0, locations()) - .mapToLong(i -> getCriticalSlots(i).stream() - .filter(Objects::nonNull) - .filter(CriticalSlot::isHittable) - .count()) - .sum() > 0; + .mapToLong(i -> getCriticalSlots(i).stream() + .filter(Objects::nonNull) + .filter(CriticalSlot::isHittable) + .count()) + .sum() > 0; } /** @@ -5773,9 +5850,9 @@ protected int scmCritStateCount(Predicate slotState) { for (int index = 0; index < crits[location].length; index++) { final CriticalSlot slot = crits[location][index]; if ((slot != null) && - (slot.getType() == CriticalSlot.TYPE_EQUIPMENT) && - slot.getMount().is(EquipmentTypeLookup.SCM) && - slotState.test(slot)) { + (slot.getType() == CriticalSlot.TYPE_EQUIPMENT) && + slot.getMount().is(EquipmentTypeLookup.SCM) && + slotState.test(slot)) { stateAppliesCount++; } } @@ -5845,7 +5922,7 @@ public String formatHeat() { /** * @return The amount of heat that the entity can sink each turn, factoring in whether the entity is standing in - * water. + * water. */ public int getHeatCapacityWithWater() { return getHeatCapacity(); @@ -5867,14 +5944,15 @@ public int getEngineCritHeat() { * cooling or reductions) * @param reason a short human-readable source label, e.g. "Movement (Running)" or "Medium Laser" */ - public void changeHeatBuildup(int heat, String reason) { + public void changeHeatBuildup(int heat, + String reason) { heatBuildup += heat; getHeatBreakdown().addBuildup(heat, reason); } /** * @return this unit's itemized heat buildup/dissipation record for the current turn (never null). Used to build the - * Heat Phase report's "gains N heat" / "sinks N heat" breakdown tooltips. + * Heat Phase report's "gains N heat" / "sinks N heat" breakdown tooltips. */ public HeatBreakdown getHeatBreakdown() { if (heatBreakdown == null) { @@ -5896,14 +5974,17 @@ public void clearHeatBreakdown() { /** * Returns a critical hit slot */ - public @Nullable CriticalSlot getCritical(int loc, int slot) { + public @Nullable CriticalSlot getCritical(int loc, + int slot) { return ((loc < crits.length) && (slot < crits[loc].length)) ? crits[loc][slot] : null; } /** * Sets a critical hit slot */ - public void setCritical(int loc, int slot, CriticalSlot cs) { + public void setCritical(int loc, + int slot, + CriticalSlot cs) { crits[loc][slot] = cs; } @@ -5913,7 +5994,8 @@ public void setCritical(int loc, int slot, CriticalSlot cs) { * * @return true if there was room for the critical */ - public boolean addCritical(int loc, CriticalSlot cs) { + public boolean addCritical(int loc, + CriticalSlot cs) { for (int i = 0; i < getNumberOfCriticalSlots(loc); i++) { if (getCritical(loc, i) == null) { crits[loc][i] = cs; @@ -5930,10 +6012,11 @@ public boolean addCritical(int loc, CriticalSlot cs) { * @param loc location on Unit. * @param cs {@link CriticalSlot} to check * @param slotNumber Slot to apply to. - * * @return true if there was room for the critical */ - public boolean addCritical(int loc, CriticalSlot cs, int slotNumber) { + public boolean addCritical(int loc, + CriticalSlot cs, + int slotNumber) { for (int i = 0; i < getNumberOfCriticalSlots(loc); i++) { if (getCritical(loc, slotNumber) == null) { crits[loc][slotNumber] = cs; @@ -5951,7 +6034,9 @@ public boolean addCritical(int loc, CriticalSlot cs, int slotNumber) { * * @return true if the crit was successfully added to any slot */ - public boolean addCritical(int loc, int slot, CriticalSlot cs) { + public boolean addCritical(int loc, + int slot, + CriticalSlot cs) { if (getCritical(loc, slot) == null) { setCritical(loc, slot, cs); return true; @@ -5962,7 +6047,8 @@ public boolean addCritical(int loc, int slot, CriticalSlot cs) { /** * Removes all matching critical slots from the location */ - public void removeCriticalSlots(int loc, CriticalSlot cs) { + public void removeCriticalSlots(int loc, + CriticalSlot cs) { for (int i = 0; i < getNumberOfCriticalSlots(loc); i++) { if ((getCritical(loc, i) != null) && getCritical(loc, i).equals(cs)) { setCritical(loc, i, null); @@ -5998,11 +6084,11 @@ public int getHittableCriticalSlots(int loc) { // Reactive armor criticalSlots in a location with armor should count // as hittable, even though they aren't actually hittable else if ((crit != null) && - (crit.getType() == CriticalSlot.TYPE_EQUIPMENT) && - (crit.getMount() != null) && - (crit.getMount().getType() instanceof MiscType miscType) && - miscType.hasFlag(MiscType.F_REACTIVE) && - (getArmor(loc) > 0)) { + (crit.getType() == CriticalSlot.TYPE_EQUIPMENT) && + (crit.getMount() != null) && + (crit.getMount().getType() instanceof MiscType miscType) && + miscType.hasFlag(MiscType.F_REACTIVE) && + (getArmor(loc) > 0)) { hittable++; } } @@ -6059,7 +6145,9 @@ public boolean isGyroDestroyed() { /** * Returns the number of operational critical slots of the specified type in the location */ - public int getGoodCriticalSlots(int type, int index, int loc) { + public int getGoodCriticalSlots(int type, + int index, + int loc) { return critStateCount(type, index, loc, cs -> !cs.isDestroyed() && !cs.isBreached()); } @@ -6067,31 +6155,40 @@ public int getGoodCriticalSlots(int type, int index, int loc) { * The number of critical slots that are destroyed or breached in the location or missing along with it (if it was * blown off). */ - public int getBadCriticalSlots(int type, int index, int loc) { + public int getBadCriticalSlots(int type, + int index, + int loc) { return critStateCount(type, index, loc, cs -> cs.isDestroyed() || cs.isBreached() || cs.isMissing()); } /** * Number of slots damaged (but not breached) in a location */ - public int getDamagedCriticalSlots(int type, int index, int loc) { + public int getDamagedCriticalSlots(int type, + int index, + int loc) { return critStateCount(type, index, loc, CriticalSlot::isDamaged); } /** * Number of slots doomed, missing or destroyed in a location */ - public int getHitCriticalSlots(int type, int index, int loc) { + public int getHitCriticalSlots(int type, + int index, + int loc) { return critStateCount(type, index, loc, cs -> cs.isDamaged() || cs.isBreached() || cs.isMissing()); } /** * @return the number of critical slots of the equipment given as index for {@link #getEquipment(int)} in location - * loc wherein the type is the critical slot type that fit the slot state given as slotState Predicate such as - * {@link CriticalSlot#isDestroyed()}. The critical slots tested are only those in location loc except for - * Super-Cooled Myomer where all locations are considered. - */ - protected int critStateCount(int type, int index, int loc, Predicate slotState) { + * loc wherein the type is the critical slot type that fit the slot state given as slotState Predicate such as + * {@link CriticalSlot#isDestroyed()}. The critical slots tested are only those in location loc except for + * Super-Cooled Myomer where all locations are considered. + */ + protected int critStateCount(int type, + int index, + int loc, + Predicate slotState) { int stateAppliesCount = 0; Mounted m = null; if (type == CriticalSlot.TYPE_EQUIPMENT) { @@ -6116,7 +6213,7 @@ protected int critStateCount(int type, int index, int loc, Predicate 0) || - (getBadCriticalSlots(CriticalSlot.TYPE_SYSTEM, Mek.ACTUATOR_UPPER_LEG, i) > 0) || - (getBadCriticalSlots(CriticalSlot.TYPE_SYSTEM, Mek.ACTUATOR_LOWER_LEG, i) > 0) || - (getBadCriticalSlots(CriticalSlot.TYPE_SYSTEM, Mek.ACTUATOR_FOOT, i) > 0)) { + (getBadCriticalSlots(CriticalSlot.TYPE_SYSTEM, Mek.ACTUATOR_UPPER_LEG, i) > 0) || + (getBadCriticalSlots(CriticalSlot.TYPE_SYSTEM, Mek.ACTUATOR_LOWER_LEG, i) > 0) || + (getBadCriticalSlots(CriticalSlot.TYPE_SYSTEM, Mek.ACTUATOR_FOOT, i) > 0)) { hasCritical = true; break; } @@ -6231,14 +6329,15 @@ public boolean hasLegActuatorCrit() { /** * Returns true if there is at least 1 functional system of the type specified in the location */ - public boolean hasWorkingSystem(int system, int loc) { + public boolean hasWorkingSystem(int system, + int loc) { for (int i = 0; i < getNumberOfCriticalSlots(loc); i++) { CriticalSlot ccs = getCritical(loc, i); if ((ccs != null) && - (ccs.getType() == CriticalSlot.TYPE_SYSTEM) && - (ccs.getIndex() == system) && - !ccs.isDamaged() && - !ccs.isBreached()) { + (ccs.getType() == CriticalSlot.TYPE_SYSTEM) && + (ccs.getIndex() == system) && + !ccs.isDamaged() && + !ccs.isBreached()) { return true; } } @@ -6248,7 +6347,8 @@ public boolean hasWorkingSystem(int system, int loc) { /** * @return true if the location has a system of the type, whether is destroyed or not */ - public boolean hasSystem(int system, int loc) { + public boolean hasSystem(int system, + int loc) { for (int i = 0; i < getNumberOfCriticalSlots(loc); i++) { CriticalSlot ccs = getCritical(loc, i); if ((ccs != null) && (ccs.getType() == CriticalSlot.TYPE_SYSTEM) && (ccs.getIndex() == system)) { @@ -6267,14 +6367,14 @@ public boolean hasVibroblades() { /** * @param location The location to check - * * @return The heat generated by active vibroblades in the given location if this unit has any */ public int getActiveVibrobladeHeat(int location) { return 0; } - public int getActiveVibrobladeHeat(int location, boolean ignoreMode) { + public int getActiveVibrobladeHeat(int location, + boolean ignoreMode) { return 0; } @@ -6282,7 +6382,7 @@ public int getActiveVibrobladeHeat(int location, boolean ignoreMode) { * Does the Mek have any shields. a Mek can have up to 2 shields. * * @return true if shieldCount is greater than 0 - * else false + * else false */ public boolean hasShield() { return false; @@ -6300,7 +6400,8 @@ public int getNumberOfShields(MiscTypeFlag shieldSize) { /** * Does the Mek have an active shield This should only be called after hasShield has been called. */ - public boolean hasRaisedShield(int location, boolean rear) { + public boolean hasRaisedShield(int location, + boolean rear) { return true; } @@ -6314,7 +6415,8 @@ public boolean hasRaisedShield(int location) { /** * Does the Mek have a passive shield This should only be called after hasShield has been called. */ - public boolean hasLoweredShield(int location, boolean rear) { + public boolean hasLoweredShield(int location, + boolean rear) { return false; } @@ -6401,9 +6503,9 @@ public boolean hasActiveECM(boolean stealth) { // TacOps p. 100 Angle ECM can have 1 ECM and 1 ECCM at the same // time if (m.getType().hasFlag(MiscType.F_ECM) && - (m.curMode().equals("ECM") || - m.curMode().equals("ECM & ECCM") || - m.curMode().equals("ECM & Ghost Targets"))) { + (m.curMode().equals("ECM") || + m.curMode().equals("ECM & ECCM") || + m.curMode().equals("ECM & Ghost Targets"))) { return !(m.isInoperable()); } } @@ -6464,7 +6566,6 @@ public boolean hasGhostTargetEquipment() { * turn eligibility checks and equipment selection in the PRE_FIRING phase. * * @param equipment the misc equipment to check - * * @return true if this equipment is generating ghost targets */ public boolean isGhostTargetCapable(MiscMounted equipment) { @@ -6473,26 +6574,26 @@ public boolean isGhostTargetCapable(MiscMounted equipment) { } MiscType type = equipment.getType(); if (type.hasFlag(MiscType.F_ECM) - && (equipment.curMode().equals("Ghost Targets") - || equipment.curMode().equals("ECM & Ghost Targets") - || equipment.curMode().equals("ECCM & Ghost Targets"))) { + && (equipment.curMode().equals("Ghost Targets") + || equipment.curMode().equals("ECM & Ghost Targets") + || equipment.curMode().equals("ECCM & Ghost Targets"))) { return true; } if (type.hasFlag(MiscType.F_COMMUNICATIONS) - && equipment.curMode().equals("Ghost Targets") - && (getTotalCommGearTons() >= 7)) { + && equipment.curMode().equals("Ghost Targets") + && (getTotalCommGearTons() >= 7)) { return true; } return type.hasFlag(MiscType.F_COMMAND_CONSOLE) - && equipment.curMode().equals("Ghost Targets"); + && equipment.curMode().equals("Ghost Targets"); } /** * Checks to see if this entity has a functional ECM unit that is using ECCM. * * @return true if the entity has AngeleCM, and it is in ECCM - * mode false if the entity does not have angel ecm, or it is not in eccm mode, or it is - * damaged. + * mode false if the entity does not have angel ecm, or it is not in eccm mode, or it is + * damaged. */ public boolean hasActiveECCM() { // no ECM in space unless strat op option enabled @@ -6500,16 +6601,16 @@ public boolean hasActiveECCM() { return false; } if ((gameOptions().booleanOption(OptionsConstants.ADVANCED_TAC_OPS_ECCM) || - gameOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_STRATOPS_ECM)) && !isShutDown()) { + gameOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_STRATOPS_ECM)) && !isShutDown()) { for (MiscMounted m : getMisc()) { MiscType type = m.getType(); // TacOps p. 100 Angle ECM can have 1 ECM and 1 ECCM at the same // time if (((type.hasFlag(MiscType.F_ECM) && (m.curMode().equals("ECCM") || - m.curMode().equals("ECM & ECCM") || - m.curMode().equals("ECCM & Ghost Targets"))) || - (type.hasFlag(MiscType.F_COMMUNICATIONS) && m.curMode().equals("ECCM")))) { + m.curMode().equals("ECM & ECCM") || + m.curMode().equals("ECCM & Ghost Targets"))) || + (type.hasFlag(MiscType.F_COMMUNICATIONS) && m.curMode().equals("ECCM")))) { return !m.isInoperable(); } } @@ -6521,7 +6622,7 @@ public boolean hasActiveECCM() { * What's the range of the ECM equipment? Infantry can have ECM that just covers their own hex. * * @return the int range of this unit's ECM. This value will be - * Entity.NONE if no ECM is active. + * Entity.NONE if no ECM is active. */ public int getECMRange() { if (game == null) { @@ -6591,8 +6692,8 @@ public boolean hasBAP(boolean checkECM) { // Watchdog/Nova CEWS the shared "Off" mode silences the probe half along with the rest of the suite. if (!m.isInoperable() && !m.isModeTurnedOff()) { return (game == null) || - Game.rulesManager.getRulesEquipment().isBAPActive(checkECM, m.getType(), - this, getPosition()); + Game.rulesManager.getRulesEquipment().isBAPActive(checkECM, m.getType(), + this, getPosition()); } } } @@ -6601,12 +6702,12 @@ public boolean hasBAP(boolean checkECM) { // Benefits don't stack - having both still only gives one probe // MM/Enhanced MM implants also provide probe capability for infantry or for units with VDNI/BVDNI boolean hasMmImplants = hasAbility(OptionsConstants.MD_MM_IMPLANTS) - || hasAbility(OptionsConstants.MD_ENH_MM_IMPLANTS); + || hasAbility(OptionsConstants.MD_ENH_MM_IMPLANTS); if (((hasAbility(OptionsConstants.MD_CYBER_IMP_AUDIO) || hasAbility(OptionsConstants.MD_CYBER_IMP_VISUAL) || hasMmImplants) && isConventionalInfantry()) - || (hasMmImplants - && (hasAbility(OptionsConstants.MD_VDNI) || hasAbility(OptionsConstants.MD_BVDNI)))) { + || (hasMmImplants + && (hasAbility(OptionsConstants.MD_VDNI) || hasAbility(OptionsConstants.MD_BVDNI)))) { return !checkECM || !ComputeECM.isAffectedByECM(this, getPosition(), getPosition()); } // check for quirk @@ -6631,7 +6732,7 @@ public boolean hasBAP(boolean checkECM) { * What's the range of the BAP equipment? * * @return the int range of this unit's BAP. This value will be - * Entity.NONE if no BAP is active. + * Entity.NONE if no BAP is active. */ public int getBAPRange() { PlanetaryConditions conditions = game.getPlanetaryConditions(); @@ -6644,15 +6745,15 @@ public int getBAPRange() { // - Enhanced MM implants: 3-hex probe for infantry, or non-infantry with VDNI/BVDNI; +2 to existing BAP // Benefits don't stack within category boolean hasMmImplants = hasAbility(OptionsConstants.MD_MM_IMPLANTS) - || hasAbility(OptionsConstants.MD_ENH_MM_IMPLANTS); + || hasAbility(OptionsConstants.MD_ENH_MM_IMPLANTS); boolean hasEnhancedMm = hasAbility(OptionsConstants.MD_ENH_MM_IMPLANTS); boolean hasBasicImplants = hasAbility(OptionsConstants.MD_CYBER_IMP_AUDIO) - || hasAbility(OptionsConstants.MD_CYBER_IMP_VISUAL); + || hasAbility(OptionsConstants.MD_CYBER_IMP_VISUAL); boolean hasVdni = hasAbility(OptionsConstants.MD_VDNI) || hasAbility(OptionsConstants.MD_BVDNI); // Check if sensory implants are active (infantry, or non-infantry with VDNI for MM implants) boolean sensoryImplantsActive = ((hasBasicImplants || hasMmImplants) && isConventionalInfantry()) - || (hasMmImplants && hasVdni); + || (hasMmImplants && hasVdni); // Base probe range from implants alone (no BAP): Enhanced=3, others=2 int cyberBaseProbe = sensoryImplantsActive ? (hasEnhancedMm ? 3 : 2) : 0; @@ -6700,9 +6801,9 @@ public int getBAPRange() { } String internalName = type.getInternalName(); if (internalName.equals(Sensor.CLAN_AP) || - internalName.equals(Sensor.WATCHDOG) || - internalName.equals(Sensor.NOVA) || - internalName.equals(Sensor.CL_BA_LIGHT_AP)) { + internalName.equals(Sensor.WATCHDOG) || + internalName.equals(Sensor.NOVA) || + internalName.equals(Sensor.CL_BA_LIGHT_AP)) { return 5 + cyberProbeBonus + quirkBonus + spaBonus; } if (internalName.equals(Sensor.LIGHT_AP)) { @@ -6737,7 +6838,7 @@ public int getBAPRange() { */ public boolean hasDroneOs() { return getMisc().stream() - .anyMatch(m -> (m.getType() != null) && m.getType().hasFlag(MiscType.F_DRONE_OPERATING_SYSTEM)); + .anyMatch(m -> (m.getType() != null) && m.getType().hasFlag(MiscType.F_DRONE_OPERATING_SYSTEM)); } /** @@ -6793,7 +6894,7 @@ public boolean hasTCPAimedShotCapability() { } boolean hasTCP = hasAbility(OptionsConstants.MD_TRIPLE_CORE_PROCESSOR); boolean hasVdni = hasAbility(OptionsConstants.MD_VDNI) - || hasAbility(OptionsConstants.MD_BVDNI); + || hasAbility(OptionsConstants.MD_BVDNI); if (!hasTCP || !hasVdni) { return false; } @@ -6842,8 +6943,8 @@ public int getTCPInitiativeBonus() { } // -1 if ECM-affected, unless unit has own ECM (counter-ECM per IO pg 81) if (getPosition() != null - && ComputeECM.isAffectedByECM(this, getPosition(), getPosition()) - && !hasECM()) { + && ComputeECM.isAffectedByECM(this, getPosition(), getPosition()) + && !hasECM()) { bonus -= 1; } // -1 if EMI conditions are active (global effect, can't be countered) @@ -6884,7 +6985,9 @@ private boolean hasTCPCommandEquipment() { /** * @return True if this unit has a C3 Slave. */ - /** @return True if this unit mounts an operable C3 Emergency Master that has not overloaded (TO:AUE p.110). */ + /** + * @return True if this unit mounts an operable C3 Emergency Master that has not overloaded (TO:AUE p.110). + */ public boolean hasC3EmergencyMaster() { if (isShutDown() || isOffBoard() || isC3EmergencyMasterOverloaded()) { return false; @@ -6897,7 +7000,9 @@ public boolean hasC3EmergencyMaster() { return false; } - /** @return True while this unit's C3 Emergency Master has taken over as a lance master. */ + /** + * @return True while this unit's C3 Emergency Master has taken over as a lance master. + */ public boolean isC3EmergencyMasterActive() { return c3emActive && hasC3EmergencyMaster(); } @@ -6906,7 +7011,9 @@ public void setC3EmergencyMasterActive(boolean active) { c3emActive = active; } - /** @return Operating turns the C3 Emergency Master has used ({@link #C3EM_MAX_OPERATING_TURNS} = overload). */ + /** + * @return Operating turns the C3 Emergency Master has used ({@link #C3EM_MAX_OPERATING_TURNS} = overload). + */ public int getC3EmergencyMasterOperatingTurns() { return c3emOperatingTurns; } @@ -6915,12 +7022,16 @@ public void setC3EmergencyMasterOperatingTurns(int operatingTurns) { c3emOperatingTurns = operatingTurns; } - /** @return True once the C3 Emergency Master has burned all its operating turns - dead for the scenario. */ + /** + * @return True once the C3 Emergency Master has burned all its operating turns - dead for the scenario. + */ public boolean isC3EmergencyMasterOverloaded() { return c3emOperatingTurns >= C3EM_MAX_OPERATING_TURNS; } - /** @return The id of the master the C3 Emergency Master substituted for, or {@code NONE}. */ + /** + * @return The id of the master the C3 Emergency Master substituted for, or {@code NONE}. + */ public int getC3EmergencyOriginalMasterId() { return c3emOriginalMasterId; } @@ -6929,8 +7040,10 @@ public void setC3EmergencyOriginalMasterId(int masterId) { c3emOriginalMasterId = masterId; } - /** @return The id of the master this unit lost to destruction or damage, or {@code NONE}. Not cleared by the - * lazy cleanup in {@link #getC3Master()}, so takeover logic can identify a dead master's network. */ + /** + * @return The id of the master this unit lost to destruction or damage, or {@code NONE}. Not cleared by the lazy + * cleanup in {@link #getC3Master()}, so takeover logic can identify a dead master's network. + */ public int getC3MasterLostId() { return c3MasterLostId; } @@ -6974,13 +7087,12 @@ public boolean hasCASEII() { * should return false. * * @param location The location to check - * * @return True if the unit has CASE II in the given location */ public boolean hasCASEII(int location) { return getMisc().stream() - .filter(m -> m.getLocation() == location) - .anyMatch(m -> m.getType().hasFlag(MiscType.F_CASEII)); + .filter(m -> m.getLocation() == location) + .anyMatch(m -> m.getType().hasFlag(MiscType.F_CASEII)); } /** @@ -6988,14 +7100,13 @@ public boolean hasCASEII(int location) { * include Harjel II or Harjel III, as they do not prevent breach checks like Harjel does. * * @param location the int location to check - * * @return a boolean value indicating a present HarJel system */ public boolean hasHarJelIn(int location) { for (MiscMounted mounted : getMisc()) { if ((mounted.getLocation() == location) && - mounted.isReady() && - (mounted.getType().hasFlag(MiscType.F_HARJEL))) { + mounted.isReady() && + (mounted.getType().hasFlag(MiscType.F_HARJEL))) { return true; } } @@ -7008,8 +7119,8 @@ public boolean hasBoostedC3() { } for (Mounted m : getEquipment()) { if (((m.getType() instanceof MiscType) && m.getType().hasFlag(MiscType.F_C3SBS)) || - ((m.getType() instanceof WeaponType) && m.getType().hasFlag(WeaponType.F_C3MBS)) && - !m.isInoperable()) { + ((m.getType() instanceof WeaponType) && m.getType().hasFlag(WeaponType.F_C3MBS)) && + !m.isInoperable()) { return true; } } @@ -7031,7 +7142,7 @@ public boolean hasC3M() { } for (WeaponMounted m : getWeaponList()) { if ((m.getType().hasFlag(WeaponType.F_C3M) || m.getType().hasFlag(WeaponType.F_C3MBS)) && - !m.isInoperable()) { + !m.isInoperable()) { // If this unit is configured as a company commander, and if this computer is the company master, // then this unit does not have a lance master computer. return !C3MasterIs(this) || (c3CompanyMasterIndex != getEquipmentNum(m)); @@ -7056,14 +7167,14 @@ public boolean hasC3MM() { while ((c3CompanyMasterIndex == LOC_DESTROYED) && e.hasNext()) { Mounted m = e.next(); if ((m.getType() instanceof WeaponType) && - (m.getType().hasFlag(WeaponType.F_C3M) || m.getType().hasFlag(WeaponType.F_C3MBS)) && - !m.isInoperable()) { + (m.getType().hasFlag(WeaponType.F_C3M) || m.getType().hasFlag(WeaponType.F_C3MBS)) && + !m.isInoperable()) { // Now look for the company command master. while ((c3CompanyMasterIndex == LOC_DESTROYED) && e.hasNext()) { m = e.next(); if ((m.getType() instanceof WeaponType) && - (m.getType().hasFlag(WeaponType.F_C3M) || m.getType().hasFlag(WeaponType.F_C3MBS)) && - !m.isInoperable()) { + (m.getType().hasFlag(WeaponType.F_C3M) || m.getType().hasFlag(WeaponType.F_C3MBS)) && + !m.isInoperable()) { // Found the company command master c3CompanyMasterIndex = getEquipmentNum(m); } @@ -7082,16 +7193,16 @@ public boolean hasC3MM() { } /** - * @return The number of operable C3 Master computers (standard or boosted) mounted on this unit. Units carrying - * two to four C3 Masters can act as consolidated company nodes: one computer provides the company-level - * link while each additional computer runs a lance of slaves (CR p.199, Configurations 2-4). Ignores - * shutdown/off-board state - callers that care use {@link #hasC3M()} or {@link #hasC3MM()} first. + * @return The number of operable C3 Master computers (standard or boosted) mounted on this unit. Units carrying two + * to four C3 Masters can act as consolidated company nodes: one computer provides the company-level link + * while each additional computer runs a lance of slaves (CR p.199, Configurations 2-4). Ignores + * shutdown/off-board state - callers that care use {@link #hasC3M()} or {@link #hasC3MM()} first. */ public int getOperableC3MCount() { int count = 0; for (WeaponMounted mounted : getWeaponList()) { if ((mounted.getType().hasFlag(WeaponType.F_C3M) || mounted.getType().hasFlag(WeaponType.F_C3MBS)) - && !mounted.isInoperable()) { + && !mounted.isInoperable()) { count++; } } @@ -7111,7 +7222,7 @@ public boolean hasC3() { /** * @return True if this unit has an active Nova CEWS that can communicate. Returns {@code false} if the unit is - * shutdown, off board, or the Nova CEWS is inoperable or switched to "Off" by the player. + * shutdown, off board, or the Nova CEWS is inoperable or switched to "Off" by the player. */ public boolean hasActiveNovaCEWS() { if (isShutDown() || isOffBoard()) { @@ -7120,7 +7231,7 @@ public boolean hasActiveNovaCEWS() { for (MiscMounted mounted : getMisc()) { MiscType miscType = mounted.getType(); if ((miscType != null) && miscType.hasFlag(MiscType.F_NOVA) - && mounted.isOperable() && !mounted.isModeTurnedOff()) { + && mounted.isOperable() && !mounted.isModeTurnedOff()) { return true; } } @@ -7129,9 +7240,9 @@ public boolean hasActiveNovaCEWS() { /** * @return True if this unit has a Nova CEWS that can network (not destroyed/breached, not shutdown, not offboard). - * Does NOT check the "Off" mode - network membership (net id, partner UUIDs) survives deactivation so the - * previous network is restored when the CEWS is switched back on; use {@link #hasActiveNovaCEWS()} for - * whether the system currently functions. + * Does NOT check the "Off" mode - network membership (net id, partner UUIDs) survives deactivation so the + * previous network is restored when the CEWS is switched back on; use {@link #hasActiveNovaCEWS()} for + * whether the system currently functions. */ public boolean hasNovaCEWS() { if (isShutDown() || isOffBoard()) { @@ -7154,7 +7265,7 @@ public boolean hasNavalC3() { /** * @return {@code true} if this unit mounts an operable minesweeper (TO:AuE p.138, Corrected Sixth Printing), - * whether or not it is currently activated. Used to offer the End-Phase activation toggle. + * whether or not it is currently activated. Used to offer the End-Phase activation toggle. */ public boolean hasMinesweeper() { for (MiscMounted mounted : getMisc()) { @@ -7346,7 +7457,6 @@ public void setC3NetIdSelf() { * Explicitly set the C3 Net ID using a string. * * @param c3NetId string value the id should be set to - * * @see megamek.common.util.C3Util * @see #setC3NetId(Entity) */ @@ -7431,7 +7541,8 @@ public int calculateFreeC3Nodes() { } if (C3MasterIs(this)) { int usableLanceComputers = Math.max(0, - Math.min(getOperableC3MCount() - 1, MAX_C3M_SUBORDINATES - masterDependents)); + Math.min(getOperableC3MCount() - 1, + MAX_C3M_SUBORDINATES - masterDependents)); nodes = Math.max(0, (MAX_C3M_SUBORDINATES * usableLanceComputers) - slaveDependents); } else { nodes = Math.max(0, MAX_C3M_SUBORDINATES - slaveDependents - masterDependents); @@ -7460,18 +7571,18 @@ public Entity getC3Top() { Entity master = m.getC3Master(); // Except if there's ECM, we can't _reach_ the master. while ((master != null) && - !master.equals(m) && - master.hasC3()) { + !master.equals(m) && + master.hasC3()) { if (Game.rulesManager.getRulesC3().c3AllowedWithECM()) { // Even with ECM we can always talk to the master m = master; master = m.getC3Master(); } else if ((m.hasBoostedC3() && - !ComputeECM.isAffectedByAngelECM(m, m.getPosition(), master.getPosition())) || - !(ComputeECM.isAffectedByECM(m, m.getPosition(), master.getPosition()))) { + !ComputeECM.isAffectedByAngelECM(m, m.getPosition(), master.getPosition())) || + !(ComputeECM.isAffectedByECM(m, m.getPosition(), master.getPosition()))) { if ((master.hasBoostedC3() && - !ComputeECM.isAffectedByAngelECM(master, master.getPosition(), master.getPosition())) || - !(ComputeECM.isAffectedByECM(master, master.getPosition(), master.getPosition()))) { + !ComputeECM.isAffectedByAngelECM(master, master.getPosition(), master.getPosition())) || + !(ComputeECM.isAffectedByECM(master, master.getPosition(), master.getPosition()))) { // punched through m = master; master = m.getC3Master(); @@ -7499,7 +7610,9 @@ public Entity getC3Top() { * null. If the value master * unit has shut down, then the value will be non-null after the master unit restarts. */ - /** Clears the c3Master pointer while recording which master was lost, for the C3EM takeover (TO:AUE p.110). */ + /** + * Clears the c3Master pointer while recording which master was lost, for the C3EM takeover (TO:AUE p.110). + */ private void rememberLostC3Master() { if (c3Master > NONE) { c3MasterLostId = c3Master; @@ -7543,7 +7656,7 @@ else if (eMaster.isShutDown()) { } // Has our company commander lost his company command computer? else if (((eMaster.c3CompanyMasterIndex > LOC_NONE) && !eMaster.hasC3MM()) || - ((eMaster.c3CompanyMasterIndex <= LOC_NONE) && !eMaster.hasC3M())) { + ((eMaster.c3CompanyMasterIndex <= LOC_NONE) && !eMaster.hasC3M())) { rememberLostC3Master(); } // maximum depth of a c3 network is 2 levels. @@ -7571,7 +7684,7 @@ else if (!isShutDown() && !hasC3MM() && (c3Master > NONE)) { * out of the C3 network, or when it was never in a C3 network. * * @return the int ID of the unit that is the master of this unit's C3 network, or - * Entity.NONE. + * Entity.NONE. */ public int getC3MasterId() { // Make sure that this unit is still on a C3 network. @@ -7589,11 +7702,10 @@ public int getC3MasterId() { * Entity, then it is an independent master. * * @param e - the Entity that may be this unit's C3 Master. - * * @return a boolean that is true when the passed - * Entity is this unit's commander. If the passed unit - * isn't this unit's commander, this routine returns - * false. + * Entity is this unit's commander. If the passed unit + * isn't this unit's commander, this routine returns + * false. */ public boolean C3MasterIs(Entity e) { if (e == null) { @@ -7610,7 +7722,8 @@ public boolean C3MasterIs(Entity e) { * * @param e - the Entity that should be set as our C3 Master. */ - public void setC3Master(Entity e, boolean reset) { + public void setC3Master(Entity e, + boolean reset) { if (e == null) { setC3Master(NONE, reset); } else if (!isEnemyOf(e)) { @@ -7621,7 +7734,8 @@ public void setC3Master(Entity e, boolean reset) { /** * */ - public void setC3Master(int entityId, boolean reset) { + public void setC3Master(int entityId, + boolean reset) { if (reset && ((id == entityId) != (id == c3Master))) { // this just changed from a company-level to lance-level (or vice versa); have to disconnect all slaved // units to maintain integrity. @@ -7663,11 +7777,11 @@ public boolean onSameC3NetworkAs(Entity e) { * * @param e The Entity to check against this entity * @param ignoreECM a boolean indicating if ECM should be ignored, we need this for c3i - * * @return a boolean that is true if the given entity is on the same network, - * false if not. + * false if not. */ - public boolean onSameC3NetworkAs(Entity e, boolean ignoreECM) { + public boolean onSameC3NetworkAs(Entity e, + boolean ignoreECM) { if (isEnemyOf(e) || isShutDown() || e.isShutDown()) { return false; } @@ -7698,7 +7812,7 @@ public boolean onSameC3NetworkAs(Entity e, boolean ignoreECM) { return true; } return !(ComputeECM.isAffectedByECM(e, e.getPosition(), e.getPosition())) && - !(ComputeECM.isAffectedByECM(this, getPosition(), getPosition())); + !(ComputeECM.isAffectedByECM(this, getPosition(), getPosition())); } // NC3 is easy too - if they both have NC3, and their net ID's match, they're on the same network! @@ -7751,7 +7865,7 @@ public boolean onSameC3NetworkAs(Entity e, boolean ignoreECM) { public boolean locationHasCase(int loc) { for (MiscMounted mounted : getMisc()) { if ((mounted.getLocation() == loc) && - (mounted.getType().hasFlag(MiscType.F_CASE) || mounted.getType().hasFlag(MiscType.F_CASEP))) { + (mounted.getType().hasFlag(MiscType.F_CASE) || mounted.getType().hasFlag(MiscType.F_CASEP))) { return true; } } @@ -7778,7 +7892,8 @@ public boolean hasCase() { * Hits all critical slots of the system occupying the specified critical slot. Used, for example, in a gauss rifle * capacitor discharge. Does not apply any special effect of hitting the critical slots, like ammo explosion. */ - public void hitAllCriticalSlots(int loc, int slot) { + public void hitAllCriticalSlots(int loc, + int slot) { CriticalSlot orig = getCritical(loc, slot); for (int i = 0; i < getNumberOfCriticalSlots(loc); i++) { CriticalSlot cs = getCritical(loc, i); @@ -8093,8 +8208,8 @@ public void reloadEmptyWeapons() { if (weaponType.getAmmoType() != AmmoType.AmmoTypeEnum.NA) { if ((mounted.getLinked() == null) || - (mounted.getLinked().getUsableShotsLeft() <= 0) || - mounted.getLinked().isDumping()) { + (mounted.getLinked().getUsableShotsLeft() <= 0) || + mounted.getLinked().isDumping()) { loadWeaponWithSameAmmo(mounted); } } @@ -8126,16 +8241,16 @@ public List getActiveAMS() { boolean baAPDS = (this instanceof BattleArmor) && (weapon.getType().getInternalName().equals("ISBAAPDS")); AmmoMounted ammo = weapon.getLinkedAmmo(); if (!(weapon.getType().hasFlag(WeaponType.F_ENERGY)) && - !baAPDS && - ((ammo == null) || (ammo.getUsableShotsLeft() == 0) || ammo.isDumping())) { + !baAPDS && + ((ammo == null) || (ammo.getUsableShotsLeft() == 0) || ammo.isDumping())) { loadWeapon(weapon); ammo = weapon.getLinkedAmmo(); } // try again if (!(weapon.getType().hasFlag(WeaponType.F_ENERGY)) && - !baAPDS && - ((ammo == null) || (ammo.getUsableShotsLeft() == 0) || ammo.isDumping())) { + !baAPDS && + ((ammo == null) || (ammo.getUsableShotsLeft() == 0) || ammo.isDumping())) { // No ammo for this AMS. continue; } @@ -8153,13 +8268,13 @@ public void assignTMAMS(final TeleMissileAttackAction telemissileAttack) { // Point defense bays are added too, provided they haven't fired at something // else already. getActiveAMS().stream() - .filter(ams -> ams.getType().hasFlag(WeaponType.F_AMS_BAY) || - (ams.getType().hasFlag(WeaponType.F_PD_BAY) && !ams.isUsedThisRound())) - .filter(ams -> ComputeArc.isInArc(game, - getId(), - getEquipmentNum(ams), - game.getEntity(telemissileAttack.getEntityId()))) - .forEach(telemissileAttack::addCounterEquipment); + .filter(ams -> ams.getType().hasFlag(WeaponType.F_AMS_BAY) || + (ams.getType().hasFlag(WeaponType.F_PD_BAY) && !ams.isUsedThisRound())) + .filter(ams -> ComputeArc.isInArc(game, + getId(), + getEquipmentNum(ams), + game.getEntity(telemissileAttack.getEntityId()))) + .forEach(telemissileAttack::addCounterEquipment); } /** @@ -8172,24 +8287,25 @@ public void assignAMS(final List attacks) { // make a new list of only incoming attacks in arc // TODO: determine if adjustments for MRM Saturation attacks are necessary. final List attacksInArc = attacks.stream() - .filter(weaponHandler -> (weaponHandler.getWeaponAttackAction() != - null) && - !targets.contains( - weaponHandler.getWeaponAttackAction()) && - ComputeArc.isInArc(getGame(), - getId(), - getEquipmentNum(ams), - (weaponHandler instanceof CapitalMissileBearingsOnlyHandler) ? - getGame().getTarget( - weaponHandler.getWeaponAttackAction() - .getOriginalTargetType(), - weaponHandler.getWeaponAttackAction() - .getOriginalTargetId()) : - getGame().getEntity( - weaponHandler.getWeaponAttackAction() - .getEntityId()))) - .map(WeaponHandler::getWeaponAttackAction) - .collect(Collectors.toList()); + .filter(weaponHandler -> (weaponHandler.getWeaponAttackAction() != + null) && + !targets.contains( + weaponHandler.getWeaponAttackAction()) && + ComputeArc.isInArc(getGame(), + getId(), + getEquipmentNum( + ams), + (weaponHandler instanceof CapitalMissileBearingsOnlyHandler) ? + getGame().getTarget( + weaponHandler.getWeaponAttackAction() + .getOriginalTargetType(), + weaponHandler.getWeaponAttackAction() + .getOriginalTargetId()) : + getGame().getEntity( + weaponHandler.getWeaponAttackAction() + .getEntityId()))) + .map(WeaponHandler::getWeaponAttackAction) + .collect(Collectors.toList()); if (attacksInArc.isEmpty()) { return; @@ -8198,8 +8314,8 @@ public void assignAMS(final List attacks) { // AMS Bays can fire at all incoming attacks each round So can standard AMS if the unofficial option is // turned on if ((ams.getType().hasFlag(WeaponType.F_AMS_BAY)) || - (gameOptions().booleanOption(OptionsConstants.ADVANCED_COMBAT_MULTI_USE_AMS) && - ams.getType().hasFlag(WeaponType.F_AMS))) { + (gameOptions().booleanOption(OptionsConstants.ADVANCED_COMBAT_MULTI_USE_AMS) && + ams.getType().hasFlag(WeaponType.F_AMS))) { attacksInArc.forEach(waa -> waa.addCounterEquipment(ams)); } else if (ams.getType().hasFlag(WeaponType.F_PD_BAY)) { // Point defense bays are assigned to the attack with the greatest threat Unlike single AMS, PD bays @@ -8216,8 +8332,8 @@ public void assignAMS(final List attacks) { targets.add(waa); if (attacksInArc.size() > 1) { final WeaponAttackAction secondWaa = Compute.getSecondHighestExpectedDamage(getGame(), - attacksInArc, - true); + attacksInArc, + true); if (secondWaa != null) { secondWaa.addCounterEquipment(ams); targets.add(secondWaa); @@ -8266,7 +8382,6 @@ public void attachINarcPod(INarcPod pod) { * Have we been iNarced with a homing pod from that team? * * @param nTeamID The id of the team that we are wondering about. - * * @return true if the Entity is narced by that team. */ public boolean isINarcedBy(int nTeamID) { @@ -8277,7 +8392,6 @@ public boolean isINarcedBy(int nTeamID) { * Have we been iNarced with the named pod from any team? * * @param type the int type of iNarc pod. - * * @return true if we have. */ public boolean isINarcedWith(long type) { @@ -8291,17 +8405,23 @@ public void removeAllINarcPods() { iNarcPods.clear(); } - /** Returns true if any iNarc pods are attached to this unit. */ + /** + * Returns true if any iNarc pods are attached to this unit. + */ public boolean hasINarcPodsAttached() { return !iNarcPods.isEmpty(); } - /** Returns true if any Narc pods are attached to this unit. (Ignores iNarc) */ + /** + * Returns true if any Narc pods are attached to this unit. (Ignores iNarc) + */ public boolean hasNarcPodsAttached() { return !narcPods.isEmpty(); } - /** Returns true if any Narc or iNarc pods are attached to this unit. */ + /** + * Returns true if any Narc or iNarc pods are attached to this unit. + */ public boolean hasAnyTypeNarcPodsAttached() { return hasINarcPodsAttached() || hasNarcPodsAttached(); } @@ -8319,9 +8439,8 @@ public Iterator getINarcPodsAttached() { * Remove an INarcPod from this entity. * * @param pod the INarcPod to be removed. - * * @return true if the pod was removed, false if - * the pod was not attached to this entity. + * the pod was not attached to this entity. */ public boolean removeINarcPod(INarcPod pod) { return iNarcPods.remove(pod); @@ -8333,7 +8452,7 @@ public boolean removeINarcPod(INarcPod pod) { * of a calculated BV value. * * @return The full Battle Value of this unit including C3 and crew skill modifications or the manual BV in case - * this unit uses a manual BV value + * this unit uses a manual BV value */ public final int calculateBattleValue() { return manualOrCalculateBV(false, false, false, new DummyCalculationReport()); @@ -8346,7 +8465,6 @@ public final int calculateBattleValue() { * * @param ignoreC3 When true, the BV contributions of any C3 computers are not added * @param ignoreSkill When true, the skill of the crew / pilot is not taken into account for BV - * * @return The Battle Value of this unit */ public final int calculateBattleValue(boolean ignoreC3, boolean ignoreSkill) { @@ -8374,9 +8492,8 @@ public final int calculateBattleValue(boolean ignoreC3, boolean ignoreSkill, boo * of a calculated BV value and no calculation report info will be generated. * * @param calculationReport A CalculationReport to write the BV calculation to - * * @return The full Battle Value of this unit including C3 and crew skill modifications or the manual BV in case - * this unit uses a manual BV value + * this unit uses a manual BV value */ public int calculateBattleValue(CalculationReport calculationReport) { return manualOrCalculateBV(false, false, false, calculationReport); @@ -8391,7 +8508,6 @@ public int calculateBattleValue(CalculationReport calculationReport) { * @param ignoreC3 When true, the BV contributions of any C3 computers are not added * @param ignoreSkill When true, the skill of the crew / pilot is not taken into account for BV * @param calculationReport A CalculationReport to write the BV calculation to - * * @return The Battle Value of this unit */ public int calculateBattleValue(boolean ignoreC3, boolean ignoreSkill, CalculationReport calculationReport) { @@ -8424,7 +8540,6 @@ public int calculateBattleValue(boolean ignoreC3, boolean ignoreSkill, boolean i * @param ignoreSkill When {@code true}, the skill of the crew / pilot is not taken into account for BV * @param ignoreTAG When {@code true}, the force bonus for friendly guided munitions (TAG/homing) is not added * @param calculationReport A CalculationReport to write the BV calculation to - * * @return The Battle Value of this unit */ private int manualOrCalculateBV(boolean ignoreC3, boolean ignoreSkill, boolean ignoreTAG, @@ -8441,7 +8556,6 @@ private int manualOrCalculateBV(boolean ignoreC3, boolean ignoreSkill, boolean i * @param ignoreSkill When {@code true}, the skill of the crew / pilot is not taken into account for BV * @param ignoreTAG When {@code true}, the force bonus for friendly guided munitions (TAG/homing) is not added * @param calculationReport A CalculationReport to write the BV calculation to - * * @return The Battle Value of this unit calculated from its current state */ protected int doBattleValueCalculation(boolean ignoreC3, boolean ignoreSkill, boolean ignoreTAG, @@ -8573,51 +8687,55 @@ else if (!getCrew().isActive()) { // gyro operational? does not apply if using tracked/quadvee vehicle/lam fighter // movement if (isGyroDestroyed() && - canFall() && - moveType != EntityMovementType.MOVE_VTOL_WALK && - moveType != EntityMovementType.MOVE_VTOL_RUN) { - return new PilotingRollData(entityId, - TargetRoll.AUTOMATIC_FAIL, - Game.rulesManager.getRulesPilot().getSeatbeltGyroModifier(getCrew().getPiloting()), - "Gyro destroyed"); + canFall() && + moveType != EntityMovementType.MOVE_VTOL_WALK && + moveType != EntityMovementType.MOVE_VTOL_RUN) { + return new PilotingRollData(entityId, + TargetRoll.AUTOMATIC_FAIL, + Game.rulesManager.getRulesPilot() + .getSeatbeltGyroModifier(getCrew().getPiloting()), + "Gyro destroyed"); } // both legs present? if ((this instanceof BipedMek) && - (((BipedMek) this).countBadLegs() == 2) && - (moveType != EntityMovementType.MOVE_VTOL_WALK) && - (moveType != EntityMovementType.MOVE_VTOL_RUN)) { - return new PilotingRollData(entityId, - TargetRoll.AUTOMATIC_FAIL, - Game.rulesManager.getRulesPilot().getSeatbeltLegModifier(getCrew().getPiloting(), 2), - "Both legs destroyed"); + (((BipedMek) this).countBadLegs() == 2) && + (moveType != EntityMovementType.MOVE_VTOL_WALK) && + (moveType != EntityMovementType.MOVE_VTOL_RUN)) { + return new PilotingRollData(entityId, + TargetRoll.AUTOMATIC_FAIL, + Game.rulesManager.getRulesPilot() + .getSeatbeltLegModifier(getCrew().getPiloting(), 2), + "Both legs destroyed"); } else if (this instanceof QuadMek) { if (((QuadMek) this).countBadLegs() >= 3) { - return new PilotingRollData(entityId, - TargetRoll.AUTOMATIC_FAIL, - Game.rulesManager.getRulesPilot().getSeatbeltLegModifier(getCrew().getPiloting(), - ((Mek) this).countBadLegs()), "legs destroyed"); + return new PilotingRollData(entityId, + TargetRoll.AUTOMATIC_FAIL, + Game.rulesManager.getRulesPilot() + .getSeatbeltLegModifier(getCrew().getPiloting(), + ((Mek) this).countBadLegs()), + "legs destroyed"); } } // entity shut down? if (isShutDown() && isShutDownThisPhase()) { return new PilotingRollData(entityId, - TargetRoll.AUTOMATIC_FAIL, - Game.rulesManager.getRulesPilot().getSeatbeltShutdown(getCrew().getPiloting()), - "Reactor shut down"); + TargetRoll.AUTOMATIC_FAIL, + Game.rulesManager.getRulesPilot().getSeatbeltShutdown(getCrew().getPiloting()), + "Reactor shut down"); } else if (isShutDown()) { return new PilotingRollData(entityId, - TargetRoll.AUTOMATIC_FAIL, - TargetRoll.IMPOSSIBLE, - "Reactor shut down"); + TargetRoll.AUTOMATIC_FAIL, + TargetRoll.IMPOSSIBLE, + "Reactor shut down"); } // okay, let's figure out the stuff then. A gamemaster's temporary modifier is taken back out of the skill // and shown as a line of its own, so a shifted target can be traced to the gamemaster's intervention. int gamemasterModifier = getCrew().appliedPilotingModifier(moveType); roll = new PilotingRollData(entityId, - getCrew().getPiloting(moveType) - gamemasterModifier, - (this instanceof Infantry) ? "Anti-Mek skill" : "Base piloting skill"); + getCrew().getPiloting(moveType) - gamemasterModifier, + (this instanceof Infantry) ? "Anti-Mek skill" : "Base piloting skill"); if (gamemasterModifier != 0) { roll.addModifier(gamemasterModifier, "GM Modifier"); } @@ -8674,7 +8792,8 @@ else if (!getCrew().isActive()) { /** * Add in any modifiers due to global conditions like light/weather/etc. */ - public PilotingRollData addConditionBonuses(PilotingRollData roll, EntityMovementType moveType) { + public PilotingRollData addConditionBonuses(PilotingRollData roll, + EntityMovementType moveType) { PlanetaryConditions conditions = game.getPlanetaryConditions(); if (moveType == EntityMovementType.MOVE_SPRINT || moveType == EntityMovementType.MOVE_VTOL_SPRINT) { @@ -8694,9 +8813,9 @@ public PilotingRollData addConditionBonuses(PilotingRollData roll, EntityMovemen } if (!hasAbility(OptionsConstants.UNOFFICIAL_ALL_WEATHER) && - getCrew().getOptions() - .stringOption(OptionsConstants.MISC_ENV_SPECIALIST) - .equals(Crew.ENVIRONMENT_SPECIALIST_RAIN)) { + getCrew().getOptions() + .stringOption(OptionsConstants.MISC_ENV_SPECIALIST) + .equals(Crew.ENVIRONMENT_SPECIALIST_RAIN)) { if (conditions.getWeather().isGustingRain()) { if ((this instanceof Mek) || isAirborne() || getMovementMode().isTrackedOrWheeled()) { roll.addModifier(-1, Messages.getString("PilotingSPA.EnvSpec.RainSpec")); @@ -8713,9 +8832,9 @@ public PilotingRollData addConditionBonuses(PilotingRollData roll, EntityMovemen } if (!hasAbility(OptionsConstants.UNOFFICIAL_ALL_WEATHER) && - getCrew().getOptions() - .stringOption(OptionsConstants.MISC_ENV_SPECIALIST) - .equals(Crew.ENVIRONMENT_SPECIALIST_SNOW)) { + getCrew().getOptions() + .stringOption(OptionsConstants.MISC_ENV_SPECIALIST) + .equals(Crew.ENVIRONMENT_SPECIALIST_SNOW)) { if (conditions.getWeather().isHeavySnow()) { roll.addModifier(-1, Messages.getString("PilotingSPA.EnvSpec.SnowSpec")); } @@ -8727,10 +8846,10 @@ public PilotingRollData addConditionBonuses(PilotingRollData roll, EntityMovemen } if (!hasAbility(OptionsConstants.UNOFFICIAL_ALL_WEATHER) && - conditions.getWeather().isClear() && - getCrew().getOptions() - .stringOption(OptionsConstants.MISC_ENV_SPECIALIST) - .equals(Crew.ENVIRONMENT_SPECIALIST_WIND)) { + conditions.getWeather().isClear() && + getCrew().getOptions() + .stringOption(OptionsConstants.MISC_ENV_SPECIALIST) + .equals(Crew.ENVIRONMENT_SPECIALIST_WIND)) { if ((conditions.getWind().isModerateGale()) && isAirborneVTOLorWIGE()) { roll.addModifier(-1, Messages.getString("PilotingSPA.EnvSpec.WindSpec")); } @@ -8766,9 +8885,10 @@ public PilotingRollData addConditionBonuses(PilotingRollData roll, EntityMovemen /** * Checks if the entity is getting up. If so, returns the target roll for the piloting skill check. */ - public PilotingRollData checkGetUp(MoveStep step, EntityMovementType moveType) { + public PilotingRollData checkGetUp(MoveStep step, + EntityMovementType moveType) { if ((step == null) || - ((step.getType() != MoveStepType.GET_UP) && (step.getType() != MoveStepType.CAREFUL_STAND))) { + ((step.getType() != MoveStepType.GET_UP) && (step.getType() != MoveStepType.CAREFUL_STAND))) { return new PilotingRollData(id, TargetRoll.CHECK_FALSE, "Check false: Entity is not attempting to get up."); } @@ -8776,7 +8896,7 @@ public PilotingRollData checkGetUp(MoveStep step, EntityMovementType moveType) { if (this instanceof BipedMek) { if ((((Mek) this).countBadLegs() >= 1) && (isLocationBad(Mek.LOC_LEFT_ARM) - && isLocationBad(Mek.LOC_RIGHT_ARM))) { + && isLocationBad(Mek.LOC_RIGHT_ARM))) { roll.addModifier(TargetRoll.IMPOSSIBLE, "can't get up with destroyed leg and arms"); return roll; } @@ -8789,9 +8909,9 @@ && isLocationBad(Mek.LOC_RIGHT_ARM))) { if (!needsRollToStand() && !isGyroDestroyed()) { roll.addModifier(TargetRoll.AUTOMATIC_SUCCESS, - "\n" + - getDisplayName() + - " does not need to make a piloting skill check to stand up because it has all four of its legs."); + "\n" + + getDisplayName() + + " does not need to make a piloting skill check to stand up because it has all four of its legs."); return roll; } @@ -8829,7 +8949,8 @@ private int maxAdditionalCarryableBAByWeightClass() { * Checks if the entity is attempting to run with damage that would force a PSR. If so, returns the target roll for * the piloting skill check. */ - public PilotingRollData checkRunningWithDamage(EntityMovementType overallMoveType, int distance) { + public PilotingRollData checkRunningWithDamage(EntityMovementType overallMoveType, + int distance) { PilotingRollData roll = getBasePilotingRoll(overallMoveType); int gyroDamage = getBadCriticalSlots(CriticalSlot.TYPE_SYSTEM, Mek.SYSTEM_GYRO, Mek.LOC_CENTER_TORSO); @@ -8844,16 +8965,17 @@ public PilotingRollData checkRunningWithDamage(EntityMovementType overallMoveTyp * Checks if the entity is attempting to sprint with MASC or Supercharger engaged (but not both). If so, returns the * target roll for the piloting skill check. */ - public PilotingRollData checkSprintingWithMASCXorSupercharger(EntityMovementType overallMoveType, int used) { + public PilotingRollData checkSprintingWithMASCXorSupercharger(EntityMovementType overallMoveType, + int used) { PilotingRollData roll = getBasePilotingRoll(overallMoveType); if ((overallMoveType == EntityMovementType.MOVE_SPRINT || - overallMoveType == EntityMovementType.MOVE_VTOL_SPRINT) && - (used > ((int) Math.ceil(2.0 * getWalkMP())))) { + overallMoveType == EntityMovementType.MOVE_VTOL_SPRINT) && + (used > ((int) Math.ceil(2.0 * getWalkMP())))) { roll.append(new PilotingRollData(getId(), 0, "sprinting with active MASC or Supercharger")); } else { roll.addModifier(TargetRoll.CHECK_FALSE, - "Check false: Entity is not attempting to sprint with MASC or Supercharger"); + "Check false: Entity is not attempting to sprint with MASC or Supercharger"); } addPilotingModifierForTerrain(roll); @@ -8864,16 +8986,17 @@ public PilotingRollData checkSprintingWithMASCXorSupercharger(EntityMovementType * Checks if the entity is attempting to sprint with MASC and supercharger engaged. If so, returns the target roll * for the piloting skill check. */ - public PilotingRollData checkSprintingWithMASCAndSupercharger(EntityMovementType overallMoveType, int used) { + public PilotingRollData checkSprintingWithMASCAndSupercharger(EntityMovementType overallMoveType, + int used) { PilotingRollData roll = getBasePilotingRoll(overallMoveType); if ((overallMoveType == EntityMovementType.MOVE_SPRINT || - overallMoveType == EntityMovementType.MOVE_VTOL_SPRINT) && - (used > ((int) Math.ceil(2.5 * getWalkMP())))) { + overallMoveType == EntityMovementType.MOVE_VTOL_SPRINT) && + (used > ((int) Math.ceil(2.5 * getWalkMP())))) { roll.append(new PilotingRollData(getId(), 0, "sprinting with active MASC and Supercharger")); } else { roll.addModifier(TargetRoll.CHECK_FALSE, - "Check false: Entity is not attempting to sprint with MASC and Supercharger"); + "Check false: Entity is not attempting to sprint with MASC and Supercharger"); } addPilotingModifierForTerrain(roll); @@ -8888,9 +9011,9 @@ public PilotingRollData checkUsingOverdrive(EntityMovementType overallMoveType) PilotingRollData roll = getBasePilotingRoll(overallMoveType); if ((overallMoveType == EntityMovementType.MOVE_SPRINT || - overallMoveType == EntityMovementType.MOVE_VTOL_SPRINT) && - (this instanceof Tank || - (this instanceof QuadVee && getConversionMode() == QuadVee.CONV_MODE_VEHICLE))) { + overallMoveType == EntityMovementType.MOVE_VTOL_SPRINT) && + (this instanceof Tank || + (this instanceof QuadVee && getConversionMode() == QuadVee.CONV_MODE_VEHICLE))) { roll.append(new PilotingRollData(getId(), 0, "using overdrive")); } else { roll.addModifier(TargetRoll.CHECK_FALSE, "Check false: Entity is not using overdrive"); @@ -8907,17 +9030,17 @@ public PilotingRollData checkGunningIt(EntityMovementType overallMoveType) { PilotingRollData roll = getBasePilotingRoll(overallMoveType); if (gameOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLE_ACCELERATION) && - (this instanceof Tank || - (this instanceof QuadVee && getConversionMode() == QuadVee.CONV_MODE_VEHICLE))) { + (this instanceof Tank || + (this instanceof QuadVee && getConversionMode() == QuadVee.CONV_MODE_VEHICLE))) { if (((overallMoveType == EntityMovementType.MOVE_SPRINT || overallMoveType == EntityMovementType.MOVE_VTOL_SPRINT) && - (movedLastRound == EntityMovementType.MOVE_WALK || - movedLastRound == EntityMovementType.MOVE_VTOL_WALK)) || - ((overallMoveType == EntityMovementType.MOVE_RUN || - overallMoveType == EntityMovementType.MOVE_VTOL_RUN) && - (movedLastRound == EntityMovementType.MOVE_NONE || - movedLastRound == EntityMovementType.MOVE_JUMP || - movedLastRound == EntityMovementType.MOVE_SKID))) { + (movedLastRound == EntityMovementType.MOVE_WALK || + movedLastRound == EntityMovementType.MOVE_VTOL_WALK)) || + ((overallMoveType == EntityMovementType.MOVE_RUN || + overallMoveType == EntityMovementType.MOVE_VTOL_RUN) && + (movedLastRound == EntityMovementType.MOVE_NONE || + movedLastRound == EntityMovementType.MOVE_JUMP || + movedLastRound == EntityMovementType.MOVE_SKID))) { roll.append(new PilotingRollData(getId(), 0, "gunning it")); return roll; } @@ -8929,8 +9052,12 @@ public PilotingRollData checkGunningIt(EntityMovementType overallMoveType) { /** * Checks if an entity is passing through certain terrain while not moving carefully */ - public PilotingRollData checkRecklessMove(MoveStep step, EntityMovementType moveType, Hex curHex, Coords lastPos, - Coords curPos, Hex prevHex) { + public PilotingRollData checkRecklessMove(MoveStep step, + EntityMovementType moveType, + Hex curHex, + Coords lastPos, + Coords curPos, + Hex prevHex) { PlanetaryConditions conditions = game.getPlanetaryConditions(); PilotingRollData roll = getBasePilotingRoll(moveType); // no need to go further if movement is careful @@ -8943,7 +9070,7 @@ public PilotingRollData checkRecklessMove(MoveStep step, EntityMovementType move boolean isBlackIce; boolean blackIceCheck = gameOptions().booleanOption(OptionsConstants.ADVANCED_BLACK_ICE) && - conditions.getTemperature() <= PlanetaryConditions.BLACK_ICE_TEMP; + conditions.getTemperature() <= PlanetaryConditions.BLACK_ICE_TEMP; isBlackIce = conditions.getWeather().isIceStorm() || blackIceCheck; // if we are jumping, then no worries @@ -8956,8 +9083,8 @@ public PilotingRollData checkRecklessMove(MoveStep step, EntityMovementType move boolean levelChange = (null != prevHex) && (prevHex.getLevel() != curHex.getLevel()); boolean moved = (curHex.movementCost(this) > 0) || levelChange; if (conditions.isRecklessConditions() && - !lastPos.equals(curPos) && - lastPos.equals(step.getEntity().getPosition())) { + !lastPos.equals(curPos) && + lastPos.equals(step.getEntity().getPosition())) { roll.append(new PilotingRollData(getId(), 0, "moving recklessly")); } // FIXME: no perfect solution in the current code to determine if hex is clear. I will use movement costs @@ -8983,10 +9110,10 @@ public boolean hasBadLegs() { int badLegs = 0; int legLocations = 0; for (int loc = 0; loc < locations(); loc++) { - if (locationIsLeg(loc) ) { + if (locationIsLeg(loc)) { if (isLocationBad(loc)) { badLegs++; - } + } legLocations++; } } @@ -9008,7 +9135,8 @@ public PilotingRollData checkLandingWithDamage(EntityMovementType overallMoveTyp int gyroModifier = Game.rulesManager.getRulesPSR().getGyroJumpModifier(gyroHits, getGyroType()); // Heavy-duty gyro does not force PSR until second hit (Only under Total Warfare) - if ((getGyroType() == Mek.GYRO_HEAVY_DUTY || getGyroType() == Mek.GYRO_SUPERHEAVY) && Game.rulesManager instanceof TWRulesManager) { + if ((getGyroType() == Mek.GYRO_HEAVY_DUTY || getGyroType() == Mek.GYRO_SUPERHEAVY) + && Game.rulesManager instanceof TWRulesManager) { gyroHits--; } @@ -9022,8 +9150,8 @@ public PilotingRollData checkLandingWithDamage(EntityMovementType overallMoveTyp addPilotingModifierForTerrain(roll); } else { roll.addModifier(TargetRoll.CHECK_FALSE, - "Entity does not have gyro or leg damage -- checking for purposes of determining PSR " + - "after jump."); + "Entity does not have gyro or leg damage -- checking for purposes of determining PSR " + + "after jump."); } return roll; } @@ -9041,7 +9169,7 @@ public PilotingRollData checkLandingWithPrototypeJJ(EntityMovementType overallMo addPilotingModifierForTerrain(roll); } else { roll.addModifier(TargetRoll.CHECK_FALSE, - "Entity does not have prototype jump jets -- checking for purposes of determining PSR after jump."); + "Entity does not have prototype jump jets -- checking for purposes of determining PSR after jump."); } return roll; } @@ -9049,7 +9177,8 @@ public PilotingRollData checkLandingWithPrototypeJJ(EntityMovementType overallMo /** * Checks if an entity is landing (from a jump) in heavy woods. */ - public PilotingRollData checkLandingInHeavyWoods(EntityMovementType overallMoveType, Hex curHex) { + public PilotingRollData checkLandingInHeavyWoods(EntityMovementType overallMoveType, + Hex curHex) { PilotingRollData roll = getBasePilotingRoll(overallMoveType); if (curHex.containsTerrain(Terrains.WOODS, 2)) { roll.append(new PilotingRollData(getId(), 0, "landing in heavy woods")); @@ -9066,7 +9195,8 @@ public PilotingRollData checkLandingInHeavyWoods(EntityMovementType overallMoveT /** * Checks if the entity is landing (from a jump) on ice-covered water. */ - public PilotingRollData checkLandingOnIce(EntityMovementType overallMoveType, Hex curHex) { + public PilotingRollData checkLandingOnIce(EntityMovementType overallMoveType, + Hex curHex) { PilotingRollData roll = getBasePilotingRoll(overallMoveType); if (curHex.containsTerrain(Terrains.ICE) && (curHex.terrainLevel(Terrains.WATER) > 0)) { @@ -9083,7 +9213,8 @@ public PilotingRollData checkLandingOnIce(EntityMovementType overallMoveType, He /** * Checks if the entity is landing (from a jump) on black ice. */ - public PilotingRollData checkLandingOnBlackIce(EntityMovementType overallMoveType, Hex curHex) { + public PilotingRollData checkLandingOnBlackIce(EntityMovementType overallMoveType, + Hex curHex) { PilotingRollData roll = getBasePilotingRoll(overallMoveType); if (curHex.containsTerrain(Terrains.BLACK_ICE)) { @@ -9100,15 +9231,16 @@ public PilotingRollData checkLandingOnBlackIce(EntityMovementType overallMoveTyp /** * @return PilotingRollData checking for whether this Entity moved too fast due to low gravity */ - public PilotingRollData checkMovedTooFast(MoveStep step, EntityMovementType moveType) { + public PilotingRollData checkMovedTooFast(MoveStep step, + EntityMovementType moveType) { PilotingRollData roll = getBasePilotingRoll(moveType); addPilotingModifierForTerrain(roll, step); int maxSafeMP; switch (moveType) { case MOVE_JUMP: maxSafeMP = step.isUsingMekJumpBooster() ? - getMechanicalJumpBoosterMP(MPCalculationSetting.NO_GRAVITY) : - getJumpMP(MPCalculationSetting.NO_GRAVITY); + getMechanicalJumpBoosterMP(MPCalculationSetting.NO_GRAVITY) : + getJumpMP(MPCalculationSetting.NO_GRAVITY); break; case MOVE_SPRINT: case MOVE_VTOL_SPRINT: @@ -9130,7 +9262,7 @@ public PilotingRollData checkMovedTooFast(MoveStep step, EntityMovementType move roll.append(new PilotingRollData(getId(), 0, "used more MPs than at 1G possible")); } else { roll.addModifier(TargetRoll.CHECK_FALSE, - "Check false: Entity did not use more MPs walking/running than possible at 1G"); + "Check false: Entity did not use more MPs walking/running than possible at 1G"); } return roll; } @@ -9138,9 +9270,17 @@ public PilotingRollData checkMovedTooFast(MoveStep step, EntityMovementType move /** * Checks if the entity might skid. If so, returns the target roll for the piloting skill check. */ - public PilotingRollData checkSkid(EntityMovementType moveType, Hex prevHex, EntityMovementType overallMoveType, - MoveStep prevStep, MoveStep currStep, int prevFacing, int curFacing, Coords lastPos, Coords curPos, - boolean isInfantry, int distance) { + public PilotingRollData checkSkid(EntityMovementType moveType, + Hex prevHex, + EntityMovementType overallMoveType, + MoveStep prevStep, + MoveStep currStep, + int prevFacing, + int curFacing, + Coords lastPos, + Coords curPos, + boolean isInfantry, + int distance) { if (isAirborne() || isAirborneVTOLorWIGE()) { return new PilotingRollData(id, TargetRoll.CHECK_FALSE, "flying units don't skid"); @@ -9167,22 +9307,22 @@ public PilotingRollData checkSkid(EntityMovementType moveType, Hex prevHex, Enti } boolean prevStepPavement = (prevStep != null) ? - prevStep.isPavementStep() : - ((prevHex != null) && prevHex.hasPavement()); + prevStep.isPavementStep() : + ((prevHex != null) && prevHex.hasPavement()); PlanetaryConditions conditions = game.getPlanetaryConditions(); boolean affectedByIce = !movementMode.isHoverOrWiGE() || conditions.getWind().isStrongerThan(Wind.STRONG_GALE); boolean runOrSprint = (overallMoveType == EntityMovementType.MOVE_RUN) || - (overallMoveType == EntityMovementType.MOVE_SPRINT); + (overallMoveType == EntityMovementType.MOVE_SPRINT); boolean unitTouchesIce = (prevHex != null) && - prevHex.containsTerrain(Terrains.ICE) && - (currStep.getElevation() == 0); + prevHex.containsTerrain(Terrains.ICE) && + (currStep.getElevation() == 0); boolean unitTouchesBlackIce = (prevHex != null) && - prevHex.containsTerrain(Terrains.BLACK_ICE) && - (((currStep.getElevation() == 0) && - prevHex.containsAnyTerrainOf(Terrains.ROAD, Terrains.PAVEMENT)) || - (prevHex.containsTerrain(Terrains.BRIDGE_ELEV) && - (currStep.getElevation() == - prevHex.terrainLevel(Terrains.BRIDGE_ELEV)))); + prevHex.containsTerrain(Terrains.BLACK_ICE) && + (((currStep.getElevation() == 0) && + prevHex.containsAnyTerrainOf(Terrains.ROAD, Terrains.PAVEMENT)) || + (prevHex.containsTerrain(Terrains.BRIDGE_ELEV) && + (currStep.getElevation() == + prevHex.terrainLevel(Terrains.BRIDGE_ELEV)))); boolean isMoveAndTurn = (prevFacing != curFacing) && !Objects.equals(curPos, lastPos); if (unitTouchesIce && affectedByIce && isMoveAndTurn) { @@ -9192,8 +9332,8 @@ public PilotingRollData checkSkid(EntityMovementType moveType, Hex prevHex, Enti } else if (unitTouchesBlackIce && affectedByIce && isMoveAndTurn) { // Turning on black ice roll.append(new PilotingRollData(getId(), - getMovementBeforeSkidPSRModifier(distance), - "turning on black ice")); + getMovementBeforeSkidPSRModifier(distance), + "turning on black ice")); } else if (prevStepPavement && runOrSprint && !movementMode.isHoverOrWiGE() && isMoveAndTurn) { // Running & turning on pavement @@ -9210,17 +9350,22 @@ public PilotingRollData checkSkid(EntityMovementType moveType, Hex prevHex, Enti /** * Checks if the entity is moving into rubble. If so, returns the target roll for the piloting skill check. */ - public PilotingRollData checkRubbleMove(MoveStep step, EntityMovementType moveType, Hex curHex, Coords lastPos, - Coords curPos, boolean isLastStep, boolean isPavementStep) { + public PilotingRollData checkRubbleMove(MoveStep step, + EntityMovementType moveType, + Hex curHex, + Coords lastPos, + Coords curPos, + boolean isLastStep, + boolean isPavementStep) { PilotingRollData roll = getBasePilotingRoll(moveType); addPilotingModifierForTerrain(roll, curPos, step.getBoardId(), true); if (!lastPos.equals(curPos) && - ((moveType != EntityMovementType.MOVE_JUMP) || isLastStep) && - (curHex.terrainLevel(Terrains.RUBBLE) > 0) && - !isPavementStep && - (step.getElevation() == 0) && - canFall()) { + ((moveType != EntityMovementType.MOVE_JUMP) || isLastStep) && + (curHex.terrainLevel(Terrains.RUBBLE) > 0) && + !isPavementStep && + (step.getElevation() == 0) && + canFall()) { adjustDifficultTerrainPSRModifier(roll); if (curHex.terrainLevel(Terrains.RUBBLE) > 5) { // Ultra-rubble (destroyed Castle Brian / fortress) is exceptionally hard to navigate (TacOps:AR p.37). @@ -9240,8 +9385,13 @@ public PilotingRollData checkRubbleMove(MoveStep step, EntityMovementType moveTy * Checks if the entity is moving into a hex that might cause it to bog down. If so, returns the target roll for the * piloting skill check. */ - public PilotingRollData checkBogDown(MoveStep step, EntityMovementType moveType, Hex curHex, Coords lastPos, - Coords curPos, int lastElev, boolean isPavementStep) { + public PilotingRollData checkBogDown(MoveStep step, + EntityMovementType moveType, + Hex curHex, + Coords lastPos, + Coords curPos, + int lastElev, + boolean isPavementStep) { PilotingRollData roll = getBasePilotingRoll(moveType); int bgMod = curHex.getBogDownModifier(getMovementMode(), this instanceof LargeSupportTank); @@ -9249,10 +9399,10 @@ public PilotingRollData checkBogDown(MoveStep step, EntityMovementType moveType, // "ground" (meaning the bottom of the lake), not susceptible to bog down as per getBogDownModifier, and not // on pavement if ((!lastPos.equals(curPos) || (step.getElevation() != lastElev)) && - (bgMod != TargetRoll.AUTOMATIC_SUCCESS) && - (moveType != EntityMovementType.MOVE_JUMP) && - (step.getElevation() == -curHex.depth()) && - !isPavementStep) { + (bgMod != TargetRoll.AUTOMATIC_SUCCESS) && + (moveType != EntityMovementType.MOVE_JUMP) && + (step.getElevation() == -curHex.depth()) && + !isPavementStep) { roll.append(new PilotingRollData(getId(), bgMod, "avoid bogging down")); @@ -9268,7 +9418,7 @@ public PilotingRollData checkBogDown(MoveStep step, EntityMovementType moveType, adjustDifficultTerrainPSRModifier(roll); } else { roll.addModifier(TargetRoll.CHECK_FALSE, - "Check false: Not entering bog-down terrain, or jumping/hovering over such terrain"); + "Check false: Not entering bog-down terrain, or jumping/hovering over such terrain"); } return roll; } @@ -9276,23 +9426,27 @@ public PilotingRollData checkBogDown(MoveStep step, EntityMovementType moveType, /** * Checks if the entity is moving into depth 1+ water. If so, returns the target roll for the piloting skill check. */ - public PilotingRollData checkWaterMove(MoveStep step, EntityMovementType moveType, Hex curHex, Coords lastPos, - Coords curPos, boolean isPavementStep) { + public PilotingRollData checkWaterMove(MoveStep step, + EntityMovementType moveType, + Hex curHex, + Coords lastPos, + Coords curPos, + boolean isPavementStep) { if ((curHex.terrainLevel(Terrains.WATER) > 0) && - (step.getElevation() < 0) && - !lastPos.equals(curPos) && - (moveType != EntityMovementType.MOVE_JUMP) && - (getMovementMode() != EntityMovementMode.HOVER) && - (getMovementMode() != EntityMovementMode.VTOL) && - (getMovementMode() != EntityMovementMode.NAVAL) && - (getMovementMode() != EntityMovementMode.HYDROFOIL) && - (getMovementMode() != EntityMovementMode.SUBMARINE) && - (getMovementMode() != EntityMovementMode.INF_UMU) && - (getMovementMode() != EntityMovementMode.BIPED_SWIM) && - (getMovementMode() != EntityMovementMode.QUAD_SWIM) && - (getMovementMode() != EntityMovementMode.WIGE) && - canFall() && - !isPavementStep) { + (step.getElevation() < 0) && + !lastPos.equals(curPos) && + (moveType != EntityMovementType.MOVE_JUMP) && + (getMovementMode() != EntityMovementMode.HOVER) && + (getMovementMode() != EntityMovementMode.VTOL) && + (getMovementMode() != EntityMovementMode.NAVAL) && + (getMovementMode() != EntityMovementMode.HYDROFOIL) && + (getMovementMode() != EntityMovementMode.SUBMARINE) && + (getMovementMode() != EntityMovementMode.INF_UMU) && + (getMovementMode() != EntityMovementMode.BIPED_SWIM) && + (getMovementMode() != EntityMovementMode.QUAD_SWIM) && + (getMovementMode() != EntityMovementMode.WIGE) && + canFall() && + !isPavementStep) { return checkWaterMove(curHex.terrainLevel(Terrains.WATER), moveType); } return checkWaterMove(0, moveType); @@ -9301,7 +9455,8 @@ public PilotingRollData checkWaterMove(MoveStep step, EntityMovementType moveTyp /** * Checks if the entity is moving into depth 1+ water. If so, returns the target roll for the piloting skill check. */ - public PilotingRollData checkWaterMove(int waterLevel, EntityMovementType overallMoveType) { + public PilotingRollData checkWaterMove(int waterLevel, + EntityMovementType overallMoveType) { PilotingRollData roll = getBasePilotingRoll(overallMoveType); int mod; @@ -9314,12 +9469,12 @@ public PilotingRollData checkWaterMove(int waterLevel, EntityMovementType overal } if ((waterLevel > 1) && - hasAbility(OptionsConstants.PILOT_TM_FROGMAN) && - ((this instanceof Mek) || (this instanceof ProtoMek))) { + hasAbility(OptionsConstants.PILOT_TM_FROGMAN) && + ((this instanceof Mek) || (this instanceof ProtoMek))) { roll.append(new PilotingRollData(getId(), -1, "Frogman")); } if (waterLevel > 0) { - if(!Game.rulesManager.getRulesPSR().psrForWaterEntry(overallMoveType)) { + if (!Game.rulesManager.getRulesPSR().psrForWaterEntry(overallMoveType)) { roll.addModifier(TargetRoll.CHECK_FALSE, "No roll required for walk"); } else { roll.append(new PilotingRollData(getId(), mod, "entering Depth " + waterLevel + " Water")); @@ -9336,7 +9491,8 @@ public PilotingRollData checkWaterMove(int waterLevel, EntityMovementType overal * Checks if the entity is being swarmed. If so, returns the target roll for the piloting skill check to dislodge * them. */ - public PilotingRollData checkDislodgeSwarmers(MoveStep step, EntityMovementType moveType) { + public PilotingRollData checkDislodgeSwarmers(MoveStep step, + EntityMovementType moveType) { // If we're not being swarmed, return CHECK_FALSE if (Entity.NONE == getSwarmAttackerId()) { @@ -9356,11 +9512,14 @@ public PilotingRollData checkDislodgeSwarmers(MoveStep step, EntityMovementType * other checkStuff() methods above. * * @return 0, no eligible building; 1, exiting; 2, entering; 3, both; 4, stepping on roof, 8 changing elevations - * within a building + * within a building */ - public int checkMovementInBuilding(MoveStep step, MoveStep prevStep, Coords curPos, Coords prevPos) { + public int checkMovementInBuilding(MoveStep step, + MoveStep prevStep, + Coords curPos, + Coords prevPos) { if ((prevPos == null) || - (prevPos.equals(curPos) && (!(this instanceof ProtoMek) && !(this instanceof Infantry)))) { + (prevPos.equals(curPos) && (!(this instanceof ProtoMek) && !(this instanceof Infantry)))) { return 0; } Board board = game.getBoard(step.getBoardId()); @@ -9377,18 +9536,18 @@ public int checkMovementInBuilding(MoveStep step, MoveStep prevStep, Coords curP } if ((this instanceof ProtoMek) && - (prevStep != null) && - (prevStep.getMovementType(false) == EntityMovementType.MOVE_JUMP)) { + (prevStep != null) && + (prevStep.getMovementType(false) == EntityMovementType.MOVE_JUMP)) { return 0; } // check for movement inside a hangar IBuilding curBldg = board.getBuildingAt(curPos); if ((null != curBldg) && - curBldg.isIn(prevPos) && - (curBldg.getBldgClass() == IBuilding.HANGAR) && - (curHex.terrainLevel(Terrains.BLDG_ELEV) > height()) && - (step.getElevation() < curHex.terrainLevel(Terrains.BLDG_ELEV))) { + curBldg.isIn(prevPos) && + (curBldg.getBldgClass() == IBuilding.HANGAR) && + (curHex.terrainLevel(Terrains.BLDG_ELEV) > height()) && + (step.getElevation() < curHex.terrainLevel(Terrains.BLDG_ELEV))) { return 0; } @@ -9397,8 +9556,8 @@ public int checkMovementInBuilding(MoveStep step, MoveStep prevStep, Coords curP if (step.getElevation() < curHex.terrainLevel(Terrains.BLDG_ELEV)) { rv += 2; } else if (((step.getElevation() == curHex.terrainLevel(Terrains.BLDG_ELEV)) || - (step.getElevation() == curHex.terrainLevel(Terrains.BRIDGE_ELEV))) && - (step.getMovementType(false) != EntityMovementType.MOVE_JUMP)) { + (step.getElevation() == curHex.terrainLevel(Terrains.BRIDGE_ELEV))) && + (step.getMovementType(false) != EntityMovementType.MOVE_JUMP)) { rv += 4; } // check previous hex for building @@ -9408,8 +9567,8 @@ public int checkMovementInBuilding(MoveStep step, MoveStep prevStep, Coords curP prevEl = prevStep.getElevation(); } if ((prevEl < prevHex.terrainLevel(Terrains.BLDG_ELEV)) && - ((curHex.terrainLevel(Terrains.BLDG_CLASS) != 1) || - (getHeight() >= curHex.terrainLevel(Terrains.BLDG_ELEV)))) { + ((curHex.terrainLevel(Terrains.BLDG_CLASS) != 1) || + (getHeight() >= curHex.terrainLevel(Terrains.BLDG_ELEV)))) { rv += 1; } } @@ -9425,11 +9584,11 @@ public int checkMovementInBuilding(MoveStep step, MoveStep prevStep, Coords curP // Check for changing levels within a building if (curPos.equals(prevPos) && - !step.isJumping() && - (curBldg != null) && - (prevStep != null) && - (step.getElevation() != prevStep.getElevation()) && - ((step.getType() == MoveStepType.UP) || (step.getType() == MoveStepType.DOWN))) { + !step.isJumping() && + (curBldg != null) && + (prevStep != null) && + (step.getElevation() != prevStep.getElevation()) && + ((step.getType() == MoveStepType.UP) || (step.getType() == MoveStepType.DOWN))) { rv = 8; } @@ -9445,8 +9604,10 @@ public int checkMovementInBuilding(MoveStep step, MoveStep prevStep, Coords curP /** * Calculates and returns the roll for an entity moving in buildings. */ - public PilotingRollData rollMovementInBuilding(IBuilding bldg, int distance, String why, - EntityMovementType overallMoveType) { + public PilotingRollData rollMovementInBuilding(IBuilding bldg, + int distance, + String why, + EntityMovementType overallMoveType) { PilotingRollData roll = getBasePilotingRoll(overallMoveType); if ((this instanceof Mek) && isSuperHeavy()) { @@ -9553,11 +9714,12 @@ public boolean usesTurnMode() { * @param mpUsed The total number of movement points used by the entity during the current turn. * @param currPos The position of the hex where the turn is taking place, which may modify a roll for * terrain. - * * @return True if the entity failed a driving check due to turning too sharply. */ - public PilotingRollData checkTurnModeFailure(EntityMovementType overallMoveType, int straightLineHexes, int mpUsed, - Coords currPos) { + public PilotingRollData checkTurnModeFailure(EntityMovementType overallMoveType, + int straightLineHexes, + int mpUsed, + Coords currPos) { PlanetaryConditions conditions = game.getPlanetaryConditions(); PilotingRollData roll = getBasePilotingRoll(overallMoveType); @@ -9574,17 +9736,17 @@ public PilotingRollData checkTurnModeFailure(EntityMovementType overallMoveType, } if (getWeightClass() < EntityWeightClass.WEIGHT_MEDIUM || - getWeightClass() == EntityWeightClass.WEIGHT_SMALL_SUPPORT) { + getWeightClass() == EntityWeightClass.WEIGHT_SMALL_SUPPORT) { roll.addModifier(-1, "light vehicle"); } else if (getWeightClass() == EntityWeightClass.WEIGHT_ASSAULT || - getWeightClass() == EntityWeightClass.WEIGHT_SUPER_HEAVY) { + getWeightClass() == EntityWeightClass.WEIGHT_SUPER_HEAVY) { roll.addModifier(+1, "assault vehicle"); } Hex currHex = game.getBoard().getHex(currPos); if (movementMode != EntityMovementMode.HOVER && - movementMode != EntityMovementMode.VTOL && - movementMode != EntityMovementMode.WIGE) { + movementMode != EntityMovementMode.VTOL && + movementMode != EntityMovementMode.WIGE) { if (currHex.containsTerrain(Terrains.MUD)) { roll.addModifier(+1, "mud"); } @@ -9592,8 +9754,8 @@ public PilotingRollData checkTurnModeFailure(EntityMovementType overallMoveType, roll.addModifier(movementMode == EntityMovementMode.TRACKED ? 1 : 2, "ice"); } if (conditions.isSleeting() || - conditions.getFog().isFogHeavy() || - conditions.getWeather().isHeavyRainOrGustingRainOrDownpour()) { + conditions.getFog().isFogHeavy() || + conditions.getWeather().isHeavyRainOrGustingRainOrDownpour()) { roll.addModifier(+1, "fog/rain"); } if (conditions.getWeather().isHeavySnow()) { @@ -9686,7 +9848,8 @@ public void addTransporter(Transporter component) { * @param component - One of this new entity's Transporters. * @param isOmniPod - Whether this is part of an omni unit's pod space. */ - public void addTransporter(Transporter component, boolean isOmniPod) { + public void addTransporter(Transporter component, + boolean isOmniPod) { component.setGame(game); transports.add(component); if (isOmniPod) { @@ -9710,7 +9873,8 @@ public void removeAllTransporters() { /** * Some entities will always have certain transporters. This method is overloaded to support that. */ - public void addIntrinsicTransporters() {} + public void addIntrinsicTransporters() { + } public void addRoofRack() { if (getTransports().stream().noneMatch(t -> t instanceof RoofRack)) { @@ -9724,11 +9888,11 @@ public void addRoofRack() { * * @param unit - the Entity to be loaded. * @param checkElev - Whether to compare elevations (e.g. for VTOL loading infantry) - * * @return true if the unit can be loaded, false - * otherwise. + * otherwise. */ - public boolean canLoad(Entity unit, boolean checkElev) { + public boolean canLoad(Entity unit, + boolean checkElev) { return canLoad(unit, checkElev, getElevation()); } @@ -9739,11 +9903,12 @@ public boolean canLoad(Entity unit, boolean checkElev) { * @param unit - the Entity to be loaded. * @param checkElev - Whether to compare elevations (e.g. for VTOL loading infantry) * @param height - the height at which to consider the loader - * * @return true if the unit can be loaded, false - * otherwise. + * otherwise. */ - public boolean canLoad(Entity unit, boolean checkElev, int height) { + public boolean canLoad(Entity unit, + boolean checkElev, + int height) { // For now, if it's infantry, it can't load anything. Period! if (this instanceof Infantry) { return false; @@ -9766,9 +9931,9 @@ public boolean canLoad(Entity unit, boolean checkElev, int height) { if (t instanceof ProtoMekClampMount) { hasExternalProtoMeks |= t.getUnused() == 0; hasExternalUltraHeavy |= t.getLoadedUnits() - .stream() - .anyMatch(e -> e.getWeightClass() == - EntityWeightClass.WEIGHT_SUPER_HEAVY); + .stream() + .anyMatch(e -> e.getWeightClass() == + EntityWeightClass.WEIGHT_SUPER_HEAVY); } else if (t instanceof BattleArmorHandles) { hasExternalBA |= t.getUnused() == 0; } @@ -9777,11 +9942,11 @@ public boolean canLoad(Entity unit, boolean checkElev, int height) { // We can't mix BA and ProtoMeks, and we can't mount an ultra heavy proto if already // carrying another. boolean noExternalMount = (unit.hasETypeFlag(Entity.ETYPE_BATTLEARMOR) && hasExternalProtoMeks) || - (unit.hasETypeFlag(Entity.ETYPE_PROTOMEK) && hasExternalBA); + (unit.hasETypeFlag(Entity.ETYPE_PROTOMEK) && hasExternalBA); if (unit.hasETypeFlag(Entity.ETYPE_PROTOMEK) && hasExternalProtoMeks) { noExternalMount |= hasExternalUltraHeavy || - (unit.getWeightClass() == EntityWeightClass.WEIGHT_SUPER_HEAVY); + (unit.getWeightClass() == EntityWeightClass.WEIGHT_SUPER_HEAVY); } // External Cargo cannot be loaded, it should be picked up @@ -9792,8 +9957,8 @@ public boolean canLoad(Entity unit, boolean checkElev, int height) { isLoungeOrUnknownPhase = getGame().getPhase().isLounge() || getGame().getPhase().isUnknown(); } if ((!(t instanceof ExternalCargo) || isLoungeOrUnknownPhase) && t.canLoad(unit) && - (!checkElev || unit.getElevation() == height) && - !((t instanceof BattleArmorHandles) && noExternalMount)) { + (!checkElev || unit.getElevation() == height) && + !((t instanceof BattleArmorHandles) && noExternalMount)) { return true; } } @@ -9812,10 +9977,11 @@ public boolean canLoad(Entity unit) { * Load the given unit. * * @param unit - the Entity to be loaded. - * * @throws IllegalArgumentException If the unit can't be loaded */ - public void load(Entity unit, boolean checkElev, int bayNumber) { + public void load(Entity unit, + boolean checkElev, + int bayNumber) { // Walk through this entity's transport components; find the one that can load the unit. Stop looking after // the first match. Enumeration iter = transports.elements(); @@ -9824,14 +9990,14 @@ public void load(Entity unit, boolean checkElev, int bayNumber) { boolean canLoadUnit = next.canLoad(unit); boolean elevationMatches = !checkElev || (unit.getElevation() == getElevation()); boolean bayNumberMatches = (bayNumber == UNSET_BAY) || - ((next instanceof Bay) && (((Bay) next).getBayNumber() == bayNumber)) || - ((next instanceof DockingCollar) && - (((DockingCollar) next).getCollarNumber() == bayNumber)); + ((next instanceof Bay) && (((Bay) next).getBayNumber() == bayNumber)) || + ((next instanceof DockingCollar) && + (((DockingCollar) next).getCollarNumber() == bayNumber)); // FIXME #7640: Update once we can properly specify any transporter an entity has, and properly // load into that transporter. boolean specificTransporterMatches = (bayNumber > getTransportBays().size() && - getTransports().indexOf(next) == Integer.MAX_VALUE - bayNumber); + getTransports().indexOf(next) == Integer.MAX_VALUE - bayNumber); if (canLoadUnit && elevationMatches && (bayNumberMatches || specificTransporterMatches)) { next.load(unit); @@ -9849,10 +10015,10 @@ public void load(Entity unit, boolean checkElev, int bayNumber) { * * @param unit the Entity to be loaded. * @param checkElev When true, only allows the load if both units are at the same elevation - * * @throws IllegalArgumentException If the unit can't be loaded */ - public void load(Entity unit, boolean checkElev) { + public void load(Entity unit, + boolean checkElev) { load(unit, checkElev, -1); } @@ -9861,10 +10027,10 @@ public void load(Entity unit, boolean checkElev) { * * @param unit the Entity to be loaded. * @param bayNumber The bay to load into - * * @throws IllegalArgumentException If the unit can't be loaded */ - public void load(Entity unit, int bayNumber) { + public void load(Entity unit, + int bayNumber) { load(unit, true, bayNumber); } @@ -9872,7 +10038,6 @@ public void load(Entity unit, int bayNumber) { * Load the given unit, checking if the elevation of both units is the same. * * @param unit the Entity to be loaded. - * * @throws IllegalArgumentException If the unit can't be loaded */ @Override @@ -9884,7 +10049,6 @@ public void load(Entity unit) { * Recover the given unit. Only for ASF and Small Craft * * @param unit - the Entity to be loaded. - * * @throws IllegalArgumentException If the unit can't be loaded */ public void recover(Entity unit) { @@ -9916,8 +10080,8 @@ public void recover(Entity unit) { } } throw new IllegalArgumentException(getDisplayName() + - " does not have a bay that can load" + - unit.getDisplayName()); + " does not have a bay that can load" + + unit.getDisplayName()); } /** @@ -10015,8 +10179,8 @@ public void resetPickedUpMekWarriors() { * Get a List of the units currently loaded into this payload. * * @return A List of loaded Entity units. This list will never be null, but - * it may be empty. The returned List is independent of the under-lying data structure; modifying - * one does not affect the other. + * it may be empty. The returned List is independent of the under-lying data structure; modifying + * one does not affect the other. */ @Override public List getLoadedUnits() { @@ -10049,7 +10213,6 @@ public int getDocks() { /** * @param forCost Whether this value is being used for cost calculations, in which case drop shuttle bays count as * two collars. - * * @return The number of docking collars */ public int getDocks(boolean forCost) { @@ -10165,45 +10328,42 @@ public Vector getDroppableUnits() { /** * @return All Entities that can at this point be unloaded from any of the bays of this Entity. This does not - * include any units that were loaded this turn or any bays where the door capacity has been exceeded this - * turn. Note that the returned list may be unmodifiable. - * + * include any units that were loaded this turn or any bays where the door capacity has been exceeded this + * turn. Note that the returned list may be unmodifiable. * @see #wasLoadedThisTurn() * @see Bay#canUnloadUnits() */ public List getUnitsUnloadableFromBays() { return transports.stream() - .filter(t -> t instanceof Bay) - .map(t -> (Bay) t) - .filter(Bay::canUnloadUnits) - .flatMap(b -> b.getUnloadableUnits().stream()) - .filter(e -> !e.wasLoadedThisTurn()) - .toList(); + .filter(t -> t instanceof Bay) + .map(t -> (Bay) t) + .filter(Bay::canUnloadUnits) + .flatMap(b -> b.getUnloadableUnits().stream()) + .filter(e -> !e.wasLoadedThisTurn()) + .toList(); } /** * @return All Entities that can at this point be unloaded from any transports of this Entity which are not Bays. - * This does not include any units that were loaded this turn. Note that the returned list may be - * unmodifiable. This shouldn't return towed entities, they're tracked separately. This shouldn't return - * entities transported by {@link ExternalCargo}, they should be picked up / dropped, not loaded/unloaded. - * + * This does not include any units that were loaded this turn. Note that the returned list may be + * unmodifiable. This shouldn't return towed entities, they're tracked separately. This shouldn't return + * entities transported by {@link ExternalCargo}, they should be picked up / dropped, not loaded/unloaded. * @see #getLoadedTrailers() * @see #wasLoadedThisTurn() * @see #getCarriedObjects() */ public List getUnitsUnloadableFromNonBays() { return transports.stream() - .filter(t -> !(t instanceof Bay) && !(t instanceof TankTrailerHitch) && !(t instanceof ExternalCargo)) - .flatMap(b -> b.getLoadedUnits().stream()) - .filter(e -> !e.wasLoadedThisTurn()) - .toList(); + .filter(t -> !(t instanceof Bay) && !(t instanceof TankTrailerHitch) && !(t instanceof ExternalCargo)) + .flatMap(b -> b.getLoadedUnits().stream()) + .filter(e -> !e.wasLoadedThisTurn()) + .toList(); } /** * @return All Entities that can at this point be unloaded from any transports of this Entity. This does not include - * any units that were loaded this turn nor units from bays where the door capacity has been exceeded this - * turn. - * + * any units that were loaded this turn nor units from bays where the door capacity has been exceeded this + * turn. * @see #wasLoadedThisTurn() * @see Bay#canUnloadUnits() */ @@ -10236,9 +10396,9 @@ public Vector getFighterBays() { */ public Vector getDockingCollars() { return transports.stream() - .filter(t -> t instanceof DockingCollar) - .map(t -> (DockingCollar) t) - .collect(Collectors.toCollection(Vector::new)); + .filter(t -> t instanceof DockingCollar) + .map(t -> (DockingCollar) t) + .collect(Collectors.toCollection(Vector::new)); } /** @@ -10308,10 +10468,10 @@ public Vector getLaunchableSmallCraft() { */ public List getLaunchableDropships() { return transports.stream() - .filter(t -> t instanceof DockingCollar) - .map(t -> ((DockingCollar) t).getLaunchableUnits()) - .flatMap(Collection::stream) - .collect(Collectors.toList()); + .filter(t -> t instanceof DockingCollar) + .map(t -> ((DockingCollar) t).getLaunchableUnits()) + .flatMap(Collection::stream) + .collect(Collectors.toList()); } @Override @@ -10344,7 +10504,6 @@ public double getUnused() { * Returns the current amount of cargo space for an entity of the given type. * * @param e An entity that defines the unit class - * * @return The number of units of the given type that can be loaded in this Entity */ public double getUnused(Entity e) { @@ -10379,8 +10538,8 @@ public String getUnusedString(ViewFormatting formatting) { if (formatting == ViewFormatting.HTML && (next instanceof Bay) && (((Bay) next).getBayDamage() > 0)) { result.append("").append(next.getUnusedString()).append(""); } else if (formatting == ViewFormatting.DISCORD && - (next instanceof Bay nextBay) && - (nextBay.getBayDamage() > 0)) { + (next instanceof Bay nextBay) && + (nextBay.getBayDamage() > 0)) { result.append(DiscordFormat.RED).append(next.getUnusedString()).append(DiscordFormat.RESET); } else { result.append(next.getUnusedString()); @@ -10407,17 +10566,19 @@ public String getUnusedString(ViewFormatting formatting) { } @Override - public boolean isWeaponBlockedAt(int loc, boolean isRear) { + public boolean isWeaponBlockedAt(int loc, + boolean isRear) { return transports.stream().anyMatch(transporter -> transporter.isWeaponBlockedAt(loc, isRear)); } @Override - public Entity getExteriorUnitAt(int loc, boolean isRear) { + public Entity getExteriorUnitAt(int loc, + boolean isRear) { return transports.stream() - .map(transporter -> transporter.getExteriorUnitAt(loc, isRear)) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); + .map(transporter -> transporter.getExteriorUnitAt(loc, isRear)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); } @Override @@ -10434,7 +10595,8 @@ public int getCargoMpReduction(Entity carrier) { return transports.stream().mapToInt(t -> t.getCargoMpReduction(carrier)).sum(); } - public HitData getTrooperAtLocation(HitData hit, Entity transport) { + public HitData getTrooperAtLocation(HitData hit, + Entity transport) { return rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT); } @@ -10472,7 +10634,7 @@ public int getBayPersonnel() { * Get the ID Entity that has loaded this one. * * @return the int ID of our transport. The ID may be invalid. This value should be - * Entity.NONE if this unit has not been loaded. + * Entity.NONE if this unit has not been loaded. */ public int getTransportId() { return conveyance; @@ -10484,8 +10646,8 @@ public int getTransportId() { * Subclasses are encouraged to override this method. * * @return true if this unit has a stealth system that is - * currently active, and it's actually working, false if there is no stealth system or if it is - * inactive. + * currently active, and it's actually working, false if there is no stealth system or if it is + * inactive. */ public boolean isStealthActive() { return false; @@ -10497,8 +10659,8 @@ public boolean isStealthActive() { * Subclasses are encouraged to override this method. * * @return true if this unit has a stealth system that is - * currently active, and it's actually working, false if there is no stealth system or if it is - * inactive. + * currently active, and it's actually working, false if there is no stealth system or if it is + * inactive. */ public boolean isStealthOn() { return false; @@ -10510,7 +10672,7 @@ public boolean isStealthOn() { * Subclasses are encouraged to override this method. * * @return true if this unit has a null signature system that - * is currently active, false if there is no stealth system or if it is inactive. + * is currently active, false if there is no stealth system or if it is inactive. */ public boolean isNullSigActive() { return false; @@ -10522,7 +10684,7 @@ public boolean isNullSigActive() { * Subclasses are encouraged to override this method. * * @return true if this unit has a null signature system that - * is currently active, false if there is no stealth system or if it is inactive. + * is currently active, false if there is no stealth system or if it is inactive. */ public boolean isNullSigOn() { return false; @@ -10534,7 +10696,7 @@ public boolean isNullSigOn() { * Subclasses are encouraged to override this method. * * @return true if this unit has a void signature system that - * is currently active, false if there is no stealth system or if it is inactive. + * is currently active, false if there is no stealth system or if it is inactive. */ public boolean isVoidSigActive() { return false; @@ -10546,7 +10708,7 @@ public boolean isVoidSigActive() { * Subclasses are encouraged to override this method. * * @return true if this unit has a void signature system that - * is currently active, false if there is no stealth system or if it is turned off. + * is currently active, false if there is no stealth system or if it is turned off. */ public boolean isVoidSigOn() { return false; @@ -10558,7 +10720,7 @@ public boolean isVoidSigOn() { * Subclasses are encouraged to override this method. * * @return true if this unit has a void signature system that - * is currently active, false if there is no stealth system or if it is inactive. + * is currently active, false if there is no stealth system or if it is inactive. */ public boolean isChameleonShieldActive() { return false; @@ -10570,7 +10732,7 @@ public boolean isChameleonShieldActive() { * Subclasses are encouraged to override this method. * * @return true if this unit has a void signature system that - * is currently active, false if there is no stealth system or if it is inactive. + * is currently active, false if there is no stealth system or if it is inactive. */ public boolean isChameleonShieldOn() { return false; @@ -10587,10 +10749,10 @@ public boolean isChameleonShieldOn() { * @param range - an int value that must match one of the * Compute class range constants. * @param ae - the entity making the attack, who maybe immune to certain kinds of stealth - * * @return a TargetRoll value that contains the stealth modifier for the given range. */ - public TargetRoll getStealthModifier(int range, Entity ae) { + public TargetRoll getStealthModifier(int range, + Entity ae) { TargetRoll result; // Stealth must be active. @@ -10634,7 +10796,7 @@ public void setSwarmTargetId(int id) { * Get the ID of the Entity that is the current target of a swarm attack by this unit. * * @return the int ID of the swarm attack's target The ID may be invalid. This value should be - * Entity.NONE if this unit is not swarming. + * Entity.NONE if this unit is not swarming. */ public int getSwarmTargetId() { return swarmTargetId; @@ -10655,7 +10817,7 @@ public void setSwarmAttackerId(int id) { * Get the ID of the Entity that is attacking this unit with a swarm attack. * * @return the int ID of the swarm attack's attacker The ID may be invalid. This value should be - * Entity.NONE if this unit is not being swarmed. + * Entity.NONE if this unit is not being swarmed. */ public int getSwarmAttackerId() { return swarmAttackerId; @@ -10769,7 +10931,7 @@ public void clearInfantryCombatState() { * Scans through the ammo on the unit for any inferno rounds. * * @return true if the unit is still loaded with Inferno - * rounds. false if no rounds were ever loaded or if they have all been fired. + * rounds. false if no rounds were ever loaded or if they have all been fired. */ public boolean hasInfernoAmmo() { boolean found = false; @@ -10778,20 +10940,20 @@ public boolean hasInfernoAmmo() { for (AmmoMounted amounted : getAmmo()) { AmmoType ammoType = amounted.getType(); if (((ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.SRM) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.SRM_IMP) || - (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.MML)) && - (ammoType.getMunitionType().contains(AmmoType.Munitions.M_INFERNO)) && - (amounted.getHittableShotsLeft() > 0)) { + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.SRM_IMP) || + (ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.MML)) && + (ammoType.getMunitionType().contains(AmmoType.Munitions.M_INFERNO)) && + (amounted.getHittableShotsLeft() > 0)) { found = true; } if ((ammoType.getAmmoType() == AmmoType.AmmoTypeEnum.IATM) && - (ammoType.getMunitionType().contains(AmmoType.Munitions.M_IATM_IIW)) && - (amounted.getHittableShotsLeft() > 0)) { + (ammoType.getMunitionType().contains(AmmoType.Munitions.M_IATM_IIW)) && + (amounted.getHittableShotsLeft() > 0)) { found = true; } // Incendiary LRM checks for heat-induced explosions as Inferno (TO:AUE pg 181) if (ammoType.getMunitionType().contains(AmmoType.Munitions.M_INCENDIARY_LRM) && - (amounted.getHittableShotsLeft() > 0)) { + (amounted.getHittableShotsLeft() > 0)) { found = true; } } @@ -10827,8 +10989,7 @@ public void setSalvage(boolean canSalvage) { * Determine if the unit is just combat-losses or if it has been utterly destroyed. * * @return A boolean that is true if the unit has salvageable components; if this value is - * false the unit is utterly destroyed. - * + * false the unit is utterly destroyed. * @see #isRepairable() */ public boolean isSalvage() { @@ -10839,9 +11000,8 @@ public boolean isSalvage() { * Determine if the unit can be repaired, or only harvested for spares. * * @return A boolean that is true if the unit can be repaired (given enough time and - * parts); if this value is - * false, the unit is only a source of spares. - * + * parts); if this value is + * false, the unit is only a source of spares. * @see #isSalvage() */ public boolean isRepairable() { @@ -10925,10 +11085,10 @@ public void setSpotting(boolean spotting) { */ public boolean canSpot() { return isActive() && - !isOffBoard() && - (moved != EntityMovementType.MOVE_SPRINT) && - (moved != EntityMovementType.MOVE_VTOL_SPRINT) && - (!isEvading()); + !isOffBoard() && + (moved != EntityMovementType.MOVE_SPRINT) && + (moved != EntityMovementType.MOVE_VTOL_SPRINT) && + (!isEvading()); } @Override @@ -10952,15 +11112,15 @@ public String statusToString() { str += " Jump: " + getAnyTypeMaxJumpMP(); } str += " Owner: " + - getOwner().getName() + - " Armor: " + - getTotalArmor() + - "/" + - getTotalOArmor() + - " Internal Structure: " + - getTotalInternal() + - "/" + - getTotalOInternal(); + getOwner().getName() + + " Armor: " + + getTotalArmor() + + "/" + + getTotalOArmor() + + " Internal Structure: " + + getTotalInternal() + + "/" + + getTotalOInternal(); if (!isActive()) { str += " Inactive"; @@ -10982,7 +11142,6 @@ public String statusToString() { * This returns a textual description of a specific location of the entity for visually impaired users. * * @param loc the location - * * @return a string describing the status of the location. */ public String statusToString(int loc) { @@ -10991,17 +11150,17 @@ public String statusToString(int loc) { } StringBuilder str = new StringBuilder(getLocationName(loc) + - " (" + - getLocationAbbr(loc) + - "): Armor: " + - getArmorString(loc) + - "/" + - getOArmor(loc) + - " Structure: " + - getInternalString(loc) + - "/" + - getOInternal(loc) + - "\n "); + " (" + + getLocationAbbr(loc) + + "): Armor: " + + getArmorString(loc) + + "/" + + getOArmor(loc) + + " Structure: " + + getInternalString(loc) + + "/" + + getOInternal(loc) + + "\n "); for (CriticalSlot cs : crits[loc]) { if (cs != null) { Mounted mount = cs.getMount(); @@ -11015,7 +11174,6 @@ public String statusToString(int loc) { /** * @param str a string defining the location - * * @return the status of the given location. */ public String statusToString(String str) { @@ -11117,7 +11275,6 @@ private boolean deploysWithTractor() { * Returns true if the off board entity should be deployed this round. * * @param round The current round number. - * * @return True if and only if the off board entity should deploy this round, otherwise false. */ public boolean shouldOffBoardDeploy(int round) { @@ -11138,7 +11295,7 @@ public void setUnitNumber(final short unit) { * Get the unit number of this entity. * * @return The unit number. If the entity does not belong to a unit, {@link Entity#NONE} will be - * returned. + * returned. */ public short getUnitNumber() { return unitNumber; @@ -11177,11 +11334,11 @@ public boolean canFlee(@Nullable Coords position) { */ public final boolean canFleeInState() { return (((getWalkMP() > 0) || (this instanceof Infantry)) && - !isProne() && - !isStuck() && - !isShutDown() && - !getCrew().isUnconscious() && - (getSwarmTargetId() == NONE)); + !isProne() && + !isStuck() && + !isShutDown() && + !getCrew().isUnconscious() && + (getSwarmTargetId() == NONE)); } /** @@ -11366,26 +11523,25 @@ public boolean hasDetectedEntity(Player p) { * Returns whether this Entity is a sensor return to the given player. * * @param spotter The player trying to view this unit - * * @return True if the given player can only see this Entity as a sensor return */ public boolean isSensorReturn(Player spotter) { boolean alliedUnit = !getOwner().isEnemyOf(spotter) || - (getOwner().getTeam() == spotter.getTeam() && - gameOptions().booleanOption(OptionsConstants.ADVANCED_TEAM_VISION)); + (getOwner().getTeam() == spotter.getTeam() && + gameOptions().booleanOption(OptionsConstants.ADVANCED_TEAM_VISION)); boolean sensors = (gameOptions().booleanOption(OptionsConstants.ADVANCED_TAC_OPS_SENSORS) || - gameOptions() - .booleanOption(OptionsConstants.ADVANCED_AERO_RULES_STRATOPS_ADVANCED_SENSORS)); + gameOptions() + .booleanOption(OptionsConstants.ADVANCED_AERO_RULES_STRATOPS_ADVANCED_SENSORS)); boolean sensorsDetectAll = gameOptions().booleanOption(OptionsConstants.ADVANCED_SENSORS_DETECT_ALL); boolean doubleBlind = gameOptions().booleanOption(OptionsConstants.ADVANCED_DOUBLE_BLIND); return sensors && - doubleBlind && - !alliedUnit && - !sensorsDetectAll && - !hasSeenEntity(spotter) && - hasDetectedEntity(spotter); + doubleBlind && + !alliedUnit && + !sensorsDetectAll && + !hasSeenEntity(spotter) && + hasDetectedEntity(spotter); } protected int applyGravityEffectsOnMP(int MP) { @@ -11455,7 +11611,14 @@ public boolean isEligibleFor(GamePhase phase) { if (!isDeployed() && phase.isSetArtilleryAutoHitHexes() && isEligibleForArtyAutoHitHexes()) { LOGGER.debug("Artillery Units Present and Advanced PreDesignate option enabled"); } else { - return false; + if (Game.rulesManager.getRulesGame().isWalkOnDeployment() && phase.isMovement()) { + // If they have not deployed yet and it is movement phase and walk on deployment is active + if (!Game.rulesManager.getRulesGame().canWalkOnThisRound(this)) { + return false; + } + } else { + return false; + } } } @@ -11472,8 +11635,8 @@ public boolean isEligibleFor(GamePhase phase) { // Standard ghost target mode: entities with ghost target equipment // are eligible during PRE_FIRING to assign targets return phase.isPreFiring() && hasGhostTargetEquipment() - && (game != null) - && game.usesStandardGhostTargetMode(); + && (game != null) + && game.usesStandardGhostTargetMode(); } // Hidden units shouldn't be counted for turn order, unless deploying or firing (spotting) @@ -11531,9 +11694,9 @@ public boolean isEligibleForFiring() { // if you're charging, no shooting if ((isUnjammingRAC() && Game.rulesManager.getRulesWeapons().getRACUnjamRestriction()) - || isCharging() - || isMakingDfa() - || isRamming()) { + || isCharging() + || isMakingDfa() + || isRamming()) { return false; } @@ -11584,15 +11747,15 @@ public boolean isEligibleForMovement() { // Must be active: this is slightly different from isActive(); we don't want to skip manually shutdown units // (so they can restart) boolean isActive = (!shutDown || isManualShutdown()) && - !destroyed && - getCrew().isActive() && - !unloadedThisTurn && - deployed; + !destroyed && + getCrew().isActive() && + !unloadedThisTurn && + (deployed || Game.rulesManager.getRulesGame().canWalkOnThisRound(this)); return isActive && - (!isImmobile() || - isManualShutdown() || - canUnjamRAC() || - gameOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLES_CAN_EJECT)); + (!isImmobile() || + isManualShutdown() || + canUnjamRAC() || + gameOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_VEHICLES_CAN_EJECT)); } public boolean isEligibleForOffboard() { @@ -11660,12 +11823,12 @@ public boolean isEligibleForPhysical() { } if (isCharging() || - isMakingDfa() || - isRamming() || - isOffBoard() || - isAssaultDropInProgress() || - isDropping() || - isBracing()) { + isMakingDfa() || + isRamming() || + isOffBoard() || + isAssaultDropInProgress() || + isDropping() || + isBracing()) { if (isCharging() && Game.rulesManager.getRulesPhysical().canChargeCancel() && getDisplacementAttack() instanceof ChargeAttackAction) { ChargeAttackAction chargeAttack = (ChargeAttackAction) getDisplacementAttack(); @@ -11697,9 +11860,9 @@ && getDisplacementAttack() instanceof ChargeAttackAction) { // check game options if (gameOptions().booleanOption(OptionsConstants.ALLOWED_NO_CLAN_PHYSICAL) && - getCrew().isClanPilot() && - !hasINarcPodsAttached() && - (getSwarmAttackerId() == NONE)) { + getCrew().isClanPilot() && + !hasINarcPodsAttached() && + (getSwarmAttackerId() == NONE)) { return false; } @@ -11751,9 +11914,9 @@ && getDisplacementAttack() instanceof ChargeAttackAction) { // check if we can dodge and target can attack us, then we are eligible. canHit |= ((this instanceof Mek) && - !isProne() && - hasAbility(OptionsConstants.PILOT_DODGE_MANEUVER) && - Compute.canPhysicalTarget(game, target.getId(), this)); + !isProne() && + hasAbility(OptionsConstants.PILOT_DODGE_MANEUVER) && + Compute.canPhysicalTarget(game, target.getId(), this)); } // If there are no valid Entity targets, check for add valid buildings. @@ -11792,8 +11955,8 @@ && getDisplacementAttack() instanceof ChargeAttackAction) { Coords adj = position.translated(dir); Hex adjHex = game.getBoard(boardId).getHex(adj); if (adjHex != null && (adjHex.containsTerrain(Terrains.WOODS) - || adjHex.containsTerrain(Terrains.JUNGLE)) - && WoodsClearingAttackAction.isInSawArc(this, adj)) { + || adjHex.containsTerrain(Terrains.JUNGLE)) + && WoodsClearingAttackAction.isInSawArc(this, adj)) { canHit = true; break; } @@ -11813,8 +11976,8 @@ && getDisplacementAttack() instanceof ChargeAttackAction) { */ public boolean canLayDemolitionCharges() { return (this instanceof Infantry) - && hasWorkingMisc(MiscTypeFlag.F_TOOLS, MiscTypeFlag.S_DEMOLITION_CHARGE) - && isInDemolishableStructureHex(); + && hasWorkingMisc(MiscTypeFlag.F_TOOLS, MiscTypeFlag.S_DEMOLITION_CHARGE) + && isInDemolishableStructureHex(); } /** @@ -11830,8 +11993,8 @@ public boolean isInDemolishableStructureHex() { } Hex hex = game.getHex(position, boardId); return (hex != null) && (hex.containsTerrain(Terrains.BUILDING) - || hex.containsTerrain(Terrains.BRIDGE) - || hex.containsTerrain(Terrains.FUEL_TANK)); + || hex.containsTerrain(Terrains.BRIDGE) + || hex.containsTerrain(Terrains.FUEL_TANK)); } /** @@ -11951,11 +12114,11 @@ public boolean isEligibleForInfantryVsInfantry() { /** * @return True if this Entity is eligible to pre-designate hexes as auto-hits. Per TacOps pg 180, if a player has - * off board artillery they get 5 pre-designated hexes per map sheet. + * off board artillery they get 5 pre-designated hexes per map sheet. */ public boolean isEligibleForArtyAutoHitHexes() { return isEligibleForTargetingPhase() && - (isOffBoard() || gameOptions().booleanOption(OptionsConstants.ADVANCED_COMBAT_ON_MAP_PREDESIGNATE)); + (isOffBoard() || gameOptions().booleanOption(OptionsConstants.ADVANCED_COMBAT_ON_MAP_PREDESIGNATE)); } public boolean isEligibleForTargetingPhase() { @@ -11976,7 +12139,7 @@ public boolean isEligibleForTargetingPhase() { // Artillery Attack Phase but this would be the arty damage phase and the targeting phase seems more // appropriate) if (((weaponType instanceof CapitalMissileBayWeapon) || (weaponType instanceof AR10BayWeapon)) - && mounted.isInBearingsOnlyMode()) { + && mounted.isInBearingsOnlyMode()) { return true; } @@ -12032,11 +12195,11 @@ public boolean isOffBoard() { * must not be negative. * @param direction the int direction from the board that the unit will be deployed; a valid value must * be selected from: NONE, NORTH, SOUTH, EAST, or WEST. - * * @throws IllegalArgumentException if a negative distance, an invalid direction is selected, or the distance does * not match the direction. */ - public void setOffBoard(int distance, OffBoardDirection direction) { + public void setOffBoard(int distance, + OffBoardDirection direction) { if (distance < 0) { throw new IllegalArgumentException("negative number given for distance off board"); } @@ -12071,7 +12234,7 @@ public void setOffBoard(int distance, OffBoardDirection direction) { * the distance will be zero (0). * * @return the int distance from the board the unit will be deployed (in hexes); this value will never - * be negative. + * be negative. */ public int getOffBoardDistance() { return offBoardDistance; @@ -12112,22 +12275,22 @@ public void deployOffBoard(int round) { return; case NORTH: setPosition(new Coords((game.getBoard().getWidth() / 2) + (game.getBoard().getWidth() % 2), - -getOffBoardDistance() - 1)); + -getOffBoardDistance() - 1)); setFacing(3); break; case SOUTH: setPosition(new Coords((game.getBoard().getWidth() / 2) + (game.getBoard().getWidth() % 2), - game.getBoard().getHeight() + getOffBoardDistance())); + game.getBoard().getHeight() + getOffBoardDistance())); setFacing(0); break; case EAST: setPosition(new Coords(game.getBoard().getWidth() + getOffBoardDistance(), - (game.getBoard().getHeight() / 2) + (game.getBoard().getHeight() % 2))); + (game.getBoard().getHeight() / 2) + (game.getBoard().getHeight() % 2))); setFacing(5); break; case WEST: setPosition(new Coords(-getOffBoardDistance() - 1, - (game.getBoard().getHeight() / 2) + (game.getBoard().getHeight() % 2))); + (game.getBoard().getHeight() / 2) + (game.getBoard().getHeight() % 2))); setFacing(1); break; } @@ -12171,7 +12334,7 @@ public boolean hasExternalSearchlight() { /** * @return True if the unit has a usable searchlight. It considers both externally mounted searchlights and - * internally mounted ones. + * internally mounted ones. */ public boolean hasSearchlight() { for (MiscMounted m : getMisc()) { @@ -12287,7 +12450,9 @@ public void setCanUnstickByJumping(boolean arg) { * exist. */ - public String destroy(String reason, boolean survivable, boolean canSalvage) { + public String destroy(String reason, + boolean survivable, + boolean canSalvage) { StringBuilder sb = new StringBuilder(); int condition = IEntityRemovalConditions.REMOVE_SALVAGEABLE; @@ -12326,7 +12491,8 @@ public String destroy(String reason, boolean survivable, boolean canSalvage) { * @param entityId The int id of the shooting entity * @param weaponId The int id of the shooting lrm launcher */ - public void addTargetedBySwarm(int entityId, int weaponId) { + public void addTargetedBySwarm(int entityId, + int weaponId) { hitBySwarmsEntity.addElement(entityId); hitBySwarmsWeapon.addElement(weaponId); } @@ -12336,10 +12502,10 @@ public void addTargetedBySwarm(int entityId, int weaponId) { * * @param entityId The int id of the shooting entity we are checking * @param weaponId The int id of the launcher to check - * * @return a fitting boolean value */ - public boolean getTargetedBySwarm(int entityId, int weaponId) { + public boolean getTargetedBySwarm(int entityId, + int weaponId) { for (int i = 0; i < hitBySwarmsEntity.size(); i++) { Integer entityIdToTest = hitBySwarmsEntity.elementAt(i); Integer weaponIdToTest = hitBySwarmsWeapon.elementAt(i); @@ -12416,9 +12582,8 @@ public int getLongRangeModifier() { * separate line item in the to-hit breakdown. * * @param rangeType the range type constant from {@link RangeType} - * * @return the modifier value, or 0 if the entity doesn't have Variable Range Targeting or the range type doesn't - * apply + * apply */ public int getVariableRangeTargetingModifier(int rangeType) { if (!hasVariableRangeTargeting()) { @@ -12455,7 +12620,8 @@ public void setArmorType(int armType) { } } - public void setArmorType(int armType, int loc) { + public void setArmorType(int armType, + int loc) { armorType[loc] = armType; recalculateTechAdvancement(); } @@ -12496,7 +12662,8 @@ public void setArmorType(String armType) { recalculateTechAdvancement(); } - public void setArmorType(String armType, int loc) { + public void setArmorType(String armType, + int loc) { if (!(armType.startsWith("Clan ") || armType.startsWith("IS "))) { armType = (TechConstants.isClan(getArmorTechLevel(0)) ? "Clan " : "IS ") + armType; } @@ -12536,8 +12703,8 @@ public void setStructureType(String structureType) { structureTechLevel = TechConstants.T_TECH_UNKNOWN; } else { structureTechLevel = structure.isMixedTech() - ? structure.getTechLevel(year, clanStructure) - : structure.getTechLevel(year); + ? structure.getTechLevel(year, clanStructure) + : structure.getTechLevel(year); // TODO: Is this needed? WTF is the point of it? if (structure.getNumCriticalSlots(this) == 0) { try { @@ -12566,7 +12733,8 @@ public void setArmorTechLevel(int newTL) { recalculateTechAdvancement(); } - public void setArmorTechLevel(int newTL, int loc) { + public void setArmorTechLevel(int newTL, + int loc) { armorTechLevel[loc] = newTL; recalculateTechAdvancement(); } @@ -12602,7 +12770,6 @@ public int getTaggedBy() { * the cost of ammunition on the unit. * * @param ignoreAmmo When true, the cost of ammo on the unit will be excluded from the cost - * * @return The cost in C-Bills of the 'Mek in question. */ public final double getCost(boolean ignoreAmmo) { @@ -12616,10 +12783,10 @@ public final double getCost(boolean ignoreAmmo) { * * @param calcReport A CalculationReport to write the report for the cost calculation to * @param ignoreAmmo When true, the cost of ammo on the unit will be excluded from the cost - * * @return The cost in C-Bills of the 'Mek in question. */ - public abstract double getCost(CalculationReport calcReport, boolean ignoreAmmo); + public abstract double getCost(CalculationReport calcReport, + boolean ignoreAmmo); /** * Returns a multiplier that combines multiplicative construction cost modifiers for this Entity. @@ -12646,7 +12813,9 @@ public int implicitClanCASE() { return 0; } - public boolean removePartialCoverHits(int location, int cover, int side) { + public boolean removePartialCoverHits(int location, + int cover, + int side) { if (cover > LosEffects.COVER_NONE) { switch (cover) { case LosEffects.COVER_LOW_LEFT: @@ -12661,13 +12830,13 @@ public boolean removePartialCoverHits(int location, int cover, int side) { break; case LosEffects.COVER_LEFT: if ((location == Mek.LOC_LEFT_LEG) || (location == Mek.LOC_LEFT_ARM) || (location - == Mek.LOC_LEFT_TORSO)) { + == Mek.LOC_LEFT_TORSO)) { return true; } break; case LosEffects.COVER_RIGHT: if ((location == Mek.LOC_RIGHT_LEG) || (location == Mek.LOC_RIGHT_ARM) || (location - == Mek.LOC_RIGHT_TORSO)) { + == Mek.LOC_RIGHT_TORSO)) { return true; } break; @@ -12724,7 +12893,7 @@ public int getTaintedAtmosphereExposureTurns() { /** * @return True when this unit is not allowed to be or will not survive in any hex of a ground map (unless it is - * being transported). + * being transported). */ public boolean doomedOnGround() { return false; @@ -12732,8 +12901,8 @@ public boolean doomedOnGround() { /** * @return True when this unit is not allowed to be or will not survive in any hex of a low altitude a.k.a. - * atmospheric map (unless it is being transported). Note that this has nothing to do with the atmosphere (or - * lack of it, depending on planetary conditions), only the map type and scale. + * atmospheric map (unless it is being transported). Note that this has nothing to do with the atmosphere (or + * lack of it, depending on planetary conditions), only the map type and scale. */ public boolean doomedInAtmosphere() { return true; @@ -12741,7 +12910,7 @@ public boolean doomedInAtmosphere() { /** * @return True when this unit is not allowed to be or will not survive in any hex of a space map (unless it is - * being transported). + * being transported). */ public boolean doomedInSpace() { return true; @@ -12755,7 +12924,6 @@ public boolean doomedInSpace() { * armor type have no real reason to specifically care about weight per location anyway. * * @param loc The code value for the location in question (unit type-specific). - * * @return The weight of the armor in the location in tons. */ public double getArmorWeight(int loc) { @@ -12802,7 +12970,7 @@ public boolean hasTAG() { /** * @return {@code true} if this unit mounts at least one artillery weapon (regardless of whether it is currently - * loaded or operational) + * loaded or operational) */ public boolean hasArtillery() { for (WeaponMounted weapon : getWeaponList()) { @@ -12861,15 +13029,15 @@ public boolean hasEiCockpit() { * to ensure safe defaults. * * @return the neural interface mode string, or {@link OptionsConstants#NEURAL_INTERFACE_MODE_OFF} if no game - * context or invalid value + * context or invalid value */ protected String getNeuralInterfaceMode() { return neuralInterfaceMode().optionValue(); } /** - * @return the neural interface setting this unit's game is playing under, or - * {@link NeuralInterfaceMode#OFF} when the unit is not in a game + * @return the neural interface setting this unit's game is playing under, or {@link NeuralInterfaceMode#OFF} when + * the unit is not in a game */ protected NeuralInterfaceMode neuralInterfaceMode() { return (game == null) ? NeuralInterfaceMode.OFF : NeuralInterfaceMode.from(gameOptions()); @@ -12903,10 +13071,10 @@ protected boolean isNeuralInterfaceFullTracking() { * * @param hasImplant whether the pilot has the required implant * @param hasHardware whether the unit has the required hardware - * * @return true if the neural interface is considered active */ - private boolean isNeuralInterfaceActive(boolean hasImplant, boolean hasHardware) { + private boolean isNeuralInterfaceActive(boolean hasImplant, + boolean hasHardware) { return neuralInterfaceMode().grantsBenefit(hasImplant, hasHardware); } @@ -12992,8 +13160,8 @@ public boolean hasDNICockpitMod() { */ public boolean hasDNIImplant() { return hasAbility(OptionsConstants.MD_VDNI) - || hasAbility(OptionsConstants.MD_BVDNI) - || hasAbility(OptionsConstants.MD_PROTO_DNI); + || hasAbility(OptionsConstants.MD_BVDNI) + || hasAbility(OptionsConstants.MD_PROTO_DNI); } /** @@ -13019,8 +13187,8 @@ public boolean canLayMine() { for (MiscMounted mounted : getMisc()) { MiscType type = mounted.getType(); if (!mounted.isMissing() && - !isLayingMines() && - (type.hasFlag(MiscType.F_MINE) || type.hasFlag(MiscType.F_VEHICLE_MINE_DISPENSER))) { + !isLayingMines() && + (type.hasFlag(MiscType.F_MINE) || type.hasFlag(MiscType.F_VEHICLE_MINE_DISPENSER))) { return true; } } @@ -13033,15 +13201,21 @@ public int sideTable(Coords src) { } @Override - public int sideTable(Coords src, boolean usePrior) { + public int sideTable(Coords src, + boolean usePrior) { return sideTable(src, usePrior, facing); } - public int sideTable(Coords src, boolean usePrior, int face) { + public int sideTable(Coords src, + boolean usePrior, + int face) { return sideTable(src, usePrior, face, getPosition()); } - public int sideTable(Coords src, boolean usePrior, int face, Coords effectivePos) { + public int sideTable(Coords src, + boolean usePrior, + int face, + Coords effectivePos) { if (usePrior) { effectivePos = getPriorPosition(); } @@ -13063,25 +13237,25 @@ public int sideTable(Coords src, boolean usePrior, int face, Coords effectivePos Hex curHex = board.getHex(getPosition()); if ((srcHex != null) && (curHex != null)) { LosEffects.AttackInfo ai = LosEffects.buildAttackInfo(src, - getPosition(), - boardId, - 1, - getElevation(), - srcHex.floor(), - curHex.floor()); + getPosition(), + boardId, + 1, + getElevation(), + srcHex.floor(), + curHex.floor()); ArrayList in = Coords.intervening(ai.attackPos, ai.targetPos, true); leftBetter = LosEffects.dividedLeftBetter(in, - game, - ai, - isInBuilding(), - new LosEffects()); + game, + ai, + isInBuilding(), + new LosEffects()); } } boolean targetIsTank = (this instanceof Tank) || - (gameOptions() - .booleanOption(OptionsConstants.ADVANCED_COMBAT_TAC_OPS_ADVANCED_MEK_HIT_LOCATIONS) && - (this instanceof QuadMek)); + (gameOptions() + .booleanOption(OptionsConstants.ADVANCED_COMBAT_TAC_OPS_ADVANCED_MEK_HIT_LOCATIONS) && + (this instanceof QuadMek)); if (targetIsTank) { if ((leftBetter == 1) && (fa == 150)) { return ToHitData.SIDE_REAR; @@ -13191,7 +13365,9 @@ public boolean isAssaultDropInProgress() { * @param c the coordinates where the PSR happens * @param boardId the board ID where the PSR happens */ - public void addPilotingModifierForTerrain(PilotingRollData roll, Coords c, int boardId) { + public void addPilotingModifierForTerrain(PilotingRollData roll, + Coords c, + int boardId) { addPilotingModifierForTerrain(roll, c, boardId, false); } @@ -13203,7 +13379,10 @@ public void addPilotingModifierForTerrain(PilotingRollData roll, Coords c, int b * @param boardId the board ID where the PSR happens * @param enteringRubble True if entering rubble */ - public void addPilotingModifierForTerrain(PilotingRollData roll, Coords c, int boardId, boolean enteringRubble) { + public void addPilotingModifierForTerrain(PilotingRollData roll, + Coords c, + int boardId, + boolean enteringRubble) { if ((c == null) || (roll == null) || isOffBoard() || !isDeployed()) { return; } @@ -13225,7 +13404,8 @@ public void addPilotingModifierForTerrain(PilotingRollData roll, Coords c, int b * @param roll the PSR to modify * @param step the move step the PSR occurs at */ - public void addPilotingModifierForTerrain(PilotingRollData roll, MoveStep step) { + public void addPilotingModifierForTerrain(PilotingRollData roll, + MoveStep step) { if (step.getElevation() <= 0) { addPilotingModifierForTerrain(roll, step.getPosition(), step.getBoardId()); } @@ -13252,9 +13432,9 @@ public boolean fixElevation() { if (!isElevationValid(getElevation(), game.getBoard(boardId).getHex(getPosition()))) { LOGGER.error("{} in hex {} is at invalid elevation {}", - getDisplayName(), - HexTarget.locationToId(getBoardLocation()), - getElevation()); + getDisplayName(), + HexTarget.locationToId(getBoardLocation()), + getElevation()); setElevation(-game.getBoard(boardId).getHex(getPosition()).depth()); LOGGER.error(" moved to elevation {}", getElevation()); return true; @@ -13337,7 +13517,6 @@ public void setEMI(boolean inVal) { * {@link ToHitData#SIDE_RANDOM}. * * @param side The attack direction as specified above. - * * @return The appropriate directional roll modifier. */ public int getMotiveSideMod(int side) { @@ -13383,7 +13562,9 @@ public void setHiddenActivationPhase(final GamePhase phase) { hiddenActivationPhase = phase; } - /** Returns true if this unit is currently hidden (hidden units, TW pg 259). */ + /** + * Returns true if this unit is currently hidden (hidden units, TW pg 259). + */ public boolean isHidden() { return isHidden; } @@ -13405,7 +13586,9 @@ public boolean canEjectCrew() { return false; } - /** Shared by the {@link #canEjectCrew()} overrides: a crew can only leave while it is aboard and alive. */ + /** + * Shared by the {@link #canEjectCrew()} overrides: a crew can only leave while it is aboard and alive. + */ protected boolean crewCanLeave() { return (getCrew() != null) && !getCrew().isEjected() && !getCrew().isDead(); } @@ -13419,7 +13602,7 @@ public boolean madePointblankShot() { /** * @return the phase that this hidden unit will activate in (generally this will be Game.Phase.UNKNOWN, indicating - * that the unit isn't activating). + * that the unit isn't activating). */ public GamePhase getHiddenActivationPhase() { return hiddenActivationPhase; @@ -13436,7 +13619,6 @@ public boolean isCarcass() { * Sets if this unit is a carcass. * * @param carcass true if this unit should be a carcass, false otherwise. - * * @see Entity#isCarcass */ public void setCarcass(boolean carcass) { @@ -13482,7 +13664,8 @@ public void destroyLocation(int loc) { * @param loc The location that is destroyed. * @param blownOff true if the location was blown off */ - public void destroyLocation(int loc, boolean blownOff) { + public void destroyLocation(int loc, + boolean blownOff) { // if it's already marked as destroyed, don't bother if (getInternal(loc) < 0) { return; @@ -13507,8 +13690,8 @@ public void destroyLocation(int loc, boolean blownOff) { if (cs != null) { // count engine hits for MaxTek engine explosions if ((cs.getType() == CriticalSlot.TYPE_SYSTEM) && - (cs.getIndex() == Mek.SYSTEM_ENGINE) && - !cs.isDamaged()) { + (cs.getIndex() == Mek.SYSTEM_ENGINE) && + !cs.isDamaged()) { engineHitsThisPhase++; } @@ -13544,7 +13727,7 @@ public void destroyLocation(int loc, boolean blownOff) { // it as well for (Mounted mounted : getEquipment()) { if (((mounted.getLocation() == loc) && mounted.getType().isHittable()) || - (mounted.isSplit() && (mounted.getSecondLocation() == loc))) { + (mounted.isSplit() && (mounted.getSecondLocation() == loc))) { if (blownOff) { mounted.setMissing(true); // we don't want to hit something twice here to avoid triggering things that fire off when a @@ -13586,33 +13769,40 @@ public void clearNarcAndiNarcPods() { iNarcPods.clear(); } - public PilotingRollData checkSideSlip(EntityMovementType moveType, Hex prevHex, EntityMovementType overallMoveType, - MoveStep prevStep, int prevFacing, int curFacing, Coords lastPos, Coords curPos, int distance, - boolean speedBooster) { + public PilotingRollData checkSideSlip(EntityMovementType moveType, + Hex prevHex, + EntityMovementType overallMoveType, + MoveStep prevStep, + int prevFacing, + int curFacing, + Coords lastPos, + Coords curPos, + int distance, + boolean speedBooster) { PilotingRollData roll = getBasePilotingRoll(overallMoveType); if ((moveType != EntityMovementType.MOVE_JUMP) && - (prevHex != null) && - (distance > 1) && - ((overallMoveType == EntityMovementType.MOVE_RUN) || - (overallMoveType == EntityMovementType.MOVE_VTOL_RUN) || - (overallMoveType == EntityMovementType.MOVE_SPRINT) || - (overallMoveType == EntityMovementType.MOVE_VTOL_SPRINT)) && - (prevFacing != curFacing) && - !lastPos.equals(curPos) && - !(this instanceof Infantry) && - !(this instanceof ProtoMek)) { + (prevHex != null) && + (distance > 1) && + ((overallMoveType == EntityMovementType.MOVE_RUN) || + (overallMoveType == EntityMovementType.MOVE_VTOL_RUN) || + (overallMoveType == EntityMovementType.MOVE_SPRINT) || + (overallMoveType == EntityMovementType.MOVE_VTOL_SPRINT)) && + (prevFacing != curFacing) && + !lastPos.equals(curPos) && + !(this instanceof Infantry) && + !(this instanceof ProtoMek)) { roll.append(new PilotingRollData(getId(), 0, "flanking and turning")); if (isUsingManAce()) { roll.addModifier(-1, "Maneuvering Ace"); } } else if (moveType != EntityMovementType.MOVE_JUMP && - prevFacing == curFacing && - !lastPos.equals(curPos) && - lastPos.direction(curPos) % 3 != curFacing % 3 && - !(isUsingManAce() && - (overallMoveType == EntityMovementType.MOVE_WALK || - overallMoveType == EntityMovementType.MOVE_VTOL_WALK))) { + prevFacing == curFacing && + !lastPos.equals(curPos) && + lastPos.direction(curPos) % 3 != curFacing % 3 && + !(isUsingManAce() && + (overallMoveType == EntityMovementType.MOVE_WALK || + overallMoveType == EntityMovementType.MOVE_VTOL_WALK))) { roll.append(new PilotingRollData(getId(), -1, "controlled sideslip")); } else { roll.addModifier(TargetRoll.CHECK_FALSE, "Check false: not apparently sideslipping"); @@ -13631,12 +13821,12 @@ public boolean isAirborneVTOLorWIGE() { // stuff that moves like a VTOL is flying unless at elevation 0 or on top of/in a building, if ((getMovementMode() == EntityMovementMode.VTOL) || (getMovementMode() == EntityMovementMode.WIGE)) { if ((game != null) && - (game.getBoard() != null) && - (getPosition() != null) && - (game.getBoard().getHex(getPosition()) != null) && - ((game.getBoard().getHex(getPosition()).terrainLevel(Terrains.BLDG_ELEV) >= getElevation()) || - (game.getBoard().getHex(getPosition()).terrainLevel(Terrains.BRIDGE_ELEV) >= - getElevation()))) { + (game.getBoard() != null) && + (getPosition() != null) && + (game.getBoard().getHex(getPosition()) != null) && + ((game.getBoard().getHex(getPosition()).terrainLevel(Terrains.BLDG_ELEV) >= getElevation()) || + (game.getBoard().getHex(getPosition()).terrainLevel(Terrains.BRIDGE_ELEV) >= + getElevation()))) { return false; } return getElevation() > 0; @@ -13663,11 +13853,11 @@ public boolean isCommander() { public boolean hasLinkedMGA(WeaponMounted mounted) { for (WeaponMounted m : getWeaponList()) { if ((m.getLocation() == mounted.getLocation()) && - m.getType().hasFlag(WeaponType.F_MGA) && - !(m.isDestroyed() || m.isBreached()) && - m.getBayWeapons().contains(mounted) && - m.hasModes() && - m.curMode().equals("Linked")) { + m.getType().hasFlag(WeaponType.F_MGA) && + !(m.isDestroyed() || m.isBreached()) && + m.getBayWeapons().contains(mounted) && + m.hasModes() && + m.curMode().equals("Linked")) { return true; } } @@ -13694,17 +13884,19 @@ public boolean isCapitalFighter(boolean lounge) { // If we're using the unofficial option for single fighters staying standard scale & we're not a member of a // squadron... then false. if (!lounge && - isFighter() && - gameOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_SINGLE_NO_CAP) && - !isPartOfFighterSquadron()) { + isFighter() && + gameOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_SINGLE_NO_CAP) && + !isPartOfFighterSquadron()) { return false; } return gameOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_STRATOPS_CAPITAL_FIGHTER) - && isFighter(); + && isFighter(); } - /** @return True when this unit has capital-scale armor. */ + /** + * @return True when this unit has capital-scale armor. + */ public boolean isCapitalScale() { return isCapitalFighter(); } @@ -13720,7 +13912,6 @@ public boolean usesWeaponBays() { * return the bay of the current weapon or ammo * * @param equipmentId The equipment index - * * @return The bay mount, or null if the equipment is not in a bay */ public WeaponMounted whichBay(int equipmentId) { @@ -13740,7 +13931,8 @@ public WeaponMounted whichBay(int equipmentId) { return null; } - public int getHeatInArc(int location, boolean rearMount) { + public int getHeatInArc(int location, + boolean rearMount) { int arcHeat = 0; @@ -13786,7 +13978,8 @@ public int getVelocity() { return total; } - public int chooseSide(Coords attackPos, boolean usePrior) { + public int chooseSide(Coords attackPos, + boolean usePrior) { // loop through directions and if we have a non-zero vector, then compute the target side table. If we come to // a higher vector, then replace. If we come to an equal vector then take it if it is better int thrust; @@ -13836,7 +14029,8 @@ public Vector getHeading() { return heading; } - public void setPlayerPickedPassThrough(int attackerId, Coords c) { + public void setPlayerPickedPassThrough(int attackerId, + Coords c) { if (playerPickedPassThrough == null) { playerPickedPassThrough = new HashMap<>(); } @@ -13882,7 +14076,6 @@ public void addPassedThrough(Coords c) { * Returns true if this Entity passed over the given target during its current path. * * @param target The target - * * @return True if this unit passed over the target this turn */ public boolean passedOver(Targetable target) { @@ -13911,7 +14104,8 @@ public boolean passedThrough(Coords c) { /** * Did the entity pass within a certain number of hexes of these coords? */ - public boolean passedWithin(Coords c, int dist) { + public boolean passedWithin(Coords c, + int dist) { for (Coords crd : passedThrough) { if (crd.distance(c) <= dist) { return true; @@ -13954,7 +14148,8 @@ public void resetFiringArcs() { } } - public boolean hasArcFired(int location, boolean rearMount) { + public boolean hasArcFired(int location, + boolean rearMount) { if ((null == frontArcFired) || (null == rearArcFired)) { resetFiringArcs(); } @@ -13968,7 +14163,8 @@ public boolean hasArcFired(int location, boolean rearMount) { return frontArcFired[location]; } - public void setArcFired(int location, boolean rearMount) { + public void setArcFired(int location, + boolean rearMount) { if ((null == frontArcFired) || (null == rearArcFired)) { resetFiringArcs(); } @@ -14005,10 +14201,10 @@ public void setRapidFire() { public void extendBlade(int loc) { for (MiscMounted m : getMisc()) { if ((m.getLocation() == loc) && - !m.isDestroyed() && - !m.isBreached() && - m.getType().hasFlag(MiscTypeFlag.F_CLUB) && - m.getType().hasFlag(MiscTypeFlag.S_RETRACTABLE_BLADE)) { + !m.isDestroyed() && + !m.isBreached() && + m.getType().hasFlag(MiscTypeFlag.F_CLUB) && + m.getType().hasFlag(MiscTypeFlag.S_RETRACTABLE_BLADE)) { m.setMode("extended"); return; } @@ -14028,11 +14224,11 @@ public void destroyRetractableBlade(int loc) { } Mounted m = slot.getMount(); if ((m.getLocation() == loc) && - !m.isHit() && - !m.isBreached() && - (m.getType() instanceof MiscType) && - m.getType().hasFlag(MiscTypeFlag.F_CLUB) && - m.getType().hasFlag(MiscTypeFlag.S_RETRACTABLE_BLADE)) { + !m.isHit() && + !m.isBreached() && + (m.getType() instanceof MiscType) && + m.getType().hasFlag(MiscTypeFlag.F_CLUB) && + m.getType().hasFlag(MiscTypeFlag.S_RETRACTABLE_BLADE)) { slot.setHit(true); m.setHit(true); return; @@ -14044,7 +14240,8 @@ public TeleMissileTracker getTMTracker() { return tmTracker; } - public void setGrappled(int id, boolean attacker) { + public void setGrappled(int id, + boolean attacker) { // This is implemented in subclasses. Do nothing in general. } @@ -14054,7 +14251,7 @@ public boolean isGrappleAttacker() { /** * @return The ID of a unit that this unit is grappling or grappled by; Entity.NONE if not grappling anything and - * not being grappled. + * not being grappled. */ public int getGrappled() { return Entity.NONE; @@ -14098,8 +14295,8 @@ public void setGameOptions() { for (MiscMounted misc : getMisc()) { if (misc.getType().hasFlag(MiscType.F_BAP) && - (this instanceof Aero || this instanceof LandAirMek) && - gameOpts.booleanOption(OptionsConstants.ADVANCED_AERO_RULES_STRATOPS_ECM)) { + (this instanceof Aero || this instanceof LandAirMek) && + gameOpts.booleanOption(OptionsConstants.ADVANCED_AERO_RULES_STRATOPS_ECM)) { ArrayList modes = new ArrayList<>(); String[] stringArray = {}; modes.add("Short"); @@ -14120,7 +14317,7 @@ public void setGameOptions() { modes.add("ECM & ECCM"); } } else if (gameOpts.booleanOption(OptionsConstants.ADVANCED_AERO_RULES_STRATOPS_ECM) && - (this instanceof Aero || this instanceof LandAirMek)) { + (this instanceof Aero || this instanceof LandAirMek)) { modes.add("ECCM"); if (misc.getType().hasFlag(MiscType.F_ANGEL_ECM)) { modes.add("ECM & ECCM"); @@ -14148,7 +14345,7 @@ public void setGameOptions() { // Vehicle Cockpit Command Console: add Ghost Targets mode when option is enabled if (misc.getType().hasFlag(MiscType.F_COMMAND_CONSOLE) - && gameOpts.booleanOption(OptionsConstants.ADVANCED_TAC_OPS_GHOST_TARGET)) { + && gameOpts.booleanOption(OptionsConstants.ADVANCED_TAC_OPS_GHOST_TARGET)) { ArrayList modes = new ArrayList<>(); modes.add("Default"); modes.add("Ghost Targets"); @@ -14316,22 +14513,24 @@ public boolean hasModularArmor(int loc) { return false; } - public int getDamageReductionFromModularArmor(HitData hit, int damage, Vector vDesc) { + public int getDamageReductionFromModularArmor(HitData hit, + int damage, + Vector vDesc) { int loc = hit.getLocation(); if (!hasModularArmor(loc)) { return damage; } for (MiscMounted mount : getMisc()) { if ((mount.getLocation() == loc) && - !mount.isDestroyed() && - mount.getType().hasFlag(MiscType.F_MODULAR_ARMOR) - // On `Mek torsos only, modular armor covers either front or rear, as mounted. - && - (!(this instanceof Mek) - || - !((loc == Mek.LOC_CENTER_TORSO) || (loc == Mek.LOC_LEFT_TORSO) || (loc == Mek.LOC_RIGHT_TORSO)) - || - (hit.isRear() == mount.isRearMounted()))) { + !mount.isDestroyed() && + mount.getType().hasFlag(MiscType.F_MODULAR_ARMOR) + // On `Mek torsos only, modular armor covers either front or rear, as mounted. + && + (!(this instanceof Mek) + || + !((loc == Mek.LOC_CENTER_TORSO) || (loc == Mek.LOC_LEFT_TORSO) || (loc == Mek.LOC_RIGHT_TORSO)) + || + (hit.isRear() == mount.isRearMounted()))) { int damageAbsorption = mount.getBaseDamageCapacity() - mount.getDamageTaken(); if (damageAbsorption > damage) { @@ -14400,7 +14599,7 @@ public int getGhostTargetOverride() { /** * @return the accumulated to-hit bonus for attacks AGAINST this unit from friendly ghost targets (Standard mode - * only, per TO:AR) + * only, per TO:AR) */ public int getGhostTargetDefensiveBonus() { return ghostTargetDefensiveBonus; @@ -14408,7 +14607,7 @@ public int getGhostTargetDefensiveBonus() { /** * @return the accumulated to-hit bonus for attacks BY this unit from enemy ghost targets (Standard mode only, per - * TO:AR) + * TO:AR) */ public int getGhostTargetOffensiveBonus() { return ghostTargetOffensiveBonus; @@ -14441,14 +14640,16 @@ public int getCoolantFailureAmount() { * * @param amount Amount of Coolant to add */ - public void addCoolantFailureAmount(int amount) {} + public void addCoolantFailureAmount(int amount) { + } /** * This is implemented in subclasses, do nothing in general *

* Resets the coolant failure amount */ - public void resetCoolantFailureAmount() {} + public void resetCoolantFailureAmount() { + } /** * @return the tonnage of additional mounted communications equipment @@ -14472,8 +14673,8 @@ public boolean isTransported() { /** * @return Information (range, location, strength) about ECM if the unit has active ECM or null if it doesn't. In - * the case of multiple ECCM systems, the best one takes precedence, as a unit can only have one active ECCM - * at a time. + * the case of multiple ECCM systems, the best one takes precedence, as a unit can only have one active ECCM + * at a time. */ @Nullable public ECMInfo getECMInfo() { @@ -14482,8 +14683,8 @@ public ECMInfo getECMInfo() { /** * @return Information (range, location, strength) about ECCM if the unit has active ECCM or null if it doesn't. In - * the case of multiple ECCM system, the best one takes precedence, as a unit can only have one active ECCM at - * a time. + * the case of multiple ECCM system, the best one takes precedence, as a unit can only have one active ECCM at + * a time. */ @Nullable public ECMInfo getECCMInfo() { @@ -14500,7 +14701,7 @@ public double getECMStrength() { if (m.curMode().equals("ECM")) { strength = 2; } else if ((strength < 1) && - (m.curMode().equals("ECM & ECCM") || m.curMode().equals("ECM & Ghost Targets"))) { + (m.curMode().equals("ECM & ECCM") || m.curMode().equals("ECM & Ghost Targets"))) { strength = 1; } } else if (m.getType().hasFlag(MiscType.F_ECM) && m.curMode().equals("ECM") && (strength < 1)) { @@ -14528,7 +14729,7 @@ public double getECCMStrength() { if (m.curMode().equals("ECM")) { strength = 2; } else if ((strength < 1) && - (m.curMode().equals("ECM & ECCM") || m.curMode().equals("ECCM & Ghost Targets"))) { + (m.curMode().equals("ECM & ECCM") || m.curMode().equals("ECCM & Ghost Targets"))) { strength = 1; } } else if (m.getType().hasFlag(MiscType.F_ECM) && m.curMode().equals("ECCM") && (strength < 1)) { @@ -14552,8 +14753,8 @@ public int getHQIniBonus() { int bonus = 0; for (MiscMounted misc : getMisc()) { if (misc.getType().hasFlag(MiscType.F_COMMUNICATIONS) && - misc.curMode().equals("Default") && - !misc.isInoperable()) { + misc.curMode().equals("Default") && + !misc.isInoperable()) { if (getTotalCommGearTons() >= 3) { bonus += 1; } @@ -14574,8 +14775,8 @@ public int getQuirkIniBonus() { if (hasQuirk(OptionsConstants.QUIRK_POS_BATTLE_COMP) && !getCrew().isDead() && !getCrew().isUnconscious()) { return 2; } else if (hasQuirk(OptionsConstants.QUIRK_POS_COMMAND_MEK) && - !getCrew().isDead() && - !getCrew().isUnconscious()) { + !getCrew().isDead() && + !getCrew().isUnconscious()) { return 1; } return 0; @@ -14585,7 +14786,6 @@ public int getQuirkIniBonus() { * Returns the Bay that the given ammo is associated with. * * @param ammoMounted an AmmoMounted to search for - * * @return The bay (WeaponMounted) that the ammo works with */ public WeaponMounted getBayByAmmo(AmmoMounted ammoMounted) { @@ -14613,7 +14813,8 @@ public int getVibroClaws() { * @param turns - the amount of rounds for which this Entity should be shutdown * @param baTaser - was this due to a BA taser? */ - public void taserShutdown(int turns, boolean baTaser) { + public void taserShutdown(int turns, + boolean baTaser) { setShutDown(true); taserShutdownRounds = turns; shutdownByBATaser = baTaser; @@ -14658,7 +14859,9 @@ public int getTaserFeedBackRounds() { return taserFeedBackRounds; } - public void setTaserInterference(int value, int rounds, boolean heat) { + public void setTaserInterference(int value, + int rounds, + boolean heat) { taserInterference = value; taserInterferenceRounds = rounds; taserInterferenceHeat = heat; @@ -14693,7 +14896,8 @@ public int getMagneticPulseRounds() { * @param missiles number of MP warheads that hit this unit * @param heatDivisor warheads needed per +1 heat (5 for LRM, 3 for SRM) */ - public void applyMagneticPulse(int missiles, int heatDivisor) { + public void applyMagneticPulse(int missiles, + int heatDivisor) { if ((missiles <= 0) || isConventionalInfantry()) { return; } @@ -14709,7 +14913,8 @@ public void applyMagneticPulse(int missiles, int heatDivisor) { * @param rounds Number of rounds the interference lasts * @param heat true if the entity suffers +5 heat per turn (Meks/Aero) */ - public void setEMPInterference(int rounds, boolean heat) { + public void setEMPInterference(int rounds, + boolean heat) { empInterferenceRounds = rounds; empInterferenceHeat = heat; } @@ -14762,8 +14967,8 @@ public void addIMPHits(int missiles) { /** * @return the +to-hit penalty this unit currently suffers on its own weapon attacks from iATM IMP missiles: +1 per - * 3 warheads that hit, capped at +2 (or +3 for ProtoMeks). This applies to fusion and non-fusion units alike - * (IO IMP rules). + * 3 warheads that hit, capped at +2 (or +3 for ProtoMeks). This applies to fusion and non-fusion units alike + * (IO IMP rules). */ public int getImpToHitModifier() { return getMagneticPulseState().getImpToHitModifier(isProtoMek()); @@ -14771,8 +14976,8 @@ public int getImpToHitModifier() { /** * @return the Walking/Cruise and Jumping/Thrust MP reduction this unit currently suffers from iATM IMP missiles: -1 - * per 3 warheads that hit, capped at -2 (or -3 for ProtoMeks). Non-fusion units ignore this reduction (IO IMP - * rules). + * per 3 warheads that hit, capped at -2 (or -3 for ProtoMeks). Non-fusion units ignore this reduction (IO IMP + * rules). */ public int getImpMpReduction() { return getMagneticPulseState().getImpMpReduction(hasEngine() && getEngine().isFusion(), isProtoMek()); @@ -14780,9 +14985,9 @@ public int getImpMpReduction() { /** * @return {@code true} if this unit is currently treated as standing inside a hostile standard ECM field because of - * Improved Magnetic Pulse (iATM IMP) missile hits (IO IMP rules). Becomes true once at least 3 IMP warheads - * are affecting the unit (the same threshold as the +1 to-hit effect). Applies to fusion and non-fusion units - * alike. + * Improved Magnetic Pulse (iATM IMP) missile hits (IO IMP rules). Becomes true once at least 3 IMP warheads + * are affecting the unit (the same threshold as the +1 to-hit effect). Applies to fusion and non-fusion units + * alike. */ public boolean isImpEcmAffected() { return getMagneticPulseState().isImpEcmAffected(); @@ -14853,7 +15058,8 @@ public void setBARRating(int rating) { * @param rating The BAR * @param loc The location index */ - public void setBARRating(int rating, int loc) { + public void setBARRating(int rating, + int loc) { } /** @@ -14879,7 +15085,6 @@ public boolean hasEnvironmentalSealing() { * Possibly do a ICE-Engine stall PSR (only intended for Meks, both Industrial and Battle). * * @param vPhaseReport the {@link Report} Vector containing the phase reports - * * @return a {@link Report} Vector containing the passed in reports, and any additional ones */ public Vector doCheckEngineStallRoll(Vector vPhaseReport) { @@ -14924,8 +15129,8 @@ public boolean isSurfaceNaval() { */ public boolean isNaval() { return (getMovementMode() == EntityMovementMode.NAVAL) || - (getMovementMode() == EntityMovementMode.HYDROFOIL) || - (getMovementMode() == EntityMovementMode.SUBMARINE); + (getMovementMode() == EntityMovementMode.HYDROFOIL) || + (getMovementMode() == EntityMovementMode.SUBMARINE); } /** @@ -14994,7 +15199,8 @@ public boolean isNonCanonBySource() { /** * @return true when no sourcebook entries are present or all listed sourcebooks are non-canon or cannot be loaded. */ - public static boolean isNonCanonBySource(String source, String published) { + public static boolean isNonCanonBySource(String source, + String published) { return SourceBooks.getStandardSourceBooks().isNonCanonBySource(source, published); } @@ -15023,7 +15229,6 @@ public synchronized void setQuirks(Quirks quirks) { * options for quirks. Use {@link #hasQuirk(String)} instead! * * @return This unit's quirks, independently of the game's settings - * * @see #hasQuirk(String) */ public synchronized Quirks getQuirks() { @@ -15067,9 +15272,9 @@ public boolean hasDNIInducedHardToPilot() { boolean hasActive = hasActiveDNI(); boolean result = !hasActive; LOGGER.trace("[DNI-HTP] {} - DNI mod: true, hasActiveDNI: {}, induces HTP: {}", - getDisplayName(), - hasActive, - result); + getDisplayName(), + hasActive, + result); return result; } @@ -15088,7 +15293,9 @@ public String getObsoleteQuirkValue() { return value != null ? value : ""; } - /** Marker value for legacy obsolete quirk with unknown year */ + /** + * Marker value for legacy obsolete quirk with unknown year + */ public static final String OBSOLETE_UNKNOWN_MARKER = "unknown"; /** @@ -15142,7 +15349,6 @@ public int getObsoleteYear() { * "2950,3146,3200" means obsolete 2950-3145, available 3146-3199, obsolete 3200+ * * @param checkYear The year to check - * * @return true if the unit is obsolete in that year */ public boolean isObsoleteInYear(int checkYear) { @@ -15175,7 +15381,6 @@ public boolean isObsoleteInYear(int checkYear) { * modifiers based on how long the unit has been obsolete. * * @param checkYear The year to check - * * @return The start year of the current obsolete period, or 0 if not obsolete */ public int getObsoleteYearForModifiers(int checkYear) { @@ -15207,7 +15412,6 @@ public int getObsoleteYearForModifiers(int checkYear) { * production ceased, maximum +5. * * @param gameYear The current game year - * * @return The TN modifier (0 to +5), or 0 if not obsolete */ public int getObsoleteRepairModifier(int gameYear) { @@ -15225,7 +15429,6 @@ public int getObsoleteRepairModifier(int gameYear) { * ceased, minimum 50%. * * @param gameYear The current game year - * * @return The resale multiplier (0.5 to 1.0), or 1.0 if not obsolete */ public double getObsoleteResaleModifier(int gameYear) { @@ -15325,8 +15528,8 @@ public int getRearArc() { @Override public boolean isAirborne() { return (!isDestroyed()) && (getAltitude() > 0) || - (getMovementMode() == EntityMovementMode.AERODYNE) || - (getMovementMode() == EntityMovementMode.SPHEROID); + (getMovementMode() == EntityMovementMode.AERODYNE) || + (getMovementMode() == EntityMovementMode.SPHEROID); } /** @@ -15486,7 +15689,7 @@ public Map getSecondaryPositions() { * Checks to see if this unit has a functional Blue Shield Particle Field Damper that is turned on * * @return true if the entity has a working, switched on blue - * field false otherwise + * field false otherwise */ public boolean hasActiveBlueShield() { if (!isShutDown()) { @@ -15514,8 +15717,8 @@ public boolean isDropping() { public boolean hasStealth() { // only non-patchwork stealth actually works as stealth return !hasPatchworkArmor() && - ((getArmorType(1) == EquipmentType.T_ARMOR_STEALTH) || - (getArmorType(1) == EquipmentType.T_ARMOR_STEALTH_VEHICLE)); + ((getArmorType(1) == EquipmentType.T_ARMOR_STEALTH) || + (getArmorType(1) == EquipmentType.T_ARMOR_STEALTH_VEHICLE)); } /** @@ -15540,21 +15743,21 @@ public double getPowerAmplifierWeight() { for (WeaponMounted m : getWeaponList()) { WeaponType wt = m.getType(); if ((wt.hasFlag(WeaponType.F_LASER) && (wt.getAmmoType() == AmmoType.AmmoTypeEnum.NA)) || - wt.hasFlag(WeaponType.F_PPC) || - wt.hasFlag(WeaponType.F_PLASMA) || - wt.hasFlag(WeaponType.F_PLASMA_MFUK) || - (wt.hasFlag(WeaponType.F_FLAMER) && (wt.getAmmoType() == AmmoType.AmmoTypeEnum.NA))) { + wt.hasFlag(WeaponType.F_PPC) || + wt.hasFlag(WeaponType.F_PLASMA) || + wt.hasFlag(WeaponType.F_PLASMA_MFUK) || + (wt.hasFlag(WeaponType.F_FLAMER) && (wt.getAmmoType() == AmmoType.AmmoTypeEnum.NA))) { total += m.getTonnage(); } if ((m.getLinkedBy() != null) && - (m.getLinkedBy().getType() instanceof MiscType) && - m.getLinkedBy().getType().hasFlag(MiscType.F_PPC_CAPACITOR)) { + (m.getLinkedBy().getType() instanceof MiscType) && + m.getLinkedBy().getType().hasFlag(MiscType.F_PPC_CAPACITOR)) { total += m.getLinkedBy().getTonnage(); } } for (MiscMounted m : getMisc()) { if (m.getType().hasFlag(MiscTypeFlag.F_CLUB) - && m.getType().hasFlag(MiscTypeFlag.S_SPOT_WELDER)) { + && m.getType().hasFlag(MiscTypeFlag.S_SPOT_WELDER)) { total += m.getTonnage(); } } @@ -15594,7 +15797,7 @@ public int getExtraC3BV(int baseBV) { // Only set this if they have C3 Boosted c3BoostedMultiplier = 0.05; } - if (numberOfC3Members > 1 && numberOfC3Members <=8) { + if (numberOfC3Members > 1 && numberOfC3Members <= 8) { returnBV = (int) Math.round(baseBV * ((multiplier * numberOfC3Members) + c3BoostedMultiplier)); } else if (numberOfC3Members > 8) { returnBV = (int) Math.round(baseBV * ((multiplier * 8) + c3BoostedMultiplier)); @@ -15606,7 +15809,7 @@ public int getExtraC3BV(int baseBV) { novaMembers++; } } - if (novaMembers > 1 && novaMembers <=7) { + if (novaMembers > 1 && novaMembers <= 7) { returnBV = (int) Math.round(baseBV * (multiplier * novaMembers)); } else if (novaMembers > 7) { // IO: Alternate Eras p.183: Nova CEWS BV bonus capped at 35% of unit's base BV @@ -15656,7 +15859,8 @@ public void setWigeLiftoffHover(boolean lifted) { wigeLiftoffHover = lifted; } - public void setHardenedArmorDamaged(HitData hit, boolean damaged) { + public void setHardenedArmorDamaged(HitData hit, + boolean damaged) { hardenedArmorDamaged[hit.getLocation()] = damaged; } @@ -15667,7 +15871,8 @@ public boolean isHardenedArmorDamaged(HitData hit) { return hardenedArmorDamaged[hit.getLocation()]; } - public void setLocationBlownOff(int loc, boolean damaged) { + public void setLocationBlownOff(int loc, + boolean damaged) { locationBlownOff[loc] = damaged; } @@ -15684,7 +15889,8 @@ public boolean isLocationBlownOff(int loc) { * @param loc Subclass-dependent code for the location. * @param damaged The location's "recently blown off" status. */ - public void setLocationBlownOffThisPhase(int loc, boolean damaged) { + public void setLocationBlownOffThisPhase(int loc, + boolean damaged) { locationBlownOffThisPhase[loc] = damaged; } @@ -15692,7 +15898,6 @@ public void setLocationBlownOffThisPhase(int loc, boolean damaged) { * Has the indicated location been blown off this phase (as opposed to either earlier or not at all)? * * @param loc Subclass-dependent code for the location. - * * @return The locations "recently blown off" status. */ public boolean isLocationBlownOffThisPhase(int loc) { @@ -15701,8 +15906,8 @@ public boolean isLocationBlownOffThisPhase(int loc) { /** * @return True if this unit has patchwork armor. Note that this is checked by comparing actual armor types (and - * tech level) across the locations - in other words, it is *not* a simple boolean flag; instead, the result - * will always correspond to the unit's armoring. + * tech level) across the locations - in other words, it is *not* a simple boolean flag; instead, the result + * will always correspond to the unit's armoring. */ public boolean hasPatchworkArmor() { int type = armorType[0]; @@ -15826,13 +16031,15 @@ public int getSuperchargerTarget() { return getMASCorSuperchargerTarget(nSuperchargerLevel); } - /** @return Target number taking into account game options */ + /** + * @return Target number taking into account game options + */ private int getMASCorSuperchargerTarget(int nLevel) { if ((game != null) && gameOptions().booleanOption(OptionsConstants.ADVANCED_ALTERNATE_MASC_ENHANCED) - && Game.rulesManager instanceof TWRulesManager) { + && Game.rulesManager instanceof TWRulesManager) { return ALTERNATE_MASC_FAILURE_ENHANCED[nLevel]; } else if (game != null && gameOptions().booleanOption(OptionsConstants.ADVANCED_ALTERNATE_MASC) - && Game.rulesManager instanceof TWRulesManager) { + && Game.rulesManager instanceof TWRulesManager) { return ALTERNATE_MASC_FAILURE[nLevel]; } else { return Game.rulesManager.getRulesEquipment().getMascFailure(nLevel); @@ -15845,11 +16052,11 @@ private int getMASCorSuperchargerTarget(int nLevel) { * @param md the movement path. * @param vDesc the description off the masc failure. used as output. * @param vCriticalSlots contains tuple of integer and critical slot. used as output. - * * @return true if there was a masc failure. */ - public boolean checkForMASCFailure(MovePath md, Vector vDesc, - HashMap> vCriticalSlots) { + public boolean checkForMASCFailure(MovePath md, + Vector vDesc, + HashMap> vCriticalSlots) { if (md.hasActiveMASC()) { boolean bFailure; @@ -15872,11 +16079,11 @@ public boolean checkForMASCFailure(MovePath md, Vector vDesc, * @param md the movement path. * @param vDesc the description off the Supercharger failure. used as output. * @param vCriticalSlots contains tuple of integer and critical slot. used as output. - * * @return true if there was a Supercharger failure. */ - public boolean checkForSuperchargerFailure(MovePath md, Vector vDesc, - HashMap> vCriticalSlots) { + public boolean checkForSuperchargerFailure(MovePath md, + Vector vDesc, + HashMap> vCriticalSlots) { if (md.hasActiveSupercharger()) { boolean bFailure; @@ -15899,11 +16106,11 @@ public boolean checkForSuperchargerFailure(MovePath md, Vector vDesc, * @param masc The Mounted for MASC * @param vDesc A reports vector to add reports to * @param vCriticalSlots A map to write critical slot results to - * * @return True if there is a MASC failure, false otherwise */ - private boolean doMASCOrSuperchargerFailureCheckFor(MiscMounted masc, Vector vDesc, - HashMap> vCriticalSlots) { + private boolean doMASCOrSuperchargerFailureCheckFor(MiscMounted masc, + Vector vDesc, + HashMap> vCriticalSlots) { if ((masc != null) && masc.curMode().equals("Armed")) { boolean bFailure = false; Roll diceRoll = Compute.rollD6(2); @@ -15912,9 +16119,9 @@ private boolean doMASCOrSuperchargerFailureCheckFor(MiscMounted masc, Vector 0) { // do the damage - engine critical slots @@ -15952,8 +16159,8 @@ private boolean doMASCOrSuperchargerFailureCheckFor(MiscMounted masc, Vector 0); i++) { CriticalSlot cs = getCritical(Mek.LOC_CENTER_TORSO, i); if ((cs.getType() == CriticalSlot.TYPE_SYSTEM) && - (cs.getIndex() == Mek.SYSTEM_ENGINE) && - cs.isHittable()) { + (cs.getIndex() == Mek.SYSTEM_ENGINE) && + cs.isHittable()) { vCriticalSlots.get(Mek.LOC_CENTER_TORSO).add(cs); hits--; } @@ -15974,16 +16181,17 @@ private boolean doMASCOrSuperchargerFailureCheckFor(MiscMounted masc, VectorEntity is required to make PSRs to avoid falling. */ public boolean canFall(boolean gyroLegDamage) { @@ -16490,7 +16699,8 @@ public int sideTableRam(Coords src) { return sideTableRam(src, facing); } - public int sideTableRam(Coords src, int facing) { + public int sideTableRam(Coords src, + int facing) { int fa = (getPosition().degree(src) + ((6 - facing) * 60)) % 360; if (((fa > 30) && (fa <= 90)) || ((fa < 330) && (fa >= 270))) { return Aero.RAM_TOWARD_OBL; @@ -16544,14 +16754,14 @@ public void loadQuirks(List quirks) { if (isLegacyVrtShort || isLegacyVrtLong) { quirkName = OptionsConstants.QUIRK_POS_VAR_RNG_TARG; LOGGER.info("Migrating legacy quirk '{}' to '{}' for {} {}", - quirkEntry.getQuirk(), quirkName, getChassis(), getModel()); + quirkEntry.getQuirk(), quirkName, getChassis(), getModel()); } // Activate the unit quirk. IOption option = getQuirks().getOption(quirkName); if (option == null) { LOGGER.warn("{} failed to load quirk for {} {} - Invalid quirk!", quirkEntry, getChassis(), - getModel()); + getModel()); continue; } @@ -16568,7 +16778,7 @@ public void loadQuirks(List quirks) { option.setValue(Integer.parseInt(quirkEntry.value())); } catch (NumberFormatException e) { LOGGER.warn("{} failed to parse quirk value for {} {} - Invalid number: {}", - quirkEntry, getChassis(), getModel(), quirkEntry.value()); + quirkEntry, getChassis(), getModel(), quirkEntry.value()); } } else if (option.getType() == IOption.STRING) { option.setValue(quirkEntry.value()); @@ -16583,7 +16793,7 @@ public void loadQuirks(List quirks) { } else if (option.getType() == IOption.INTEGER) { // Old unit files may have integer quirks without values - use default LOGGER.warn("Quirk {} for {} {} has no value, using default", - quirkEntry.getQuirk(), getChassis(), getModel()); + quirkEntry.getQuirk(), getChassis(), getModel()); option.setValue(option.getDefault()); } else if (option.getType() == IOption.STRING) { // Handle backward compatibility for old boolean-style obsolete quirk @@ -16592,14 +16802,14 @@ public void loadQuirks(List quirks) { // but we don't know when. Set to "unknown" as a marker value. // This marks the quirk as active but won't add invalid extinction dates. LOGGER.info("Legacy obsolete quirk found for {} {} - converting to 'unknown' marker", - getChassis(), getModel()); + getChassis(), getModel()); option.setValue("unknown"); } else if (OptionsConstants.QUIRK_POS_DIRECTIONAL_TORSO_MOUNT.equals(quirkEntry.getQuirk())) { // Legacy bare form (pre-torso-set): the boolean quirk applied the 2-point Directional // Torso Mount to every eligible torso weapon (BMM p.83), so default to all three torsos. // Units affected include the Barghest, Blitzkrieg and Omni-Marauder. LOGGER.info("[DirTorsoMount] Legacy directional_torso_mount quirk found for {} {}" - + " - defaulting to all torso locations (LT RT CT)", getChassis(), getModel()); + + " - defaulting to all torso locations (LT RT CT)", getChassis(), getModel()); option.setValue("LT RT CT"); } else { option.setValue(""); @@ -16626,7 +16836,6 @@ public void loadQuirks(List quirks) { * in a location must still be addressed. * * @param quirkEntry The weapon quirk entry - * * @return The Mounted at the specified location */ protected Mounted getEquipmentForWeaponQuirk(QuirkEntry quirkEntry) { @@ -16636,11 +16845,11 @@ protected Mounted getEquipmentForWeaponQuirk(QuirkEntry quirkEntry) { return cs.getMount(); } else { LOGGER.warn("{} failed for {} {} - Critical slot ({} - {}) did not load!", - quirkEntry, - getChassis(), - getModel(), - quirkEntry.location(), - quirkEntry.slot()); + quirkEntry, + getChassis(), + getModel(), + quirkEntry.location(), + quirkEntry.slot()); return null; } } @@ -16649,11 +16858,11 @@ protected void assignWeaponQuirk(QuirkEntry quirkEntry) { Mounted m = getEquipmentForWeaponQuirk(quirkEntry); if (m == null) { LOGGER.warn("{} failed for {} {} - Critical slot ({} - {}) is empty!", - quirkEntry, - getChassis(), - getModel(), - quirkEntry.location(), - quirkEntry.slot()); + quirkEntry, + getChassis(), + getModel(), + quirkEntry.location(), + quirkEntry.slot()); return; } @@ -16676,11 +16885,11 @@ protected void assignWeaponQuirk(QuirkEntry quirkEntry) { if (!matchFound) { LOGGER.warn("{} failed for {} {} - {} != {}", - quirkEntry, - getChassis(), - getModel(), - m.getType().getName(), - quirkEntry.weaponName()); + quirkEntry, + getChassis(), + getModel(), + m.getType().getName(), + quirkEntry.weaponName()); return; } @@ -16712,7 +16921,9 @@ public void newPhase(GamePhase phase) { setIsJumpingNow(false); } - /** @return True if this entity is completely submerged. */ + /** + * @return True if this entity is completely submerged. + */ public boolean isUnderwater() { if (!hasOccupiedHex()) { return false; @@ -16730,10 +16941,10 @@ public Hex getOccupiedHex() { */ public boolean hasOccupiedHex() { return !isOffBoard() && - (getPosition() != null) && - (game != null) && - (game.getBoard() != null) && - (game.getBoard().getHex(getPosition()) != null); + (getPosition() != null) && + (game != null) && + (game.getBoard() != null) && + (game.getBoard().getHex(getPosition()) != null); } public int getTechLevelYear() { @@ -16768,7 +16979,6 @@ public void setTargetBay(int tb) { * Convenience method that checks whether a bit is set in the entity type field. * * @param flag An ETYPE_* value - * * @return true if getEntityType() has the flag set */ public boolean hasETypeFlag(long flag) { @@ -16779,7 +16989,6 @@ public boolean hasETypeFlag(long flag) { * Given an Entity type, return the name of the major class it belongs to (eg: Mek, Aero, Tank, Infantry). * * @param typeId The type ID to get a major name for - * * @return The major class name for the given type id */ public static String getEntityMajorTypeName(long typeId) { @@ -16808,7 +17017,6 @@ public static String getEntityMajorTypeName(long typeId) { * Returns the specific entity type name for the given type id (eg: Biped Mek, Conventional Fighter, VTOL). * * @param typeId The ETYPE_* type - * * @return A String representation of the given type */ public static String getEntityTypeName(long typeId) { @@ -16877,13 +17085,18 @@ public static String getEntityTypeName(long typeId) { } } - public void damageSystem(int type, int slot, int hits) { + public void damageSystem(int type, + int slot, + int hits) { for (int loc = 0; loc < locations(); loc++) { hits -= damageSystem(type, slot, loc, hits); } } - public int damageSystem(int type, int slot, int loc, int hits) { + public int damageSystem(int type, + int slot, + int loc, + int hits) { int numHits = 0; for (int i = 0; i < getNumberOfCriticalSlots(loc); i++) { CriticalSlot cs = getCritical(loc, i); @@ -16896,8 +17109,8 @@ public int damageSystem(int type, int slot, int loc, int hits) { m = getEquipment(slot); } if (((type == CriticalSlot.TYPE_SYSTEM) && (cs.getIndex() == slot)) || - ((type == CriticalSlot.TYPE_EQUIPMENT) && - (m.equals(cs.getMount()) || m.equals(cs.getMount2())))) { + ((type == CriticalSlot.TYPE_EQUIPMENT) && + (m.equals(cs.getMount()) || m.equals(cs.getMount2())))) { if (numHits < hits) { cs.setHit(true); cs.setDestroyed(true); @@ -16940,7 +17153,6 @@ public int getMaxWeaponRange() { * and whether the target is airborne. * * @param targetIsAirborne True to assume the target is airborne - * * @return The maximum weapon range of weapons on this unit */ public int getMaxWeaponRange(boolean targetIsAirborne) { @@ -16952,8 +17164,8 @@ public int getMaxWeaponRange(boolean targetIsAirborne) { int maxRange = 0; if ((ETYPE_MEK == getEntityType()) || - (ETYPE_INFANTRY == getEntityType()) || - (ETYPE_PROTOMEK == getEntityType())) { + (ETYPE_INFANTRY == getEntityType()) || + (ETYPE_PROTOMEK == getEntityType())) { // account for physical attacks. maxRange = 1; } @@ -16973,9 +17185,9 @@ public int getMaxWeaponRange(boolean targetIsAirborne) { range = WeaponType.AIRBORNE_WEAPON_RANGES[type.getMaxRange(weapon)] * rangeMultiplier; } else { range = (game != null && gameOptions() - .booleanOption(OptionsConstants.ADVANCED_COMBAT_TAC_OPS_RANGE) ? - type.getExtremeRange() : - type.getLongRange()); + .booleanOption(OptionsConstants.ADVANCED_COMBAT_TAC_OPS_RANGE) ? + type.getExtremeRange() : + type.getLongRange()); } if (range > maxRange) { @@ -17158,7 +17370,8 @@ public int getCustomWeaponOrder(WeaponMounted weapon) { return Objects.requireNonNullElse(order, -1); } - public void setCustomWeaponOrder(WeaponMounted weapon, int order) { + public void setCustomWeaponOrder(WeaponMounted weapon, + int order) { setWeaponOrderChanged(true); int eqId = getEquipmentNum(weapon); if (eqId == -1) { @@ -17199,7 +17412,7 @@ public void setStructuralTechRating(int structuralTechRating) { this.structuralTechRating = TechRating.fromIndex(structuralTechRating); } else { LOGGER.warn("Invalid structural TechRating index {} for {} {}, defaulting to C", - structuralTechRating, getChassis(), getModel()); + structuralTechRating, getChassis(), getModel()); this.structuralTechRating = TechRating.C; } } @@ -17234,7 +17447,7 @@ public void setArmorTechRating(int armorTechRating) { this.armorTechRating = TechRating.fromIndex(armorTechRating); } else { LOGGER.warn("Invalid armor TechRating index {} for {} {}, defaulting to C", - armorTechRating, getChassis(), getModel()); + armorTechRating, getChassis(), getModel()); this.armorTechRating = TechRating.C; } } @@ -17255,7 +17468,7 @@ public void setEngineTechRating(int engineTechRating) { this.engineTechRating = TechRating.fromIndex(engineTechRating); } else { LOGGER.warn("Invalid engine TechRating index {} for {} {}, defaulting to C", - engineTechRating, getChassis(), getModel()); + engineTechRating, getChassis(), getModel()); this.engineTechRating = TechRating.C; } } @@ -17332,7 +17545,7 @@ public List getSubEntities() { * A list of all active sub-entities. In most cases, this is simply an empty list. * * @return an optional collection of sub-entities, if this entity is considered a grouping of them, pre-filtered to - * only contain active (non-destroyed and non-doomed) entities. + * only contain active (non-destroyed and non-doomed) entities. */ public List getActiveSubEntities() { return Collections.emptyList(); @@ -17373,7 +17586,7 @@ public boolean isTractor() { /** * @return A Set of Coords that need to be checked for entities that can be towed. This accounts for the hexes - * occupied by each entity in the 'train', plus hexes in front of or behind each trailer hitch. + * occupied by each entity in the 'train', plus hexes in front of or behind each trailer hitch. */ public Set getHitchLocations() { Set trailerPos = new HashSet<>(); @@ -17406,7 +17619,6 @@ public Set getHitchLocations() { * loading a saved game * * @param id - the id of the loaded Entity we're trying to find - * * @return the {@link TankTrailerHitch} corresponding to the passed-in value */ public TankTrailerHitch getHitchCarrying(int id) { @@ -17424,9 +17636,8 @@ public TankTrailerHitch getHitchCarrying(int id) { * Determines if this vehicle is currently able to tow designated trailer. * * @param trailerId - the ID of the Entity to be towed. - * * @return true if the trailer can be towed, false - * otherwise. + * otherwise. */ public boolean canTow(int trailerId) { Entity trailer = game.getEntity(trailerId); @@ -17531,7 +17742,6 @@ public boolean canTow(int trailerId) { * The unit at the back of a train, which is where a new trailer is hitched. * * @param trainMembers the tractor followed by the units it tows - * * @return the member with nothing behind it, or the first member when the train is not linked up */ private static Entity findTrainTail(List trainMembers) { @@ -17637,7 +17847,7 @@ public void towUnit(int id) { // is clearing as it goes. if (getAllTowedUnits().contains(id)) { LOGGER.warn("[Train] {} already lists {} as part of its train; the trailer was not attached again", - getDisplayName(), towed.getDisplayName()); + getDisplayName(), towed.getDisplayName()); return; } @@ -17693,7 +17903,7 @@ public void towUnit(int id) { // Nothing at the back of the train can take it. Undo the membership rather than leaving a unit that // counts as part of the train but has no hitch holding it. LOGGER.warn("[Train] {} has no free hitch for {}; the trailer was not attached", - (towingEnt == null) ? getDisplayName() : towingEnt.getDisplayName(), towed.getDisplayName()); + (towingEnt == null) ? getDisplayName() : towingEnt.getDisplayName(), towed.getDisplayName()); removeTowedUnit(id); towed.setTractor(Entity.NONE); for (Entity otherTrailer : otherTrailers) { @@ -17826,10 +18036,10 @@ public void removeTowedUnit(int id) { * Get a List of the trailers currently loaded into this payload. * * @return A List of loaded Entity units. This list will never be null, but - * it may be empty. The returned List is independent of the under-lying data structure; modifying - * one does not affect the other. - *

- * This will only return loaded trailers + * it may be empty. The returned List is independent of the under-lying data structure; modifying + * one does not affect the other. + *

+ * This will only return loaded trailers */ public List getLoadedTrailers() { List result = new ArrayList<>(); @@ -17872,15 +18082,16 @@ public List getLoadedTrailers() { * @param secondaryFacing - the int direction the turret is facing if the weapon is mounted there. * @param isRear - a boolean value stating if the given location is rear facing; if * false, the location is front facing. - * * @return true if a tractor/trailer unit is in the way, - * false if the weapon can fire. + * false if the weapon can fire. */ - public boolean isWeaponBlockedByTowing(int loc, int secondaryFacing, boolean isRear) { + public boolean isWeaponBlockedByTowing(int loc, + int secondaryFacing, + boolean isRear) { boolean tankOrHeavyTankTurret = loc == Tank.LOC_TURRET || - loc == Tank.LOC_TURRET_2 || - loc == SuperHeavyTank.LOC_TURRET || - loc == SuperHeavyTank.LOC_TURRET_2; + loc == Tank.LOC_TURRET_2 || + loc == SuperHeavyTank.LOC_TURRET || + loc == SuperHeavyTank.LOC_TURRET_2; // Per TW p205, assume our trailer is being towed from the front. if (getTowedBy() != Entity.NONE) { @@ -17905,13 +18116,13 @@ public boolean isWeaponBlockedByTowing(int loc, int secondaryFacing, boolean isR TankTrailerHitch hitch = getHitchCarrying(towed.getId()); if (hitch != null) { if ((hitch.getRearMounted()) && loc == Tank.LOC_REAR || - isRear || - loc == SuperHeavyTank.LOC_REAR || - (tankOrHeavyTankTurret && (secondaryFacing == ((getFacing() + 3) % 6)))) { + isRear || + loc == SuperHeavyTank.LOC_REAR || + (tankOrHeavyTankTurret && (secondaryFacing == ((getFacing() + 3) % 6)))) { return true; } else { return !hitch.getRearMounted() && - (loc == Tank.LOC_FRONT || (tankOrHeavyTankTurret && (secondaryFacing == getFacing()))); + (loc == Tank.LOC_FRONT || (tankOrHeavyTankTurret && (secondaryFacing == getFacing()))); } } } @@ -17923,7 +18134,6 @@ public boolean isWeaponBlockedByTowing(int loc, int secondaryFacing, boolean isR * this ability from different places, not exclusively the crew. * * @param name - name of the ability as recorded in the options - * * @return true if the entity has this ability from some source */ public boolean hasAbility(String name) { @@ -17940,10 +18150,10 @@ public boolean hasAbility(String name) { * * @param name - name of the ability as recorded in the options me * @param choice - A string indicating the given level being asked about - * * @return true if the entity has this ability at the given choice from some source */ - public boolean hasAbility(String name, String choice) { + public boolean hasAbility(String name, + String choice) { if (null != getCrew()) { return getCrew().getOptions().stringOption(name).equals(choice); } @@ -17973,7 +18183,6 @@ public Set getSensorContacts() { * Checks the sensorContacts set for a specific target's ID number * * @param targetId the ID number of the target entity to check for - * * @return true if the entity's sensorContacts set contains the passed-in target ID */ public boolean hasSensorContactFor(int targetId) { @@ -18018,7 +18227,6 @@ public Set getFiringSolutions() { * Checks the firingSolutions set for a specific target's ID number * * @param targetId the ID number of the target entity to check for - * * @return true if the entity's firingSolutions set contains the passed-in target ID */ public boolean hasFiringSolutionFor(int targetId) { @@ -18126,7 +18334,6 @@ public int getStartingWidth(boolean inheritFromOwner) { return getOwner().getStartWidth(); } } - return startingWidth; } @@ -18352,7 +18559,9 @@ public int getStrength() { return calculateBattleValue(); } - /** @return The persistent BV Calculator object for this entity. */ + /** + * @return The persistent BV Calculator object for this entity. + */ public BVCalculator getBvCalculator() { if (bvCalculator == null) { bvCalculator = BVCalculator.getBVCalculator(this); @@ -18395,8 +18604,8 @@ public List getForceGeneratorAvailability() { public void setForceGeneratorAvailability(List forceGeneratorAvailability) { this.forceGeneratorAvailability = (forceGeneratorAvailability == null) - ? new ArrayList<>() - : new ArrayList<>(forceGeneratorAvailability); + ? new ArrayList<>() + : new ArrayList<>(forceGeneratorAvailability); } /** @@ -18419,7 +18628,6 @@ public void setMissionRoles(String missionRoles) { * in a valid location or cannot be found. * * @param mounted the equipment to look for - * * @return the (first) slot number that holds the mounted or -1 if none can be found */ public int slotNumber(Mounted mounted) { @@ -18458,7 +18666,9 @@ public Image getIcon() { return icon.getImage(); } - /** Sets the embedded icon for this unit to the given base64 string. */ + /** + * Sets the embedded icon for this unit to the given base64 string. + */ public void setIcon(String icon64) { icon = new Base64Image(icon64); } @@ -18473,7 +18683,9 @@ public boolean hasEmbeddedIcon() { return !icon.isEmpty() && getTilesetModeString().isBlank(); } - /** @return The embedded icon of this unit in the full Base64Image form. */ + /** + * @return The embedded icon of this unit in the full Base64Image form. + */ public Base64Image getBase64Icon() { return icon; } @@ -18488,9 +18700,9 @@ public boolean countForStrengthSum() { */ public boolean shouldUseEdge(String option) { return (gameOptions().booleanOption(OptionsConstants.EDGE) && - getCrew() != null && - getCrew().hasEdgeRemaining() && - getCrew().getOptions().booleanOption(option)); + getCrew() != null && + getCrew().hasEdgeRemaining() && + getCrew().getOptions().booleanOption(option)); } public boolean hasFlotationHull() { @@ -18553,7 +18765,7 @@ public void setBoardId(int boardId) { /** * @return True when this unit is inside a building. Returns false when it does not have a game, is not on a board, - * its hex has no building or its elevation is below the basement or on or above the building. + * its hex has no building or its elevation is below the basement or on or above the building. */ public boolean isInBuilding() { return Compute.isInBuilding(game, elevation, position, boardId); @@ -18564,7 +18776,6 @@ public boolean isInBuilding() { * a convenience method to avoid NPEs. * * @param optionName The name of the game option, e.g. OptionsConstants.ADVANCED_AERO_RULES_STRATOPS_ECM - * * @return True when the Entity is part of a game and the option is active in that game */ protected boolean isActiveOption(String optionName) { @@ -18578,9 +18789,7 @@ protected boolean isActiveOption(String optionName) { * one. * * @param amount The damage - * * @return True if the cargo is destroyed by the damage, false otherwise - * * @see #getTonnage() */ @Override @@ -18623,8 +18832,11 @@ public Entity getAttackingEntity() { } @Override - public void processPickupStep(MoveStep step, Integer cargoPickupLocation, - TWGameManager gameManager, Entity entityPickingUpTarget, EntityMovementType overallMoveType) { + public void processPickupStep(MoveStep step, + Integer cargoPickupLocation, + TWGameManager gameManager, + Entity entityPickingUpTarget, + EntityMovementType overallMoveType) { if (entityPickingUpTarget.maxGroundObjectTonnage() >= getTonnage()) { // PSR PilotingRollData roll = entityPickingUpTarget.getBasePilotingRoll(overallMoveType); @@ -18656,8 +18868,10 @@ public void processPickupStep(MoveStep step, Integer cargoPickupLocation, } } - protected void processPickupStepEntity(MoveStep step, Integer cargoPickupLocation, TWGameManager gameManager, - Entity entityPickingUpTarget) { + protected void processPickupStepEntity(MoveStep step, + Integer cargoPickupLocation, + TWGameManager gameManager, + Entity entityPickingUpTarget) { int bayNumber = Bay.UNSET_BAY; if (cargoPickupLocation >= locations()) { @@ -18704,7 +18918,6 @@ public boolean getC3ecmAffected() { *

Manual Reference: CamOps pg 214

* * @return The recovery time in minutes. The default implementation returns 60 minutes. - * * @author Illiani * @since 0.50.10 */ @@ -18722,8 +18935,7 @@ public int getRecoveryTime() { * scenario conditions, campaign settings, or other relevant factors.

* * @return {@code true} if salvage operations can be performed, {@code false} otherwise. The default implementation - * always returns {@code false}. - * + * always returns {@code false}. * @author Illiani * @since 0.50.10 */ @@ -18740,8 +18952,7 @@ public boolean canPerformGroundSalvageOperations() { * scenario conditions, campaign settings, or other relevant factors.

* * @return {@code true} if salvage operations can be performed, {@code false} otherwise. The default implementation - * always returns {@code false}. - * + * always returns {@code false}. * @author Illiani * @since 0.50.10 */ diff --git a/megamek/src/megamek/server/totalWarfare/DeploymentProcessor.java b/megamek/src/megamek/server/totalWarfare/DeploymentProcessor.java index de295dca552..72f8d21e639 100644 --- a/megamek/src/megamek/server/totalWarfare/DeploymentProcessor.java +++ b/megamek/src/megamek/server/totalWarfare/DeploymentProcessor.java @@ -39,26 +39,14 @@ import megamek.common.Hex; import megamek.common.OffBoardDirection; -import megamek.common.board.Board; import megamek.common.board.Coords; -import megamek.common.compute.Compute; -import megamek.common.enums.BuildingType; import megamek.common.game.GameTurn; import megamek.common.net.packets.InvalidPacketDataException; import megamek.common.net.packets.Packet; import megamek.common.options.OptionsConstants; -import megamek.common.planetaryConditions.Atmosphere; import megamek.common.turns.SpecificEntityTurn; -import megamek.common.units.AbstractBuildingEntity; import megamek.common.units.Entity; -import megamek.common.units.EntityMovementMode; import megamek.common.units.IAero; -import megamek.common.units.IBuilding; -import megamek.common.units.Infantry; -import megamek.common.units.Tank; -import megamek.common.units.Terrains; -import megamek.common.units.TrainLayout; -import megamek.common.units.VTOL; import megamek.logging.MMLogger; /** @@ -75,7 +63,8 @@ public class DeploymentProcessor extends AbstractTWRuleHandler { /** * Receives a deployment packet from a Client connection. If valid, executes it and ends the current turn. */ - void receiveDeployment(Packet packet, int connId) throws InvalidPacketDataException { + void receiveDeployment(Packet packet, + int connId) throws InvalidPacketDataException { Entity entity = getGame().getEntity(packet.getIntValue(0)); if (entity == null) { @@ -111,9 +100,8 @@ void receiveDeployment(Packet packet, int connId) throws InvalidPacketDataExcept turn = getGame().getTurnForPlayer(connId); } - boolean isLegalLocation = getGame().hasBoardLocation(coords, boardId) - && getGame().getBoard(boardId).isLegalDeployment(coords, entity) - && isLegalTrainFootprint(entity, coords, boardId, nFacing); + DeploymentServerHelper deploymentServerHelper = new DeploymentServerHelper(gameManager); + boolean isLegalLocation = deploymentServerHelper.isLegalDeployment(coords, boardId, entity, nFacing); if ((turn == null) || !turn.isValid(connId, entity, getGame()) // FIXME: The combination with assault drop and the assault drop check dont look right: @@ -133,7 +121,17 @@ && getGame().getBoard(boardId).isLegalDeployment(coords, entity) } // looks like mostly everything's okay - processDeployment(entity, coords, boardId, nFacing, elevation, loadVector, assaultDrop); + boolean setDone = true; + deploymentServerHelper.processDeployment(entity, + coords, + boardId, + nFacing, + elevation, + loadVector, + assaultDrop, + setDone); + Hex hex = gameManager.getGame().getBoard(boardId).getHex(coords); + addReport(gameManager.doSetLocationsExposure(entity, hex, false, entity.getElevation())); // Update Aero sensors for a space or atmospheric game if (entity instanceof IAero aero) { @@ -154,7 +152,8 @@ && getGame().getBoard(boardId).isLegalDeployment(coords, entity) * @param packet the packet to be processed * @param connId the id for connection that received the packet. */ - void receiveDeploymentUnload(Packet packet, int connId) throws InvalidPacketDataException { + void receiveDeploymentUnload(Packet packet, + int connId) throws InvalidPacketDataException { Entity loader = getGame().getEntity(packet.getIntValue(0)); Entity loaded = getGame().getEntity(packet.getIntValue(1)); @@ -206,227 +205,13 @@ void receiveDeploymentUnload(Packet packet, int connId) throws InvalidPacketData gameManager.send(gameManager.getPacketHelper().createTurnListPacket()); } - /** - * Process a deployment packet by... deploying the entity! We load any other specified entities inside of it too. - * Also, check that the deployment is valid. - */ - private void processDeployment(Entity entity, Coords coords, int boardId, int nFacing, int elevation, - Vector loadVector, - boolean assaultDrop) { - for (Entity loaded : loadVector) { - if (loaded.getTransportId() != Entity.NONE) { - // we probably already loaded this unit in the chat lounge - continue; - } - if (loaded.getPosition() != null) { - // Something is fishy in Denmark. - LOGGER.error("{} can not load entity #{}", entity, loaded); - break; - } - // Have the deployed unit load the indicated unit. - gameManager.loadUnit(entity, loaded, loaded.getTargetBay()); - } - - /* - * deal with starting velocity for advanced movement. Probably not the - * best place to do it, but what are you going to do - */ - if (entity.isAero() && getGame().useVectorMove()) { - IAero a = (IAero) entity; - int[] v = { 0, 0, 0, 0, 0, 0 }; - - // if this is the entity's first time deploying, we want to respect the - // "velocity" setting from the lobby - if (entity.wasNeverDeployed()) { - if (a.getCurrentVelocityActual() > 0) { - v[nFacing] = a.getCurrentVelocityActual(); - entity.setVectors(v); - } - // this means the entity is coming back from off board, so we'll rotate the - // velocity vector by 180 - // and set it to 1/2 the magnitude - } else { - for (int x = 0; x < 6; x++) { - v[(x + 3) % 6] = entity.getVector(x) / 2; - } - - entity.setVectors(v); - } - } - - entity.setPosition(coords); - entity.setBoardId(boardId); - entity.setFacing(nFacing); - entity.setSecondaryFacing(nFacing); - - // For returning climb out units, restore the exit altitude - // (entity was not never deployed if returning from off-map) - if (!entity.wasNeverDeployed() && entity instanceof IAero aeroReturning) { - int exitAlt = aeroReturning.getExitAltitude(); - if (exitAlt > 0) { - elevation = exitAlt; - aeroReturning.setExitAltitude(0); // Clear after use - } - } - - // entity.isAero will check if a unit is a LAM in Fighter mode - if (entity instanceof IAero aero && entity.isAero()) { - entity.setAltitude(elevation); - if ((elevation == 0) && !entity.isSpaceborne()) { - aero.land(); - } else { - aero.liftOff(elevation); - } - } else { - entity.setElevation(elevation); - } - - Hex hex = getGame().getBoard(boardId).getHex(coords); - if (assaultDrop) { - entity.setAltitude(1); - // from the sky! - entity.setAssaultDropInProgress(true); - } else if ((entity instanceof VTOL) && (entity.getExternalUnits().isEmpty())) { - while ((Compute.stackingViolation(getGame(), entity, coords, null, entity.climbMode(), false) != null) && - (entity.getElevation() <= 500)) { - entity.setElevation(entity.getElevation() + 1); - } - } else if (entity.isAero()) { - // if the entity is airborne, then we don't want to set its - // elevation below, because that will - // default to 999 - if (entity.isAirborne()) { - entity.setElevation(0); - } - if (!entity.isSpaceborne() && entity instanceof IAero a) { - // all spheroid craft should have velocity of zero in atmosphere - // regardless of what was entered - if (a.isSpheroid() || getGame().getPlanetaryConditions() - .getAtmosphere() - .isLighterThan(Atmosphere.THIN)) { - a.setCurrentVelocity(0); - a.setNextVelocity(0); - } - // make sure that entity is above the level of the hex if in - // atmosphere - if (getGame().getBoard(boardId).isLowAltitude() - && (entity.getAltitude() <= hex.ceiling(true))) { - // you can't be grounded on low atmosphere map - entity.setAltitude(hex.ceiling(true) + 1); - } - } - } else { - IBuilding bld = getGame().getBoard(boardId).getBuildingAt(entity.getPosition()); - if ((bld != null) && (bld.getBuildingType() == BuildingType.WALL)) { - entity.setElevation(hex.terrainLevel(Terrains.BLDG_ELEV)); - } - - } - - boolean wigeFlyover = entity.getMovementMode() == EntityMovementMode.WIGE && - hex.containsTerrain(Terrains.BLDG_ELEV) && - entity.getElevation() > hex.terrainLevel(Terrains.BLDG_ELEV); - - // when first entering a building, we need to roll what type - // of basement it has - IBuilding bldg = getGame().getBoard(boardId).getBuildingAt(entity.getPosition()); - if ((bldg != null)) { - if (bldg.rollBasement(entity.getPosition(), - getGame().getBoard(boardId), - gameManager.getMainPhaseReport())) { - gameManager.sendChangedHex(entity.getPosition(), boardId); - Vector buildings = new Vector<>(); - buildings.add(bldg); - gameManager.sendChangedBuildings(buildings); - } - boolean collapse = gameManager.checkBuildingCollapseWhileMoving(bldg, entity, entity.getPosition()); - if (collapse) { - gameManager.addAffectedBldg(bldg, true); - if (wigeFlyover) { - // If the building is collapsed by a WiGE flying over it, the WiGE drops one - // level of elevation. - entity.setElevation(entity.getElevation() - 1); - } - } - } - - // If deploying a BuildingEntity, add building terrain to all hexes it occupies - if (entity instanceof AbstractBuildingEntity buildingEntity) { - buildingEntity.updateBuildingEntityHexes(boardId, gameManager); - } - - // A vehicle may only be hull-down in a fortified ("infantry-built") hex, and only hull-down-capable vehicles - // can do so (not Large Vehicles, not naval/submarine) - TO:AR p.19. Re-validate authoritatively here and clear - // an illegal hull-down state, because combat code such as Tank.rollHitLocation keys off isHullDown() without - // re-checking terrain, so a state from an old/crafted client or corrupted save could otherwise grant the - // hull-down hit-location benefit on open ground. - if ((entity instanceof Tank deployingVehicle) && entity.isHullDown()) { - boolean fortifiedHex = hex.containsTerrain(Terrains.FORTIFIED); - if (!deployingVehicle.isHullDownCapable() || !fortifiedHex) { - entity.setHullDown(false); - LOGGER.debug("[HullDown] {}: cleared illegal deploy hull-down - {}", entity.getDisplayName(), - !deployingVehicle.isHullDownCapable() ? "vehicle type cannot hull down" : "deploy hex is not fortified"); - } else { - LOGGER.debug("[HullDown] {}: deployed hull-down on a fortified hex", entity.getDisplayName()); - } - } - - // Infantry deploying onto a fortified hex already gets the dug-in cover from the terrain, so it cannot also be - // separately dug in (the two postures don't stack - TO:AR p.106 / TO:AUE p.153). Keep the fortified-hex state - // and clear the redundant dug-in, mirroring what completeFortification does for co-located infantry. - if ((entity instanceof Infantry deployingInfantry) - && (deployingInfantry.getDugIn() != Infantry.DUG_IN_NONE) - && hex.containsTerrain(Terrains.FORTIFIED)) { - deployingInfantry.setDugIn(Infantry.DUG_IN_NONE); - LOGGER.debug("[Fortify] {}: cleared redundant dug-in - deployed onto a fortified hex", - entity.getDisplayName()); - } - - entity.setDone(true); - entity.setDeployed(true); - gameManager.entityUpdate(entity.getId()); - addReport(gameManager.doSetLocationsExposure(entity, hex, false, entity.getElevation())); - - deployTowedTrailers(entity); - } - - /** - * Whether every hex a train would occupy is a legal deployment hex, not just the tractor's own. - *

- * The client refuses such a placement before sending it, but the placement arrives from the client, so the server - * has to decide for itself rather than trust it. A unit towing nothing is always its own footprint. - *

- * - * @param tractor the unit being deployed - * @param coords the hex it is being placed in - * @param boardId the board it is being placed on - * @param facing the facing it is being placed with - * - * @return true when the whole train fits in the deployment zone - */ - private boolean isLegalTrainFootprint(Entity tractor, Coords coords, int boardId, int facing) { - if (tractor.getAllTowedUnits().isEmpty()) { - return true; - } - - Board board = getGame().getBoard(boardId); - for (Coords trainHex : TrainLayout.deploymentFootprint(getGame(), tractor, coords, facing)) { - if (!getGame().hasBoardLocation(trainHex, boardId) || !board.isLegalDeployment(trainHex, tractor)) { - LOGGER.warn("[Train] rejected deployment of {} at {} facing {}: trailer hex {} is not a legal " - + "deployment hex", tractor.getDisplayName(), coords, facing, trainHex); - return false; - } - } - return true; - } - /** * Gives every trailer the off board edge and distance of the tractor towing it, so a train goes off board as a * whole rather than being split between the board and the map edge. *

* Runs before deployment turns are generated. Without it a trailer behind an off board tractor would never be - * placed at all: an off board tractor takes no deployment turn, so nothing would run to position its trailers, - * and the trailer no longer deploys itself. See RFE #8506. + * placed at all: an off board tractor takes no deployment turn, so nothing would run to position its trailers, and + * the trailer no longer deploys itself. See RFE #8506. *

*/ void followTractorsOffBoard() { @@ -454,80 +239,4 @@ void followTractorsOffBoard() { trailersFollowingOffBoard.size(), String.join(", ", trailersFollowingOffBoard)); } } - - /** - * Places the trailers of a train when its tractor deploys. Trailers get no deployment turn of their own, so this - * is the only chance they have to reach the board. - * - * @param tractor the unit that has just been deployed - */ - private void deployTowedTrailers(Entity tractor) { - if (tractor.getAllTowedUnits().isEmpty()) { - return; - } - - // A hitched train deploys wherever its tractor does, so a trailer keeps no off board setting of its own. The - // lobby does not let one be set on a hitched trailer, but it can be set before the trailer is hitched, so - // clear it here rather than leaving the trailer thinking it belongs somewhere else. - clearTrailerOffBoardSettings(tractor); - - int trailerCount = tractor.getAllTowedUnits().size(); - List trainPath = TrainLayout.deploymentPath(tractor.getPosition(), tractor.getFacing(), trailerCount); - List trainFacings = new ArrayList<>(); - for (int step = 0; step < trainPath.size(); step++) { - trainFacings.add(tractor.getFacing()); - } - - List placements = TrainLayout.computeLayout(getGame(), tractor, - tractor.getPosition(), tractor.getFacing(), trainPath, trainFacings); - - // The footprint was checked against the deployment zone in receiveDeployment, before the tractor was placed. - - TrainLayout.applyLayout(getGame(), placements); - - for (TrainLayout.TrainPlacement placement : placements) { - Entity trailer = getGame().getEntity(placement.entityId()); - if (trailer == null) { - continue; - } - trailer.setBoardId(tractor.getBoardId()); - trailer.setElevation(tractor.getElevation()); - trailer.setSecondaryFacing(trailer.getFacing()); - trailer.setDone(true); - trailer.setDeployed(true); - gameManager.entityUpdate(trailer.getId()); - } - - LOGGER.info("[Train] {} deployed at {} with {} trailer(s) placed behind it", - tractor.getDisplayName(), tractor.getPosition(), trailerCount); - } - - /** - * Clears any off board setting left on the trailers of a train that is deploying. - *

- * A hitched train goes wherever its tractor goes, so a trailer never carries a deployment of its own. The lobby - * refuses to set one on a hitched trailer, but the setting can survive from before the trailer was hitched, so - * it is cleared here and logged rather than quietly changing where the unit ends up. - *

- * - * @param tractor the unit that has just been deployed - */ - void clearTrailerOffBoardSettings(Entity tractor) { - List clearedTrailers = new ArrayList<>(); - - for (int towedId : tractor.getAllTowedUnits()) { - Entity trailer = getGame().getEntity(towedId); - - if ((trailer != null) && trailer.isOffBoard()) { - trailer.setOffBoard(0, OffBoardDirection.NONE); - clearedTrailers.add(trailer.getDisplayName()); - } - } - - if (!clearedTrailers.isEmpty()) { - LOGGER.info("[Train] {} trailer(s) had an off board setting of their own; a train deploys with {}: {}", - clearedTrailers.size(), tractor.getDisplayName(), String.join(", ", clearedTrailers)); - } - } - } diff --git a/megamek/src/megamek/server/totalWarfare/DeploymentServerHelper.java b/megamek/src/megamek/server/totalWarfare/DeploymentServerHelper.java new file mode 100644 index 00000000000..aa7a677baeb --- /dev/null +++ b/megamek/src/megamek/server/totalWarfare/DeploymentServerHelper.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + * + * NOTICE: The MegaMek organization is a non-profit group of volunteers + * creating free software for the BattleTech community. + * + * MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks + * of The Topps Company, Inc. All Rights Reserved. + * + * Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of + * InMediaRes Productions, LLC. + * + * MechWarrior Copyright Microsoft Corporation. MegaMek was created under + * Microsoft's "Game Content Usage Rules" + * and it is not endorsed by or + * affiliated with Microsoft. + */ + +package megamek.server.totalWarfare; + +import java.util.List; +import java.util.Vector; + +import megamek.common.Hex; +import megamek.common.OffBoardDirection; +import megamek.common.board.Board; +import megamek.common.board.Coords; +import megamek.common.compute.Compute; +import megamek.common.enums.BuildingType; +import megamek.common.game.Game; +import megamek.common.planetaryConditions.Atmosphere; +import megamek.common.units.*; +import megamek.logging.MMLogger; + +/** + * Helper class for processing deployment whether it is from Deployment Phase or Movement Phase. + */ +public class DeploymentServerHelper { + + private static final MMLogger LOGGER = MMLogger.create(DeploymentServerHelper.class); + private final TWGameManager gameManager; + + DeploymentServerHelper(TWGameManager gameManager) { + this.gameManager = gameManager; + } + + /** + * Process a deployment packet by... deploying the entity! We load any other specified entities inside of it too. + * Also, check that the deployment is valid. + */ + public void processDeployment(Entity entity, + Coords coords, + int boardId, + int nFacing, + int elevation, + Vector loadVector, + boolean assaultDrop, + boolean setDone) { + for (Entity loaded : loadVector) { + if (loaded.getTransportId() != Entity.NONE) { + // we probably already loaded this unit in the chat lounge + continue; + } + if (loaded.getPosition() != null) { + // Something is fishy in Denmark. + LOGGER.error("{} can not load entity #{}", entity, loaded); + break; + } + // Have the deployed unit load the indicated unit. + gameManager.loadUnit(entity, loaded, loaded.getTargetBay()); + } + + /* + * deal with starting velocity for advanced movement. Probably not the + * best place to do it, but what are you going to do + */ + if (entity.isAero() && gameManager.getGame().useVectorMove()) { + IAero a = (IAero) entity; + int[] v = { 0, 0, 0, 0, 0, 0 }; + + // if this is the entity's first time deploying, we want to respect the + // "velocity" setting from the lobby + if (entity.wasNeverDeployed()) { + if (a.getCurrentVelocityActual() > 0) { + v[nFacing] = a.getCurrentVelocityActual(); + entity.setVectors(v); + } + // this means the entity is coming back from off board, so we'll rotate the + // velocity vector by 180 + // and set it to 1/2 the magnitude + } else { + for (int x = 0; x < 6; x++) { + v[(x + 3) % 6] = entity.getVector(x) / 2; + } + + entity.setVectors(v); + } + } + + entity.setPosition(coords); + entity.setBoardId(boardId); + entity.setFacing(nFacing); + entity.setSecondaryFacing(nFacing); + + // For returning climb out units, restore the exit altitude + // (entity was not never deployed if returning from off-map) + if (!entity.wasNeverDeployed() && entity instanceof IAero aeroReturning) { + int exitAlt = aeroReturning.getExitAltitude(); + if (exitAlt > 0) { + elevation = exitAlt; + aeroReturning.setExitAltitude(0); // Clear after use + } + } + + // entity.isAero will check if a unit is a LAM in Fighter mode + if (entity instanceof IAero aero && entity.isAero()) { + entity.setAltitude(elevation); + if ((elevation == 0) && !entity.isSpaceborne()) { + aero.land(); + } else { + aero.liftOff(elevation); + } + } else { + entity.setElevation(elevation); + } + + Hex hex = gameManager.getGame().getBoard(boardId).getHex(coords); + if (assaultDrop) { + entity.setAltitude(1); + // from the sky! + entity.setAssaultDropInProgress(true); + } else if ((entity instanceof VTOL) && (entity.getExternalUnits().isEmpty())) { + while ((Compute.stackingViolation(gameManager.getGame(), + entity, + coords, + null, + entity.climbMode(), + false) != null) && + (entity.getElevation() <= 500)) { + entity.setElevation(entity.getElevation() + 1); + } + } else if (entity.isAero()) { + // if the entity is airborne, then we don't want to set its + // elevation below, because that will + // default to 999 + if (entity.isAirborne()) { + entity.setElevation(0); + } + if (!entity.isSpaceborne() && entity instanceof IAero a) { + // all spheroid craft should have velocity of zero in atmosphere + // regardless of what was entered + if (a.isSpheroid() || gameManager.getGame().getPlanetaryConditions() + .getAtmosphere() + .isLighterThan(Atmosphere.THIN)) { + a.setCurrentVelocity(0); + a.setNextVelocity(0); + } + // make sure that entity is above the level of the hex if in + // atmosphere + if (gameManager.getGame().getBoard(boardId).isLowAltitude() + && (entity.getAltitude() <= hex.ceiling(true))) { + // you can't be grounded on low atmosphere map + entity.setAltitude(hex.ceiling(true) + 1); + } + } + } else { + IBuilding bld = gameManager.getGame() + .getBoard(boardId) + .getBuildingAt(entity.getPosition()); + if ((bld != null) && (bld.getBuildingType() == BuildingType.WALL)) { + entity.setElevation(hex.terrainLevel(Terrains.BLDG_ELEV)); + } + + } + + boolean wigeFlyover = entity.getMovementMode() == EntityMovementMode.WIGE && + hex.containsTerrain(Terrains.BLDG_ELEV) && + entity.getElevation() > hex.terrainLevel(Terrains.BLDG_ELEV); + + // when first entering a building, we need to roll what type + // of basement it has + IBuilding bldg = gameManager.getGame() + .getBoard(boardId) + .getBuildingAt(entity.getPosition()); + if ((bldg != null)) { + if (bldg.rollBasement(entity.getPosition(), + gameManager.getGame().getBoard(boardId), + gameManager.getMainPhaseReport())) { + gameManager.sendChangedHex(entity.getPosition(), boardId); + Vector buildings = new Vector<>(); + buildings.add(bldg); + gameManager.sendChangedBuildings(buildings); + } + boolean collapse = gameManager.checkBuildingCollapseWhileMoving(bldg, entity, entity.getPosition()); + if (collapse) { + gameManager.addAffectedBldg(bldg, true); + if (wigeFlyover) { + // If the building is collapsed by a WiGE flying over it, the WiGE drops one + // level of elevation. + entity.setElevation(entity.getElevation() - 1); + } + } + } + + // If deploying a BuildingEntity, add building terrain to all hexes it occupies + if (entity instanceof AbstractBuildingEntity buildingEntity) { + buildingEntity.updateBuildingEntityHexes(boardId, gameManager); + } + + // A vehicle may only be hull-down in a fortified ("infantry-built") hex, and only hull-down-capable vehicles + // can do so (not Large Vehicles, not naval/submarine) - TO:AR p.19. Re-validate authoritatively here and clear + // an illegal hull-down state, because combat code such as Tank.rollHitLocation keys off isHullDown() without + // re-checking terrain, so a state from an old/crafted client or corrupted save could otherwise grant the + // hull-down hit-location benefit on open ground. + if ((entity instanceof Tank deployingVehicle) && entity.isHullDown()) { + boolean fortifiedHex = hex.containsTerrain(Terrains.FORTIFIED); + if (!deployingVehicle.isHullDownCapable() || !fortifiedHex) { + entity.setHullDown(false); + LOGGER.debug("[HullDown] {}: cleared illegal deploy hull-down - {}", entity.getDisplayName(), + !deployingVehicle.isHullDownCapable() ? + "vehicle type cannot hull down" : + "deploy hex is not fortified"); + } else { + LOGGER.debug("[HullDown] {}: deployed hull-down on a fortified hex", entity.getDisplayName()); + } + } + + // Infantry deploying onto a fortified hex already gets the dug-in cover from the terrain, so it cannot also be + // separately dug in (the two postures don't stack - TO:AR p.106 / TO:AUE p.153). Keep the fortified-hex state + // and clear the redundant dug-in, mirroring what completeFortification does for co-located infantry. + if ((entity instanceof Infantry deployingInfantry) + && (deployingInfantry.getDugIn() != Infantry.DUG_IN_NONE) + && hex.containsTerrain(Terrains.FORTIFIED)) { + deployingInfantry.setDugIn(Infantry.DUG_IN_NONE); + LOGGER.debug("[Fortify] {}: cleared redundant dug-in - deployed onto a fortified hex", + entity.getDisplayName()); + } + + entity.setDone(setDone); + entity.setDeployed(true); + gameManager.entityUpdate(entity.getId()); + + deployTowedTrailers(entity); + } + + /** + * Places the trailers of a train when its tractor deploys. Trailers get no deployment turn of their own, so this is + * the only chance they have to reach the board. + * + * @param tractor the unit that has just been deployed + */ + private void deployTowedTrailers(Entity tractor) { + if (tractor.getAllTowedUnits().isEmpty()) { + return; + } + + // A hitched train deploys wherever its tractor does, so a trailer keeps no off board setting of its own. The + // lobby does not let one be set on a hitched trailer, but it can be set before the trailer is hitched, so + // clear it here rather than leaving the trailer thinking it belongs somewhere else. + clearTrailerOffBoardSettings(tractor); + + int trailerCount = tractor.getAllTowedUnits().size(); + List trainPath = TrainLayout.deploymentPath(tractor.getPosition(), + tractor.getFacing(), + trailerCount); + java.util.List trainFacings = new java.util.ArrayList<>(); + for (int step = 0; step < trainPath.size(); step++) { + trainFacings.add(tractor.getFacing()); + } + + List placements = TrainLayout.computeLayout( + gameManager.getGame(), + tractor, + tractor.getPosition(), + tractor.getFacing(), + trainPath, + trainFacings); + + // The footprint was checked against the deployment zone in receiveDeployment, before the tractor was placed. + + TrainLayout.applyLayout(gameManager.getGame(), placements); + + for (TrainLayout.TrainPlacement placement : placements) { + Entity trailer = gameManager.getGame().getEntity(placement.entityId()); + if (trailer == null) { + continue; + } + trailer.setBoardId(tractor.getBoardId()); + trailer.setElevation(tractor.getElevation()); + trailer.setSecondaryFacing(trailer.getFacing()); + trailer.setDone(true); + trailer.setDeployed(true); + gameManager.entityUpdate(trailer.getId()); + } + + LOGGER.info("[Train] {} deployed at {} with {} trailer(s) placed behind it", + tractor.getDisplayName(), tractor.getPosition(), trailerCount); + } + + /** + * Clears any off board setting left on the trailers of a train that is deploying. + *

+ * A hitched train goes wherever its tractor goes, so a trailer never carries a deployment of its own. The lobby + * refuses to set one on a hitched trailer, but the setting can survive from before the trailer was hitched, so it + * is cleared here and logged rather than quietly changing where the unit ends up. + *

+ * + * @param tractor the unit that has just been deployed + */ + void clearTrailerOffBoardSettings(Entity tractor) { + java.util.List clearedTrailers = new java.util.ArrayList<>(); + + for (int towedId : tractor.getAllTowedUnits()) { + Entity trailer = gameManager.getGame().getEntity(towedId); + + if ((trailer != null) && trailer.isOffBoard()) { + trailer.setOffBoard(0, OffBoardDirection.NONE); + clearedTrailers.add(trailer.getDisplayName()); + } + } + + if (!clearedTrailers.isEmpty()) { + LOGGER.info("[Train] {} trailer(s) had an off board setting of their own; a train deploys with {}: {}", + clearedTrailers.size(), tractor.getDisplayName(), String.join(", ", clearedTrailers)); + } + } + + /** + * Whether every hex a train would occupy is a legal deployment hex, not just the tractor's own. + *

+ * The client refuses such a placement before sending it, but the placement arrives from the client, so the server + * has to decide for itself rather than trust it. A unit towing nothing is always its own footprint. + *

+ * + * @param tractor the unit being deployed + * @param coords the hex it is being placed in + * @param boardId the board it is being placed on + * @param facing the facing it is being placed with + * @return true when the whole train fits in the deployment zone + */ + private boolean isLegalTrainFootprint(Entity tractor, + Coords coords, + int boardId, + int facing) { + if (tractor.getAllTowedUnits().isEmpty()) { + return true; + } + Game game = gameManager.getGame(); + + Board board = game.getBoard(boardId); + for (Coords trainHex : TrainLayout.deploymentFootprint(game, tractor, coords, facing)) { + if (!game.hasBoardLocation(trainHex, boardId) || !board.isLegalDeployment(trainHex, + tractor)) { + LOGGER.warn("[Train] rejected deployment of {} at {} facing {}: trailer hex {} is not a legal " + + "deployment hex", tractor.getDisplayName(), coords, facing, trainHex); + return false; + } + } + return true; + } + + public boolean isLegalDeployment(Coords coords, + int boardId, + Entity entity, + int nFacing) { + Game game = gameManager.getGame(); + if (game == null) { + return false; + } + return game.hasBoardLocation(coords, boardId) + && game.getBoard(boardId).isLegalDeployment(coords, entity) + && isLegalTrainFootprint(entity, coords, boardId, nFacing); + } +} diff --git a/megamek/src/megamek/server/totalWarfare/MovePathHandler.java b/megamek/src/megamek/server/totalWarfare/MovePathHandler.java index 0095dec453f..475207815b9 100644 --- a/megamek/src/megamek/server/totalWarfare/MovePathHandler.java +++ b/megamek/src/megamek/server/totalWarfare/MovePathHandler.java @@ -93,6 +93,7 @@ * Processes an Entity's MovePath when an ENTITY_MOVE packet is received. */ class MovePathHandler extends AbstractTWRuleHandler { + private static final MMLogger logger = MMLogger.create(MovePathHandler.class); @@ -166,7 +167,10 @@ class MovePathHandler extends AbstractTWRuleHandler { * @param losCache A cache that stores Los between various Entities and targets. In double-blind games, we may * need to compute a lot of LosEffects, so caching them can really speed things up. */ - MovePathHandler(TWGameManager gameManager, Entity entity, MovePath md, Map losCache) { + MovePathHandler(TWGameManager gameManager, + Entity entity, + MovePath md, + Map losCache) { super(gameManager); this.entity = entity; this.md = md; @@ -182,7 +186,10 @@ class MovePathHandler extends AbstractTWRuleHandler { * @param curBoardId the board the unit is on * @param curElevation the unit's elevation after the step */ - private void moveElevatorPlatformWithEntity(MoveStep step, Coords curPos, int curBoardId, int curElevation) { + private void moveElevatorPlatformWithEntity(MoveStep step, + Coords curPos, + int curBoardId, + int curElevation) { if ((step.getType() != MoveStepType.ELEVATOR_ASCEND) && (step.getType() != MoveStepType.ELEVATOR_DESCEND)) { return; } @@ -208,16 +215,19 @@ private void moveElevatorPlatformWithEntity(MoveStep step, Coords curPos, int cu * @param curBoardId the board the unit is on * @param curElevation the unit's elevation after the step * @param stepMoveType the movement type of this step - * * @return {@code true} if the unit fell, which ends its movement */ - private boolean entityFallsDownElevatorShaft(MoveStep step, Hex curHex, Coords curPos, int curBoardId, - int curElevation, EntityMovementType stepMoveType) { - boolean isElevatorStep = (step.getType() == MoveStepType.ELEVATOR_ASCEND) - || (step.getType() == MoveStepType.ELEVATOR_DESCEND); + private boolean entityFallsDownElevatorShaft(MoveStep step, + Hex curHex, + Coords curPos, + int curBoardId, + int curElevation, + EntityMovementType stepMoveType) { + boolean isElevatorStep = (step.getType() == MoveStepType.ELEVATOR_ASCEND) || (step.getType() + == MoveStepType.ELEVATOR_DESCEND); if (!curHex.containsTerrain(Terrains.INDUSTRIAL_ELEVATOR) - || (stepMoveType == EntityMovementType.MOVE_JUMP) - || isElevatorStep) { + || (stepMoveType == EntityMovementType.MOVE_JUMP) + || isElevatorStep) { return false; } IndustrialElevator elevator = getGame().getIndustrialElevator(BoardLocation.of(curPos, curBoardId)); @@ -232,7 +242,10 @@ private boolean entityFallsDownElevatorShaft(MoveStep step, Hex curHex, Coords c return false; } logger.debug("[IndustrialElevator] {} entered shaft at {} with platform at level {}; falling {} levels", - entity.getShortName(), curPos, platformLevel, fallDistance); + entity.getShortName(), + curPos, + platformLevel, + fallDistance); Report fallReport = new Report(5298, Report.PUBLIC); fallReport.subject = entity.getId(); fallReport.add(entity.getDisplayName()); @@ -253,13 +266,13 @@ private boolean entityFallsDownElevatorShaft(MoveStep step, Hex curHex, Coords c * @param step the move step being processed * @param curElevation the unit's elevation after the step */ - private void reportElevatorUse(MoveStep step, int curElevation) { + private void reportElevatorUse(MoveStep step, + int curElevation) { if ((step.getType() != MoveStepType.ELEVATOR_ASCEND) && (step.getType() != MoveStepType.ELEVATOR_DESCEND)) { return; } - String direction = (step.getType() == MoveStepType.ELEVATOR_ASCEND) - ? Messages.getString("IndustrialElevator.directionUp") - : Messages.getString("IndustrialElevator.directionDown"); + String direction = (step.getType() == MoveStepType.ELEVATOR_ASCEND) ? Messages.getString( + "IndustrialElevator.directionUp") : Messages.getString("IndustrialElevator.directionDown"); Report elevatorReport = new Report(5299, Report.PUBLIC); elevatorReport.subject = entity.getId(); elevatorReport.add(entity.getDisplayName()); @@ -269,22 +282,23 @@ private void reportElevatorUse(MoveStep step, int curElevation) { } /** - * Checks whether the TW p.54 rule applies that VTOL-capable creatures must spend 1 MP per turn, even if - * remaining stationary. This is a pure check with no side effects: when it returns {@code true}, the caller - * ({@code processMovement()}) converts the entity's movement type to - * {@link EntityMovementType#MOVE_VTOL_WALK} and records 1 MP spent, so an airborne beast-mounted platoon - * (e.g. Branth) that holds its hex still counts as having walked. + * Checks whether the TW p.54 rule applies that VTOL-capable creatures must spend 1 MP per turn, even if remaining + * stationary. This is a pure check with no side effects: when it returns {@code true}, the caller + * ({@code processMovement()}) converts the entity's movement type to {@link EntityMovementType#MOVE_VTOL_WALK} and + * records 1 MP spent, so an airborne beast-mounted platoon (e.g. Branth) that holds its hex still counts as having + * walked. * * @param entity the entity whose movement was just processed - * * @return {@code true} if the entity is airborne VTOL beast-mounted infantry that did not move this turn */ static boolean mustSpendStationaryFlightMP(Entity entity) { return (entity instanceof ConvInfantry beastInfantry) - && (beastInfantry.getMount() != null) - && beastInfantry.getMount().movementMode().isVTOL() - && entity.isAirborneVTOLorWIGE() - && (entity.moved == EntityMovementType.MOVE_NONE); + && (beastInfantry.getMount() != null) + && beastInfantry.getMount() + .movementMode() + .isVTOL() + && entity.isAirborneVTOLorWIGE() + && (entity.moved == EntityMovementType.MOVE_NONE); } void processMovement() { @@ -293,12 +307,13 @@ void processMovement() { // Climbing requires 1+ arms; dangling requires 2 arms if (entity.isClimbing() && (entity instanceof Mek climbingMek)) { int climbableArms = ClimbingHelper.countClimbableArms(climbingMek); - boolean cannotHoldOn = (climbableArms == 0) - || (entity.isDangling() && (climbableArms < 2)); + boolean cannotHoldOn = (climbableArms == 0) || (entity.isDangling() && (climbableArms < 2)); if (cannotHoldOn) { - logger.debug("[FALL-TRACE] Climbing/dangling Mek {} lost required arms, auto-falling " + - "from elevation {} in hex {}", - entity.getDisplayName(), entity.getElevation(), entity.getPosition()); + logger.debug("[FALL-TRACE] Climbing/dangling Mek {} lost required arms, auto-falling " + + "from elevation {} in hex {}", + entity.getDisplayName(), + entity.getElevation(), + entity.getPosition()); // Surface as a special-report toast (kill-feed style) AND mirror to chat so the // player sees the auto-fall immediately, not just buried in the round report. // Matches the building-too-damaged path in TWGameManager.checkClimbingEntitiesOnBuilding. @@ -308,9 +323,8 @@ void processMovement() { Vector armsLostSpecial = new Vector<>(); armsLostSpecial.add(armsLostReport); gameManager.send(gameManager.createSpecialReportPacket(armsLostSpecial)); - gameManager.sendServerChat(Messages.getString( - "MovementDisplay.ClimbingDialog.armsLostChat", - entity.getDisplayName())); + gameManager.sendServerChat(Messages.getString("MovementDisplay.ClimbingDialog.armsLostChat", + entity.getDisplayName())); // Clear climbing/dangling flags BEFORE doEntityFallsInto so the entity // updates pushed during fall processing carry the cleared state to clients. // Otherwise the client copy retains isClimbing=true and the next-turn @@ -319,9 +333,15 @@ void processMovement() { entity.setDangling(false); entity.setClimbingLevelsChosen(0); PilotingRollData autoFallRoll = new PilotingRollData(entity.getId(), - TargetRoll.AUTOMATIC_FAIL, "climbing arms destroyed"); - addReport(gameManager.doEntityFallsInto(entity, entity.getElevation(), - entity.getPosition(), entity.getPosition(), autoFallRoll, true, 0)); + TargetRoll.AUTOMATIC_FAIL, + "climbing arms destroyed"); + addReport(gameManager.doEntityFallsInto(entity, + entity.getElevation(), + entity.getPosition(), + entity.getPosition(), + autoFallRoll, + true, + 0)); addNewLines(); // The Mek just fell — terminate movement processing immediately. Without // this return, processMovement would fall through to the edge-dangle block @@ -350,19 +370,23 @@ void processMovement() { // jets and use normal jump-landing mechanics, so a jump from a cliff-top hex must NOT // false-trigger the dangle handler — that would put the unit into a climbing state at // the destination, which it never was. - boolean isJumpPath = md.contains(MoveStepType.START_JUMP) - || (md.getLastStepMovementType() == EntityMovementType.MOVE_JUMP); + boolean isJumpPath = md.contains(MoveStepType.START_JUMP) || (md.getLastStepMovementType() + == EntityMovementType.MOVE_JUMP); // canClimb (1 arm) — not canDangle (2 arms) — so a one-armed Mek can still // initiate edge climb-down. Whether the chosen action is dangle vs climb-down // is determined later by entity.climbingLevelsChosen; dangle requires 2 arms // and the client dialog already filters that option. - if (!entity.isClimbing() && !entity.isDangling() - && (entity instanceof Mek) - && ClimbingHelper.canClimb(entity) - && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING) - && md.getFinalClimbMode() - && md.contains(MoveStepType.FORWARDS) - && !isJumpPath) { + if (!entity.isClimbing() + && !entity.isDangling() + && (entity instanceof Mek) + && ClimbingHelper.canClimb(entity) + && getGame().getOptions() + .booleanOption( + OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING) + && md.getFinalClimbMode() + && md.contains( + MoveStepType.FORWARDS) + && !isJumpPath) { // Check if the FORWARDS step goes to a lower hex (3+ levels down) // Entity must not have moved (target must be adjacent to starting position) MoveStep lastStep = md.getLastStep(); @@ -379,13 +403,18 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // The entity's climbing/dangling state is server-controlled, so this also implicitly // validates that the prior dangle initiation was approved. boolean tacOpsClimbingEnabled = getGame().getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING); - boolean canProcessDangle = tacOpsClimbingEnabled - && (entity.isClimbing() || entity.isDangling()) && hasDownStep; - logger.debug("[DANGLE-TRACE] processMovement check: canProcessDangle={}, isEdgeDangle={}, " + - "hasDownStep={}, isClimbing={}, isDangling={}, elevation={}, climbMode={}, stepCount={}", - canProcessDangle, isEdgeDangle, hasDownStep, entity.isClimbing(), entity.isDangling(), - entity.getElevation(), md.getFinalClimbMode(), md.length()); + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_CLIMBING); + boolean canProcessDangle = tacOpsClimbingEnabled && (entity.isClimbing() || entity.isDangling()) && hasDownStep; + logger.debug("[DANGLE-TRACE] processMovement check: canProcessDangle={}, isEdgeDangle={}, " + + "hasDownStep={}, isClimbing={}, isDangling={}, elevation={}, climbMode={}, stepCount={}", + canProcessDangle, + isEdgeDangle, + hasDownStep, + entity.isClimbing(), + entity.isDangling(), + entity.getElevation(), + md.getFinalClimbMode(), + md.length()); if (isEdgeDangle && (edgeDangleTargetPos != null)) { // Edge initiation: move entity to lower hex, face the cliff/building. // Discriminator: entity.climbingLevelsChosen > 0 = controlled climb-down (PSR per @@ -397,8 +426,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN Hex destHex = getGame().getBoard(entity.getBoardId()).getHex(edgeDangleTargetPos); int facingToCliff = edgeDangleTargetPos.direction(originalPos); int chosenEdgeDescent = entity.getClimbingLevelsChosen(); - if ((chosenEdgeDescent > 0) && (entity instanceof Mek edgeMek) - && ClimbingHelper.canClimb(entity)) { + if ((chosenEdgeDescent > 0) && (entity instanceof Mek edgeMek) && ClimbingHelper.canClimb(entity)) { // EDGE CLIMB-DOWN (TO:AR p.20): PSR per level. On failure, fall from current // descended elevation. Mek ends in lower hex at (cliffTopAlt - levelsDescended). // Total drop measures to the destination hex's FLOOR — water bottom for water, @@ -409,9 +437,14 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN int totalDrop = cliffTopAlt - destHex.floor(); int levelsToDescend = Math.min(chosenEdgeDescent, totalDrop); logger.debug("[CLIMB-TRACE] Server processing EDGE climb-down: entity={}, " - + "from={} (alt {}), to={} (level {}), levelsToDescend={}, costPerLevel={}", - entity.getDisplayName(), originalPos, cliffTopAlt, - edgeDangleTargetPos, destHex.getLevel(), levelsToDescend, costPerLevel); + + "from={} (alt {}), to={} (level {}), levelsToDescend={}, costPerLevel={}", + entity.getDisplayName(), + originalPos, + cliffTopAlt, + edgeDangleTargetPos, + destHex.getLevel(), + levelsToDescend, + costPerLevel); // Move to lower hex first so the PSR rolls happen at the new position. entity.setPosition(edgeDangleTargetPos); entity.setFacing(facingToCliff); @@ -421,15 +454,20 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN for (int i = 1; i <= levelsToDescend; i++) { PilotingRollData psr = entity.getBasePilotingRoll(EntityMovementType.MOVE_WALK); psr.append(new PilotingRollData(entity.getId(), - ClimbingHelper.CLIMBING_PSR_MODIFIER, - "climbing down (level " + i + " of " + levelsToDescend + ")")); + ClimbingHelper.CLIMBING_PSR_MODIFIER, + "climbing down (level " + i + " of " + levelsToDescend + ")")); if (climbableArms == 1) { psr.append(new PilotingRollData(entity.getId(), - ClimbingHelper.ONE_ARM_PSR_MODIFIER, "climbing with one arm")); + ClimbingHelper.ONE_ARM_PSR_MODIFIER, + "climbing with one arm")); } int psrElevation = (cliffTopAlt - destHex.getLevel()) - levelsDescended; - if (gameManager.doSkillCheckWhileMoving(entity, psrElevation, - edgeDangleTargetPos, edgeDangleTargetPos, psr, true) > 0) { + if (gameManager.doSkillCheckWhileMoving(entity, + psrElevation, + edgeDangleTargetPos, + edgeDangleTargetPos, + psr, + true) > 0) { // PSR failed - fall from current climb-down elevation entity.setClimbing(false); entity.setDangling(false); @@ -441,8 +479,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN } if (!fellWhileDescending) { int floorRelative = destHex.floor() - destHex.getLevel(); - int finalElevation = Math.max(floorRelative, - (cliffTopAlt - destHex.getLevel()) - levelsDescended); + int finalElevation = Math.max(floorRelative, (cliffTopAlt - destHex.getLevel()) - levelsDescended); entity.setElevation(finalElevation); // Climbing flag clears only at the actual hex floor — for a water hex that // means the water bottom, not the surface. Anywhere above the floor the Mek @@ -475,7 +512,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN if (!ClimbingHelper.canDangle(entity)) { logger.warn("[DANGLE-TRACE] Server rejecting EDGE dangle: entity={} lacks two " + "functional climbing arms; chosenLevels was 0 (dangle path).", - entity.getDisplayName()); + entity.getDisplayName()); entity.setDone(true); entity.moved = EntityMovementType.MOVE_NONE; entity.mpUsed = 0; @@ -483,13 +520,16 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN gameManager.entityUpdate(entity.getId()); return; } - int dangleElevation = cliffTopAlt - ClimbingHelper.DANGLE_LEVELS_PER_TURN - - destHex.getLevel(); + int dangleElevation = cliffTopAlt - ClimbingHelper.DANGLE_LEVELS_PER_TURN - destHex.getLevel(); dangleElevation = Math.max(0, dangleElevation); - logger.debug("[DANGLE-TRACE] Server processing EDGE dangle: entity={}, " + - "from={} (alt {}), to={} (level {}), dangleElevation={}", - entity.getDisplayName(), originalPos, cliffTopAlt, - edgeDangleTargetPos, destHex.getLevel(), dangleElevation); + logger.debug("[DANGLE-TRACE] Server processing EDGE dangle: entity={}, " + + "from={} (alt {}), to={} (level {}), dangleElevation={}", + entity.getDisplayName(), + originalPos, + cliffTopAlt, + edgeDangleTargetPos, + destHex.getLevel(), + dangleElevation); Report dangleReport = new Report(6462, Report.PUBLIC); dangleReport.add(entity.getDisplayName()); dangleReport.add(ClimbingHelper.DANGLE_LEVELS_PER_TURN); @@ -517,17 +557,18 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN return; } if (canProcessDangle) { - int downStepCount = (int) md.getStepVector().stream() - .filter(s -> s.getType() == MoveStepType.DOWN) - .count(); + int downStepCount = (int) md.getStepVector().stream().filter(s -> s.getType() == MoveStepType.DOWN).count(); // Controlled CLIMB DOWN (TO:AR p.20): same MP cost and PSRs as climbing up. // Discriminated from DANGLE (1 bare DOWN) and DROP (2 bare DOWN) by the // CLIMB_MODE_ON marker step the client adds before the DOWN steps. // Edge dangle uses CLIMB_MODE_ON + FORWARDS (no DOWN), so no false positive. // entity.climbingLevelsChosen isn't transmitted to the server — the path is. boolean hasClimbModeOnMarker = md.contains(MoveStepType.CLIMB_MODE_ON); - if (hasClimbModeOnMarker && (downStepCount > 0) && (entity instanceof Mek descendingMek) - && ClimbingHelper.canClimb(entity)) { + if (hasClimbModeOnMarker + && (downStepCount > 0) + && (entity instanceof Mek descendingMek) + && ClimbingHelper.canClimb( + entity)) { int costPerLevel = ClimbingHelper.getClimbingMPCostPerLevel(descendingMek); int climbableArms = ClimbingHelper.countClimbableArms(descendingMek); int currentElevation = entity.getElevation(); @@ -545,27 +586,37 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN logger.warn("[CLIMB-TRACE] Server capping CLIMB_DOWN: entity={} requested {} " + "levels but {} walk MP at {} MP/level only allows {} (clinging at " + "intermediate elevation).", - entity.getDisplayName(), requestedLevels, availableMP, - costPerLevel, levelsToDescend); - } - logger.debug("[CLIMB-TRACE] Server processing CLIMB_DOWN: entity={}, " + - "currentElevation={}, levelsToDescend={}, costPerLevel={}", - entity.getDisplayName(), currentElevation, levelsToDescend, costPerLevel); + entity.getDisplayName(), + requestedLevels, + availableMP, + costPerLevel, + levelsToDescend); + } + logger.debug("[CLIMB-TRACE] Server processing CLIMB_DOWN: entity={}, " + + "currentElevation={}, levelsToDescend={}, costPerLevel={}", + entity.getDisplayName(), + currentElevation, + levelsToDescend, + costPerLevel); int levelsDescended = 0; boolean fellWhileDescending = false; for (int i = 1; i <= levelsToDescend; i++) { PilotingRollData psr = entity.getBasePilotingRoll(EntityMovementType.MOVE_WALK); psr.append(new PilotingRollData(entity.getId(), - ClimbingHelper.CLIMBING_PSR_MODIFIER, - "climbing down (level " + i + " of " + levelsToDescend + ")")); + ClimbingHelper.CLIMBING_PSR_MODIFIER, + "climbing down (level " + i + " of " + levelsToDescend + ")")); if (climbableArms == 1) { psr.append(new PilotingRollData(entity.getId(), - ClimbingHelper.ONE_ARM_PSR_MODIFIER, - "climbing with one arm")); + ClimbingHelper.ONE_ARM_PSR_MODIFIER, + "climbing with one arm")); } int psrElevation = currentElevation - levelsDescended; - if (gameManager.doSkillCheckWhileMoving(entity, psrElevation, - entity.getPosition(), entity.getPosition(), psr, true) > 0) { + if (gameManager.doSkillCheckWhileMoving(entity, + psrElevation, + entity.getPosition(), + entity.getPosition(), + psr, + true) > 0) { // PSR failed - fall from current climb-down elevation entity.setClimbing(false); entity.setDangling(false); @@ -576,8 +627,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN levelsDescended++; } if (!fellWhileDescending) { - int newElevation = Math.max(floorRelativeForDescent, - currentElevation - levelsDescended); + int newElevation = Math.max(floorRelativeForDescent, currentElevation - levelsDescended); entity.setElevation(newElevation); // Climbing flag clears only when the Mek truly hits the hex floor — water // bottom counts, water surface doesn't. Above the floor the Mek is still @@ -611,7 +661,9 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN if (availableMP < ClimbingHelper.DROP_MP_COST) { logger.warn("[DANGLE-TRACE] Server rejecting DROP: entity={} has {} walk MP, " + "needs {} for drop. Treating as cling.", - entity.getDisplayName(), availableMP, ClimbingHelper.DROP_MP_COST); + entity.getDisplayName(), + availableMP, + ClimbingHelper.DROP_MP_COST); entity.setDone(true); entity.moved = EntityMovementType.MOVE_NONE; entity.mpUsed = 0; @@ -627,49 +679,52 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN int dropDistance = Math.max(0, entity.getElevation()); int modifierReduction = entity.isDangling() ? ClimbingHelper.DANGLE_LEVELS_PER_TURN : 0; int effectiveDistance = Math.max(0, dropDistance - modifierReduction); - logger.debug("[DANGLE-TRACE] Server processing DROP: entity={}, " + - "dropDistance={}, effectiveDistance={}, isDangling={}, modifierReduction={}", - entity.getDisplayName(), dropDistance, effectiveDistance, - entity.isDangling(), modifierReduction); + logger.debug("[DANGLE-TRACE] Server processing DROP: entity={}, " + + "dropDistance={}, effectiveDistance={}, isDangling={}, modifierReduction={}", + entity.getDisplayName(), + dropDistance, + effectiveDistance, + entity.isDangling(), + modifierReduction); // PSR 1: Leg damage check (modifier = 2 * effectiveDistance) - String legDamageDesc = entity.isDangling() - ? String.format("hanging from level %d, effective height for leg damage roll %d", - dropDistance, effectiveDistance) - : String.format("dropping from level %d, leg damage roll", dropDistance); - String fallDesc = entity.isDangling() - ? String.format("hanging from level %d, effective height for fall roll %d", - dropDistance, effectiveDistance) - : String.format("dropping from level %d, fall roll", dropDistance); + String legDamageDesc = entity.isDangling() ? String.format( + "hanging from level %d, effective height for leg damage roll %d", + dropDistance, + effectiveDistance) : String.format("dropping from level %d, leg damage roll", dropDistance); + String fallDesc = entity.isDangling() ? String.format( + "hanging from level %d, effective height for fall roll %d", + dropDistance, + effectiveDistance) : String.format("dropping from level %d, fall roll", dropDistance); if (effectiveDistance > 0) { rollTarget = entity.getBasePilotingRoll(EntityMovementType.MOVE_WALK); - entity.addPilotingModifierForTerrain(rollTarget, entity.getPosition(), - entity.getBoardId()); - rollTarget.append(new PilotingRollData(entity.getId(), - 2 * effectiveDistance, legDamageDesc)); - if (0 < gameManager.doSkillCheckWhileMoving(entity, entity.getElevation(), - entity.getPosition(), entity.getPosition(), rollTarget, false)) { + entity.addPilotingModifierForTerrain(rollTarget, entity.getPosition(), entity.getBoardId()); + rollTarget.append(new PilotingRollData(entity.getId(), 2 * effectiveDistance, legDamageDesc)); + if (0 < gameManager.doSkillCheckWhileMoving(entity, + entity.getElevation(), + entity.getPosition(), + entity.getPosition(), + rollTarget, + false)) { // Leg damage equal to effective distance - addReport(gameManager.damageEntity(entity, - new HitData(Mek.LOC_LEFT_LEG), effectiveDistance)); - addReport(gameManager.damageEntity(entity, - new HitData(Mek.LOC_RIGHT_LEG), effectiveDistance)); + addReport(gameManager.damageEntity(entity, new HitData(Mek.LOC_LEFT_LEG), effectiveDistance)); + addReport(gameManager.damageEntity(entity, new HitData(Mek.LOC_RIGHT_LEG), effectiveDistance)); addNewLines(); - addReport(gameManager.criticalEntity(entity, - Mek.LOC_LEFT_LEG, false, 0, 0)); + addReport(gameManager.criticalEntity(entity, Mek.LOC_LEFT_LEG, false, 0, 0)); addNewLines(); - addReport(gameManager.criticalEntity(entity, - Mek.LOC_RIGHT_LEG, false, 0, 0)); + addReport(gameManager.criticalEntity(entity, Mek.LOC_RIGHT_LEG, false, 0, 0)); } // PSR 2: Fall check (modifier = effectiveDistance) rollTarget = entity.getBasePilotingRoll(EntityMovementType.MOVE_WALK); - entity.addPilotingModifierForTerrain(rollTarget, entity.getPosition(), - entity.getBoardId()); - rollTarget.append(new PilotingRollData(entity.getId(), - effectiveDistance, fallDesc)); - if (0 < gameManager.doSkillCheckWhileMoving(entity, entity.getElevation(), - entity.getPosition(), entity.getPosition(), rollTarget, true)) { + entity.addPilotingModifierForTerrain(rollTarget, entity.getPosition(), entity.getBoardId()); + rollTarget.append(new PilotingRollData(entity.getId(), effectiveDistance, fallDesc)); + if (0 < gameManager.doSkillCheckWhileMoving(entity, + entity.getElevation(), + entity.getPosition(), + entity.getPosition(), + rollTarget, + true)) { // Fall from elevation entity.setDangling(false); entity.setClimbing(false); @@ -685,9 +740,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // hex floor: for dry hexes that's elev 0, for water/basement hexes the Mek sinks // through and settles on the floor below the surface. Hex landingHex = getGame().getBoard(entity.getBoardId()).getHex(entity.getPosition()); - int landingElevation = (landingHex != null) - ? landingHex.floor() - landingHex.getLevel() - : 0; + int landingElevation = (landingHex != null) ? landingHex.floor() - landingHex.getLevel() : 0; entity.setElevation(landingElevation); entity.setDangling(false); entity.setClimbing(false); @@ -706,12 +759,14 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // underwater cliff face too, capped at the actual hex floor). int dangleFloor = hexFloorRelative(entity); int dangleableLevels = entity.getElevation() - dangleFloor; - int dangleLevels = Math.min(ClimbingHelper.DANGLE_LEVELS_PER_TURN, - dangleableLevels); + int dangleLevels = Math.min(ClimbingHelper.DANGLE_LEVELS_PER_TURN, dangleableLevels); int newElevation = Math.max(dangleFloor, entity.getElevation() - dangleLevels); - logger.debug("[DANGLE-TRACE] Server processing dangle: entity={}, " + - "currentElevation={}, dangleLevels={}, newElevation={}", - entity.getDisplayName(), entity.getElevation(), dangleLevels, newElevation); + logger.debug("[DANGLE-TRACE] Server processing dangle: entity={}, " + + "currentElevation={}, dangleLevels={}, newElevation={}", + entity.getDisplayName(), + entity.getElevation(), + dangleLevels, + newElevation); Report dangleReport = new Report(6462, Report.PUBLIC); dangleReport.add(entity.getDisplayName()); dangleReport.add(dangleLevels); @@ -753,15 +808,16 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN gameManager.entityUpdate(ship.getId()); Coords legalPos = entity.getPosition(); // Get the step so we can pass it in and get the abandon coords from it - for (final ListIterator i = md.getSteps(); i - .hasNext(); ) { + for (final ListIterator i = md.getSteps(); i.hasNext(); ) { final MoveStep step = i.next(); if (step.getType() == MoveStepType.EJECT) { legalPos = step.getTargetPosition(); } } - addReport(gameManager.ejectSpacecraft(ship, ship.isSpaceborne(), - (ship.isAirborne() && !ship.isSpaceborne()), legalPos)); + addReport(gameManager.ejectSpacecraft(ship, + ship.isSpaceborne(), + (ship.isAirborne() && !ship.isSpaceborne()), + legalPos)); // If we're grounded or destroyed by crew loss, end movement if (entity.isDoomed() || (!entity.isSpaceborne() && !entity.isAirborne())) { return; @@ -836,8 +892,8 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN int boardId = entity.getBoardId(); // The roll is made at the start of takeoff, so before the craft is moved off its hex (TO:AR p.54). new TaintedAtmosphereHandler(gameManager).checkExhaustWashIgnition(entity, entity.getPosition(), - boardId, entity.getFacing(), - TaintedAtmosphereHandler.ExhaustWashMoment.TAKEOFF); + boardId, entity.getFacing(), + TaintedAtmosphereHandler.ExhaustWashMoment.TAKEOFF); if (usingAeroOnGroundMovement()) { entity.setPosition(entity.getPosition().translated(entity.getFacing(), aero.getTakeOffLength())); } else { @@ -846,8 +902,11 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN aero.setCurrentVelocity(1); aero.liftOff(1); if (entity instanceof Dropship) { - gameManager.applyDropShipProximityDamage(md.getFinalCoords(), boardId, true, md.getFinalFacing(), - entity); + gameManager.applyDropShipProximityDamage(md.getFinalCoords(), + boardId, + true, + md.getFinalFacing(), + entity); } gameManager.checkForTakeoffDamage(aero); } @@ -865,8 +924,8 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN int boardId = entity.getBoardId(); // The roll is made at the start of takeoff, so before the craft leaves its hex (TO:AR p.54). new TaintedAtmosphereHandler(gameManager).checkExhaustWashIgnition(entity, entity.getPosition(), - boardId, entity.getFacing(), - TaintedAtmosphereHandler.ExhaustWashMoment.TAKEOFF); + boardId, entity.getFacing(), + TaintedAtmosphereHandler.ExhaustWashMoment.TAKEOFF); if (!usingAeroOnGroundMovement()) { positionOnAtmosphericMap(); } @@ -892,11 +951,15 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN if (isVertical && entity instanceof Dropship) { gameManager.applyDropShipLandingDamage(md.getFinalCoords(), md.getFinalBoardId(), (Dropship) aero); } - Coords finalPosition = isVertical ? - md.getFinalCoords() : - md.getFinalCoords().translated(md.getFinalFacing(), aero.getLandingLength()); - gameManager.checkLandingTerrainEffects(aero, isVertical, md.getFinalCoords(), finalPosition, - md.getFinalBoardId(), md.getFinalFacing()); + Coords finalPosition = isVertical ? md.getFinalCoords() : md.getFinalCoords() + .translated(md.getFinalFacing(), + aero.getLandingLength()); + gameManager.checkLandingTerrainEffects(aero, + isVertical, + md.getFinalCoords(), + finalPosition, + md.getFinalBoardId(), + md.getFinalFacing()); aero.land(); entity.setPosition(finalPosition); // Taken from the hex the landing was declared in - where the craft meets the ground - not the hex @@ -904,14 +967,78 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // the landing rather than the start, and says nothing about which hex the arc is measured from. // The craft touches down under power; by the time it has rolled out the pilot has pulled the // power off, so it is the ground behind the touchdown point that gets scorched. - new TaintedAtmosphereHandler(gameManager).checkExhaustWashIgnition(entity, md.getFinalCoords(), - md.getFinalBoardId(), md.getFinalFacing(), - TaintedAtmosphereHandler.ExhaustWashMoment.LANDING); + new TaintedAtmosphereHandler(gameManager).checkExhaustWashIgnition(entity, + md.getFinalCoords(), + md.getFinalBoardId(), + md.getFinalFacing(), + TaintedAtmosphereHandler.ExhaustWashMoment.LANDING); entity.setDone(true); gameManager.entityUpdate(entity.getId()); return; } + if (md.contains(MoveStepType.DEPLOY) && Game.rulesManager.getRulesGame().isWalkOnDeployment()) { + DeploymentServerHelper deploymentProcess = new DeploymentServerHelper(gameManager); + // We don't set done in a walk on deployment + boolean setDone = false; + boolean assaultDrop = false; + Coords position; + int boardId; + int facing; + int entityElevation; + Vector loadVector = new Vector<>(entity.getLoadedUnits()); + MoveStep step = null; + // Ok, so deployment wasn't the first step. let's get where it was + ListIterator steps = md.getSteps(); + while (steps.hasNext()) { + step = steps.next(); + if (step.getType() == MoveStepType.DEPLOY) { + break; + } + } + if (step != null) { + position = step.getPosition(); + boardId = step.getBoardId(); + facing = step.getFacing(); + entityElevation = entity.isAero() ? step.getAltitude() : step.getElevation(); + } else { + position = entity.getPosition(); + boardId = entity.getBoardId(); + facing = entity.getFacing(); + entityElevation = entity.getElevation(); + } + + boolean isLegalLocation = deploymentProcess.isLegalDeployment(position, boardId, entity, facing); + if (!isLegalLocation) { + String msg = "server got invalid deployment packet from connection " + entity.getOwnerId(); + msg += ", Entity: " + entity.getShortName(); + logger.error(msg); + gameManager.send(entity.getOwnerId(), gameManager.getPacketHelper().createTurnListPacket()); + + return; + } + + deploymentProcess.processDeployment(entity, + position, + boardId, + facing, + entityElevation, + loadVector, + assaultDrop, + setDone); + Hex hex = gameManager.getGame().getBoard(boardId).getHex(position); + addReport(gameManager.doSetLocationsExposure(entity, hex, false, entity.getElevation())); + // Update Aero sensors for a space or atmospheric game + if (entity instanceof IAero aero) { + aero.updateSensorOptions(); + } + + // Update visibility indications if using double-blind. + if (gameManager.doBlind()) { + gameManager.updateVisibilityIndicator(null); + } + TWGameManager.datasetLogger.append(getGame(), true); + } // okay, proceed with movement calculations lastPos = entity.getPosition(); curPos = entity.getPosition(); @@ -949,8 +1076,8 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN if (md.getFlightPathHex() != null && !md.getFlightPathHex().isNoLocation()) { BoardLocation location = md.getFlightPathHex(); entity.setPassedThrough(new Vector<>(BoardHelper.coordsLine(getGame().getBoard(location), - location.coords(), - md.getFinalFacing()))); + location.coords(), + md.getFinalFacing()))); } // get a list of coordinates that the unit passed through this turn @@ -973,8 +1100,9 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN Hex startingHex = getGame().getHex(entity.getBoardLocation()); if (startingHex == null) { - logger.warn(String.format("Attempted to skip turn for %s (%s) which is not on the board!", entity, - entity.getOwner().getName())); + logger.warn(String.format("Attempted to skip turn for %s (%s) which is not on the board!", + entity, + entity.getOwner().getName())); return; } @@ -1044,8 +1172,8 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN processSteps(); // If a unit started & ended its turn in magma, let's damage it again (TO:AR 35) TODO: build report for end of move - if (tookMagmaDamageAtStart && prevHex.terrainLevel(Terrains.MAGMA) == 2 - && !(entity.getElevation() > 0 || entity.getMovementMode() == EntityMovementMode.HOVER)) { + if (tookMagmaDamageAtStart && prevHex.terrainLevel(Terrains.MAGMA) == 2 && !(entity.getElevation() > 0 + || entity.getMovementMode() == EntityMovementMode.HOVER)) { report = new Report(2404); report.addDesc(entity); report.subject = entity.getId(); @@ -1065,14 +1193,19 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN if ((entity.isClimbing() || entity.isDangling()) && (entity.getElevation() == 0)) { Hex finalHex = getGame().getBoard(entity.getBoardId()).getHex(curPos); boolean onBuildingRoof = (finalHex != null) - && finalHex.containsTerrain(Terrains.BUILDING) - && (entity.getElevation() >= finalHex.terrainLevel(Terrains.BLDG_ELEV)); - boolean clingingToAdjacentClimbable = entity.isClimbing() - && isClingingToAdjacentClimbable(entity, curPos, finalHex); + && finalHex.containsTerrain(Terrains.BUILDING) + && (entity.getElevation() >= finalHex.terrainLevel( + Terrains.BLDG_ELEV)); + boolean clingingToAdjacentClimbable = entity.isClimbing() && isClingingToAdjacentClimbable(entity, + curPos, + finalHex); if (!onBuildingRoof && !clingingToAdjacentClimbable) { logger.debug("[CLIMB-TRACE] Clearing stale climbing flags at end of move: entity={}, " - + "climbing={}, dangling={}, elevation=0 in hex {}", - entity.getDisplayName(), entity.isClimbing(), entity.isDangling(), curPos); + + "climbing={}, dangling={}, elevation=0 in hex {}", + entity.getDisplayName(), + entity.isClimbing(), + entity.isDangling(), + curPos); entity.setClimbing(false); entity.setDangling(false); entity.setClimbingLevelsChosen(0); @@ -1081,7 +1214,11 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // set entity parameters logger.debug("End of movement: entity={}, curPos={}, climbing={}, elevation={}, curVTOLElevation={}", - entity.getDisplayName(), curPos, entity.isClimbing(), entity.getElevation(), curVTOLElevation); + entity.getDisplayName(), + curPos, + entity.isClimbing(), + entity.getElevation(), + curVTOLElevation); entity.setPosition(curPos); entity.setFacing(curFacing); entity.setSecondaryFacing(curFacing); @@ -1090,9 +1227,9 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN entity.mpUsed = mpUsed; if (md.isAllUnderwater(getGame())) { entity.underwaterRounds++; - if ((entity instanceof ConvInfantry infantry) && (infantry.getMount() != null) - && entity.getMovementMode().isSubmarine() - && entity.underwaterRounds > infantry.getMount().getUWEndurance()) { + if ((entity instanceof ConvInfantry infantry) && (infantry.getMount() != null) && entity.getMovementMode() + .isSubmarine() && entity.underwaterRounds > infantry.getMount() + .getUWEndurance()) { report = new Report(2412); report.addDesc(entity); addReport(report); @@ -1106,8 +1243,8 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN entity.mpUsed = Math.max(entity.mpUsed, 1); } entity.setClimbMode(curClimbMode); - if (!sideslipped && !fellDuringMovement && !crashedDuringMovement - && (entity.getMovementMode() == EntityMovementMode.VTOL)) { + if (!sideslipped && !fellDuringMovement && !crashedDuringMovement && (entity.getMovementMode() + == EntityMovementMode.VTOL)) { entity.setElevation(curVTOLElevation); } entity.setAltitude(curAltitude); @@ -1123,8 +1260,8 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN if ((md.getFlightPathHex() != null) && !md.getFlightPathHex().isNoLocation()) { BoardLocation location = md.getFlightPathHex(); entity.setPassedThrough(new Vector<>(BoardHelper.coordsLine(getGame().getBoard(location), - location.coords(), - md.getFinalFacing()))); + location.coords(), + md.getFinalFacing()))); List facings = new ArrayList<>(); for (int i = 0; i < entity.getPassedThrough().size(); i++) { facings.add(md.getFinalFacing()); @@ -1140,23 +1277,22 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN } // if we moved a hex with a destroyed leg, but it was not a run - rollTarget = Game.rulesManager.getRulesPSR().checkWalkWithLegDestroyed(entity, - overallMoveType, md.getHexesMoved()); + rollTarget = Game.rulesManager.getRulesPSR() + .checkWalkWithLegDestroyed(entity, overallMoveType, md.getHexesMoved()); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE && entity.canFall()) { gameManager.doSkillCheckInPlace(entity, rollTarget); } // if we sprinted with MASC or a supercharger, then we need a PSR - rollTarget = entity.checkSprintingWithMASCXorSupercharger(overallMoveType, - entity.mpUsed); + rollTarget = entity.checkSprintingWithMASCXorSupercharger(overallMoveType, entity.mpUsed); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE && entity.canFall()) { gameManager.doSkillCheckInPlace(entity, rollTarget); } // if we used ProtoMek myomer booster, roll 2d6 // pilot damage on a 2 - if ((entity instanceof ProtoMek) && ((ProtoMek) entity).hasMyomerBooster() - && (md.getMpUsed() > entity.getRunMP(MPCalculationSetting.NO_MYOMER_BOOSTER))) { + if ((entity instanceof ProtoMek) && ((ProtoMek) entity).hasMyomerBooster() && (md.getMpUsed() > entity.getRunMP( + MPCalculationSetting.NO_MYOMER_BOOSTER))) { report = new Report(2373); report.addDesc(entity); report.subject = entity.getId(); @@ -1177,8 +1313,8 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { gameManager.doSkillCheckInPlace(entity, rollTarget); } - if ((md.getLastStepMovementType() == EntityMovementType.MOVE_SPRINT) - && (md.hasActiveMASC() || md.hasActiveSupercharger()) && entity.canFall()) { + if ((md.getLastStepMovementType() == EntityMovementType.MOVE_SPRINT) && (md.hasActiveMASC() + || md.hasActiveSupercharger()) && entity.canFall()) { gameManager.doSkillCheckInPlace(entity, entity.getBasePilotingRoll(EntityMovementType.MOVE_SPRINT)); } @@ -1188,17 +1324,17 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN int thrust = md.getMpUsed(); // consume fuel - if (((entity.isAero()) - && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUEL_CONSUMPTION)) - || (entity instanceof TeleMissile)) { + if (((entity.isAero()) && getGame().getOptions() + .booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUEL_CONSUMPTION)) + || (entity instanceof TeleMissile)) { int fuelUsed = ((IAero) entity).getFuelUsed(thrust); // if we're a gas hog, aerospace fighter and going faster than walking, then use // 2x fuel - if (((overallMoveType == EntityMovementType.MOVE_RUN) || - (overallMoveType == EntityMovementType.MOVE_SPRINT) || - (overallMoveType == EntityMovementType.MOVE_OVER_THRUST)) && - entity.hasQuirk(OptionsConstants.QUIRK_NEG_GAS_HOG)) { + if (((overallMoveType == EntityMovementType.MOVE_RUN) || (overallMoveType + == EntityMovementType.MOVE_SPRINT) || (overallMoveType == EntityMovementType.MOVE_OVER_THRUST)) + && entity.hasQuirk( + OptionsConstants.QUIRK_NEG_GAS_HOG)) { fuelUsed *= 2; } @@ -1229,30 +1365,30 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE rollTarget = a.checkThrustSITotal(thrust, overallMoveType); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { - getGame().addControlRoll(new PilotingRollData(entity.getId(), 0, - "Thrust spent during turn exceeds SI")); + getGame().addControlRoll(new PilotingRollData(entity.getId(), + 0, + "Thrust spent during turn exceeds SI")); } if (!getGame().getBoard(entity.getBoardId()).isSpace()) { - rollTarget = a.checkVelocityDouble(md.getFinalVelocity(), - overallMoveType); + rollTarget = a.checkVelocityDouble(md.getFinalVelocity(), overallMoveType); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { - getGame().addControlRoll(new PilotingRollData(entity.getId(), 0, - "Velocity greater than 2x safe thrust")); + getGame().addControlRoll(new PilotingRollData(entity.getId(), + 0, + "Velocity greater than 2x safe thrust")); } rollTarget = a.checkDown(md.getFinalNDown(), overallMoveType); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { - getGame().addControlRoll( - new PilotingRollData(entity.getId(), md.getFinalNDown(), - "descended more than two altitudes")); + getGame().addControlRoll(new PilotingRollData(entity.getId(), + md.getFinalNDown(), + "descended more than two altitudes")); } // check for hovering rollTarget = a.checkHover(md); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { - getGame().addControlRoll( - new PilotingRollData(entity.getId(), 0, "hovering")); + getGame().addControlRoll(new PilotingRollData(entity.getId(), 0, "hovering")); } // check for aero stall @@ -1262,8 +1398,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE report.subject = entity.getId(); report.addDesc(entity); addReport(report); - getGame().addControlRoll(new PilotingRollData(entity.getId(), 0, - "stalled out")); + getGame().addControlRoll(new PilotingRollData(entity.getId(), 0, "stalled out")); entity.setAltitude(entity.getAltitude() - 1); // check for crash if (gameManager.checkCrash(entity)) { @@ -1272,8 +1407,11 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE } // check to see if spheroids should lose one altitude - if (a.isSpheroid() && !a.isSpaceborne() - && a.isAirborne() && (md.getFinalNDown() == 0) && (md.getMpUsed() == 0)) { + if (a.isSpheroid() + && !a.isSpaceborne() + && a.isAirborne() + && (md.getFinalNDown() == 0) + && (md.getMpUsed() == 0)) { report = new Report(9392); report.subject = entity.getId(); report.addDesc(entity); @@ -1286,8 +1424,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE } else if (entity instanceof EscapePods && entity.isAirborne() && md.getFinalVelocity() < 2) { // Atmospheric Escape Pods that drop below velocity 2 lose altitude as dropping // units - entity.setAltitude(entity.getAltitude() - - getGame().getPlanetaryConditions().getDropRate()); + entity.setAltitude(entity.getAltitude() - getGame().getPlanetaryConditions().getDropRate()); report = new Report(6676); report.subject = entity.getId(); report.addDesc(entity); @@ -1300,11 +1437,10 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // We need to check for the removal of hull-down for tanks. // Tanks can just drive out of hull-down: if the tank was hull-down // and doesn't end hull-down we can remove the hull-down status - if (entity.isHullDown() && !md.getFinalHullDown() - && (entity instanceof Tank - || (entity instanceof QuadVee && entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE))) { + if (entity.isHullDown() && !md.getFinalHullDown() && (entity instanceof Tank || (entity instanceof QuadVee + && entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE))) { logger.debug("[HullDown] {}: hull-down cleared on move - path did not end hull-down", - entity.getDisplayName()); + entity.getDisplayName()); entity.setHullDown(false); } @@ -1387,8 +1523,9 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE report.add("3d6"); addReport(report); addReport(gameManager.damageEntity(swarmer, - swarmer.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT), - Compute.d6(3))); + swarmer.rollHitLocation(ToHitData.HIT_NORMAL, + ToHitData.SIDE_FRONT), + Compute.d6(3))); addNewLines(); swarmer.setPosition(curPos); } @@ -1423,10 +1560,14 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // Mechanical jump boosters fall damage if (md.shouldMechanicalJumpCauseFallDamage()) { - gameManager.getMainPhaseReport().addAll(gameManager.doEntityFallsInto(entity, - entity.getElevation(), md.getJumpPathHighestPoint(), - curPos, entity.getBasePilotingRoll(overallMoveType), - false, entity.getMechanicalJumpBoosterMP())); + gameManager.getMainPhaseReport() + .addAll(gameManager.doEntityFallsInto(entity, + entity.getElevation(), + md.getJumpPathHighestPoint(), + curPos, + entity.getBasePilotingRoll(overallMoveType), + false, + entity.getMechanicalJumpBoosterMP())); } // jumped into water? @@ -1469,8 +1610,8 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE } } } - } else if (!(prevStep.climbMode() && curHex.containsTerrain(Terrains.BRIDGE)) - && !(entity.getMovementMode().isHoverOrWiGE())) { + } else if (!(prevStep.climbMode() && curHex.containsTerrain(Terrains.BRIDGE)) && !(entity.getMovementMode() + .isHoverOrWiGE())) { rollTarget = entity.checkWaterMove(waterLevel, overallMoveType); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { // For falling elevation, Entity must not on hex surface @@ -1490,11 +1631,15 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // check for black ice boolean useBlackIce = getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_BLACK_ICE); boolean goodTemp = getGame().getPlanetaryConditions() - .getTemperature() <= PlanetaryConditions.BLACK_ICE_TEMP; + .getTemperature() <= PlanetaryConditions.BLACK_ICE_TEMP; boolean goodWeather = getGame().getPlanetaryConditions().getWeather().isIceStorm(); if ((useBlackIce && goodTemp) || goodWeather) { - if (ServerHelper.checkEnteringBlackIce(gameManager, curPos, curHex, useBlackIce, goodTemp, - goodWeather)) { + if (ServerHelper.checkEnteringBlackIce(gameManager, + curPos, + curHex, + useBlackIce, + goodTemp, + goodWeather)) { rollTarget = entity.checkLandingOnBlackIce(overallMoveType, curHex); if (!gameManager.doSkillCheckInPlace(entity, rollTarget)) { entity.applyDamage(); @@ -1505,21 +1650,25 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // check for building collapse IBuilding bldg = getGame().getBoard(curBoardId).getBuildingAt(curPos); if (bldg != null) { - gameManager.checkForCollapse(bldg, curPos, true, - gameManager.getMainPhaseReport()); + gameManager.checkForCollapse(bldg, curPos, true, gameManager.getMainPhaseReport()); } // Don't interact with terrain when jumping onto a building or a bridge if (entity.getElevation() == 0) { - ServerHelper.checkAndApplyMagmaCrust(curHex, entity.getElevation(), entity, curPos, true, - gameManager.getMainPhaseReport(), gameManager); + ServerHelper.checkAndApplyMagmaCrust(curHex, + entity.getElevation(), + entity, + curPos, + true, + gameManager.getMainPhaseReport(), + gameManager); ServerHelper.checkEnteringMagma(curHex, entity.getElevation(), entity, gameManager); ServerHelper.checkEnteringHazardousLiquid(curHex, entity.getElevation(), entity, gameManager); ServerHelper.checkEnteringUltraSublevel(curHex, entity.getElevation(), entity, gameManager); // jumped into swamp? maybe stuck! if (curHex.getBogDownModifier(entity.getMovementMode(), - entity instanceof LargeSupportTank) != TargetRoll.AUTOMATIC_SUCCESS) { + entity instanceof LargeSupportTank) != TargetRoll.AUTOMATIC_SUCCESS) { if (entity instanceof Mek) { entity.setStuck(true); report = new Report(2121); @@ -1529,14 +1678,17 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // check for quicksand addReport(gameManager.checkQuickSand(curPos)); } else if (!entity.hasETypeFlag(Entity.ETYPE_INFANTRY)) { - rollTarget = new PilotingRollData(entity.getId(), - 5, "entering boggy terrain"); + rollTarget = new PilotingRollData(entity.getId(), 5, "entering boggy terrain"); rollTarget.append(new PilotingRollData(entity.getId(), - curHex.getBogDownModifier(entity.getMovementMode(), - entity instanceof LargeSupportTank), - "avoid bogging down")); - if (0 < gameManager.doSkillCheckWhileMoving(entity, entity.getElevation(), curPos, curPos, - rollTarget, false)) { + curHex.getBogDownModifier(entity.getMovementMode(), + entity instanceof LargeSupportTank), + "avoid bogging down")); + if (0 < gameManager.doSkillCheckWhileMoving(entity, + entity.getElevation(), + curPos, + curPos, + rollTarget, + false)) { entity.setStuck(true); report = new Report(2081); report.add(entity.getDisplayName()); @@ -1614,8 +1766,9 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE report.add("3d6"); addReport(report); addReport(gameManager.damageEntity(swarmer, - swarmer.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT), - Compute.d6(3))); + swarmer.rollHitLocation(ToHitData.HIT_NORMAL, + ToHitData.SIDE_FRONT), + Compute.d6(3))); addNewLines(); swarmer.setPosition(curPos); } @@ -1631,27 +1784,28 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE if (entity instanceof Tank) { int modifier = 0; if (curHex.containsTerrain(Terrains.ROUGH) - || curHex.containsTerrain(Terrains.WOODS) - || curHex.containsTerrain(Terrains.JUNGLE)) { + || curHex.containsTerrain(Terrains.WOODS) + || curHex.containsTerrain( + Terrains.JUNGLE)) { modifier = 1; } report = new Report(2126); report.subject = entity.getId(); report.addDesc(entity); gameManager.getMainPhaseReport().add(report); - gameManager.getMainPhaseReport().addAll(gameManager.vehicleMotiveDamage((Tank) entity, modifier, - false, -1, true)); + gameManager.getMainPhaseReport() + .addAll(gameManager.vehicleMotiveDamage((Tank) entity, modifier, false, -1, true)); Report.addNewline(gameManager.getMainPhaseReport()); } // Jump exhaust can set a flammable atmosphere alight at both ends of the jump (TO:AR p.54). TaintedAtmosphereHandler taintedAtmosphereHandler = new TaintedAtmosphereHandler(gameManager); taintedAtmosphereHandler.checkJumpIgnition(entity, jumpLiftOffCoords, jumpLiftOffBoardId, - gameManager.getMainPhaseReport()); + gameManager.getMainPhaseReport()); boolean landedElsewhere = !curPos.equals(jumpLiftOffCoords) || (curBoardId != jumpLiftOffBoardId); if (landedElsewhere) { taintedAtmosphereHandler.checkJumpIgnition(entity, curPos, curBoardId, - gameManager.getMainPhaseReport()); + gameManager.getMainPhaseReport()); } } // End entity-is-jumping @@ -1661,8 +1815,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE report = new Report(1210); report.subject = entity.getId(); report.addDesc(entity); - if (entity instanceof QuadVee && entity.isProne() - && entity.getConversionMode() == QuadVee.CONV_MODE_MEK) { + if (entity instanceof QuadVee && entity.isProne() && entity.getConversionMode() == QuadVee.CONV_MODE_MEK) { // Fall while converting to vehicle mode cancels conversion. entity.setConvertingNow(false); report.messageId = 2454; @@ -1670,7 +1823,8 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // LAMs converting from fighter mode need to have the elevation set properly. if (entity.isAero()) { if (md.getFinalConversionMode() == EntityMovementMode.WIGE - && entity.getAltitude() > 0 && entity.getAltitude() <= 3) { + && entity.getAltitude() > 0 + && entity.getAltitude() <= 3) { entity.setElevation(entity.getAltitude() * 10); entity.setAltitude(0); } else { @@ -1687,7 +1841,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE report.messageId = 2455; report.choose(entity.getMovementMode() == EntityMovementMode.TRACKED); } else if (entity.getMovementMode() == EntityMovementMode.TRACKED - || entity.getMovementMode() == EntityMovementMode.WHEELED) { + || entity.getMovementMode() == EntityMovementMode.WHEELED) { report.messageId = 2451; } else if (entity.getMovementMode() == EntityMovementMode.WIGE) { report.messageId = 2452; @@ -1720,15 +1874,21 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // here because 'fellDuringMovement' is sometimes abused just to force // another turn and so doesn't reliably tell us. boolean continueTurnFromFall = !(getGame().getOptions() - .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_FALLS_END_MOVEMENT) - && (entity instanceof Mek) && !wasProne && entity.isProne()) - && (fellDuringMovement && !entity.isCarefulStand()) // Careful standing takes up the whole turn - && !turnOver && (entity.mpUsed < entity.getRunMP()) - && (overallMoveType != EntityMovementType.MOVE_JUMP); - if ((continueTurnFromFall || continueTurnFromPBS || continueTurnFromFishtail || continueTurnFromLevelDrop - || continueTurnFromCliffAscent - || detectedHiddenHazard) - && entity.isSelectableThisTurn() && !entity.isDoomed()) { + .booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_FALLS_END_MOVEMENT) + && (entity instanceof Mek) + && !wasProne + && entity.isProne()) + && (fellDuringMovement && !entity.isCarefulStand()) + // Careful standing takes up the whole turn + && !turnOver + && (entity.mpUsed < entity.getRunMP()) + && (overallMoveType != EntityMovementType.MOVE_JUMP); + if ((continueTurnFromFall + || continueTurnFromPBS + || continueTurnFromFishtail + || continueTurnFromLevelDrop + || continueTurnFromCliffAscent + || detectedHiddenHazard) && entity.isSelectableThisTurn() && !entity.isDoomed()) { entity.applyDamage(); entity.setDone(false); entity.setTurnInterrupted(true); @@ -1754,15 +1914,15 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE int elevation = (null == prevStep) ? entity.getElevation() : prevStep.getElevation(); if (entity.hasETypeFlag(Entity.ETYPE_LAND_AIR_MEK) && entity instanceof LandAirMek landAirMek) { addReport(gameManager.landAirMek(landAirMek, - entity.getPosition(), - elevation, - entity.delta_distance)); + entity.getPosition(), + elevation, + entity.delta_distance)); } else if (entity.hasETypeFlag(Entity.ETYPE_PROTOMEK) && entity instanceof ProtoMek protoMek) { gameManager.getMainPhaseReport() - .addAll(gameManager.landGliderPM(protoMek, - entity.getPosition(), - elevation, - entity.delta_distance)); + .addAll(gameManager.landGliderPM(protoMek, + entity.getPosition(), + elevation, + entity.delta_distance)); } else { report = new Report(2123); report.addDesc(entity); @@ -1774,7 +1934,9 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE IBuilding bldg = getGame().getBoard(curBoardId).getBuildingAt(entity.getPosition()); entity.setElevation(hex.terrainLevel(Terrains.BLDG_ELEV)); gameManager.addAffectedBldg(bldg, - gameManager.checkBuildingCollapseWhileMoving(bldg, entity, entity.getPosition())); + gameManager.checkBuildingCollapseWhileMoving(bldg, + entity, + entity.getPosition())); } else if (entity.isLocationProhibited(entity.getPosition(), 0) && !hex.hasPavement()) { // crash report = new Report(2124); @@ -1791,19 +1953,26 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // Check for stacking violations in the target hex Entity violation = Compute.stackingViolation(getGame(), - entity, entity.getPosition(), null, entity.climbMode(), false); + entity, + entity.getPosition(), + null, + entity.climbMode(), + false); if (violation != null) { - PilotingRollData prd = new PilotingRollData( - violation.getId(), 2, "fallen on"); + PilotingRollData prd = new PilotingRollData(violation.getId(), 2, "fallen on"); if (violation instanceof Dropship) { violation = entity; prd = null; } Coords targetDest = Compute.getValidDisplacement(getGame(), - violation.getId(), entity.getPosition(), 0); + violation.getId(), + entity.getPosition(), + 0); if (targetDest != null) { addReport(gameManager.doEntityDisplacement(violation, - entity.getPosition(), targetDest, prd)); + entity.getPosition(), + targetDest, + prd)); // Update the violating entity's position on the // client. @@ -1813,13 +1982,13 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // suffer an ammo/power plant hit. // TODO : a Mek suffers a Head Blown Off crit. addReport(gameManager.destroyEntity(violation, - "impossible displacement", - violation instanceof Mek, - violation instanceof Mek)); + "impossible displacement", + violation instanceof Mek, + violation instanceof Mek)); } } } else if (!entity.hasETypeFlag(Entity.ETYPE_LAND_AIR_MEK) - && !entity.hasETypeFlag(Entity.ETYPE_PROTOMEK)) { + && !entity.hasETypeFlag(Entity.ETYPE_PROTOMEK)) { // we didn't land, so we go to elevation 1 above the terrain // features @@ -1828,26 +1997,30 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // end up at one entity.setElevation(Math.min(entity.getElevation(), - 1 + hex.maxTerrainFeatureElevation( - getGame().getBoard(curBoardId).isLowAltitude()))); + 1 + hex.maxTerrainFeatureElevation(getGame().getBoard(curBoardId) + .isLowAltitude()))); } } // If we've somehow gotten here as an airborne LAM with a destroyed side torso // (such as conversion while dropping), crash now. - if (entity instanceof LandAirMek - && (entity.isLocationBad(Mek.LOC_RIGHT_TORSO) || entity.isLocationBad(Mek.LOC_LEFT_TORSO))) { + if (entity instanceof LandAirMek && (entity.isLocationBad(Mek.LOC_RIGHT_TORSO) + || entity.isLocationBad(Mek.LOC_LEFT_TORSO))) { report = new Report(9710); report.subject = entity.getId(); report.addDesc(entity); if (entity.isAirborneVTOLorWIGE()) { addReport(report); - gameManager.crashAirMek(entity, new PilotingRollData(entity.getId(), TargetRoll.AUTOMATIC_FAIL, - "side torso destroyed"), gameManager.getMainPhaseReport()); + gameManager.crashAirMek(entity, + new PilotingRollData(entity.getId(), + TargetRoll.AUTOMATIC_FAIL, + "side torso destroyed"), + gameManager.getMainPhaseReport()); } else if (entity.isAirborne() && entity.isAero()) { addReport(report); - addReport(gameManager.processCrash(entity, ((IAero) entity).getCurrentVelocity(), - entity.getPosition())); + addReport(gameManager.processCrash(entity, + ((IAero) entity).getCurrentVelocity(), + entity.getPosition())); } } @@ -1867,7 +2040,8 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // If the hex is on fire, and the swarming infantry is // *not* Battle Armor, it drops off. if (!(swarmer instanceof BattleArmor) && getGame().getBoard(curBoardId) - .getHex(curPos).containsTerrain(Terrains.FIRE)) { + .getHex(curPos) + .containsTerrain(Terrains.FIRE)) { swarmer.setSwarmTargetId(Entity.NONE); entity.setSwarmAttackerId(Entity.NONE); report = new Report(2145); @@ -1885,8 +2059,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE if (!getGame().isOutOfGame(entity)) { gameManager.entityUpdate(entity.getId(), movePath, true, losCache); if (entity.isDoomed()) { - gameManager.send(gameManager.createRemoveEntityPacket(entity.getId(), - entity.getRemovalCondition())); + gameManager.send(gameManager.createRemoveEntityPacket(entity.getId(), entity.getRemovalCondition())); } } @@ -1953,7 +2126,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // if using double-blind, update the player on new units he might see if (gameManager.doBlind()) { gameManager.send(entity.getOwner().getId(), - gameManager.createFilteredFullEntitiesPacket(entity.getOwner(), losCache)); + gameManager.createFilteredFullEntitiesPacket(entity.getOwner(), losCache)); } // if we generated a charge attack, report it now @@ -1969,15 +2142,15 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE // fuel cell, fission or fusion power plant; lacking either, it is destroyed in the End Phase of the turn // after the one it entered the water in (TW p.52, Movement Costs Table footnote 8). if ((entity instanceof Mek industrialMek) - && industrialMek.isIndustrial() - && !EnvironmentalSealingRules.canOperateFullySubmerged(industrialMek)) { + && industrialMek.isIndustrial() + && !EnvironmentalSealingRules.canOperateFullySubmerged(industrialMek)) { int waterDepth = getGame().getBoard(curBoardId).getHex(entity.getPosition()) - .terrainLevel(Terrains.WATER); + .terrainLevel(Terrains.WATER); // The Depth 2 clause of the rule carries no stance qualifier, so the parenthetical adds the shallow // prone case rather than limiting the deep one to units still on their feet. if (EnvironmentalSealingRules.isMekCompletelySubmerged(entity.isProne(), waterDepth)) { logger.debug("[EnvironmentalSealing] {}: submerged in depth {} water with an engine that cannot run " - + "sealed - drowning at end of turn", entity.getShortName(), waterDepth); + + "sealed - drowning at end of turn", entity.getShortName(), waterDepth); industrialMek.setJustMovedIntoIndustrialKillingWater(true); } else { industrialMek.setJustMovedIntoIndustrialKillingWater(false); @@ -1998,10 +2171,9 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_AERO_RULES_FUE } /** - * Returns the entity-relative elevation of its current hex's actual floor — 0 for dry hexes, - * negative for water/basement hexes. Multi-arity descents (climb-down, dangle, drop) treat - * this as the lower bound: descending past elev 0 in a water hex is fine as long as the Mek - * stays at or above this floor. + * Returns the entity-relative elevation of its current hex's actual floor — 0 for dry hexes, negative for + * water/basement hexes. Multi-arity descents (climb-down, dangle, drop) treat this as the lower bound: descending + * past elev 0 in a water hex is fine as long as the Mek stays at or above this floor. */ private int hexFloorRelative(Entity entity) { Hex hex = getGame().getBoard(entity.getBoardId()).getHex(entity.getPosition()); @@ -2009,33 +2181,33 @@ private int hexFloorRelative(Entity entity) { } /** - * Returns true when the entity has reached the actual floor of its current hex — dry ground - * for a normal hex, water bottom for a water hex, basement floor for a basement hex. Used by - * every climb / dangle / drop completion path: only at the hex floor is the descent really - * "done"; anywhere above it (clinging on a cliff face, on a building wall, or on the - * underwater portion of either) the unit is still in the air/water column and the climbing - * or dangling flag must stay set so the next-turn dialog fires. + * Returns true when the entity has reached the actual floor of its current hex — dry ground for a normal hex, water + * bottom for a water hex, basement floor for a basement hex. Used by every climb / dangle / drop completion path: + * only at the hex floor is the descent really "done"; anywhere above it (clinging on a cliff face, on a building + * wall, or on the underwater portion of either) the unit is still in the air/water column and the climbing or + * dangling flag must stay set so the next-turn dialog fires. */ private boolean entityHasReachedFloor(Entity entity) { return entity.getElevation() == hexFloorRelative(entity); } /** - * Returns true when the entity at {@code curPos} looks like it is clinging to a climbable - * feature (bridge or building) in its facing hex — i.e. that hex has a bridge or building roof - * higher than the entity's current absolute altitude. Used by the end-of-movement defensive - * cleanup to distinguish a legitimate mid-multi-turn climb (where the partial-climb branch in - * {@link #processSteps} intentionally leaves {@code climbing=true} in the SOURCE hex) from a - * stale flag that should be wiped. Without this gate, a Mek that climbed out of deep water onto - * a bridge stops mid-climb at water-surface (elevation 0), gets its climbing flag cleared, and - * never sees the continue-climbing dialog on the following turn. + * Returns true when the entity at {@code curPos} looks like it is clinging to a climbable feature (bridge or + * building) in its facing hex — i.e. that hex has a bridge or building roof higher than the entity's current + * absolute altitude. Used by the end-of-movement defensive cleanup to distinguish a legitimate mid-multi-turn climb + * (where the partial-climb branch in {@link #processSteps} intentionally leaves {@code climbing=true} in the SOURCE + * hex) from a stale flag that should be wiped. Without this gate, a Mek that climbed out of deep water onto a + * bridge stops mid-climb at water-surface (elevation 0), gets its climbing flag cleared, and never sees the + * continue-climbing dialog on the following turn. * - * @param entity the entity being checked - * @param curPos the entity's current hex (source hex of an in-progress climb) - * @param curHex the resolved hex for {@code curPos}, or null if the board doesn't have it + * @param entity the entity being checked + * @param curPos the entity's current hex (source hex of an in-progress climb) + * @param curHex the resolved hex for {@code curPos}, or null if the board doesn't have it * @return true if the facing hex has a climbable feature above the entity */ - private boolean isClingingToAdjacentClimbable(Entity entity, Coords curPos, @Nullable Hex curHex) { + private boolean isClingingToAdjacentClimbable(Entity entity, + Coords curPos, + @Nullable Hex curHex) { if (curHex == null) { return false; } @@ -2047,11 +2219,11 @@ private boolean isClingingToAdjacentClimbable(Entity entity, Coords curPos, @Nul int entityAbsAlt = curHex.getLevel() + entity.getElevation(); int adjacentBase = adjacentHex.getLevel(); if (adjacentHex.containsTerrain(Terrains.BRIDGE) - && (adjacentBase + adjacentHex.terrainLevel(Terrains.BRIDGE_ELEV)) > entityAbsAlt) { + && (adjacentBase + adjacentHex.terrainLevel(Terrains.BRIDGE_ELEV)) > entityAbsAlt) { return true; } if (adjacentHex.containsTerrain(Terrains.BUILDING) - && (adjacentBase + adjacentHex.terrainLevel(Terrains.BLDG_ELEV)) > entityAbsAlt) { + && (adjacentBase + adjacentHex.terrainLevel(Terrains.BLDG_ELEV)) > entityAbsAlt) { return true; } // Cliff edges count too: the climbable feature may be a bare elevation difference @@ -2132,11 +2304,12 @@ private void processSteps() { // Does not cover VTOLs ending in an occupied hex above the height of the enemy, or ASFs overflying // hidden enemies, or ground units ending their movement adjacent to a hex hiding an enemy (see // next clause). - if ((dist == 0) && !continueTurnFromPBS && - (Compute.stackingViolation(getGame(), this.entity, - step.getPosition(), null, this.entity.climbMode(), false - ) != null) - ) { + if ((dist == 0) && !continueTurnFromPBS && (Compute.stackingViolation(getGame(), + this.entity, + step.getPosition(), + null, + this.entity.climbMode(), + false) != null)) { // Attempting to move into hex of a hidden unit detects the unit hiddenEntity.setHidden(false); @@ -2156,15 +2329,12 @@ private void processSteps() { // Handle jumping unit's domino effect now; this does not apply to normal movement if (jumping) { // handle domino effect; report immediately - addReport( - gameManager.doEntityDisplacement( - this.entity, - previousStep.getPosition(), - step.getPosition(), - new PilotingRollData(this.entity.getId(), 0, - "Domino effect from jumping into hidden unit!") - ) - ); + addReport(gameManager.doEntityDisplacement(this.entity, + previousStep.getPosition(), + step.getPosition(), + new PilotingRollData(this.entity.getId(), + 0, + "Domino effect from jumping into hidden unit!"))); } else { // Not domino effect from jumping so mover stops short of the occupied hex; // report halted movement @@ -2199,11 +2369,11 @@ private void processSteps() { // 2. mover is Aerospace and hidden unit is within detection range of its flight path // (with or without Active Probe). // and the revealed hidden unit has not already made a pointblank shot this turn. - } else if ( - (dist <= 1) && !hiddenEntity.madePointblankShot() && - ((!this.entity.isAirborne() && md.isEndStep(step)) || - (this.entity.isAirborne() && (dist == ((this.entity.getBAPRange() > 0) ? 1 : 0)))) - ) { + } else if ((dist <= 1) && !hiddenEntity.madePointblankShot() && ((!this.entity.isAirborne() + && md.isEndStep( + step)) || (this.entity.isAirborne() && (dist == ((this.entity.getBAPRange() > 0) ? + 1 : + 0))))) { // Hidden unit should always be revealed as the PBS trigger _is_ getting revealed. hiddenEntity.setHidden(false); @@ -2259,11 +2429,9 @@ private void processSteps() { // If this is the first step, use the Entity's starting elevation int elevation = (prevStep == null) ? entity.getElevation() : prevStep.getElevation(); if (entity instanceof LandAirMek) { - addReport(gameManager.landAirMek((LandAirMek) entity, step.getPosition(), elevation, - distance)); + addReport(gameManager.landAirMek((LandAirMek) entity, step.getPosition(), elevation, distance)); } else if (entity instanceof ProtoMek) { - addReport(gameManager.landGliderPM((ProtoMek) entity, step.getPosition(), elevation, - distance)); + addReport(gameManager.landGliderPM((ProtoMek) entity, step.getPosition(), elevation, distance)); } // landing always ends movement whether successful or not } @@ -2280,21 +2448,33 @@ private void processSteps() { // Not necessarily a fall, but we need to give them a new turn to plot movement // with // likely reduced MP. - fellDuringMovement = gameManager.checkMASCFailure(entity, md) - || gameManager.checkSuperchargerFailure(entity, md); + fellDuringMovement = gameManager.checkMASCFailure(entity, + md) || gameManager.checkSuperchargerFailure(entity, + md); } } if (firstStep) { rollTarget = entity.checkGunningIt(overallMoveType); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { - int mof = gameManager.doSkillCheckWhileMoving(entity, lastElevation, lastPos, - curPos, rollTarget, false); + int mof = gameManager.doSkillCheckWhileMoving(entity, + lastElevation, + lastPos, + curPos, + rollTarget, + false); if (mof > 0) { // Since this is the first step, we don't have a previous step so we'll pass // this one in case it's needed to process a skid. - if (gameManager.processFailedVehicleManeuver(entity, curPos, 0, step, - step.isThisStepBackwards(), lastStepMoveType, distance, 2, mof)) { + if (gameManager.processFailedVehicleManeuver(entity, + curPos, + 0, + step, + step.isThisStepBackwards(), + lastStepMoveType, + distance, + 2, + mof)) { if (md.hasActiveMASC() || md.hasActiveSupercharger()) { mpUsed = entity.getRunMP(); } else { @@ -2325,15 +2505,25 @@ private void processSteps() { // and gunning does state explicitly that the roll is made before movement, this // implies the same for overdrive. if (firstStep && (overallMoveType == EntityMovementType.MOVE_SPRINT - || overallMoveType == EntityMovementType.MOVE_VTOL_SPRINT)) { + || overallMoveType == EntityMovementType.MOVE_VTOL_SPRINT)) { rollTarget = entity.checkUsingOverdrive(EntityMovementType.MOVE_SPRINT); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { - int mof = gameManager.doSkillCheckWhileMoving(entity, lastElevation, lastPos, - curPos, rollTarget, false); + int mof = gameManager.doSkillCheckWhileMoving(entity, + lastElevation, + lastPos, + curPos, + rollTarget, + false); if (mof > 0) { - if (gameManager.processFailedVehicleManeuver(entity, curPos, 0, step, - step.isThisStepBackwards(), - lastStepMoveType, distance, 2, mof)) { + if (gameManager.processFailedVehicleManeuver(entity, + curPos, + 0, + step, + step.isThisStepBackwards(), + lastStepMoveType, + distance, + 2, + mof)) { if (md.hasActiveMASC() || md.hasActiveSupercharger()) { mpUsed = entity.getRunMP(); } else { @@ -2363,24 +2553,40 @@ private void processSteps() { // Non-omni QuadVees converting to vehicle mode dump any riding BA in the // starting hex if they fail to make an anti-mek check. // http://bg.battletech.com/forums/index.php?topic=55263.msg1271423#msg1271423 - if (entity instanceof QuadVee && entity.getConversionMode() == QuadVee.CONV_MODE_MEK - && !entity.isOmni()) { + if (entity instanceof QuadVee + && entity.getConversionMode() == QuadVee.CONV_MODE_MEK + && !entity.isOmni()) { for (Entity rider : entity.getExternalUnits()) { - addReport(gameManager.checkDropBAFromConverting(entity, rider, curPos, curFacing, - false, false, false)); + addReport(gameManager.checkDropBAFromConverting(entity, + rider, + curPos, + curFacing, + false, + false, + false)); } } else if ((entity.getEntityType() & Entity.ETYPE_LAND_AIR_MEK) != 0) { // External units on LAMs, including swarmers, fall automatically and take // damage, // and the LAM itself may take one or more criticalSlots. for (Entity rider : entity.getExternalUnits()) { - addReport(gameManager.checkDropBAFromConverting(entity, rider, curPos, curFacing, true, true, - true)); + addReport(gameManager.checkDropBAFromConverting(entity, + rider, + curPos, + curFacing, + true, + true, + true)); } final int swarmerId = entity.getSwarmAttackerId(); if (Entity.NONE != swarmerId) { - addReport(gameManager.checkDropBAFromConverting(entity, getGame().getEntity(swarmerId), - curPos, curFacing, true, true, true)); + addReport(gameManager.checkDropBAFromConverting(entity, + getGame().getEntity(swarmerId), + curPos, + curFacing, + true, + true, + true)); } } @@ -2413,7 +2619,8 @@ private void processSteps() { // structural damage rollTarget = a.checkThrustSI(thrustUsed, overallMoveType); if ((rollTarget.getValue() != TargetRoll.CHECK_FALSE) - && !(entity instanceof FighterSquadron) && !getGame().useVectorMove()) { + && !(entity instanceof FighterSquadron) + && !getGame().useVectorMove()) { if (!gameManager.doSkillCheckInSpace(entity, rollTarget)) { a.setSI(a.getSI() - 1); if (entity instanceof LandAirMek) { @@ -2424,28 +2631,27 @@ private void processSteps() { // Lets auto-eject if we can! if (a instanceof LandAirMek lam) { // LAMs eject if the CT destroyed switch is on - if (lam.isAutoEject() - && (!getGame().getOptions() - .booleanOption(OptionsConstants.RPG_CONDITIONAL_EJECTION) - || (getGame().getOptions() - .booleanOption(OptionsConstants.RPG_CONDITIONAL_EJECTION) - && lam.isCondEjectCTDest()))) { + if (lam.isAutoEject() && (!getGame().getOptions() + .booleanOption(OptionsConstants.RPG_CONDITIONAL_EJECTION) + || (getGame().getOptions() + .booleanOption( + OptionsConstants.RPG_CONDITIONAL_EJECTION) + && lam.isCondEjectCTDest()))) { addReport(gameManager.ejectEntity(entity, true, false)); } } else { // Aeros eject if the SI Destroyed switch is on Aero aero = (Aero) a; - if (aero.isAutoEject() - && (!getGame().getOptions() - .booleanOption(OptionsConstants.RPG_CONDITIONAL_EJECTION) - || (getGame().getOptions() - .booleanOption(OptionsConstants.RPG_CONDITIONAL_EJECTION) - && aero.isCondEjectSIDest()))) { + if (aero.isAutoEject() && (!getGame().getOptions() + .booleanOption(OptionsConstants.RPG_CONDITIONAL_EJECTION) + || (getGame().getOptions() + .booleanOption( + OptionsConstants.RPG_CONDITIONAL_EJECTION) + && aero.isCondEjectSIDest()))) { addReport(gameManager.ejectEntity(entity, true, false)); } } - addReport(gameManager.destroyEntity(entity, "Structural Integrity Collapse", - false)); + addReport(gameManager.destroyEntity(entity, "Structural Integrity Collapse", false)); } } } @@ -2454,10 +2660,8 @@ private void processSteps() { int hits = entity.getCrew().getHits(); int health = 6 - hits; - if ((thrustUsed > (2 * health)) && !getGame().useVectorMove() - && !(entity instanceof TeleMissile)) { - int targetRoll = 2 + (thrustUsed - (2 * health)) - + (2 * hits); + if ((thrustUsed > (2 * health)) && !getGame().useVectorMove() && !(entity instanceof TeleMissile)) { + int targetRoll = 2 + (thrustUsed - (2 * health)) + (2 * hits); gameManager.resistGForce(entity, targetRoll); } @@ -2502,7 +2706,7 @@ private void processSteps() { Vector maneuverSpecial = new Vector<>(); maneuverSpecial.add(maneuverReport); maneuverSpecial.addAll(gameManager.getMainPhaseReport() - .subList(reportsBefore, gameManager.getMainPhaseReport().size())); + .subList(reportsBefore, gameManager.getMainPhaseReport().size())); gameManager.sendSpecialReport(maneuverSpecial); if (maneuverFailed) { a.setFailedManeuver(true); @@ -2581,7 +2785,7 @@ private void processSteps() { // ASF can actually include anything, // because we might // have combat dropping troops - potentialASF.addElement(id); + potentialASF.addElement(id); } } @@ -2711,16 +2915,21 @@ private void processSteps() { fighterCount = 0; doorDamage = false; } - int bonus = Math.max(0, - distribution[currentDoor] - 2); + int bonus = Math.max(0, distribution[currentDoor] - 2); Entity fighter = getGame().getEntity(fighterId); - if (!gameManager.launchUnit(entity, fighter, curPos, curFacing, step.getVelocity(), - step.getAltitude(), step.getVectors(), bonus)) { + if (!gameManager.launchUnit(entity, + fighter, + curPos, + curFacing, + step.getVelocity(), + step.getAltitude(), + step.getVectors(), + bonus)) { logger.error("Server was told to unload {} from {} into {}", - fighter.getDisplayName(), - entity.getDisplayName(), - curPos.getBoardNum()); + fighter.getDisplayName(), + entity.getDisplayName(), + curPos.getBoardNum()); } if (doorReport != null) { addReport(doorReport); @@ -2748,13 +2957,18 @@ private void processSteps() { for (int dropShipId : launches) { // check to see if we are in the same door Entity ds = getGame().getEntity(dropShipId); - if (!gameManager.launchUnit(entity, ds, curPos, curFacing, - step.getVelocity(), step.getAltitude(), - step.getVectors(), 0)) { + if (!gameManager.launchUnit(entity, + ds, + curPos, + curFacing, + step.getVelocity(), + step.getAltitude(), + step.getVectors(), + 0)) { logger.error("Error! Server was told to unload {} from {} into {}", - ds.getDisplayName(), - entity.getDisplayName(), - curPos.getBoardNum()); + ds.getDisplayName(), + entity.getDisplayName(), + curPos.getBoardNum()); } } } @@ -2810,8 +3024,9 @@ private void processSteps() { // Unless we're an ICE- or fuel cell-powered IndustrialMek, // standing up may build heat. if ((entity instanceof Mek) && entity.hasEngine() && !(((Mek) entity).isIndustrial() - && ((entity.getEngine().getEngineType() == Engine.COMBUSTION_ENGINE) - || (entity.getEngine().getEngineType() == Engine.FUEL_CELL)))) { + && ((entity.getEngine() + .getEngineType() == Engine.COMBUSTION_ENGINE) || (entity.getEngine() + .getEngineType() == Engine.FUEL_CELL)))) { entity.heatBuildup += Game.rulesManager.getRulesHeat().standingHeat(); } entity.setProne(false); @@ -2857,8 +3072,7 @@ private void processSteps() { break; } - if ((step.getType() == MoveStepType.SEARCHLIGHT) - && entity.hasSearchlight()) { + if ((step.getType() == MoveStepType.SEARCHLIGHT) && entity.hasSearchlight()) { final boolean SearchOn = !entity.isUsingSearchlight(); entity.setSearchlightState(SearchOn); if (gameManager.doBlind()) { // if double-blind, we may need to filter the @@ -2868,19 +3082,16 @@ private void processSteps() { for (Player p : playersVector) { if (vCanSee.contains(p)) { // Player sees the unit gameManager.sendServerChat(p.getId(), - entity.getDisplayName() - + " switched searchlight " - + (SearchOn ? "on" : "off") + '.'); + entity.getDisplayName() + " switched searchlight " + (SearchOn ? "on" : "off") + '.'); } else { gameManager.sendServerChat(p.getId(), - "An unseen unit" + " switched searchlight " - + (SearchOn ? "on" : "off") + '.'); + "An unseen unit" + " switched searchlight " + (SearchOn ? "on" : "off") + '.'); } } } else { // No double-blind, everyone can see this - gameManager.sendServerChat( - entity.getDisplayName() + " switched searchlight " - + (SearchOn ? "on" : "off") + '.'); + gameManager.sendServerChat(entity.getDisplayName() + " switched searchlight " + (SearchOn ? + "on" : + "off") + '.'); } } @@ -2890,61 +3101,66 @@ private void processSteps() { mpUsed = step.getMpUsed(); if (cachedGravityLimit < 0) { - cachedGravityLimit = EntityMovementType.MOVE_JUMP == moveType - ? (step.isUsingMekJumpBooster() - ? entity.getMechanicalJumpBoosterMP(MPCalculationSetting.NO_GRAVITY) - : entity.getJumpMP(MPCalculationSetting.NO_GRAVITY)) - : entity.getRunningGravityLimit(); + cachedGravityLimit = EntityMovementType.MOVE_JUMP == moveType ? + (step.isUsingMekJumpBooster() ? entity.getMechanicalJumpBoosterMP( + MPCalculationSetting.NO_GRAVITY) : entity.getJumpMP(MPCalculationSetting.NO_GRAVITY)) : + entity.getRunningGravityLimit(); } // check for charge if (step.getType() == MoveStepType.CHARGE) { if (entity.canCharge()) { - gameManager.checkExtremeGravityMovement(entity, step, lastStepMoveType, - curPos, cachedGravityLimit); + gameManager.checkExtremeGravityMovement(entity, step, lastStepMoveType, curPos, cachedGravityLimit); Targetable target = step.getTarget(getGame()); if (target != null) { - ChargeAttackAction caa = new ChargeAttackAction( - entity.getId(), target.getTargetType(), - target.getId(), target.getPosition()); + ChargeAttackAction caa = new ChargeAttackAction(entity.getId(), + target.getTargetType(), + target.getId(), + target.getPosition()); entity.setDisplacementAttack(caa); getGame().addDisplacementAttack(caa); charge = caa; } else { - String message = "Illegal charge!! " + entity.getDisplayName() + - " is attempting to charge a null target!"; + String message = "Illegal charge!! " + + entity.getDisplayName() + + " is attempting to charge a null target!"; logger.info(message); gameManager.sendServerChat(message); return; } } else if (entity.isAirborneVTOLorWIGE() && entity.canRam()) { - gameManager.checkExtremeGravityMovement(entity, step, lastStepMoveType, - curPos, cachedGravityLimit); + gameManager.checkExtremeGravityMovement(entity, step, lastStepMoveType, curPos, cachedGravityLimit); Targetable target = step.getTarget(getGame()); if (target != null) { - AirMekRamAttackAction raa = new AirMekRamAttackAction( - entity.getId(), target.getTargetType(), - target.getId(), target.getPosition()); + AirMekRamAttackAction raa = new AirMekRamAttackAction(entity.getId(), + target.getTargetType(), + target.getId(), + target.getPosition()); entity.setDisplacementAttack(raa); entity.setRamming(true); getGame().addDisplacementAttack(raa); charge = raa; } else { - String message = "Illegal charge!! " + entity.getDisplayName() - + " is attempting to charge a null target!"; + String message = "Illegal charge!! " + + entity.getDisplayName() + + " is attempting to charge a null target!"; logger.info(message); gameManager.sendServerChat(message); return; } } else { gameManager.sendServerChat("Illegal charge!! I don't think " - + entity.getDisplayName() - + " should be allowed to charge," - + " but the client of " - + entity.getOwner().getName() + " disagrees."); + + entity.getDisplayName() + + " should be allowed to charge," + + " but the client of " + + entity.getOwner() + .getName() + + " disagrees."); gameManager.sendServerChat("Please make sure " - + entity.getOwner().getName() - + " is running MegaMek " + MMConstants.VERSION - + ", or if that is already the case, submit a bug report at https://github.com/MegaMek/megamek/issues"); + + entity.getOwner() + .getName() + + " is running MegaMek " + + MMConstants.VERSION + + ", or if that is already the case, submit a bug report at https://github.com/MegaMek/megamek/issues"); return; } break; @@ -2970,8 +3186,10 @@ private void processSteps() { } else { // A DFA step only carries a target position when the target hex was picked in a dialog, so // report the hex the step lands in instead - that is the one the player is looking at. - String errorMessage = "Illegal DFA by " + entity.getDisplayName() - + " against non-existent entity at " + step.getPosition(); + String errorMessage = "Illegal DFA by " + + entity.getDisplayName() + + " against non-existent entity at " + + step.getPosition(); gameManager.sendServerChat(errorMessage); logger.error(errorMessage); targetID = Entity.NONE; @@ -2980,25 +3198,30 @@ private void processSteps() { targetType = Targetable.TYPE_ENTITY; } - DfaAttackAction daa = new DfaAttackAction(entity.getId(), - targetType, targetID, - step.getPosition()); + DfaAttackAction daa = new DfaAttackAction(entity.getId(), targetType, targetID, step.getPosition()); entity.setDisplacementAttack(daa); - entity.setElevation(Game.rulesManager.getRulesMovement().getDFAElevation(entity.getGame(), - entity.getId(), targetID, step)); + entity.setElevation(Game.rulesManager.getRulesMovement() + .getDFAElevation(entity.getGame(), + entity.getId(), + targetID, + step)); getGame().addDisplacementAttack(daa); charge = daa; } else { gameManager.sendServerChat("Illegal DFA!! I don't think " - + entity.getDisplayName() - + " should be allowed to DFA," - + " but the client of " - + entity.getOwner().getName() + " disagrees."); + + entity.getDisplayName() + + " should be allowed to DFA," + + " but the client of " + + entity.getOwner() + .getName() + + " disagrees."); gameManager.sendServerChat("Please make sure " - + entity.getOwner().getName() - + " is running MegaMek " + MMConstants.VERSION - + ", or if that is already the case, submit a bug report at https://github.com/MegaMek/megamek/issues"); + + entity.getOwner() + .getName() + + " is running MegaMek " + + MMConstants.VERSION + + ", or if that is already the case, submit a bug report at https://github.com/MegaMek/megamek/issues"); return; } @@ -3010,21 +3233,26 @@ private void processSteps() { if (entity.canRam()) { Targetable target = step.getTarget(getGame()); RamAttackAction raa = new RamAttackAction(entity.getId(), - target.getTargetType(), target.getId(), - target.getPosition()); + target.getTargetType(), + target.getId(), + target.getPosition()); entity.setRamming(true); getGame().addRam(raa); ram = raa; } else { gameManager.sendServerChat("Illegal ram!! I don't think " - + entity.getDisplayName() - + " should be allowed to charge," - + " but the client of " - + entity.getOwner().getName() + " disagrees."); + + entity.getDisplayName() + + " should be allowed to charge," + + " but the client of " + + entity.getOwner() + .getName() + + " disagrees."); gameManager.sendServerChat("Please make sure " - + entity.getOwner().getName() - + " is running MegaMek " + MMConstants.VERSION - + ", or if that is already the case, submit a bug report at https://github.com/MegaMek/megamek/issues"); + + entity.getOwner() + .getName() + + " is running MegaMek " + + MMConstants.VERSION + + ", or if that is already the case, submit a bug report at https://github.com/MegaMek/megamek/issues"); return; } break; @@ -3105,7 +3333,8 @@ private void processSteps() { // step is already gated client-side, so this is a defensive server-side enforcement. if (!inf.hasWorkingMisc(MiscType.F_TRENCH_CAPABLE)) { logger.debug("[Fortify] {}: fortify rejected - no fieldworks-capable equipment " - + "(F_TRENCH_CAPABLE)", entity.getDisplayName()); + + "(F_TRENCH_CAPABLE)", + entity.getDisplayName()); continue; } inf.beginFortify(); @@ -3125,21 +3354,19 @@ private void processSteps() { } else if (step.getType() == MoveStepType.ABANDON_BRIDGE) { processAbandonBridgeStep(inf); continue; - } else if ((step.getType() != MoveStepType.TURN_LEFT) - && (step.getType() != MoveStepType.TURN_RIGHT)) { + } else if ((step.getType() != MoveStepType.TURN_LEFT) && (step.getType() != MoveStepType.TURN_RIGHT)) { // other movement clears dug in and hitting the deck status inf.clearGroundPostures(); } if (step.getType() == MoveStepType.TAKE_COVER) { - if (Infantry.hasValidCover(getGame(), step.getPosition(), - step.getElevation())) { + if (Infantry.hasValidCover(getGame(), step.getPosition(), step.getElevation())) { inf.setTakingCover(true); } else { gameManager.sendServerChat(entity.getDisplayName() - + " failed to take cover: " - + "no valid unit found in " - + step.getPosition()); + + " failed to take cover: " + + "no valid unit found in " + + step.getPosition()); } } } @@ -3151,7 +3378,8 @@ private void processSteps() { // Defensive server-side check mirroring the MoveStep legality. if (!entity.hasWorkingMisc(MiscType.F_TRENCH_CAPABLE)) { logger.debug("[Fortify] {}: fortify rejected - no fieldworks-capable equipment " - + "(F_TRENCH_CAPABLE)", entity.getDisplayName()); + + "(F_TRENCH_CAPABLE)", + entity.getDisplayName()); } else { fortifier.beginFortify(); } @@ -3161,24 +3389,30 @@ private void processSteps() { // If we have turned, check whether we have fulfilled any turn mode // requirements. - if ((step.getType() == MoveStepType.TURN_LEFT - || step.getType() == MoveStepType.TURN_RIGHT) - && entity.usesTurnMode()) { + if ((step.getType() == MoveStepType.TURN_LEFT || step.getType() == MoveStepType.TURN_RIGHT) + && entity.usesTurnMode()) { int straight = 0; if (prevStep != null) { straight = prevStep.getNStraight(); } - rollTarget = entity.checkTurnModeFailure(overallMoveType, straight, - md.getMpUsed(), step.getPosition()); + rollTarget = entity.checkTurnModeFailure(overallMoveType, straight, md.getMpUsed(), step.getPosition()); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { - int mof = gameManager.doSkillCheckWhileMoving(entity, lastElevation, lastPos, - curPos, rollTarget, false); + int mof = gameManager.doSkillCheckWhileMoving(entity, + lastElevation, + lastPos, + curPos, + rollTarget, + false); if (mof > 0) { - if (gameManager.processFailedVehicleManeuver(entity, curPos, - step.getFacing() - curFacing, - (null == prevStep) ? step : prevStep, - step.isThisStepBackwards(), - lastStepMoveType, distance, mof, mof)) { + if (gameManager.processFailedVehicleManeuver(entity, + curPos, + step.getFacing() - curFacing, + (null == prevStep) ? step : prevStep, + step.isThisStepBackwards(), + lastStepMoveType, + distance, + mof, + mof)) { if (md.hasActiveMASC() || md.hasActiveSupercharger()) { mpUsed = entity.getRunMP(); } else { @@ -3202,14 +3436,19 @@ private void processSteps() { rollTarget = entity.getBasePilotingRoll(); entity.addPilotingModifierForTerrain(rollTarget); rollTarget.addModifier(0, "bootlegger maneuver"); - int mof = gameManager.doSkillCheckWhileMoving(entity, lastElevation, - curPos, curPos, rollTarget, false); + int mof = gameManager.doSkillCheckWhileMoving(entity, lastElevation, curPos, curPos, rollTarget, false); if (mof > 0) { // If the bootlegger maneuver fails, we treat it as a turn in a random // direction. - gameManager.processFailedVehicleManeuver(entity, curPos, Compute.d6() < 4 ? -1 : 1, - (null == prevStep) ? step : prevStep, - step.isThisStepBackwards(), lastStepMoveType, distance, 2, mof); + gameManager.processFailedVehicleManeuver(entity, + curPos, + Compute.d6() < 4 ? -1 : 1, + (null == prevStep) ? step : prevStep, + step.isThisStepBackwards(), + lastStepMoveType, + distance, + 2, + mof); curFacing = entity.getFacing(); curPos = entity.getPosition(); break; @@ -3257,34 +3496,45 @@ private void processSteps() { reportElevatorUse(step, curElevation); // check for automatic unstick - if (entity.canUnstickByJumping() && entity.isStuck() - && (moveType == EntityMovementType.MOVE_JUMP)) { + if (entity.canUnstickByJumping() && entity.isStuck() && (moveType == EntityMovementType.MOVE_JUMP)) { entity.setStuck(false); entity.setCanUnstickByJumping(false); } // check for leap if (!lastPos.equals(curPos) - && (stepMoveType != EntityMovementType.MOVE_JUMP) && (entity instanceof Mek) - && !entity.isAirborne() && (step.getClearance() <= 0) // Don't check airborne LAMs - && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_LEAPING)) { - int leapDistance = (lastElevation - + getGame().getBoard(curBoardId).getHex(lastPos).getLevel()) - - (curElevation + curHex.getLevel()); - logger.debug("[LEAP-TRACE] Leap check: lastPos={}, curPos={}, lastElevation={}, " + - "curElevation={}, leapDistance={}, isClimbing={}, isDangling={}", - lastPos, curPos, lastElevation, curElevation, leapDistance, - entity.isClimbing(), entity.isDangling()); + && (stepMoveType != EntityMovementType.MOVE_JUMP) + && (entity instanceof Mek) + && !entity.isAirborne() + && (step.getClearance() <= 0) + // Don't check airborne LAMs + && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMENT_TAC_OPS_LEAPING)) { + int leapDistance = (lastElevation + getGame().getBoard(curBoardId) + .getHex(lastPos) + .getLevel()) - (curElevation + curHex.getLevel()); + logger.debug("[LEAP-TRACE] Leap check: lastPos={}, curPos={}, lastElevation={}, " + + "curElevation={}, leapDistance={}, isClimbing={}, isDangling={}", + lastPos, + curPos, + lastElevation, + curElevation, + leapDistance, + entity.isClimbing(), + entity.isDangling()); if (leapDistance > 2) { - logger.debug("[LEAP-TRACE] Leaping {} levels from {} to {}", - leapDistance, lastPos, curPos); + logger.debug("[LEAP-TRACE] Leaping {} levels from {} to {}", leapDistance, lastPos, curPos); // skill check for leg damage rollTarget = entity.getBasePilotingRoll(stepMoveType); entity.addPilotingModifierForTerrain(rollTarget, curPos, step.getBoardId()); rollTarget.append(new PilotingRollData(entity.getId(), - 2 * leapDistance, Messages.getString("TacOps.leaping.leg_damage"))); - if (0 < gameManager.doSkillCheckWhileMoving(entity, lastElevation, - lastPos, curPos, rollTarget, false)) { + 2 * leapDistance, + Messages.getString("TacOps.leaping.leg_damage"))); + if (0 < gameManager.doSkillCheckWhileMoving(entity, + lastElevation, + lastPos, + curPos, + rollTarget, + false)) { // do leg damage addReport(gameManager.damageEntity(entity, new HitData(Mek.LOC_LEFT_LEG), leapDistance)); addReport(gameManager.damageEntity(entity, new HitData(Mek.LOC_RIGHT_LEG), leapDistance)); @@ -3305,23 +3555,31 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN rollTarget = entity.getBasePilotingRoll(stepMoveType); entity.addPilotingModifierForTerrain(rollTarget, curPos, step.getBoardId()); rollTarget.append(new PilotingRollData(entity.getId(), - leapDistance, Messages.getString("TacOps.leaping.fall_damage"))); - if (0 < gameManager.doSkillCheckWhileMoving(entity, lastElevation, - lastPos, curPos, rollTarget, false)) { + leapDistance, + Messages.getString("TacOps.leaping.fall_damage"))); + if (0 < gameManager.doSkillCheckWhileMoving(entity, + lastElevation, + lastPos, + curPos, + rollTarget, + false)) { entity.setElevation(lastElevation); - addReport(gameManager.doEntityFallsInto(entity, lastElevation, - lastPos, curPos, - entity.getBasePilotingRoll(overallMoveType), false)); + addReport(gameManager.doEntityFallsInto(entity, + lastElevation, + lastPos, + curPos, + entity.getBasePilotingRoll(overallMoveType), + false)); } } } // Check for infantry gliding down terrain with glider wings (IO p.85) - if (!lastPos.equals(curPos) && (entity instanceof ConvInfantry infantry) - && infantry.hasAbility(OptionsConstants.MD_PL_GLIDER) - && infantry.canUseGliderWings()) { - int glideDistance = (lastElevation + getGame().getBoard(curBoardId).getHex(lastPos).getLevel()) - - (curElevation + curHex.getLevel()); + if (!lastPos.equals(curPos) && (entity instanceof ConvInfantry infantry) && infantry.hasAbility( + OptionsConstants.MD_PL_GLIDER) && infantry.canUseGliderWings()) { + int glideDistance = (lastElevation + getGame().getBoard(curBoardId) + .getHex(lastPos) + .getLevel()) - (curElevation + curHex.getLevel()); // Report if descending more than normal max (1 for regular infantry) if (glideDistance > infantry.getMaxElevationChange()) { Report r = new Report(2522); @@ -3334,9 +3592,17 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN } // Check for skid. - rollTarget = entity.checkSkid(moveType, prevHex, overallMoveType, - prevStep, step, prevFacing, curFacing, lastPos, curPos, - isInfantry, distance - 1); + rollTarget = entity.checkSkid(moveType, + prevHex, + overallMoveType, + prevStep, + step, + prevFacing, + curFacing, + lastPos, + curPos, + isInfantry, + distance - 1); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { // Have an entity-meaningful PSR message. boolean psrFailed; @@ -3346,10 +3612,18 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // facing entity.setFacing(curFacing); psrFailed = (0 < gameManager.doSkillCheckWhileMoving(entity, - lastElevation, lastPos, lastPos, rollTarget, true)); + lastElevation, + lastPos, + lastPos, + rollTarget, + true)); } else { psrFailed = (0 < gameManager.doSkillCheckWhileMoving(entity, - lastElevation, lastPos, lastPos, rollTarget, false)); + lastElevation, + lastPos, + lastPos, + rollTarget, + false)); } // Does the entity skid? @@ -3375,9 +3649,13 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN skidDirection = (skidDirection + 3) % 6; } - if (gameManager.processSkid(entity, curPos, prevStep.getElevation(), - skidDirection, skidDistance, prevStep, - lastStepMoveType)) { + if (gameManager.processSkid(entity, + curPos, + prevStep.getElevation(), + skidDirection, + skidDistance, + prevStep, + lastStepMoveType)) { return; } @@ -3409,24 +3687,34 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // check sideslip if ((entity instanceof VTOL) - || (entity.getMovementMode() == EntityMovementMode.HOVER) - || (entity.getMovementMode() == EntityMovementMode.WIGE - && step.getClearance() > 0)) { - rollTarget = entity.checkSideSlip(moveType, prevHex, - overallMoveType, prevStep, prevFacing, curFacing, - lastPos, curPos, distance, md.hasActiveMASC()); + || (entity.getMovementMode() == EntityMovementMode.HOVER) + || (entity.getMovementMode() == EntityMovementMode.WIGE && step.getClearance() > 0)) { + rollTarget = entity.checkSideSlip(moveType, + prevHex, + overallMoveType, + prevStep, + prevFacing, + curFacing, + lastPos, + curPos, + distance, + md.hasActiveMASC()); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { - int moF = gameManager.doSkillCheckWhileMoving(entity, lastElevation, - lastPos, curPos, rollTarget, false); + int moF = gameManager.doSkillCheckWhileMoving(entity, + lastElevation, + lastPos, + curPos, + rollTarget, + false); if (moF > 0) { int elev; int sideslipDistance; int skidDirection; Coords start; if (step.getType() == MoveStepType.LATERAL_LEFT - || step.getType() == MoveStepType.LATERAL_RIGHT - || step.getType() == MoveStepType.LATERAL_LEFT_BACKWARDS - || step.getType() == MoveStepType.LATERAL_RIGHT_BACKWARDS) { + || step.getType() == MoveStepType.LATERAL_RIGHT + || step.getType() == MoveStepType.LATERAL_LEFT_BACKWARDS + || step.getType() == MoveStepType.LATERAL_RIGHT_BACKWARDS) { // A failed controlled sideslip always results in moving one additional hex // in the direction of the intentional sideslip. elev = step.getElevation(); @@ -3448,21 +3736,23 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN report.add(sideslipDistance); addReport(report); - if (gameManager.processSkid(entity, start, elev, skidDirection, - sideslipDistance, (null == prevStep) ? step : prevStep, - lastStepMoveType)) { + if (gameManager.processSkid(entity, + start, + elev, + skidDirection, + sideslipDistance, + (null == prevStep) ? step : prevStep, + lastStepMoveType)) { return; } - if (!entity.isDestroyed() && !entity.isDoomed() - && (mpUsed < entity.getRunMP())) { + if (!entity.isDestroyed() && !entity.isDoomed() && (mpUsed < entity.getRunMP())) { fellDuringMovement = true; // No, but it should // work... } - if ((entity.getElevation() == 0) - && ((entity.getMovementMode() == EntityMovementMode.VTOL) - || (entity.getMovementMode() == EntityMovementMode.WIGE))) { + if ((entity.getElevation() == 0) && ((entity.getMovementMode() == EntityMovementMode.VTOL) + || (entity.getMovementMode() == EntityMovementMode.WIGE))) { turnOver = true; } // set entity parameters @@ -3477,26 +3767,31 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // check if we've moved into rubble boolean isLastStep = step.equals(md.getLastStep()); - rollTarget = entity.checkRubbleMove(step, overallMoveType, curHex, - lastPos, curPos, isLastStep, isPavementStep); + rollTarget = entity.checkRubbleMove(step, + overallMoveType, + curHex, + lastPos, + curPos, + isLastStep, + isPavementStep); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { - gameManager.doSkillCheckWhileMoving(entity, lastElevation, lastPos, curPos, - rollTarget, true); + gameManager.doSkillCheckWhileMoving(entity, lastElevation, lastPos, curPos, rollTarget, true); } // check if we are using reckless movement - rollTarget = entity.checkRecklessMove(step, overallMoveType, curHex, - lastPos, curPos, prevHex); + rollTarget = entity.checkRecklessMove(step, overallMoveType, curHex, lastPos, curPos, prevHex); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { if (entity instanceof Mek) { - gameManager.doSkillCheckWhileMoving(entity, lastElevation, lastPos, - curPos, rollTarget, true); + gameManager.doSkillCheckWhileMoving(entity, lastElevation, lastPos, curPos, rollTarget, true); } else if (entity instanceof Tank) { - if (0 < gameManager.doSkillCheckWhileMoving(entity, lastElevation, - lastPos, curPos, rollTarget, false)) { + if (0 < gameManager.doSkillCheckWhileMoving(entity, + lastElevation, + lastPos, + curPos, + rollTarget, + false)) { // assume VTOLs in flight are always in clear terrain - if ((0 == curHex.terrainsPresent()) - || (step.getClearance() > 0)) { + if ((0 == curHex.terrainsPresent()) || (step.getClearance() > 0)) { if (entity instanceof VTOL) { report = new Report(2208); } else { @@ -3517,8 +3812,9 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // entity is both giver and taker // for charge damage HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT); - addReport(gameManager.damageEntity(entity, hit, ChargeAttackAction - .getDamageTakenBy(entity, entity))); + addReport(gameManager.damageEntity(entity, + hit, + ChargeAttackAction.getDamageTakenBy(entity, entity))); turnOver = true; break; } @@ -3529,8 +3825,13 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // Let's check for hazardous liquid damage too if (stepMoveType != EntityMovementType.MOVE_JUMP) { if (!curPos.equals(lastPos)) { - ServerHelper.checkAndApplyMagmaCrust(curHex, step.getElevation(), entity, curPos, false, - gameManager.getMainPhaseReport(), gameManager); + ServerHelper.checkAndApplyMagmaCrust(curHex, + step.getElevation(), + entity, + curPos, + false, + gameManager.getMainPhaseReport(), + gameManager); ServerHelper.checkEnteringMagma(curHex, step.getElevation(), entity, gameManager); ServerHelper.checkEnteringHazardousLiquid(curHex, step.getElevation(), entity, gameManager); ServerHelper.checkEnteringUltraSublevel(curHex, entity.getElevation(), entity, gameManager); @@ -3539,18 +3840,19 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN if (step.getType() == MoveStepType.CHAFF) { List> chaffDispensers = entity.getMiscEquipment(MiscType.F_CHAFF_POD) - .stream().filter(Mounted::isReady) - .collect(Collectors.toList()); + .stream() + .filter(Mounted::isReady) + .collect(Collectors.toList()); if (!chaffDispensers.isEmpty()) { chaffDispensers.getFirst().setFired(true); - gameManager.createSmoke(curPos, getGame().getBoard(step.getBoardId()), - SmokeCloud.SMOKE_CHAFF_LIGHT, 1); + gameManager.createSmoke(curPos, + getGame().getBoard(step.getBoardId()), + SmokeCloud.SMOKE_CHAFF_LIGHT, + 1); Hex hex = getGame().getBoard(curBoardId).getHex(curPos); hex.addTerrain(new Terrain(Terrains.SMOKE, SmokeCloud.SMOKE_CHAFF_LIGHT)); gameManager.sendChangedHex(curPos); - report = new Report(2512) - .addDesc(entity) - .subject(entity.getId()); + report = new Report(2512).addDesc(entity).subject(entity.getId()); addReport(report); } @@ -3563,11 +3865,20 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN } if (curHex.terrainLevel(Terrains.MAGMA) != 2 || jumpedIntoMagma) { // check if we've moved into a swamp - rollTarget = entity.checkBogDown(step, lastStepMoveType, curHex, - lastPos, curPos, lastElevation, isPavementStep); + rollTarget = entity.checkBogDown(step, + lastStepMoveType, + curHex, + lastPos, + curPos, + lastElevation, + isPavementStep); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { - if (0 < gameManager.doSkillCheckWhileMoving(entity, lastElevation, lastPos, - curPos, rollTarget, false)) { + if (0 < gameManager.doSkillCheckWhileMoving(entity, + lastElevation, + lastPos, + curPos, + rollTarget, + false)) { entity.setStuck(true); entity.setCanUnstickByJumping(true); report = new Report(2081); @@ -3578,16 +3889,24 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN addReport(gameManager.checkQuickSand(curPos)); // check for accidental stacking violation (not ignoring hidden units here) Entity violation = Compute.stackingViolation(getGame(), - entity, curPos, null, entity.climbMode(), false); + entity, + curPos, + null, + entity.climbMode(), + false); if (violation != null) { // target gets displaced, because of low elevation int direction = lastPos.direction(curPos); Coords targetDest = Compute.getValidDisplacement(getGame(), - entity.getId(), curPos, direction); - addReport(gameManager.doEntityDisplacement(violation, curPos, - targetDest, - new PilotingRollData(violation.getId(), 0, - "domino effect"))); + entity.getId(), + curPos, + direction); + addReport(gameManager.doEntityDisplacement(violation, + curPos, + targetDest, + new PilotingRollData(violation.getId(), + 0, + "domino effect"))); // Update the violating entity's position on the client. gameManager.entityUpdate(violation.getId()); } @@ -3599,16 +3918,17 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // check to see if we are a mek and we've moved OUT of fire Hex lastHex = getGame().getBoard(curBoardId).getHex(lastPos); if (entity.tracksHeat() && !entity.isAirborne()) { - if (!lastPos.equals(curPos) && (prevStep != null) - && ((lastHex.containsTerrain(Terrains.FIRE) && (prevStep.getElevation() <= 1)) - || (lastHex.containsTerrain(Terrains.MAGMA) && (prevStep.getElevation() == 0))) - && ((stepMoveType != EntityMovementType.MOVE_JUMP) - // Bug #828741 -- jumping bypasses fire, but not on the first step - // getMpUsed -- total MP used to this step - // getMp -- MP used in this step - // the difference will always be 0 on the "first step" of a jump, - // and >0 on a step in the midst of a jump - || (0 == (step.getMpUsed() - step.getMp())))) { + if (!lastPos.equals(curPos) && (prevStep != null) && ((lastHex.containsTerrain(Terrains.FIRE) && ( + prevStep.getElevation() + <= 1)) || (lastHex.containsTerrain( + Terrains.MAGMA) && (prevStep.getElevation() == 0))) && ((stepMoveType + != EntityMovementType.MOVE_JUMP) + // Bug #828741 -- jumping bypasses fire, but not on the first step + // getMpUsed -- total MP used to this step + // getMp -- MP used in this step + // the difference will always be 0 on the "first step" of a jump, + // and >0 on a step in the midst of a jump + || (0 == (step.getMpUsed() - step.getMp())))) { int heat = 0; if (lastHex.containsTerrain(Terrains.FIRE)) { heat += 2; @@ -3619,7 +3939,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN heat += 5; } boolean isMekWithHeatDissipatingArmor = (entity instanceof Mek) - && ((Mek) entity).hasIntactHeatDissipatingArmor(); + && ((Mek) entity).hasIntactHeatDissipatingArmor(); if (isMekWithHeatDissipatingArmor) { heat /= 2; } @@ -3638,20 +3958,26 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // check to see if we are not a mek and we've moved INTO fire if (!(entity instanceof Mek)) { - boolean underwater = getGame().getBoard(curBoardId).getHex(curPos) - .containsTerrain(Terrains.WATER) - && (getGame().getBoard(curBoardId).getHex(curPos).depth() > 0) - && (step.getElevation() < getGame().getBoard(curBoardId).getHex(curPos).getLevel()); - if (getGame().getBoard(curBoardId).getHex(curPos).containsTerrain( - Terrains.FIRE) && !lastPos.equals(curPos) - && (stepMoveType != EntityMovementType.MOVE_JUMP) - && (step.getElevation() <= 1) && !underwater) { + boolean underwater = getGame().getBoard(curBoardId) + .getHex(curPos) + .containsTerrain(Terrains.WATER) && (getGame().getBoard(curBoardId) + .getHex(curPos) + .depth() > 0) && (step.getElevation() < getGame().getBoard( + curBoardId).getHex(curPos).getLevel()); + if (getGame().getBoard(curBoardId).getHex(curPos).containsTerrain(Terrains.FIRE) + && !lastPos.equals( + curPos) + && (stepMoveType != EntityMovementType.MOVE_JUMP) + && (step.getElevation() <= 1) + && !underwater) { gameManager.doFlamingDamage(entity, curPos); } } - if ((getGame().getBoard(curBoardId).getHex(curPos).terrainLevel(Terrains.SMOKE) == SmokeCloud.SMOKE_GREEN) - && !stepMoveType.equals(EntityMovementType.MOVE_JUMP) && entity.antiTSMVulnerable()) { + if ((getGame().getBoard(curBoardId) + .getHex(curPos) + .terrainLevel(Terrains.SMOKE) == SmokeCloud.SMOKE_GREEN) && !stepMoveType.equals( + EntityMovementType.MOVE_JUMP) && entity.antiTSMVulnerable()) { addReport(gameManager.doGreenSmokeDamage(entity)); } @@ -3665,16 +3991,18 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // every time we enter a new hex // Also perform per-step Hidden Unit checks if TacOps Advanced Active Probe is enabled. // Aerospace BAP hidden unit detection also occurs here (with or without the above option) - if ((getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_TAC_OPS_BAP) || entity.isAerospace()) - && !lastPos.equals(curPos)) { + if ((getGame().getOptions() + .booleanOption(OptionsConstants.ADVANCED_TAC_OPS_BAP) || entity.isAerospace()) && !lastPos.equals( + curPos)) { if (ServerHelper.detectMinefields(getGame(), - entity, - curPos, - gameManager.getMainPhaseReport(), - gameManager) - || - ServerHelper.detectHiddenUnits(getGame(), entity, curPos, gameManager.getMainPhaseReport(), - gameManager)) { + entity, + curPos, + gameManager.getMainPhaseReport(), + gameManager) || ServerHelper.detectHiddenUnits(getGame(), + entity, + curPos, + gameManager.getMainPhaseReport(), + gameManager)) { detectedHiddenHazard = true; if (i.hasNext() && (stepMoveType != EntityMovementType.MOVE_JUMP)) { @@ -3687,8 +4015,8 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // VTOLs may land and submarines may rise or lower into a minefield // jumping units may end their movement with a turn but should still check at // end of movement - if (!lastPos.equals(curPos) || (lastElevation != curElevation) || - ((stepMoveType == EntityMovementType.MOVE_JUMP) && !i.hasNext())) { + if (!lastPos.equals(curPos) || (lastElevation != curElevation) || ((stepMoveType + == EntityMovementType.MOVE_JUMP) && !i.hasNext())) { boolean boom = false; if (isOnGround) { boom = gameManager.checkVibraBombs(entity, curPos, false, lastPos, curPos, @@ -3702,8 +4030,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN boolean empBoom = gameManager.checkEMPMines(entity, curPos, empReports); // Send popup FIRST with only EMP reports, before adding to mainPhaseReport if (empBoom && !empReports.isEmpty()) { - gameManager.send(entity.getOwner().getId(), - gameManager.createSpecialReportPacket(empReports)); + gameManager.send(entity.getOwner().getId(), gameManager.createSpecialReportPacket(empReports)); sentEMPPopupThisMove = true; } // Now add to main phase report for end-of-phase display @@ -3715,8 +4042,11 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN // infantry otherwise would get double damage // when moving from clear into mined woods entity.setPosition(curPos); - if (gameManager.enterMinefield(entity, curPos, step.getElevation(), - isOnGround, gameManager.getMainPhaseReport())) { + if (gameManager.enterMinefield(entity, + curPos, + step.getElevation(), + isOnGround, + gameManager.getMainPhaseReport())) { // resolve any piloting rolls from damage unless unit // was jumping if (stepMoveType != EntityMovementType.MOVE_JUMP) { @@ -3763,8 +4093,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN } // check if we've moved into water - rollTarget = entity.checkWaterMove(step, lastStepMoveType, curHex, - lastPos, curPos, isPavementStep); + rollTarget = entity.checkWaterMove(step, lastStepMoveType, curHex, lastPos, curPos, isPavementStep); if (rollTarget.getValue() != TargetRoll.CHECK_FALSE) { // Swarmers need special handling. final int swarmerId = entity.getSwarmAttackerId(); @@ -3785,8 +4114,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN } // Do we need to remove a game turn for the swarmer - if (!swarmerDone && (swarmer != null) - && (swarmer.isDoomed() || swarmer.isDestroyed())) { + if (!swarmerDone && (swarmer != null) && (swarmer.isDoomed() || swarmer.isDestroyed())) { // We have to diddle with the swarmer's // status to get its turn removed. swarmer.setDone(false); @@ -3815,11 +4143,13 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN } // check for breaking ice by breaking through from below - if ((lastElevation < 0) && (step.getElevation() == 0) - && lastHex.containsTerrain(Terrains.ICE) - && lastHex.containsTerrain(Terrains.WATER) - && (stepMoveType != EntityMovementType.MOVE_JUMP) - && !lastPos.equals(curPos)) { + if ((lastElevation < 0) + && (step.getElevation() == 0) + && lastHex.containsTerrain(Terrains.ICE) + && lastHex.containsTerrain( + Terrains.WATER) + && (stepMoveType != EntityMovementType.MOVE_JUMP) + && !lastPos.equals(curPos)) { // need to temporarily reset entity's position so it doesn't // fall in the ice entity.setPosition(curPos); @@ -3831,11 +4161,10 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVANCED_GROUND_MOVEMEN entity.setPosition(lastPos); } // check for breaking ice by stepping on it - if (curHex.containsTerrain(Terrains.ICE) - && curHex.containsTerrain(Terrains.WATER) - && (stepMoveType != EntityMovementType.MOVE_JUMP) - && !lastPos.equals(curPos) && !(entity instanceof Infantry) - && !(isPavementStep && curHex.containsTerrain(Terrains.BRIDGE))) { + if (curHex.containsTerrain(Terrains.ICE) && curHex.containsTerrain(Terrains.WATER) && (stepMoveType + != EntityMovementType.MOVE_JUMP) && !lastPos.equals( + curPos) && !(entity instanceof Infantry) && !(isPavementStep + && curHex.containsTerrain(Terrains.BRIDGE))) { if (step.getElevation() == 0) { Roll diceRoll = Compute.rollD6(1); report = new Report(2118); @@ -3885,9 +4214,9 @@ else if ((step.getElevation() + entity.height()) == 0) { loaded = (Entity) target; } else { // Randomly select a likely-looking entity from those in the allowed loading area - Iterator entities = getGame().getEntities( - Compute.getLoadableCoords(entity, curPos, curBoardId) - ); + Iterator entities = getGame().getEntities(Compute.getLoadableCoords(entity, + curPos, + curBoardId)); while (entities.hasNext()) { // Is the other unit friendly and not the current entity? @@ -3964,8 +4293,9 @@ else if ((step.getElevation() + entity.height()) == 0) { if (targetToMountInto instanceof Entity entityToMountInto) { if (!entityToMountInto.canLoad(entity)) { // Something is fishy in Denmark. - logger.error("(Mounted) {} can not load {}", entityToMountInto.getShortName(), - entity.getShortName()); + logger.error("(Mounted) {} can not load {}", + entityToMountInto.getShortName(), + entity.getShortName()); } else { // Have the indicated unit load this unit. entity.setDone(true); @@ -3990,9 +4320,9 @@ else if ((step.getElevation() + entity.height()) == 0) { if (step.getType() == MoveStepType.PICKUP_CARGO) { var carryableObjects = getGame().getGroundObjects(step.getPosition()); carryableObjects.addAll(getGame().getEntitiesVector(step.getPosition()) - .stream() - .filter(entity::canPickupCarryableObject) - .toList()); + .stream() + .filter(entity::canPickupCarryableObject) + .toList()); Integer cargoPickupIndex; // if there's only one object on the ground, let's just get that one and ignore @@ -4006,31 +4336,30 @@ else if ((step.getElevation() + entity.height()) == 0) { Integer cargoPickupLocation = step.getAdditionalData(MoveStep.CARGO_LOCATION_KEY); // there have to be objects on the ground, and we have to be trying to pick up one of them - if ((!carryableObjects.isEmpty()) && - (cargoPickupIndex != null) && (cargoPickupIndex >= 0) - && (cargoPickupIndex < carryableObjects.size())) { + if ((!carryableObjects.isEmpty()) && (cargoPickupIndex != null) && (cargoPickupIndex >= 0) && ( + cargoPickupIndex + < carryableObjects.size())) { ICarryable pickupTarget = carryableObjects.get(cargoPickupIndex); // FIXME #7640: Update once we can properly specify any transporter an entity has, and properly load into that transporter. - if (entity.maxGroundObjectTonnage() >= pickupTarget.getTonnage() || ((entity.getTransports().size() - > (Integer.MAX_VALUE - cargoPickupLocation)) - && (entity.getTransports() - .get(Integer.MAX_VALUE - cargoPickupLocation) instanceof ExternalCargo externalCargo - && externalCargo.canLoadCarryable(pickupTarget)))) { - pickupTarget.processPickupStep(step, cargoPickupLocation, gameManager, entity, - overallMoveType); + if (entity.maxGroundObjectTonnage() >= pickupTarget.getTonnage() || ((entity.getTransports() + .size() > (Integer.MAX_VALUE - cargoPickupLocation)) && (entity.getTransports() + .get(Integer.MAX_VALUE - cargoPickupLocation) instanceof ExternalCargo externalCargo + && externalCargo.canLoadCarryable( + pickupTarget)))) { + pickupTarget.processPickupStep(step, cargoPickupLocation, gameManager, entity, overallMoveType); } else { logger.warn( - "{} attempted to pick up object but it is too heavy. Carry capacity: {}, object weight: {}", - entity.getShortName(), - entity.maxGroundObjectTonnage(), - pickupTarget.getTonnage()); + "{} attempted to pick up object but it is too heavy. Carry capacity: {}, object weight: {}", + entity.getShortName(), + entity.maxGroundObjectTonnage(), + pickupTarget.getTonnage()); } } else { logger.warn("{} attempted to pick up non existent object at coords {}, index {}", - entity.getShortName(), - step.getPosition(), - cargoPickupIndex); + entity.getShortName(), + step.getPosition(), + cargoPickupIndex); } } @@ -4045,7 +4374,7 @@ else if ((step.getElevation() + entity.height()) == 0) { } else if (entity.getCarriedObject(cargoLocation) != null) { cargo = entity.getCarriedObject(cargoLocation); } else if ((cargoLocation >= 0) && (Integer.MAX_VALUE - cargoLocation < entity.getTransports() - .size())) { + .size())) { // FIXME #7640: Update once we can properly specify any transporter an entity has, and properly load into that transporter. Transporter transporter = entity.getTransports().get(Integer.MAX_VALUE - cargoLocation); if (transporter instanceof ExternalCargo externalCargo) { @@ -4059,8 +4388,11 @@ else if ((step.getElevation() + entity.height()) == 0) { entity.dropCarriedObject(cargo, isLastStep); if (cargo instanceof Entity carriedEntity) { - gameManager.unloadUnit(entity, carriedEntity, step.getPosition(), step.getFacing(), - step.getElevation()); + gameManager.unloadUnit(entity, + carriedEntity, + step.getPosition(), + step.getFacing(), + step.getElevation()); } @@ -4131,8 +4463,9 @@ else if ((step.getElevation() + entity.height()) == 0) { addReport(report); // damage unit HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT); - addReport(gameManager.damageEntity(entity, hit, - 2 * (rollTarget.getValue() - diceRoll.getIntValue()))); + addReport(gameManager.damageEntity(entity, + hit, + 2 * (rollTarget.getValue() - diceRoll.getIntValue()))); } else { report.choose(true); addReport(report); @@ -4168,12 +4501,11 @@ else if ((step.getElevation() + entity.height()) == 0) { unloadFacing = curPos.direction(unloadPos); } - if (!gameManager.unloadUnit(entity, unloaded, unloadPos, unloadFacing, - step.getElevation())) { + if (!gameManager.unloadUnit(entity, unloaded, unloadPos, unloadFacing, step.getElevation())) { logger.error("Server was told to unload {} from {} into {}", - unloaded.getDisplayName(), - entity.getDisplayName(), - curPos.getBoardNum()); + unloaded.getDisplayName(), + entity.getDisplayName(), + curPos.getBoardNum()); } else { // Report unloading; anyone who can see the carrier should see the new unit too. report = new Report(2514); @@ -4199,9 +4531,7 @@ else if ((step.getElevation() + entity.height()) == 0) { // some additional stuff to take care of for small // craft/DropShip unloading if ((entity instanceof SmallCraft) && (unloaded instanceof Entity)) { - if ((null != currentBay) && (!(unloaded.isInfantry())) - && (Compute.d6(2) == 2) - ) { + if ((null != currentBay) && (!(unloaded.isInfantry())) && (Compute.d6(2) == 2)) { report = new Report(9390); report.subject = entity.getId(); report.indent(1); @@ -4216,17 +4546,15 @@ else if ((step.getElevation() + entity.height()) == 0) { // continue to unload units if (!entity.getUnitsUnloadableFromBays().isEmpty()) { dropshipStillUnloading = true; - GameTurn newTurn = new SpecificEntityTurn( - entity.getOwner().getId(), entity.getId()); + GameTurn newTurn = new SpecificEntityTurn(entity.getOwner().getId(), entity.getId()); // Need to set the new turn's multiTurn state newTurn.setMultiTurn(true); getGame().insertNextTurn(newTurn); } // ok add another turn for the unloaded entity so that it can move if (!(unloaded instanceof Infantry)) { - GameTurn newTurn = new SpecificEntityTurn( - ((Entity) unloaded).getOwner().getId(), - unloaded.getId()); + GameTurn newTurn = new SpecificEntityTurn(((Entity) unloaded).getOwner().getId(), + unloaded.getId()); // Need to set the new turn's multiTurn state newTurn.setMultiTurn(true); getGame().insertNextTurn(newTurn); @@ -4248,41 +4576,45 @@ else if ((step.getElevation() + entity.height()) == 0) { // button, so reaching here means the request did not come from it. if (entity.isOffBoard()) { logger.warn("[Train] refused to disconnect {} from {}: the train is off board", - unloaded.getDisplayName(), entity.getDisplayName()); + unloaded.getDisplayName(), + entity.getDisplayName()); } else if (!gameManager.disconnectUnit(entity, unloaded, unloadPos)) { logger.error("Server was told to disconnect {} from {} into {}", - unloaded.getDisplayName(), - entity.getDisplayName(), - curPos.getBoardNum()); + unloaded.getDisplayName(), + entity.getDisplayName(), + curPos.getBoardNum()); } } // moving backwards over elevation change if (((step.getType() == MoveStepType.BACKWARDS) - || (step.getType() == MoveStepType.LATERAL_LEFT_BACKWARDS) - || (step.getType() == MoveStepType.LATERAL_RIGHT_BACKWARDS)) - && !(md.isJumping() && step.isUsingMekJumpBooster()) - && (lastHex.getLevel() + lastElevation != curHex.getLevel() + step.getElevation()) - && !(entity instanceof VTOL) - && !(curClimbMode - && curHex.containsTerrain(Terrains.BRIDGE) - && ((curHex.terrainLevel(Terrains.BRIDGE_ELEV) + curHex.getLevel()) == (prevHex.getLevel() - + (prevHex.containsTerrain(Terrains.BRIDGE) - ? prevHex.terrainLevel(Terrains.BRIDGE_ELEV) - : 0))))) { + || (step.getType() == MoveStepType.LATERAL_LEFT_BACKWARDS) + || (step.getType() == MoveStepType.LATERAL_RIGHT_BACKWARDS)) && !(md.isJumping() + && step.isUsingMekJumpBooster()) && (lastHex.getLevel() + lastElevation + != curHex.getLevel() + step.getElevation()) && !(entity instanceof VTOL) && !(curClimbMode + && curHex.containsTerrain( + Terrains.BRIDGE) + && ((curHex.terrainLevel( + Terrains.BRIDGE_ELEV) + curHex.getLevel()) == (prevHex.getLevel() + + (prevHex.containsTerrain( + Terrains.BRIDGE) ? prevHex.terrainLevel(Terrains.BRIDGE_ELEV) : 0))))) { // per TacOps, if the mek is walking backwards over an elevation change and // falls // it falls into the lower hex. The caveat is if it already fell from some other // PSR in this // invocation of processMovement, then it can't fall again. - if ((entity instanceof Mek) - && (curHex.getLevel() < getGame().getBoard(curBoardId).getHex(lastPos).getLevel()) - && !entity.hasFallen()) { + if ((entity instanceof Mek) && (curHex.getLevel() < getGame().getBoard(curBoardId) + .getHex(lastPos) + .getLevel()) && !entity.hasFallen()) { rollTarget = entity.getBasePilotingRoll(overallMoveType); rollTarget.addModifier(0, "moving backwards over an elevation change"); - gameManager.doSkillCheckWhileMoving(entity, entity.getElevation(), - curPos, curPos, rollTarget, true); + gameManager.doSkillCheckWhileMoving(entity, + entity.getElevation(), + curPos, + curPos, + rollTarget, + true); } else if ((entity instanceof Mek) && !entity.hasFallen()) { rollTarget = entity.getBasePilotingRoll(overallMoveType); rollTarget.addModifier(0, "moving backwards over an elevation change"); @@ -4290,8 +4622,12 @@ else if ((step.getElevation() + entity.height()) == 0) { } else if (entity instanceof Tank) { rollTarget = entity.getBasePilotingRoll(overallMoveType); rollTarget.addModifier(0, "moving backwards over an elevation change"); - if (gameManager.doSkillCheckWhileMoving(entity, entity.getElevation(), curPos, lastPos, - rollTarget, false) < 0) { + if (gameManager.doSkillCheckWhileMoving(entity, + entity.getElevation(), + curPos, + lastPos, + rollTarget, + false) < 0) { curPos = lastPos; } } @@ -4326,14 +4662,14 @@ else if ((step.getElevation() + entity.height()) == 0) { String reason = getReason(bldgExited, bldgEntered); gameManager.passBuildingWall(entity, - bldgEntered, - lastPos, - curPos, - distance, - reason, - step.isThisStepBackwards(), - lastStepMoveType, - true); + bldgEntered, + lastPos, + curPos, + distance, + reason, + step.isThisStepBackwards(), + lastStepMoveType, + true); gameManager.addAffectedBldg(bldgEntered, collapsed); } @@ -4350,18 +4686,16 @@ else if ((step.getElevation() + entity.height()) == 0) { // TODO : what if a building collapses into rubble? } - if (stepMoveType != EntityMovementType.MOVE_JUMP - && (step.getClearance() == 0 - || (entity.getMovementMode().isWiGE() && (step.getClearance() == 1)) - || curElevation == curHex.terrainLevel(Terrains.BLDG_ELEV) - || curElevation == curHex.terrainLevel(Terrains.BRIDGE_ELEV))) { + if (stepMoveType != EntityMovementType.MOVE_JUMP && (step.getClearance() == 0 || (entity.getMovementMode() + .isWiGE() && (step.getClearance() == 1)) || curElevation == curHex.terrainLevel( + Terrains.BLDG_ELEV) || curElevation == curHex.terrainLevel(Terrains.BRIDGE_ELEV))) { IBuilding bldg = getGame().getBoard(curBoardId).getBuildingAt(curPos); if ((bldg != null) && (entity.getElevation() >= 0)) { boolean wigeFlyingOver = entity.getMovementMode() == EntityMovementMode.WIGE - && ((curHex.containsTerrain(Terrains.BLDG_ELEV) - && curElevation > curHex.terrainLevel(Terrains.BLDG_ELEV)) || - (curHex.containsTerrain(Terrains.BRIDGE_ELEV) - && curElevation > curHex.terrainLevel(Terrains.BRIDGE_ELEV))); + && ((curHex.containsTerrain( + Terrains.BLDG_ELEV) && curElevation > curHex.terrainLevel(Terrains.BLDG_ELEV)) + || (curHex.containsTerrain( + Terrains.BRIDGE_ELEV) && curElevation > curHex.terrainLevel(Terrains.BRIDGE_ELEV))); boolean collapse = gameManager.checkBuildingCollapseWhileMoving(bldg, entity, curPos); gameManager.addAffectedBldg(bldg, collapse); // If the building is collapsed by a WiGE flying over it, the WiGE drops one @@ -4385,31 +4719,31 @@ else if ((step.getElevation() + entity.height()) == 0) { } // Sheer Cliffs, TO p.39 - boolean vehicleAffectedByCliff = entity instanceof Tank - && !entity.isAirborneVTOLorWIGE(); + boolean vehicleAffectedByCliff = entity instanceof Tank && !entity.isAirborneVTOLorWIGE(); boolean quadveeVehMode = entity instanceof QuadVee - && entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE; + && entity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE; boolean mekAffectedByCliff = (entity instanceof Mek || entity instanceof ProtoMek) - && moveType != EntityMovementType.MOVE_JUMP - && !entity.isAero(); + && moveType != EntityMovementType.MOVE_JUMP + && !entity.isAero(); // Cliffs should only exist towards 1 or 2 level drops, check just to make sure // Everything that does not have a 1 or 2 level drop shouldn't be handled as a // cliff - int stepHeight = curElevation + curHex.getLevel() - - (lastElevation + prevHex.getLevel()); - boolean isUpCliff = !lastPos.equals(curPos) - && curHex.hasCliffTopTowards(prevHex) - && (stepHeight == 1 || stepHeight == 2); - boolean isDownCliff = !lastPos.equals(curPos) - && prevHex.hasCliffTopTowards(curHex) - && (stepHeight == -1 || stepHeight == -2); + int stepHeight = curElevation + curHex.getLevel() - (lastElevation + prevHex.getLevel()); + boolean isUpCliff = !lastPos.equals(curPos) && curHex.hasCliffTopTowards(prevHex) && (stepHeight == 1 + || stepHeight == 2); + boolean isDownCliff = !lastPos.equals(curPos) && prevHex.hasCliffTopTowards(curHex) && (stepHeight == -1 + || stepHeight == -2); // Vehicles (exc. WIGE/VTOL) moving down a cliff if (vehicleAffectedByCliff && isDownCliff && !isPavementStep) { rollTarget = entity.getBasePilotingRoll(stepMoveType); rollTarget.append(new PilotingRollData(entity.getId(), 0, "moving down a sheer cliff")); - if (gameManager.doSkillCheckWhileMoving(entity, lastElevation, - lastPos, curPos, rollTarget, false) > 0) { + if (gameManager.doSkillCheckWhileMoving(entity, + lastElevation, + lastPos, + curPos, + rollTarget, + false) > 0) { addReport(gameManager.vehicleMotiveDamage((Tank) entity, 0)); addNewLines(); turnOver = true; @@ -4422,8 +4756,7 @@ else if ((step.getElevation() + entity.height()) == 0) { if (mekAffectedByCliff && !quadveeVehMode && isDownCliff && !isPavementStep) { rollTarget = entity.getBasePilotingRoll(moveType); rollTarget.append(new PilotingRollData(entity.getId(), -stepHeight - 1, "moving down a sheer cliff")); - if (gameManager.doSkillCheckWhileMoving(entity, lastElevation, - lastPos, curPos, rollTarget, true) > 0) { + if (gameManager.doSkillCheckWhileMoving(entity, lastElevation, lastPos, curPos, rollTarget, true) > 0) { addNewLines(); turnOver = true; break; @@ -4434,8 +4767,12 @@ else if ((step.getElevation() + entity.height()) == 0) { if (mekAffectedByCliff && !quadveeVehMode && isUpCliff && !isPavementStep) { rollTarget = entity.getBasePilotingRoll(moveType); rollTarget.append(new PilotingRollData(entity.getId(), stepHeight, "moving up a sheer cliff")); - if (gameManager.doSkillCheckWhileMoving(entity, lastElevation, - lastPos, lastPos, rollTarget, false) > 0) { + if (gameManager.doSkillCheckWhileMoving(entity, + lastElevation, + lastPos, + lastPos, + rollTarget, + false) > 0) { report = new Report(2209); report.addDesc(entity); report.subject = entity.getId(); @@ -4454,8 +4791,7 @@ else if ((step.getElevation() + entity.height()) == 0) { // On failure, the Mek falls from the last level successfully reached. // Multi-turn: if the climb costs more MP than available, only climb // affordable levels this turn and persist climbing state for next turn. - if (step.isClimbing() && (entity instanceof Mek climbingMek) - && (stepHeight > 0)) { + if (step.isClimbing() && (entity instanceof Mek climbingMek) && (stepHeight > 0)) { // Only process climbing PSRs for upward movement (stepHeight > 0) // Downward movement is handled by leaping rules int totalLevelsToClimb = Math.abs(stepHeight); @@ -4491,23 +4827,29 @@ else if ((step.getElevation() + entity.height()) == 0) { int levelsAlreadyClimbed = climbingElevation; int overallClimbHeight = levelsAlreadyClimbed + totalLevelsToClimb; - logger.debug("Climbing: totalLevels={}, affordableLevels={}, levelsThisTurn={}, " + - "walkMP={}, availableMP={}, costPerLevel={}, nonClimbMpUsed={}, " + - "levelsAlreadyClimbed={}, overallClimbHeight={}", - totalLevelsToClimb, affordableLevels, levelsThisTurn, - walkMP, availableMP, costPerLevel, nonClimbMpUsed, - levelsAlreadyClimbed, overallClimbHeight); + logger.debug("Climbing: totalLevels={}, affordableLevels={}, levelsThisTurn={}, " + + "walkMP={}, availableMP={}, costPerLevel={}, nonClimbMpUsed={}, " + + "levelsAlreadyClimbed={}, overallClimbHeight={}", + totalLevelsToClimb, + affordableLevels, + levelsThisTurn, + walkMP, + availableMP, + costPerLevel, + nonClimbMpUsed, + levelsAlreadyClimbed, + overallClimbHeight); for (int levelClimbed = 1; levelClimbed <= levelsThisTurn; levelClimbed++) { int overallLevel = levelsAlreadyClimbed + levelClimbed; rollTarget = entity.getBasePilotingRoll(moveType); rollTarget.append(new PilotingRollData(entity.getId(), - ClimbingHelper.CLIMBING_PSR_MODIFIER, - "climbing (level " + overallLevel + " of " + overallClimbHeight + ")")); + ClimbingHelper.CLIMBING_PSR_MODIFIER, + "climbing (level " + overallLevel + " of " + overallClimbHeight + ")")); if (climbableArms == 1) { rollTarget.append(new PilotingRollData(entity.getId(), - ClimbingHelper.ONE_ARM_PSR_MODIFIER, - "climbing with one arm")); + ClimbingHelper.ONE_ARM_PSR_MODIFIER, + "climbing with one arm")); } // When climbingElevation is 0 the Mek hasn't actually risen yet — failing @@ -4516,13 +4858,18 @@ else if ((step.getElevation() + entity.height()) == 0) { // turn but the Mek stays on the ground. Per pragmatic reading of TO:AR p.20: // "fall from the height it has currently reached" — height 0 = no fall. boolean canFallFromHere = climbingElevation > 0; - if (gameManager.doSkillCheckWhileMoving(entity, climbingElevation, - lastPos, lastPos, rollTarget, canFallFromHere) > 0) { + if (gameManager.doSkillCheckWhileMoving(entity, + climbingElevation, + lastPos, + lastPos, + rollTarget, + canFallFromHere) > 0) { if (!canFallFromHere) { // Failed first PSR from elevation 0 — abort climb without fall. logger.debug("[CLIMB-TRACE] Failed first climbing PSR from elevation 0; " - + "no fall (entity stays on ground). entity={}, lastPos={}", - entity.getDisplayName(), lastPos); + + "no fall (entity stays on ground). entity={}, lastPos={}", + entity.getDisplayName(), + lastPos); Report failReport = new Report(6464, Report.PUBLIC); failReport.add(entity.getDisplayName()); addReport(failReport); @@ -4548,18 +4895,26 @@ else if ((step.getElevation() + entity.height()) == 0) { entity.setClimbing(false); curPos = entity.getPosition(); curVTOLElevation = entity.getElevation(); - logger.debug("[FALL-TRACE] After doSkillCheckWhileMoving fall: " + - "entity.position={}, entity.elevation={}, entity.isProne={}, " + - "climbingElevation={}, lastPos={}, curPos={}, curVTOLElevation={}", - entity.getPosition(), entity.getElevation(), entity.isProne(), - climbingElevation, lastPos, curPos, curVTOLElevation); + logger.debug("[FALL-TRACE] After doSkillCheckWhileMoving fall: " + + "entity.position={}, entity.elevation={}, entity.isProne={}, " + + "climbingElevation={}, lastPos={}, curPos={}, curVTOLElevation={}", + entity.getPosition(), + entity.getElevation(), + entity.isProne(), + climbingElevation, + lastPos, + curPos, + curVTOLElevation); Hex fallHex = getGame().getBoard(entity.getBoardId()).getHex(entity.getPosition()); if (fallHex != null) { - logger.debug("[FALL-TRACE] Fall hex: level={}, ceiling={}, floor={}, depth={}, " + - "containsWater={}, isElevationValid={}", - fallHex.getLevel(), fallHex.ceiling(), fallHex.floor(), fallHex.depth(), - fallHex.containsTerrain(Terrains.WATER), - entity.isElevationValid(entity.getElevation(), fallHex)); + logger.debug("[FALL-TRACE] Fall hex: level={}, ceiling={}, floor={}, depth={}, " + + "containsWater={}, isElevationValid={}", + fallHex.getLevel(), + fallHex.ceiling(), + fallHex.floor(), + fallHex.depth(), + fallHex.containsTerrain(Terrains.WATER), + entity.isElevationValid(entity.getElevation(), fallHex)); } fellWhileClimbing = true; fellDuringMovement = true; @@ -4575,9 +4930,10 @@ else if ((step.getElevation() + entity.height()) == 0) { // the climb — the hex collapses." // The building being climbed is at the destination hex (curPos) Hex climbHex = getGame().getBoard(entity.getBoardId()).getHex(curPos); - logger.debug("[FALL-TRACE] Building check: curPos={}, lastPos={}, " + - "containsBuilding={}", curPos, lastPos, - climbHex.containsTerrain(Terrains.BUILDING)); + logger.debug("[FALL-TRACE] Building check: curPos={}, lastPos={}, " + "containsBuilding={}", + curPos, + lastPos, + climbHex.containsTerrain(Terrains.BUILDING)); if (climbHex.containsTerrain(Terrains.BUILDING)) { IBuilding climbBldg = getGame().getBoard(entity.getBoardId()).getBuildingAt(curPos); if (climbBldg != null) { @@ -4586,7 +4942,9 @@ else if ((step.getElevation() + entity.height()) == 0) { if ((levelClimbed == 1) && (levelsAlreadyClimbed == 0)) { int buildingDamage = (int) Math.ceil(entity.getWeight() / 10.0); logger.debug("[FALL-TRACE] Building climbing damage: {} points to {} at {}", - buildingDamage, climbBldg.getName(), curPos); + buildingDamage, + climbBldg.getName(), + curPos); addReport(gameManager.damageBuilding(climbBldg, buildingDamage, curPos)); } @@ -4594,12 +4952,16 @@ else if ((step.getElevation() + entity.height()) == 0) { // due to damage from other sources) int currentCF = climbBldg.getCurrentCF(curPos); logger.debug("[FALL-TRACE] Building CF check: weight={}, CF={}", - entity.getWeight(), - currentCF); + entity.getWeight(), + currentCF); if (entity.getWeight() > currentCF) { - logger.debug("[FALL-TRACE] Building too weak for climbing entity: " + - "weight={}, CF={}, climbingElevation={}, curPos={}, lastPos={}", - entity.getWeight(), currentCF, climbingElevation, curPos, lastPos); + logger.debug("[FALL-TRACE] Building too weak for climbing entity: " + + "weight={}, CF={}, climbingElevation={}, curPos={}, lastPos={}", + entity.getWeight(), + currentCF, + climbingElevation, + curPos, + lastPos); // Entity falls from climbing elevation before collapse entity.setClimbing(false); entity.setDangling(false); @@ -4607,9 +4969,15 @@ else if ((step.getElevation() + entity.height()) == 0) { entity.setPosition(lastPos); // Process the fall from climbing elevation PilotingRollData climbFallRoll = new PilotingRollData(entity.getId(), - TargetRoll.AUTOMATIC_FAIL, "building too weak"); - addReport(gameManager.doEntityFallsInto(entity, climbingElevation, - lastPos, lastPos, climbFallRoll, true, 0)); + TargetRoll.AUTOMATIC_FAIL, + "building too weak"); + addReport(gameManager.doEntityFallsInto(entity, + climbingElevation, + lastPos, + lastPos, + climbFallRoll, + true, + 0)); curPos = entity.getPosition(); curVTOLElevation = entity.getElevation(); fellWhileClimbing = true; @@ -4640,9 +5008,11 @@ else if ((step.getElevation() + entity.height()) == 0) { curPos = lastPos; curVTOLElevation = climbingElevation; mpUsed = walkMP; - logger.debug("Climbing: partial climb, {} of {} levels. " + - "Clinging at elevation {} in hex {}", - levelsThisTurn, totalLevelsToClimb, climbingElevation, lastPos); + logger.debug("Climbing: partial climb, {} of {} levels. " + "Clinging at elevation {} in hex {}", + levelsThisTurn, + totalLevelsToClimb, + climbingElevation, + lastPos); // End movement - spent all MP climbing turnOver = true; break; @@ -4669,7 +5039,7 @@ else if ((step.getElevation() + entity.height()) == 0) { entity.setClimbing(true); entity.setElevation(climbingElevation); logger.debug("Climbing: no levels to climb but still at elevation {}, keeping climbing state", - climbingElevation); + climbingElevation); } else { entity.setClimbing(false); entity.setDangling(false); @@ -4742,8 +5112,11 @@ else if ((step.getElevation() + entity.height()) == 0) { } // Track this step's location. - movePath.addElement(new UnitLocation(entity.getId(), curPos, - curFacing, step.getElevation(), entity.getBoardLocation().boardId())); + movePath.addElement(new UnitLocation(entity.getId(), + curPos, + curFacing, + step.getElevation(), + entity.getBoardLocation().boardId())); // if the lastpos is not the same as the current position // then add the current position to the list of places passed @@ -4778,15 +5151,14 @@ else if ((step.getElevation() + entity.height()) == 0) { * * @param step MoveStep to prompt for a PBS * @param hiddenEntity Candidate to fire PBS - * * @return Vector collection of reports; caller responsible for displaying these. */ - protected Vector processPossiblePBS(MoveStep step, Entity hiddenEntity) { + protected Vector processPossiblePBS(MoveStep step, + Entity hiddenEntity) { Vector pbsReports = new Vector<>(); Vector attackReports = new Vector<>(); // Update hidden entity owner with current mover's position. - gameManager.send(hiddenEntity.getOwnerId(), - gameManager.createEntityPacket(this.entity.getId(), null)); + gameManager.send(hiddenEntity.getOwnerId(), gameManager.createEntityPacket(this.entity.getId(), null)); // Allow for packet data read failure try { @@ -4819,13 +5191,13 @@ protected Vector processPossiblePBS(MoveStep step, Entity hiddenEntity) return pbsReports; } - private String getReason(IBuilding bldgExited, IBuilding bldgEntered) { + private String getReason(IBuilding bldgExited, + IBuilding bldgEntered) { String reason; if (bldgExited == null) { // If we're not leaving a building, just handle the "entered". reason = "entering"; - } else if (bldgExited.equals(bldgEntered) && !(entity instanceof ProtoMek) - && !(entity instanceof Infantry)) { + } else if (bldgExited.equals(bldgEntered) && !(entity instanceof ProtoMek) && !(entity instanceof Infantry)) { // If we're moving within the same building, just handle the "within". reason = "moving in"; } else { @@ -4841,10 +5213,10 @@ private String getReason(IBuilding bldgExited, IBuilding bldgEntered) { * * @param position entity's current position * @param facing entity's current facing - * * @return new coords that are on the board */ - private Coords nudgeOntoBoard(Coords position, int facing) { + private Coords nudgeOntoBoard(Coords position, + int facing) { Coords newPosition = position; Game game = getGame(); @@ -4898,34 +5270,44 @@ private boolean usingAeroOnGroundMovement() { * @param infantry the platoon starting the build * @param step the BUILD_BRIDGE step carrying the site, orientation and bridge type */ - private void processBuildBridgeStep(Infantry infantry, MoveStep step) { + private void processBuildBridgeStep(Infantry infantry, + MoveStep step) { if (!(infantry instanceof ConvInfantry convInfantry)) { logger.warn("[BuildBridge] BUILD_BRIDGE step ignored for {}: not a conventional infantry platoon", - infantry.getShortName()); + infantry.getShortName()); return; } if (step.getBridgeTargetCoords() == null) { logger.warn("[BuildBridge] BUILD_BRIDGE step ignored for {}: no target hex in the step data", - convInfantry.getShortName()); + convInfantry.getShortName()); return; } // Another platoon may have started/paused a bridge in this hex since the path was plotted; an in-progress // bridge places no terrain, so reject a second build in the same hex here. - if (ConvInfantry.isBridgeTargetClaimed(getGame(), convInfantry.getBoardId(), step.getBridgeTargetCoords(), - convInfantry)) { + if (ConvInfantry.isBridgeTargetClaimed(getGame(), + convInfantry.getBoardId(), + step.getBridgeTargetCoords(), + convInfantry)) { logger.warn("[BuildBridge] BUILD_BRIDGE step ignored for {}: another platoon already has a bridge in " - + "progress at {}", convInfantry.getShortName(), step.getBridgeTargetCoords()); + + "progress at {}", + convInfantry.getShortName(), + step.getBridgeTargetCoords()); return; } // A gap in an existing bridge (a destroyed section) is repaired rather than freshly built when the unofficial // bridge-repair option is on; the work is identical but the finished section matches the surviving span's deck. boolean repairAllowed = getGame().getOptions() - .booleanOption(OptionsConstants.UNOFFICIAL_BRIDGE_REPAIR_ENGINEERS); - boolean isRepair = repairAllowed && BridgeConstruction.isBridgeRepairSite( - getGame().getBoard(convInfantry.getBoardId()), step.getBridgeTargetCoords(), step.getBridgeExits()); + .booleanOption(OptionsConstants.UNOFFICIAL_BRIDGE_REPAIR_ENGINEERS); + boolean isRepair = repairAllowed + && BridgeConstruction.isBridgeRepairSite(getGame().getBoard(convInfantry.getBoardId()), + step.getBridgeTargetCoords(), + step.getBridgeExits()); logger.info("[BuildBridge] {} begins a bridge {}: target {}, exits bitmask {}, type {} (1=light, 2=medium)", - convInfantry.getShortName(), isRepair ? "repair" : "build", step.getBridgeTargetCoords(), - step.getBridgeExits(), step.getBridgeType()); + convInfantry.getShortName(), + isRepair ? "repair" : "build", + step.getBridgeTargetCoords(), + step.getBridgeExits(), + step.getBridgeType()); // Building a bridge is the platoon's sole action, so any other ground posture (dug in / hitting the deck) ends convInfantry.clearGroundPostures(); if (isRepair) { @@ -4957,18 +5339,20 @@ private void processBuildBridgeStep(Infantry infantry, MoveStep step) { private void processCancelBridgeStep(Infantry infantry) { if (!(infantry instanceof ConvInfantry convInfantry)) { logger.warn("[BuildBridge] CANCEL_BRIDGE step ignored for {}: not a conventional infantry platoon", - infantry.getShortName()); + infantry.getShortName()); return; } if (!convInfantry.isBuildingBridge()) { logger.warn("[BuildBridge] CANCEL_BRIDGE step ignored for {}: not currently building a bridge", - convInfantry.getShortName()); + convInfantry.getShortName()); return; } Coords target = convInfantry.getBridgeTargetCoords(); convInfantry.startBridgeDismantle(); logger.info("[BuildBridge] {} cancels its bridge build at {}: dismantling will take {} turn(s)", - convInfantry.getShortName(), target, convInfantry.getBridgeDismantleRequiredTurns()); + convInfantry.getShortName(), + target, + convInfantry.getBridgeDismantleRequiredTurns()); Report report = new Report(4281); report.subject = convInfantry.getId(); report.addDesc(convInfantry); @@ -4990,19 +5374,21 @@ private void processCancelBridgeStep(Infantry infantry) { private void processResumeBridgeStep(Infantry infantry) { if (!(infantry instanceof ConvInfantry convInfantry)) { logger.warn("[BuildBridge] RESUME_BRIDGE step ignored for {}: not a conventional infantry platoon", - infantry.getShortName()); + infantry.getShortName()); return; } if (!convInfantry.isDismantlingBridge() && !convInfantry.isBridgePaused()) { logger.warn("[BuildBridge] RESUME_BRIDGE step ignored for {}: not currently dismantling or paused", - convInfantry.getShortName()); + convInfantry.getShortName()); return; } Coords target = convInfantry.getBridgeTargetCoords(); convInfantry.resumeBridgeBuild(); logger.info("[BuildBridge] {} resumes its bridge build at {}: {} of {} turns built", - convInfantry.getShortName(), target, convInfantry.getBridgeBuildTurns(), - convInfantry.getBridgeBuildRequiredTurns()); + convInfantry.getShortName(), + target, + convInfantry.getBridgeBuildTurns(), + convInfantry.getBridgeBuildRequiredTurns()); Report report = new Report(4284); report.subject = convInfantry.getId(); report.addDesc(convInfantry); @@ -5020,19 +5406,21 @@ private void processResumeBridgeStep(Infantry infantry) { private void processPauseBridgeStep(Infantry infantry) { if (!(infantry instanceof ConvInfantry convInfantry)) { logger.warn("[BuildBridge] PAUSE_BRIDGE step ignored for {}: not a conventional infantry platoon", - infantry.getShortName()); + infantry.getShortName()); return; } if (!convInfantry.isBuildingBridge()) { logger.warn("[BuildBridge] PAUSE_BRIDGE step ignored for {}: not currently building a bridge", - convInfantry.getShortName()); + convInfantry.getShortName()); return; } Coords target = convInfantry.getBridgeTargetCoords(); convInfantry.pauseBridgeBuild(); logger.info("[BuildBridge] {} pauses its bridge build at {}: {} of {} turns held", - convInfantry.getShortName(), target, convInfantry.getBridgeBuildTurns(), - convInfantry.getBridgeBuildRequiredTurns()); + convInfantry.getShortName(), + target, + convInfantry.getBridgeBuildTurns(), + convInfantry.getBridgeBuildRequiredTurns()); Report report = new Report(4286); report.subject = convInfantry.getId(); report.addDesc(convInfantry); @@ -5050,18 +5438,19 @@ private void processPauseBridgeStep(Infantry infantry) { private void processAbandonBridgeStep(Infantry infantry) { if (!(infantry instanceof ConvInfantry convInfantry)) { logger.warn("[BuildBridge] ABANDON_BRIDGE step ignored for {}: not a conventional infantry platoon", - infantry.getShortName()); + infantry.getShortName()); return; } if (!convInfantry.hasBridgeInProgress()) { logger.warn("[BuildBridge] ABANDON_BRIDGE step ignored for {}: no bridge work in progress", - convInfantry.getShortName()); + convInfantry.getShortName()); return; } Coords target = convInfantry.getBridgeTargetCoords(); convInfantry.abandonBridge(); logger.info("[BuildBridge] {} abandons its bridge at {}; progress lost, points forfeit", - convInfantry.getShortName(), target); + convInfantry.getShortName(), + target); Report report = new Report(4287); report.subject = convInfantry.getId(); report.addDesc(convInfantry); @@ -5081,7 +5470,8 @@ private void processAbandonBridgeStep(Infantry infantry) { * @param entity the clearing unit (a {@link RubbleClearer}) * @param position the rubble hex being cleared (the unit's own hex) */ - private void beginRubbleClearing(Entity entity, Coords position) { + private void beginRubbleClearing(Entity entity, + Coords position) { if (!(entity instanceof RubbleClearer clearer)) { return; } @@ -5089,13 +5479,14 @@ private void beginRubbleClearing(Entity entity, Coords position) { // under the unofficial rule) and a rubble hex. if (!BulldozerRules.canClearRubble(entity, getGame())) { logger.debug("[Bulldozer] {}: clear rubble rejected - no working rubble-clearing equipment / rule off", - entity.getDisplayName()); + entity.getDisplayName()); return; } Hex hex = getGame().getBoard(entity.getBoardId()).getHex(position); if ((hex == null) || (hex.terrainLevel(Terrains.RUBBLE) <= 0)) { logger.debug("[Bulldozer] {}: clear rubble rejected - hex at {} contains no rubble", - entity.getDisplayName(), position); + entity.getDisplayName(), + position); return; } // Already clearing this same hex: leave the in-progress counter untouched so progress is not reset. @@ -5117,8 +5508,12 @@ private void beginRubbleClearing(Entity entity, Coords position) { report.add(requiredTurns); addReport(report); gameManager.sendToast(GameToastEvent.Level.INFO, - Messages.getString("Bulldozer.beginClearToast", entity.getShortName(), requiredTurns), entity); + Messages.getString("Bulldozer.beginClearToast", entity.getShortName(), requiredTurns), + entity); logger.info("[Bulldozer] {} begins clearing rubble (level {}) at {}: {} turns required", - entity.getShortName(), rubbleLevel, position, requiredTurns); + entity.getShortName(), + rubbleLevel, + position, + requiredTurns); } } diff --git a/megamek/src/megamek/server/totalWarfare/TWGameManager.java b/megamek/src/megamek/server/totalWarfare/TWGameManager.java index 4a56a512ed5..5b29bdf9e94 100644 --- a/megamek/src/megamek/server/totalWarfare/TWGameManager.java +++ b/megamek/src/megamek/server/totalWarfare/TWGameManager.java @@ -3122,6 +3122,12 @@ public boolean accept(Entity entity) { for (Entity entity : game.inGameTWEntities()) { if (entity.isSelectableThisTurn()) { final Player player = entity.getOwner(); + if (phase.isDeployment() && Game.rulesManager.getRulesGame().canWalkOnThisRound(entity)) { + continue; + } + if (!Game.rulesManager.getRulesGame().eligibleForPhase(entity, phase)) { + continue; + } if ((entity instanceof SpaceStation) && (game.getPhase().isMovement() || game.getPhase().isDeployment())) { player.incrementSpaceStationTurns(); diff --git a/megamek/unittests/megamek/client/ui/panels/phaseDisplay/DeploymentDisplayTest.java b/megamek/unittests/megamek/client/ui/panels/phaseDisplay/DeploymentDisplayTest.java index 1b498dc90e1..5eec1f5db08 100644 --- a/megamek/unittests/megamek/client/ui/panels/phaseDisplay/DeploymentDisplayTest.java +++ b/megamek/unittests/megamek/client/ui/panels/phaseDisplay/DeploymentDisplayTest.java @@ -51,8 +51,8 @@ import megamek.client.event.BoardViewEvent; import megamek.client.ui.clientGUI.ClientGUI; import megamek.client.ui.clientGUI.MegaMekGUI; -import megamek.client.ui.panels.phaseDisplay.DeploymentDisplay.BoardValidationResult; import megamek.client.ui.panels.phaseDisplay.DeploymentDisplay.DeploymentPosition; +import megamek.client.ui.panels.phaseDisplay.DeploymentHelper.BoardValidationResult; import megamek.client.ui.util.MegaMekController; import megamek.common.GameBoardTestCase; import megamek.common.Player; @@ -76,6 +76,7 @@ public class DeploymentDisplayTest { private DeploymentDisplay deploymentDisplay; + private DeploymentHelper deploymentHelper; private ClientGUI mockClientGUI; private Client mockClient; private MockedStatic mockedMegaMekGUI; @@ -106,6 +107,7 @@ void beforeEach() { // Create DeploymentDisplay with mocked dependencies deploymentDisplay = new DeploymentDisplay(mockClientGUI); + deploymentHelper = new DeploymentHelper(mockClientGUI); } @AfterEach @@ -127,12 +129,12 @@ class ShouldProcessDeploymentTests extends GameBoardTestCase { static { initializeBoard("BOARD", """ - size 2 2 - hex 0101 0 "" "" - hex 0201 0 "" "" - hex 0102 0 "" "" - hex 0202 0 "" "" - end""" + size 2 2 + hex 0101 0 "" "" + hex 0201 0 "" "" + hex 0102 0 "" "" + hex 0202 0 "" "" + end""" ); } @@ -157,6 +159,7 @@ void beforeEach() { // Create DeploymentDisplay with mocked dependencies deploymentDisplay = new DeploymentDisplay(mockClientGUI); + deploymentHelper = new DeploymentHelper(mockClientGUI); } @Test @@ -277,7 +280,7 @@ void shouldReturnTrue_WhenShiftHeld() { } /** - * Tests for {@link DeploymentDisplay#validateDeploymentBoard(Entity, Board, Coords)} + * Tests for DeploymentValidation *

* Note: {@link #getGame()} isn't {@link DeploymentDisplayTest#getGame()} */ @@ -287,9 +290,9 @@ class ValidateDeploymentBoardTests extends GameBoardTestCase { static { initializeBoard("GROUND_BOARD", """ - size 1 1 - hex 0101 0 "" "" - end""" + size 1 1 + hex 0101 0 "" "" + end""" ); } @@ -314,12 +317,12 @@ void setUpBoard() { @DisplayName("should return VALID for normal ground deployment") void shouldReturnValid_ForNormalGroundDeployment() { // Act - BoardValidationResult result = deploymentDisplay.validateDeploymentBoard( - groundEntity, groundBoard, coords); + BoardValidationResult result = deploymentHelper.validateDeploymentBoard( + groundEntity, groundBoard, coords, false); // Assert assertEquals(BoardValidationResult.VALID, result, - "Should return VALID for legal ground deployment"); + "Should return VALID for legal ground deployment"); } @Test @@ -330,12 +333,12 @@ void shouldReturnWrongBoardType_WhenBoardProhibited() { when(spyEntity.isBoardProhibited(groundBoard)).thenReturn(true); // Act - BoardValidationResult result = deploymentDisplay.validateDeploymentBoard( - spyEntity, groundBoard, coords); + BoardValidationResult result = deploymentHelper.validateDeploymentBoard( + spyEntity, groundBoard, coords, false); // Assert assertEquals(BoardValidationResult.WRONG_BOARD_TYPE, result, - "Should return WRONG_BOARD_TYPE when board is prohibited"); + "Should return WRONG_BOARD_TYPE when board is prohibited"); } @Test @@ -345,12 +348,12 @@ void shouldReturnOutsideDeploymentArea_WhenIllegalCoords() { Coords illegalCoords = new Coords(-1, -1); // Act - BoardValidationResult result = deploymentDisplay.validateDeploymentBoard( - groundEntity, groundBoard, illegalCoords); + BoardValidationResult result = deploymentHelper.validateDeploymentBoard( + groundEntity, groundBoard, illegalCoords, false); // Assert assertEquals(BoardValidationResult.OUTSIDE_DEPLOYMENT_AREA, result, - "Should return OUTSIDE_DEPLOYMENT_AREA for illegal coordinates"); + "Should return OUTSIDE_DEPLOYMENT_AREA for illegal coordinates"); } } @@ -366,18 +369,18 @@ class ApplyDeploymentToEntityTests { void shouldSetElevation_ForGroundUnit() { // Arrange Entity groundUnit = new BipedMek(); - DeploymentPosition position = new DeploymentPosition(2, 3); + DeploymentPosition position = new DeploymentPosition(2, 3, null); // Act deploymentDisplay.applyDeploymentToEntity(groundUnit, position); // Assert assertEquals(2, groundUnit.getElevation(), - "Ground unit elevation should be set to 2"); + "Ground unit elevation should be set to 2"); assertEquals(3, groundUnit.getFacing(), - "Ground unit facing should be set to 3"); + "Ground unit facing should be set to 3"); assertEquals(0, groundUnit.getAltitude(), - "Ground unit should not have altitude."); + "Ground unit should not have altitude."); } @Test @@ -385,18 +388,18 @@ void shouldSetElevation_ForGroundUnit() { void shouldSetAltitude_ForAeroUnitNonZero() { // Arrange SmallCraft aeroUnit = new SmallCraft(); - DeploymentPosition position = new DeploymentPosition(5, 2); + DeploymentPosition position = new DeploymentPosition(5, 2, null); // Act deploymentDisplay.applyDeploymentToEntity(aeroUnit, position); // Assert assertEquals(5, aeroUnit.getAltitude(), - "Aero unit altitude should be set to 5"); + "Aero unit altitude should be set to 5"); assertEquals(2, aeroUnit.getFacing(), - "Aero unit facing should be set to 2"); + "Aero unit facing should be set to 2"); assertNotEquals(EntityMovementMode.WHEELED, aeroUnit.getMovementMode(), - "Aero unit should not be wheeled (ground) movement mode."); + "Aero unit should not be wheeled (ground) movement mode."); } @Test @@ -404,18 +407,18 @@ void shouldSetAltitude_ForAeroUnitNonZero() { void shouldLandAero_AtZeroElevation() { // Arrange SmallCraft aeroUnit = new SmallCraft(); - DeploymentPosition position = new DeploymentPosition(0, 4); + DeploymentPosition position = new DeploymentPosition(0, 4, null); // Act deploymentDisplay.applyDeploymentToEntity(aeroUnit, position); // Assert assertEquals(0, aeroUnit.getAltitude(), - "Aero unit altitude should be 0"); + "Aero unit altitude should be 0"); assertEquals(4, aeroUnit.getFacing(), - "Aero unit facing should be set to 4"); + "Aero unit facing should be set to 4"); assertEquals(EntityMovementMode.WHEELED, aeroUnit.getMovementMode(), - "Aero should be landed on ground"); + "Aero should be landed on ground"); } @Test @@ -426,11 +429,11 @@ void shouldHandleFacings_Correctly() { // Act & Assert for (int facing = 0; facing < 6; facing++) { - DeploymentPosition position = new DeploymentPosition(1, facing); + DeploymentPosition position = new DeploymentPosition(1, facing, null); deploymentDisplay.applyDeploymentToEntity(entity, position); assertEquals(facing, entity.getFacing(), - "Entity facing should be set to " + facing); + "Entity facing should be set to " + facing); } } @@ -439,14 +442,14 @@ void shouldHandleFacings_Correctly() { void shouldHandleNegativeElevation_ForUnderwater() { // Arrange Entity entity = new BipedMek(); - DeploymentPosition position = new DeploymentPosition(-2, 0); + DeploymentPosition position = new DeploymentPosition(-2, 0, null); // Act deploymentDisplay.applyDeploymentToEntity(entity, position); // Assert assertEquals(-2, entity.getElevation(), - "Entity should have negative elevation for underwater deployment"); + "Entity should have negative elevation for underwater deployment"); } } @@ -461,7 +464,7 @@ class DeploymentPositionTests { @DisplayName("should create DeploymentPosition with correct values") void shouldCreateDeploymentPosition() { // Arrange & Act - DeploymentPosition position = new DeploymentPosition(3, 4); + DeploymentPosition position = new DeploymentPosition(3, 4, null); // Assert assertEquals(3, position.elevation(), "Elevation should be 3"); @@ -472,9 +475,9 @@ void shouldCreateDeploymentPosition() { @DisplayName("should support equality comparison") void shouldSupportEquality() { // Arrange - DeploymentPosition pos1 = new DeploymentPosition(2, 3); - DeploymentPosition pos2 = new DeploymentPosition(2, 3); - DeploymentPosition pos3 = new DeploymentPosition(2, 4); + DeploymentPosition pos1 = new DeploymentPosition(2, 3, null); + DeploymentPosition pos2 = new DeploymentPosition(2, 3, null); + DeploymentPosition pos3 = new DeploymentPosition(2, 4, null); // Act & Assert assertEquals(pos1, pos2, "Same values should be equal"); diff --git a/megamek/unittests/megamek/client/ui/panels/phaseDisplay/MovementDisplayTest.java b/megamek/unittests/megamek/client/ui/panels/phaseDisplay/MovementDisplayTest.java new file mode 100644 index 00000000000..13cfc38c51c --- /dev/null +++ b/megamek/unittests/megamek/client/ui/panels/phaseDisplay/MovementDisplayTest.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2025 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + * + * NOTICE: The MegaMek organization is a non-profit group of volunteers + * creating free software for the BattleTech community. + * + * MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks + * of The Topps Company, Inc. All Rights Reserved. + * + * Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of + * InMediaRes Productions, LLC. + * + * MechWarrior Copyright Microsoft Corporation. MegaMek was created under + * Microsoft's "Game Content Usage Rules" + * and it is not endorsed by or + * affiliated with Microsoft. + */ + +package megamek.client.ui.panels.phaseDisplay; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.List; + +import megamek.client.Client; +import megamek.client.ui.clientGUI.ClientGUI; +import megamek.client.ui.clientGUI.CommonMenuBar; +import megamek.client.ui.clientGUI.MegaMekGUI; +import megamek.client.ui.clientGUI.boardview.BoardView; +import megamek.client.ui.util.MegaMekController; +import megamek.common.Hex; +import megamek.common.board.Board; +import megamek.common.board.Coords; +import megamek.common.enums.MoveStepType; +import megamek.common.game.Game; +import megamek.common.moves.MovePath; +import megamek.common.units.BipedMek; +import megamek.common.units.Targetable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +@DisplayName("MovementDisplay Unit Tests") +class MovementDisplayTest { + + private MovementDisplay movementDisplay; + private ClientGUI mockClientGUI; + private Client mockClient; + private MockedStatic mockedMegaMekGUI; + private Game game; + + @BeforeEach + void beforeEach() { + MegaMekController mockController = mock(MegaMekController.class); + mockedMegaMekGUI = mockStatic(MegaMekGUI.class); + mockedMegaMekGUI.when(MegaMekGUI::getKeyDispatcher).thenReturn(mockController); + + Board board = new Board(4, 4); + for (int x = 0; x < board.getWidth(); x++) { + for (int y = 0; y < board.getHeight(); y++) { + board.setHex(x, y, new Hex()); + } + } + + mockClientGUI = mock(ClientGUI.class); + mockClient = mock(Client.class); + game = new Game(board); + BoardView boardView = mock(BoardView.class); + CommonMenuBar menuBar = mock(CommonMenuBar.class); + mockClientGUI.controller = mockController; + + when(mockClientGUI.getClient()).thenReturn(mockClient); + when(mockClient.getGame()).thenReturn(game); + when(mockClientGUI.boardViews()).thenReturn(List.of()); + when(mockClientGUI.getMenuBar()).thenReturn(menuBar); + when(mockClientGUI.getBoardView((Targetable) any())).thenReturn(boardView); + when(mockClientGUI.getBoardView(anyInt())).thenReturn(boardView); + + movementDisplay = new MovementDisplay(mockClientGUI); + } + + @AfterEach + void tearDown() { + if (mockedMegaMekGUI != null) { + mockedMegaMekGUI.close(); + } + } + + @Test + @DisplayName("clear resets a walk-on movement path without losing deployment") + void clearWithWalkPathKeepsDeploymentAnchored() throws Exception { + BipedMek mek = new BipedMek(); + mek.setGame(game); + mek.setPosition(new Coords(0, 0)); + mek.setBoardId(game.getBoard().getBoardId()); + game.addEntity(mek); + movementDisplay.currentEntity = mek.getId(); + + MovePath walkPath = new MovePath(game, mek); + walkPath.addStep(MoveStepType.DEPLOY); + setField(movementDisplay, "cmd", walkPath); + setField(movementDisplay, "gear", MovementDisplay.GEAR_LAND); + + movementDisplay.clear(); + + assertTrue(mek.isDeployed(), "Walk-on clear should keep the deployment anchor intact."); + assertEquals(MovementDisplay.GEAR_LAND, readField(movementDisplay, "gear"), + "clear should reset gear to walk after a walk-on move path."); + + MovePath refreshed = (MovePath) readField(movementDisplay, "cmd"); + assertNotNull(refreshed, "clear should rebuild the move path."); + assertTrue(refreshed.getStepVector().stream().anyMatch(step -> step.getType() == MoveStepType.DEPLOY), + "clear should preserve the deployment step on a walk path."); + } + + @Test + @DisplayName("clear resets jump movement paths to the default gear while keeping deployment anchor") + void clearWithJumpPathResetsToLandGear() throws Exception { + BipedMek mek = new BipedMek(); + mek.setGame(game); + mek.setPosition(new Coords(0, 0)); + mek.setBoardId(game.getBoard().getBoardId()); + game.addEntity(mek); + movementDisplay.currentEntity = mek.getId(); + + MovePath jumpPath = new MovePath(game, mek); + jumpPath.addStep(MoveStepType.DEPLOY); + setField(movementDisplay, "cmd", jumpPath); + setField(movementDisplay, "gear", MovementDisplay.GEAR_JUMP); + + movementDisplay.clear(); + + assertTrue(mek.isDeployed(), "clear should keep the deployed position for a jump path."); + assertEquals(MovementDisplay.GEAR_JUMP, readField(movementDisplay, "gear"), + "clear should reset to jumping after reinitializing a jump path."); + + MovePath refreshed = (MovePath) readField(movementDisplay, "cmd"); + assertNotNull(refreshed, "clear should rebuild the jump path."); + assertTrue(refreshed.getStepVector().stream().anyMatch(step -> step.getType() == MoveStepType.DEPLOY), + "clear should retain the deployment step before a jump move is reinitialized."); + } + + @Test + @DisplayName("movement path rebuild keeps a deploy step for walk-on deployments") + void clearRebuildsWalkOnMovementPathWithDeployStep() throws Exception { + BipedMek mek = new BipedMek(); + mek.setGame(game); + mek.setPosition(new Coords(0, 0)); + mek.setBoardId(game.getBoard().getBoardId()); + game.addEntity(mek); + movementDisplay.currentEntity = mek.getId(); + + MovePath walkPath = new MovePath(game, mek); + walkPath.addStep(MoveStepType.DEPLOY); + setField(movementDisplay, "cmd", walkPath); + setField(movementDisplay, "gear", MovementDisplay.GEAR_LAND); + + movementDisplay.clear(); + + MovePath refreshed = (MovePath) readField(movementDisplay, "cmd"); + assertNotNull(refreshed, "clear should rebuild the walk-on movement path."); + assertTrue(refreshed.getStepVector().stream().anyMatch(step -> step.getType() == MoveStepType.DEPLOY), + "clear should preserve the deploy step when rebuilding a walk-on movement path."); + assertTrue(mek.isDeployed(), "clear should keep the deployment anchor active for walk-on movement."); + } + + @Test + @DisplayName("deployment position keeps the current facing when all facings remain valid") + void determineDeploymentPositionPreservesCurrentFacingWhenFacingsAreValid() { + Board board = new Board(7, 7); + for (int x = 0; x < board.getWidth(); x++) { + for (int y = 0; y < board.getHeight(); y++) { + board.setHex(x, y, new Hex()); + } + } + + BipedMek unit = new BipedMek(); + unit.setGame(game); + unit.setStartingPos(Board.START_EDGE); + unit.setFacing(3); + Coords deploymentCoords = new Coords(0, 0); + + var result = new DeploymentHelper(mockClientGUI) + .determineDeploymentPosition(unit, deploymentCoords, board, new HashSet<>(), null); + + assertNotNull(result, "A valid deployment should return a facing."); + assertEquals(unit.getFacing(), result.facing(), + "Deployment helpers keep the unit's current facing when no facing restriction is applied."); + } + + @Test + @DisplayName("center deployment keeps the current facing when all facings remain valid") + void determineDeploymentPositionKeepsCurrentFacingForCenterDeployment() { + Board board = new Board(7, 7); + for (int x = 0; x < board.getWidth(); x++) { + for (int y = 0; y < board.getHeight(); y++) { + board.setHex(x, y, new Hex()); + } + } + + BipedMek unit = new BipedMek(); + unit.setGame(game); + unit.setStartingPos(Board.START_CENTER); + unit.setFacing(5); + Coords deploymentCoords = new Coords(0, 0); + + var result = new DeploymentHelper(mockClientGUI) + .determineDeploymentPosition(unit, deploymentCoords, board, new HashSet<>(), null); + + assertNotNull(result, "A valid center deployment should return a facing."); + assertEquals(unit.getFacing(), result.facing(), + "Center deployments do not force a facing override when all facings remain valid."); + } + + private static void setField(Object target, + String fieldName, + Object value) throws Exception { + Field field = MovementDisplay.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } + + private static Object readField(Object target, + String fieldName) throws Exception { + Field field = MovementDisplay.class.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(target); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesAmmoTest.java b/megamek/unittests/megamek/common/rules/RulesAmmoTest.java new file mode 100644 index 00000000000..d5eae820bc1 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesAmmoTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import megamek.common.ToHitData; +import megamek.common.equipment.AmmoType; +import megamek.common.rules.core.CoreRulesAmmo; +import megamek.common.rules.totalwarfare.TWRulesAmmo; +import megamek.server.totalWarfare.TWDamageManager; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class RulesAmmoTest { + @Test + void coreAndTotalWarfareRulesExerciseAmmoLogic() { + CoreRulesAmmo core = new CoreRulesAmmo(); + TWRulesAmmo total = new TWRulesAmmo(); + AmmoType ammo = Mockito.mock(AmmoType.class); + Mockito.when(ammo.getRackSize()).thenReturn(4); + + assertEquals(-2, core.armorPiercingMod(ammo)); + assertEquals(-3, total.armorPiercingMod(ammo)); + + ToHitData toHit = new ToHitData(); + core.armorPiercingAttackMod(AmmoType.AmmoTypeEnum.AC, toHit, true); + total.armorPiercingAttackMod(AmmoType.AmmoTypeEnum.AC, toHit, false); + core.narcHomingTarget(toHit); + + assertEquals(-1, toHit.getValue()); + assertEquals(-1, core.getAXMissileModifier()); + assertEquals(-2, total.getAXMissileModifier()); + assertEquals(5, core.getAXMissileDamage(4, new TWDamageManager.ModsInfo(), 5)); + assertEquals(5, total.getAXMissileDamage(4, new TWDamageManager.ModsInfo(), 5)); + assertEquals(2, core.getSemiGuidedAdjustment(3, true, true)); + assertEquals(0, total.getSemiGuidedAdjustment(3, false, true)); + assertTrue(core.semiGuidedIgnoresCover()); + assertTrue(!total.semiGuidedIgnoresCover()); + assertEquals(2, core.getSemiGuidedNMissiles(true, false)); + assertEquals(0, total.getSemiGuidedNMissiles(true, true)); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesArmorTest.java b/megamek/unittests/megamek/common/rules/RulesArmorTest.java new file mode 100644 index 00000000000..a956bf0ee14 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesArmorTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import megamek.common.rules.core.CoreRulesArmor; +import megamek.common.rules.totalwarfare.TWRulesArmor; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("RulesArmor rules variants") +class RulesArmorTest { + + @Test + @DisplayName("core and total warfare variants construct and inherit from the base rules type") + void coreAndTotalWarfareVariantsConstructAndInherit() { + CoreRulesArmor core = new CoreRulesArmor(); + TWRulesArmor totalWarfare = new TWRulesArmor(); + + assertNotNull(core, "Core rules implementation should be constructible."); + assertNotNull(totalWarfare, "Total warfare rules implementation should be constructible."); + assertInstanceOf(RulesArmor.class, core, "Core rules should extend the base rules type."); + assertInstanceOf(RulesArmor.class, totalWarfare, "Total warfare rules should extend the base rules type."); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesArtilleryTest.java b/megamek/unittests/megamek/common/rules/RulesArtilleryTest.java new file mode 100644 index 00000000000..a3e1c83dc4c --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesArtilleryTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import megamek.common.rules.core.CoreRulesArtillery; +import megamek.common.rules.totalwarfare.TWRulesArtillery; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("RulesArtillery rules variants") +class RulesArtilleryTest { + + @Test + @DisplayName("core and total warfare variants construct and inherit from the base rules type") + void coreAndTotalWarfareVariantsConstructAndInherit() { + CoreRulesArtillery core = new CoreRulesArtillery(); + TWRulesArtillery totalWarfare = new TWRulesArtillery(); + + assertNotNull(core, "Core rules implementation should be constructible."); + assertNotNull(totalWarfare, "Total warfare rules implementation should be constructible."); + assertInstanceOf(RulesArtillery.class, core, "Core rules should extend the base rules type."); + assertInstanceOf(RulesArtillery.class, totalWarfare, "Total warfare rules should extend the base rules type."); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesC3Test.java b/megamek/unittests/megamek/common/rules/RulesC3Test.java new file mode 100644 index 00000000000..2e93ccbde3e --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesC3Test.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import megamek.common.rules.core.CoreRulesC3; +import megamek.common.rules.totalwarfare.TWRulesC3; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("RulesC3 rules variants") +class RulesC3Test { + + @Test + @DisplayName("core and total warfare variants construct and inherit from the base rules type") + void coreAndTotalWarfareVariantsConstructAndInherit() { + CoreRulesC3 core = new CoreRulesC3(); + TWRulesC3 totalWarfare = new TWRulesC3(); + + assertNotNull(core, "Core rules implementation should be constructible."); + assertNotNull(totalWarfare, "Total warfare rules implementation should be constructible."); + assertInstanceOf(RulesC3.class, core, "Core rules should extend the base rules type."); + assertInstanceOf(RulesC3.class, totalWarfare, "Total warfare rules should extend the base rules type."); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesChartsTest.java b/megamek/unittests/megamek/common/rules/RulesChartsTest.java new file mode 100644 index 00000000000..2c6d7fd1fe2 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesChartsTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import megamek.common.Messages; +import megamek.common.ToHitData; +import megamek.common.rules.core.CoreRulesCharts; +import megamek.common.rules.totalwarfare.TWRulesCharts; +import megamek.common.units.Mek; +import org.junit.jupiter.api.Test; + +class RulesChartsTest { + + @Test + void coreAndTotalWarfareRulesExerciseChartLogic() { + CoreRulesCharts core = new CoreRulesCharts(); + TWRulesCharts total = new TWRulesCharts(); + + assertNotNull(core); + assertNotNull(total); + assertEquals(2, core.escalatingFailure(0)); + assertEquals(11, total.escalatingFailure(6)); + assertTrue(core.getLocationName(Mek.LOC_LEFT_ARM, false).equals(Messages.getString("Locations.LeftArm"))); + assertTrue(total.getLocationName(Mek.LOC_RIGHT_ARM, true) + .equals(Messages.getString("Locations.FrontRightLeg"))); + assertEquals(Mek.LOC_LEFT_TORSO, core.getPunchHitLocationSide(1, ToHitData.SIDE_LEFT, false)); + assertEquals(Mek.LOC_HEAD, total.getPunchHitLocationSide(6, ToHitData.SIDE_RIGHT, true)); + assertEquals(Mek.LOC_LEFT_ARM, core.getPunchHitLocation(5, ToHitData.SIDE_LEFT)); + assertEquals(Mek.LOC_HEAD, total.getPunchHitLocation(6, ToHitData.SIDE_RIGHT, true)); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesEnvironmentTest.java b/megamek/unittests/megamek/common/rules/RulesEnvironmentTest.java new file mode 100644 index 00000000000..7aaef9ecb66 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesEnvironmentTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import megamek.common.rules.core.CoreRulesEnvironment; +import megamek.common.rules.totalwarfare.TWRulesEnvironment; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Simple construct-and-inherit checks for environment rule implementations. Kept concise but adopting the + * documented test layout used elsewhere: descriptive DisplayNames and clear messages for assertions. + */ +@DisplayName("RulesEnvironment rules variants") +class RulesEnvironmentTest { + + @Test + @DisplayName("core and total warfare environment rule instances are constructible") + void coreAndTotalWarfareEnvironmentRulesAreConstructible() { + CoreRulesEnvironment core = new CoreRulesEnvironment(); + TWRulesEnvironment totalWarfare = new TWRulesEnvironment(); + + assertNotNull(core, "Core rules implementation should be constructible."); + assertNotNull(totalWarfare, "Total warfare rules implementation should be constructible."); + assertInstanceOf(RulesEnvironment.class, core, "Core rules should extend the base rules type."); + assertInstanceOf(RulesEnvironment.class, totalWarfare, "Total warfare rules should extend the base rules type."); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesEquipmentTest.java b/megamek/unittests/megamek/common/rules/RulesEquipmentTest.java new file mode 100644 index 00000000000..7ae34dd9422 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesEquipmentTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import megamek.common.rules.core.CoreRulesEquipment; +import megamek.common.rules.totalwarfare.TWRulesEquipment; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("RulesEquipment rules variants") +class RulesEquipmentTest { + + @Test + @DisplayName("core and total warfare variants construct and inherit from the base rules type") + void coreAndTotalWarfareVariantsConstructAndInherit() { + CoreRulesEquipment core = new CoreRulesEquipment(); + TWRulesEquipment totalWarfare = new TWRulesEquipment(); + + assertNotNull(core, "Core rules implementation should be constructible."); + assertNotNull(totalWarfare, "Total warfare rules implementation should be constructible."); + assertInstanceOf(RulesEquipment.class, core, "Core rules should extend the base rules type."); + assertInstanceOf(RulesEquipment.class, totalWarfare, "Total warfare rules should extend the base rules type."); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesExplosionsTest.java b/megamek/unittests/megamek/common/rules/RulesExplosionsTest.java new file mode 100644 index 00000000000..eab88a0e797 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesExplosionsTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import megamek.common.rules.core.CoreRulesExplosions; +import megamek.common.rules.totalwarfare.TWRulesExplosions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("RulesExplosions rules variants") +class RulesExplosionsTest { + + @Test + @DisplayName("core and total warfare variants construct and inherit from the base rules type") + void coreAndTotalWarfareVariantsConstructAndInherit() { + CoreRulesExplosions core = new CoreRulesExplosions(); + TWRulesExplosions totalWarfare = new TWRulesExplosions(); + + assertNotNull(core, "Core rules implementation should be constructible."); + assertNotNull(totalWarfare, "Total warfare rules implementation should be constructible."); + assertInstanceOf(RulesExplosions.class, core, "Core rules should extend the base rules type."); + assertInstanceOf(RulesExplosions.class, totalWarfare, "Total warfare rules should extend the base rules type."); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesGameTest.java b/megamek/unittests/megamek/common/rules/RulesGameTest.java new file mode 100644 index 00000000000..3c2ff04d7db --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesGameTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import megamek.common.Player; +import megamek.common.board.Board; +import megamek.common.enums.GamePhase; +import megamek.common.game.Game; +import megamek.common.rules.core.CoreRulesGame; +import megamek.common.rules.totalwarfare.TWRulesGame; +import megamek.common.units.Entity; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class RulesGameTest { + @Test + void coreAndTotalWarfareRulesExerciseGameLogic() { + CoreRulesGame core = new CoreRulesGame(); + TWRulesGame total = new TWRulesGame(); + Game game = Mockito.mock(Game.class); + Entity entity = Mockito.mock(Entity.class); + Player player = Mockito.mock(Player.class); + + Mockito.when(entity.getGame()).thenReturn(game); + Mockito.when(entity.getDeployRound()).thenReturn(0); + Mockito.when(entity.isDeployed()).thenReturn(false); + Mockito.when(entity.getStartingPos()).thenReturn(Board.START_EDGE); + Mockito.when(entity.getOwner()).thenReturn(player); + Mockito.when(player.isBot()).thenReturn(false); + Mockito.when(game.getCurrentRound()).thenReturn(2); + Mockito.when(entity.isImmobile()).thenReturn(false); + + assertTrue(core.ammoDumping() == false); + assertTrue(total.ammoDumping()); + assertTrue(core.eligibleForPhase(entity, GamePhase.MOVEMENT)); + assertTrue(total.eligibleForPhase(entity, GamePhase.MOVEMENT)); + assertTrue(core.getInitiativeOrder(new int[] {6, 3}, 0, 3, false) >= 1); + assertTrue(total.getInitiativeOrder(new int[] {6, 3}, 0, 3, true) >= 1); + assertTrue(core.isWalkOnDeployment()); + total.setWalkOnDeployment(true); + assertTrue(total.isWalkOnDeployment()); + assertTrue(core.canWalkOnThisRound(entity)); + assertTrue(total.canWalkOnThisRound(entity)); + assertTrue(core.includeInMovement(GamePhase.MOVEMENT, entity)); + assertTrue(total.includeInMovement(GamePhase.MOVEMENT, entity)); + assertEquals(1, core.getDeploymentWidth(player, Board.START_EDGE, 8)); + assertEquals(1, total.getDeploymentWidth(player, Board.START_EDGE, 8)); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesHeatTest.java b/megamek/unittests/megamek/common/rules/RulesHeatTest.java new file mode 100644 index 00000000000..ea29c545647 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesHeatTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import megamek.common.rules.core.CoreRulesHeat; +import megamek.common.rules.totalwarfare.TWRulesHeat; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("RulesHeat rules variants") +class RulesHeatTest { + + @Test + @DisplayName("core and total warfare variants construct and inherit from the base rules type") + void coreAndTotalWarfareVariantsConstructAndInherit() { + CoreRulesHeat core = new CoreRulesHeat(); + TWRulesHeat totalWarfare = new TWRulesHeat(); + + assertNotNull(core, "Core rules implementation should be constructible."); + assertNotNull(totalWarfare, "Total warfare rules implementation should be constructible."); + assertInstanceOf(RulesHeat.class, core, "Core rules should extend the base rules type."); + assertInstanceOf(RulesHeat.class, totalWarfare, "Total warfare rules should extend the base rules type."); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesManagerTest.java b/megamek/unittests/megamek/common/rules/RulesManagerTest.java new file mode 100644 index 00000000000..bba60bcd48e --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesManagerTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import megamek.common.rules.core.CoreRulesManager; +import megamek.common.rules.core.CoreRulesTarget; +import megamek.common.rules.totalwarfare.TWRulesManager; +import megamek.common.rules.totalwarfare.TWRulesTarget; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Smoke tests for the RulesManager implementations ensuring each manager exposes the expected + * concrete rule implementations. The style mirrors documented tests: clear intent and helpful + * assertion messages for quick diagnosis. + */ +@DisplayName("RulesManager rules variants") +class RulesManagerTest { + + @Test + @DisplayName("core and total warfare managers expose the expected rule implementations") + void coreAndTotalWarfareManagersExposeRuleImplementations() { + CoreRulesManager core = new CoreRulesManager(); + TWRulesManager totalWarfare = new TWRulesManager(); + + assertNotNull(core, "Core rules implementation should be constructible."); + assertNotNull(totalWarfare, "Total warfare rules implementation should be constructible."); + assertInstanceOf(RulesManager.class, core, "Core rules should extend the base rules type."); + assertInstanceOf(RulesManager.class, totalWarfare, "Total warfare rules should extend the base rules type."); + assertInstanceOf(CoreRulesTarget.class, core.getRulesTarget(), "Core manager should expose core targeting rules."); + assertInstanceOf(TWRulesTarget.class, totalWarfare.getRulesTarget(), "Total warfare manager should expose total warfare targeting rules."); + assertNotNull(core.getRulesAmmo(), "Core manager should provide ammo rules."); + assertNotNull(totalWarfare.getRulesAmmo(), "Total warfare manager should provide ammo rules."); + assertNotNull(core.getRulesTerrain(), "Core manager should provide terrain rules."); + assertNotNull(totalWarfare.getRulesTerrain(), "Total warfare manager should provide terrain rules."); + assertNotNull(core.getRulesUnderwater(), "Core manager should provide underwater rules."); + assertNotNull(totalWarfare.getRulesUnderwater(), "Total warfare manager should provide underwater rules."); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesMovementTest.java b/megamek/unittests/megamek/common/rules/RulesMovementTest.java new file mode 100644 index 00000000000..dd9ea0e6117 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesMovementTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import megamek.common.board.Coords; +import megamek.common.game.Game; +import megamek.common.moves.MoveStep; +import megamek.common.rules.core.CoreRulesMovement; +import megamek.common.rules.totalwarfare.TWRulesMovement; +import megamek.common.units.Entity; +import megamek.common.units.EntityMovementMode; +import megamek.common.units.EntityMovementType; +import megamek.common.units.Mek; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class RulesMovementTest { + @Test + void coreAndTotalWarfareRulesExerciseMovementLogic() { + CoreRulesMovement core = new CoreRulesMovement(); + TWRulesMovement total = new TWRulesMovement(); + Game game = Mockito.mock(Game.class); + Mek mek = Mockito.mock(Mek.class); + MoveStep coreStep = Mockito.mock(MoveStep.class); + MoveStep totalStep = Mockito.mock(MoveStep.class); + Entity attacker = Mockito.mock(Entity.class); + Entity defender = Mockito.mock(Entity.class); + megamek.common.board.Board board = Mockito.mock(megamek.common.board.Board.class); + megamek.common.Hex hex = Mockito.mock(megamek.common.Hex.class); + + Mockito.when(coreStep.getElevation()).thenReturn(2); + Mockito.when(totalStep.getElevation()).thenReturn(3); + Mockito.when(game.getEntity(1)).thenReturn(attacker); + Mockito.when(game.getEntity(2)).thenReturn(defender); + Mockito.when(attacker.getBoardId()).thenReturn(1); + Mockito.when(defender.getBoardId()).thenReturn(1); + Mockito.when(attacker.getPosition()).thenReturn(new Coords(0, 0)); + Mockito.when(defender.getPosition()).thenReturn(new Coords(1, 0)); + Mockito.when(game.getBoard(1)).thenReturn(board); + Mockito.when(board.getHex(new Coords(0, 0))).thenReturn(hex); + Mockito.when(board.getHex(new Coords(1, 0))).thenReturn(hex); + Mockito.when(hex.getLevel()).thenReturn(0); + Mockito.when(attacker.getElevation()).thenReturn(1); + Mockito.when(defender.getElevation()).thenReturn(2); + Mockito.when(defender.getHeight()).thenReturn(1); + + assertNotNull(core); + assertNotNull(total); + assertTrue(core.checkMPZeroCauseImmobile(0)); + assertFalse(total.checkMPZeroCauseImmobile(0)); + assertTrue(core.getMekRunMP(0, 6, 8, false) >= 0); + assertTrue(total.getMekRunMP(1, 5, 7, false) >= 0); + assertEquals(2, core.getUnderwaterMPCost()); + assertEquals(3, total.getUnderwaterMPCost()); + assertTrue(core.isMoveIntoWaterDangerous(EntityMovementType.MOVE_RUN, EntityMovementMode.BIPED)); + assertTrue(total.isMoveIntoWaterDangerous(EntityMovementType.MOVE_RUN, EntityMovementMode.BIPED)); + assertEquals(2, core.getAccidentalFallElevation(5, 3)); + assertEquals(4, total.getAccidentalFallElevation(4, 3)); + assertEquals(3, core.getDFAElevation(game, 1, 2, coreStep)); + assertEquals(3, total.getDFAElevation(game, 1, 2, totalStep)); + assertTrue(core.enableBackwardsElevationChange(true, mek)); + assertTrue(total.enableBackwardsElevationChange(true, mek)); + assertFalse(core.reduceMaxElevation(mek)); + assertFalse(total.reduceMaxElevation(mek)); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesPSRTest.java b/megamek/unittests/megamek/common/rules/RulesPSRTest.java new file mode 100644 index 00000000000..52d41b086c5 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesPSRTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; + +import megamek.common.rolls.PilotingRollData; +import megamek.common.rules.core.CoreRulesPSR; +import megamek.common.rules.totalwarfare.TWRulesPSR; +import megamek.common.units.EntityMovementType; +import org.junit.jupiter.api.Test; + +class RulesPSRTest { + + @Test + void coreAndTotalWarfareRulesExercisePSRLogic() { + CoreRulesPSR core = new CoreRulesPSR(); + TWRulesPSR total = new TWRulesPSR(); + ArrayList list = new ArrayList<>(); + list.add(new PilotingRollData(1, 2, "first", 0)); + list.add(new PilotingRollData(2, 1, "second", 0)); + + assertNotNull(core); + assertNotNull(total); + core.rollRemoveHighest(list); + total.rollRemoveHighest(list); + assertTrue(list.size() <= 2); + assertTrue(core.getHipPenalty() >= 1); + assertTrue(total.getHipPenalty() >= 1); + assertTrue(core.getGyroModifier(1, 0) >= 2); + assertTrue(total.getGyroModifier(1, 0) >= 2); + assertTrue(core.getLegDestroyedModifier() >= 4); + assertTrue(total.getLegDestroyedModifier() >= 4); + assertTrue(core.getSuccessfulDFAModifier() >= 2); + assertTrue(total.getSuccessfulDFAModifier() >= 2); + assertTrue(core.getGyroJumpModifier(1, 0) >= 0); + assertTrue(total.getGyroJumpModifier(1, 0) >= 0); + assertTrue(core.psrForWaterEntry(EntityMovementType.MOVE_RUN)); + assertTrue(total.psrForWaterEntry(EntityMovementType.MOVE_RUN)); + assertTrue(core.getGyroModifier(0, 0) >= 2); + assertTrue(total.getGyroModifier(0, 0) >= 2); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesPhysicalTest.java b/megamek/unittests/megamek/common/rules/RulesPhysicalTest.java new file mode 100644 index 00000000000..0b5346b1f15 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesPhysicalTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import megamek.common.rules.core.CoreRulesPhysical; +import megamek.common.rules.totalwarfare.TWRulesPhysical; +import megamek.common.units.Entity; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class RulesPhysicalTest { + + @Test + void coreAndTotalWarfareRulesExercisePhysicalLogic() { + CoreRulesPhysical core = new CoreRulesPhysical(); + TWRulesPhysical total = new TWRulesPhysical(); + Entity attacker = Mockito.mock(Entity.class); + Entity target = Mockito.mock(Entity.class); + + Mockito.when(attacker.getWeight()).thenReturn(50.0); + Mockito.when(target.getWeight()).thenReturn(40.0); + Mockito.when(attacker.getLocationStatus(1)).thenReturn(0); + + assertNotNull(core); + assertNotNull(total); + assertEquals(0, core.getShieldDamageBoost(attacker, 0)); + assertEquals(0, total.getShieldDamageBoost(attacker, 1)); + assertEquals(0, core.getClawToHitModifier()); + assertEquals(1, total.getClawToHitModifier()); + assertEquals(-1, core.getKickModifier()); + assertEquals(-2, total.getKickModifier()); + assertEquals(-1, core.getPunchModifier()); + assertEquals(0, total.getPunchModifier()); + assertTrue(core.getChargeDamage(attacker, target, false, 3, 2) >= 0); + assertTrue(total.getChargeDamage(attacker, target, true, 3, 2) >= 0); + assertTrue(core.getChargeDamageTakenBy(attacker, 5.0, false, 2) >= 0); + assertTrue(total.getChargeDamageTakenBy(attacker, 6.0, true, 2) >= 0); + assertEquals(-1, core.getPilotDiffModifier(4, 5, false)); + assertEquals(1, total.getPilotDiffModifier(5, 4, true)); + assertTrue(core.getClubFindInRubble() >= 1); + assertTrue(total.getClubFindInRubble() >= 1); + assertEquals(9, core.getLanceTarget()); + assertEquals(10, total.getLanceTarget()); + assertTrue(core.canChargeCancel()); + assertTrue(!total.canChargeCancel()); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesPilotTest.java b/megamek/unittests/megamek/common/rules/RulesPilotTest.java new file mode 100644 index 00000000000..4cddd859a41 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesPilotTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import megamek.common.rules.core.CoreRulesPilot; +import megamek.common.rules.totalwarfare.TWRulesPilot; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests for pilot-related rule differences between Core and Total Warfare rule sets. + * + * Matches the documented test style: short helpers and clear, explanatory assertions. + */ +@DisplayName("RulesPilot rules variants") +class RulesPilotTest { + + @Test + @DisplayName("core and total warfare pilot rules apply different explosion and seatbelt modifiers") + void coreAndTotalWarfarePilotRulesApplyDistinctSeatbeltLogic() { + CoreRulesPilot core = new CoreRulesPilot(); + TWRulesPilot totalWarfare = new TWRulesPilot(); + + assertNotNull(core, "Core rules implementation should be constructible."); + assertNotNull(totalWarfare, "Total warfare rules implementation should be constructible."); + assertInstanceOf(RulesPilot.class, core, "Core rules should extend the base rules type."); + assertInstanceOf(RulesPilot.class, totalWarfare, "Total warfare rules should extend the base rules type."); + + assertEquals(1, core.getExplosionPilotHits(), "Core explosions cause one pilot hit."); + assertEquals(2, totalWarfare.getExplosionPilotHits(), "Total Warfare explosions cause two pilot hits."); + + // Gyro modifier differences + assertEquals(7, core.getSeatbeltGyroModifier(7), "Core gyro checks are not modified."); + assertEquals(13, totalWarfare.getSeatbeltGyroModifier(7), "Total Warfare gyro checks add six."); + + // Leg modifier differences + assertEquals(7, core.getSeatbeltLegModifier(7, 1), "Core legs do not modify seatbelt checks."); + assertEquals(17, totalWarfare.getSeatbeltLegModifier(7, 2), "Total Warfare two destroyed legs add ten."); + + // Shutdown checks + assertEquals(7, core.getSeatbeltShutdown(7), "Core shutdown seatbelt checks are unchanged."); + assertEquals(10, totalWarfare.getSeatbeltShutdown(7), "Total Warfare shutdown seatbelt checks are +3."); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesTargetTest.java b/megamek/unittests/megamek/common/rules/RulesTargetTest.java new file mode 100644 index 00000000000..e0ec8589345 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesTargetTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import megamek.common.LosEffects; +import megamek.common.rules.core.CoreRulesTarget; +import megamek.common.rules.totalwarfare.TWRulesTarget; +import megamek.common.units.Entity; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class RulesTargetTest { + @Test + void coreAndTotalWarfareRulesExerciseTargetLogic() { + CoreRulesTarget core = new CoreRulesTarget(); + TWRulesTarget total = new TWRulesTarget(); + Entity entityTarget = Mockito.mock(Entity.class); + + Mockito.when(entityTarget.getBadCriticalSlots(Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt())).thenReturn(0); + + assertNotNull(core); + assertNotNull(total); + assertEquals(-1, core.largeTargetModifier(4, true)); + assertEquals(-1, total.largeTargetModifier(5, false)); + assertEquals(1, core.getSecondaryArcModifier()); + assertEquals(2, total.getSecondaryArcModifier()); + assertEquals(0, core.getArmActuatorHitMod(entityTarget, 0)); + assertEquals(0, total.getArmActuatorHitMod(entityTarget, 1)); + assertTrue(core.getBAPSmokeReduction(new LosEffects()) >= 0); + assertTrue(total.getBAPSmokeReduction(new LosEffects()) >= 0); + assertEquals(1, core.getSecondaryTargetModifier()); + assertEquals(1, total.getSecondaryTargetModifier()); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesTerrainTest.java b/megamek/unittests/megamek/common/rules/RulesTerrainTest.java new file mode 100644 index 00000000000..cc9707097ad --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesTerrainTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import megamek.common.Hex; +import megamek.common.rules.core.CoreRulesTerrain; +import megamek.common.rules.totalwarfare.TWRulesTerrain; +import megamek.common.units.Terrain; +import megamek.common.units.Terrains; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests for terrain-specific rule differences between Core and Total Warfare rule sets. + * + * The tests aim to mirror the documented style used in the planetary conditions tests: small helper + * constructors for test fixtures, clear DisplayName annotations and explanatory assertion messages. + */ +@DisplayName("RulesTerrain rules variants") +class RulesTerrainTest { + + private Hex roadHex() { + return new Hex(0, new Terrain[] {new Terrain(Terrains.ROAD, 1)}, null); + } + + @Test + @DisplayName("core and total warfare road and elevation logic differ by rule set") + void coreAndTotalWarfareTerrainLogicDiffersByRuleSet() { + CoreRulesTerrain core = new CoreRulesTerrain(); + TWRulesTerrain totalWarfare = new TWRulesTerrain(); + Hex roadA = roadHex(); + Hex roadB = roadHex(); + + assertNotNull(core, "Core rules implementation should be constructible."); + assertNotNull(totalWarfare, "Total warfare rules implementation should be constructible."); + assertInstanceOf(RulesTerrain.class, core, "Core rules should extend the base rules type."); + assertInstanceOf(RulesTerrain.class, totalWarfare, "Total warfare rules should extend the base rules type."); + + assertEquals(-1, core.getRoadElevationCostDifference(roadA, roadB, 2), + "Core road movement should reduce elevation cost by one when traversing roads upward."); + assertEquals(0, totalWarfare.getRoadElevationCostDifference(roadA, roadB, 2), + "Total Warfare does not change road elevation cost."); + assertEquals(4, core.getMaxElevationChangeAllowed(roadA, roadB, 3), + "Core roads allow one more elevation change than the normal max."); + assertEquals(3, totalWarfare.getMaxElevationChangeAllowed(roadA, roadB, 3), + "Total Warfare keeps the normal elevation cap even on roads."); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesUnderwaterTest.java b/megamek/unittests/megamek/common/rules/RulesUnderwaterTest.java new file mode 100644 index 00000000000..2929f79ad12 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesUnderwaterTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import megamek.common.equipment.WeaponType; +import megamek.common.rules.core.CoreRulesUnderwater; +import megamek.common.rules.totalwarfare.TWRulesUnderwater; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +/** + * Tests for underwater weapon range translation and LOS behavior across Core and Total Warfare rules. + * + * Patterned after the documented planetary conditions tests: helper builders, clear DisplayNames, and + * descriptive assertion messages so failures are easy to interpret. + */ +@DisplayName("RulesUnderwater rules variants") +class RulesUnderwaterTest { + + private WeaponType mockEnergyWeapon() { + WeaponType wt = Mockito.mock(WeaponType.class); + Mockito.when(wt.hasFlag(WeaponType.F_ENERGY)).thenReturn(true); + Mockito.when(wt.getShortRange()).thenReturn(4); + Mockito.when(wt.getMediumRange()).thenReturn(8); + Mockito.when(wt.getLongRange()).thenReturn(12); + return wt; + } + + private WeaponType mockSolidWeapon() { + WeaponType wt = Mockito.mock(WeaponType.class); + Mockito.when(wt.hasFlag(WeaponType.F_ENERGY)).thenReturn(false); + Mockito.when(wt.getWShortRange()).thenReturn(3); + Mockito.when(wt.getWMediumRange()).thenReturn(6); + Mockito.when(wt.getWLongRange()).thenReturn(9); + Mockito.when(wt.getWExtremeRange()).thenReturn(12); + return wt; + } + + @Test + @DisplayName("core and total warfare underwater rules apply different LOS and range behavior") + void coreAndTotalWarfareUnderwaterRulesApplyDistinctRules() { + CoreRulesUnderwater core = new CoreRulesUnderwater(); + TWRulesUnderwater totalWarfare = new TWRulesUnderwater(); + WeaponType energyWeapon = mockEnergyWeapon(); + WeaponType solidWeapon = mockSolidWeapon(); + + assertNotNull(core, "Core rules implementation should be constructible."); + assertNotNull(totalWarfare, "Total warfare rules implementation should be constructible."); + assertInstanceOf(RulesUnderwater.class, core, "Core rules should extend the base rules type."); + assertInstanceOf(RulesUnderwater.class, totalWarfare, "Total warfare rules should extend the base rules type."); + + assertEquals(4, core.getBreachTarget(), "Core underwater breaches are easier to trigger."); + assertEquals(10, totalWarfare.getBreachTarget(), "Total Warfare underwater breaches are harder to trigger."); + assertFalse(core.waterBlocksLOS(), "Core rules do not block LOS underwater."); + assertTrue(totalWarfare.waterBlocksLOS(), "Total Warfare underwater rules block LOS across the waterline."); + assertEquals(0, core.getShortRange(energyWeapon), "Core energy weapons lose short range underwater."); + assertEquals(3, core.getShortRange(solidWeapon), "Core ballistic weapons keep their normal short range."); + assertEquals(4, core.getMediumRange(energyWeapon), "Core energy weapons map medium range to short range underwater."); + assertEquals(6, core.getMediumRange(solidWeapon), "Core solid weapons keep their normal medium range."); + assertEquals(8, core.getLongRange(energyWeapon), "Core energy weapons map long range to medium range underwater."); + assertEquals(9, core.getLongRange(solidWeapon), "Core solid weapons keep their normal long range."); + assertEquals(12, core.getExtremeRange(energyWeapon), "Core energy weapons map extreme range to long range underwater."); + assertEquals(12, core.getExtremeRange(solidWeapon), "Core solid weapons keep their normal extreme range."); + + assertEquals(3, totalWarfare.getShortRange(solidWeapon), "Total Warfare keeps the base short range underwater."); + assertEquals(6, totalWarfare.getMediumRange(solidWeapon), "Total Warfare keeps the base medium range underwater."); + assertEquals(9, totalWarfare.getLongRange(solidWeapon), "Total Warfare keeps the base long range underwater."); + assertEquals(12, totalWarfare.getExtremeRange(solidWeapon), "Total Warfare keeps the base extreme range underwater."); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesUnitsTest.java b/megamek/unittests/megamek/common/rules/RulesUnitsTest.java new file mode 100644 index 00000000000..ebdcc143078 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesUnitsTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +/** + * Tests for unit-specific rule differences between Core and Total Warfare rule sets. + * + * Updated to follow the documented, example-driven style used in the planetary condition tests: + * helper-focused fixtures, clear DisplayName annotations and explicit assertion messages so test + * failures are self-explanatory. + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import megamek.common.rules.core.CoreRulesUnits; +import megamek.common.rules.totalwarfare.TWRulesUnits; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("RulesUnits rules variants") +class RulesUnitsTest { + + /** + * Verify the key behavioral divergences between Core and Total Warfare unit rules. + */ + @Test + @DisplayName("core and total warfare unit rules apply different leg and MP logic") + void coreAndTotalWarfareUnitRulesApplyDifferentLegAndMovementLogic() { + CoreRulesUnits core = new CoreRulesUnits(); + TWRulesUnits totalWarfare = new TWRulesUnits(); + + assertNotNull(core, "Core rules implementation should be constructible."); + assertNotNull(totalWarfare, "Total warfare rules implementation should be constructible."); + assertInstanceOf(RulesUnits.class, core, "Core rules should extend the base rules type."); + assertInstanceOf(RulesUnits.class, totalWarfare, "Total warfare rules should extend the base rules type."); + + // Mule kick differences + assertEquals(0, core.getMuleKickModifier(), "Core mule kicks do not have a hit modifier."); + assertEquals(1, totalWarfare.getMuleKickModifier(), "Total Warfare mule kicks add one to hit."); + + // Quad movement reductions for destroyed legs + assertEquals(5, core.reduceQuadWalkMP(6, 1, 0, 0, false), "Core quad leg loss reduces walk MP by one."); + assertEquals(1, core.reduceQuadWalkMP(3, 3, 0, 0, false), "Core quad walk MP is reduced to one after three destroyed legs."); + assertEquals(0, core.reduceQuadWalkMP(3, 4, 0, 0, false), "Core quad walk MP reaches zero with all legs destroyed."); + assertEquals(1, totalWarfare.reduceQuadWalkMP(6, 2, 0, 0, false), "Total Warfare reduces a quad to one MP with two destroyed legs."); + + // Hip hits and actuator effects + assertEquals(4, totalWarfare.reduceQuadWalkMP(8, 1, 1, 0, false), "Total Warfare halves MP for a single hip hit after leg loss."); + assertEquals(8, core.getMekMPReduction(2, true, 10), "Core hip hit reduction is a flat reduction of hip hits."); + assertEquals(5, totalWarfare.getMekMPReduction(1, false, 10), "Total Warfare halves MP for a single hip hit."); + + // Minimum MP differences + assertEquals(1, core.getMinimumMP(0), "Core minimum MP is one."); + assertEquals(0, totalWarfare.getMinimumMP(0), "Total Warfare minimum MP is zero."); + + // Physical phase torso twist behavior + assertTrue(core.getPhysicalTwistEnabled(), "Core rules allow torso twists in the physical phase."); + assertFalse(totalWarfare.getPhysicalTwistEnabled(), "Total Warfare does not enable the base RulesUnits torso twist override."); + } +} diff --git a/megamek/unittests/megamek/common/rules/RulesWeaponsTest.java b/megamek/unittests/megamek/common/rules/RulesWeaponsTest.java new file mode 100644 index 00000000000..5685685b515 --- /dev/null +++ b/megamek/unittests/megamek/common/rules/RulesWeaponsTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2026 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 (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * 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. + * + * A copy of the GPL should have been included with this project; + * if not, see . + */ +package megamek.common.rules; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import megamek.common.rules.core.CoreRulesWeapons; +import megamek.common.rules.totalwarfare.TWRulesWeapons; +import org.junit.jupiter.api.Test; + +class RulesWeaponsTest { + @Test + void coreAndTotalWarfareRulesExerciseWeaponLogic() { + CoreRulesWeapons core = new CoreRulesWeapons(); + TWRulesWeapons total = new TWRulesWeapons(); + + assertNotNull(core); + assertNotNull(total); + assertTrue(core.getRACUnjamRestriction() == false || core.getRACUnjamRestriction() == true); + assertTrue(total.getRACUnjamRestriction() == false || total.getRACUnjamRestriction() == true); + assertTrue(core.getATMClusterSize() >= 0); + assertTrue(total.getATMClusterSize() >= 0); + assertTrue(core.canUACsJam() == false || core.canUACsJam() == true); + assertTrue(total.canUACsJam() == false || total.canUACsJam() == true); + assertTrue(core.getELRMMinimumRackSize(4) >= 0); + assertTrue(total.getELRMMinimumRackSize(4) >= 0); + assertTrue(core.getMRMModifier(2) >= 0); + assertTrue(total.getMRMModifier(2) >= 0); + assertTrue(core.getMRMClusterModifier(true) >= -1); + assertTrue(total.getMRMClusterModifier(false) >= -1); + assertTrue(core.getApolloToHit() >= -1); + assertTrue(total.getApolloToHit() >= -1); + assertTrue(core.flamerHeatAndDamage(true) == false || core.flamerHeatAndDamage(true) == true); + assertTrue(total.flamerHeatAndDamage(false) == false || total.flamerHeatAndDamage(false) == true); + assertTrue(core.getApolloSaturationMode() == true || core.getApolloSaturationMode() == false); + assertTrue(total.getApolloSaturationMode() == true || total.getApolloSaturationMode() == false); + } +} diff --git a/megamek/unittests/megamek/server/totalWarfare/DeploymentProcessorTest.java b/megamek/unittests/megamek/server/totalWarfare/DeploymentProcessorTest.java index f4584bb1691..6a6e1f4d57f 100644 --- a/megamek/unittests/megamek/server/totalWarfare/DeploymentProcessorTest.java +++ b/megamek/unittests/megamek/server/totalWarfare/DeploymentProcessorTest.java @@ -42,7 +42,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.lang.reflect.Method; import java.util.HashSet; import java.util.Vector; import java.util.stream.Stream; @@ -58,6 +57,7 @@ import megamek.common.enums.BuildingType; import megamek.common.game.Game; import megamek.common.units.BuildingEntity; +import megamek.common.units.Entity; import megamek.common.units.IBuilding; import megamek.common.units.Terrains; import org.junit.jupiter.api.BeforeEach; @@ -84,17 +84,17 @@ void beforeEach() { // Create a fresh board for each test (boards are reused from static cache, so we need a fresh copy) // Re-initialize the board each time to avoid terrain contamination between tests initializeBoard("EMPTY_3x3", """ - size 3 3 - hex 0101 0 "" "" - hex 0201 0 "" "" - hex 0301 0 "" "" - hex 0102 0 "" "" - hex 0202 0 "" "" - hex 0302 0 "" "" - hex 0103 0 "" "" - hex 0203 0 "" "" - hex 0303 0 "" "" - end""" + size 3 3 + hex 0101 0 "" "" + hex 0201 0 "" "" + hex 0301 0 "" "" + hex 0102 0 "" "" + hex 0202 0 "" "" + hex 0302 0 "" "" + hex 0103 0 "" "" + hex 0203 0 "" "" + hex 0303 0 "" "" + end""" ); board = getBoard("EMPTY_3x3"); game.setBoard(0, board); @@ -117,35 +117,26 @@ void beforeEach() { /** * Helper method to call the private processDeployment method via reflection */ - private void callProcessDeployment(megamek.common.units.Entity entity, Coords coords) throws Exception { - Method processDeployment = DeploymentProcessor.class.getDeclaredMethod( - "processDeployment", - megamek.common.units.Entity.class, - Coords.class, - int.class, - int.class, - int.class, - Vector.class, - boolean.class - ); - processDeployment.setAccessible(true); - processDeployment.invoke(deploymentProcessor, entity, coords, 0, 0, 0, new Vector<>(), false); + private void callProcessDeployment(Entity entity, + Coords coords) throws Exception { + DeploymentServerHelper deploymentServerHelper = new DeploymentServerHelper(mockTWGameManager); + deploymentServerHelper.processDeployment(entity, coords, 0, 0, 0, new Vector<>(), false, true); } static { // Empty 3x3 board for BuildingEntity deployment tests initializeBoard("EMPTY_3x3", """ - size 3 3 - hex 0101 0 "" "" - hex 0201 0 "" "" - hex 0301 0 "" "" - hex 0102 0 "" "" - hex 0202 0 "" "" - hex 0302 0 "" "" - hex 0103 0 "" "" - hex 0203 0 "" "" - hex 0303 0 "" "" - end""" + size 3 3 + hex 0101 0 "" "" + hex 0201 0 "" "" + hex 0301 0 "" "" + hex 0102 0 "" "" + hex 0202 0 "" "" + hex 0302 0 "" "" + hex 0103 0 "" "" + hex 0203 0 "" "" + hex 0303 0 "" "" + end""" ); } @@ -153,10 +144,10 @@ private void callProcessDeployment(megamek.common.units.Entity entity, Coords co static Stream buildingTypeAndClass() { return Stream.of( - Arguments.of(BuildingType.LIGHT, IBuilding.STANDARD, 1), - Arguments.of(BuildingType.MEDIUM, IBuilding.HANGAR, 2), - Arguments.of(BuildingType.HEAVY, IBuilding.FORTRESS, 3), - Arguments.of(BuildingType.HARDENED, IBuilding.GUN_EMPLACEMENT, 4) + Arguments.of(BuildingType.LIGHT, IBuilding.STANDARD, 1), + Arguments.of(BuildingType.MEDIUM, IBuilding.HANGAR, 2), + Arguments.of(BuildingType.HEAVY, IBuilding.FORTRESS, 3), + Arguments.of(BuildingType.HARDENED, IBuilding.GUN_EMPLACEMENT, 4) ); } @@ -164,8 +155,10 @@ static Stream buildingTypeAndClass() { @ParameterizedTest(name = "BuildingType={0}, Class={1}") @MethodSource("buildingTypeAndClass") - void testDeployBuildingEntity_AddsTerrainToHex(BuildingType buildingType, int buildingClass, int expectedTypeValue) - throws Exception { + void testDeployBuildingEntity_AddsTerrainToHex(BuildingType buildingType, + int buildingClass, + int expectedTypeValue) + throws Exception { // Create BuildingEntity with specific properties BuildingEntity buildingEntity = new BuildingEntity(buildingType, buildingClass); buildingEntity.setOwner(game.getPlayer(0)); @@ -180,39 +173,40 @@ void testDeployBuildingEntity_AddsTerrainToHex(BuildingType buildingType, int bu // Verify hex starts empty assertFalse(targetHex.containsTerrain(Terrains.BUILDING), - "Hex should not contain BUILDING terrain before deployment"); + "Hex should not contain BUILDING terrain before deployment"); // Call processDeployment callProcessDeployment(buildingEntity, new Coords(0, 0)); // Verify BUILDING terrain was added with correct type value assertTrue(targetHex.containsTerrain(Terrains.BUILDING), - "Hex should contain BUILDING terrain after deployment"); + "Hex should contain BUILDING terrain after deployment"); assertEquals(expectedTypeValue, targetHex.terrainLevel(Terrains.BUILDING), - String.format("BUILDING terrain level: expected %d, got %d", - expectedTypeValue, targetHex.terrainLevel(Terrains.BUILDING))); + String.format("BUILDING terrain level: expected %d, got %d", + expectedTypeValue, targetHex.terrainLevel(Terrains.BUILDING))); // Verify BLDG_CLASS terrain assertEquals(buildingClass, targetHex.terrainLevel(Terrains.BLDG_CLASS), - String.format("BLDG_CLASS: expected %d, got %d", - buildingClass, targetHex.terrainLevel(Terrains.BLDG_CLASS))); + String.format("BLDG_CLASS: expected %d, got %d", + buildingClass, targetHex.terrainLevel(Terrains.BLDG_CLASS))); // Verify BLDG_CF terrain assertEquals(50, targetHex.terrainLevel(Terrains.BLDG_CF), - String.format("BLDG_CF: expected 50, got %d", targetHex.terrainLevel(Terrains.BLDG_CF))); + String.format("BLDG_CF: expected 50, got %d", targetHex.terrainLevel(Terrains.BLDG_CF))); // Verify BLDG_ARMOR terrain assertEquals(10, targetHex.terrainLevel(Terrains.BLDG_ARMOR), - String.format("BLDG_ARMOR: expected 10, got %d", targetHex.terrainLevel(Terrains.BLDG_ARMOR))); + String.format("BLDG_ARMOR: expected 10, got %d", targetHex.terrainLevel(Terrains.BLDG_ARMOR))); // Verify BLDG_ELEV terrain assertEquals(4, targetHex.terrainLevel(Terrains.BLDG_ELEV), - String.format("BLDG_ELEV: expected 4, got %d", targetHex.terrainLevel(Terrains.BLDG_ELEV))); + String.format("BLDG_ELEV: expected 4, got %d", targetHex.terrainLevel(Terrains.BLDG_ELEV))); // Verify BLDG_BASEMENT_TYPE terrain assertEquals(BasementType.ONE_DEEP_FEET.ordinal(), targetHex.terrainLevel(Terrains.BLDG_BASEMENT_TYPE), - String.format("BLDG_BASEMENT_TYPE: expected %d, got %d", - BasementType.ONE_DEEP_FEET.ordinal(), targetHex.terrainLevel(Terrains.BLDG_BASEMENT_TYPE))); + String.format("BLDG_BASEMENT_TYPE: expected %d, got %d", + BasementType.ONE_DEEP_FEET.ordinal(), + targetHex.terrainLevel(Terrains.BLDG_BASEMENT_TYPE))); } @Test @@ -234,13 +228,13 @@ void testDeployBuildingEntity_NoArmorSkipsArmorTerrain() throws Exception { // Verify armor terrain was NOT added (armor = 0) assertFalse(targetHex.containsTerrain(Terrains.BLDG_ARMOR), - "Hex should NOT contain BLDG_ARMOR terrain when armor is 0"); + "Hex should NOT contain BLDG_ARMOR terrain when armor is 0"); // Verify other terrains were still added assertTrue(targetHex.containsTerrain(Terrains.BUILDING), - "Hex should contain BUILDING terrain"); + "Hex should contain BUILDING terrain"); assertTrue(targetHex.containsTerrain(Terrains.BLDG_CF), - "Hex should contain BLDG_CF terrain"); + "Hex should contain BLDG_CF terrain"); } @Test @@ -262,13 +256,13 @@ void testDeployBuildingEntity_NoBasementSkipsBasementTerrain() throws Exception // Verify basement terrain was NOT added (basement = null) assertFalse(targetHex.containsTerrain(Terrains.BLDG_BASEMENT_TYPE), - "Hex should NOT contain BLDG_BASEMENT_TYPE terrain when basement is null"); + "Hex should NOT contain BLDG_BASEMENT_TYPE terrain when basement is null"); // Verify other terrains were still added assertTrue(targetHex.containsTerrain(Terrains.BUILDING), - "Hex should contain BUILDING terrain"); + "Hex should contain BUILDING terrain"); assertTrue(targetHex.containsTerrain(Terrains.BLDG_ELEV), - "Hex should contain BLDG_ELEV terrain"); + "Hex should contain BLDG_ELEV terrain"); } @Test @@ -291,22 +285,24 @@ void testDeployBuildingEntity_MultipleHexes() throws Exception { // Verify all 3 hexes have building terrain assertEquals(3, buildingEntity.getCoordsList().size(), - "Building should occupy 3 hexes"); + "Building should occupy 3 hexes"); // The 3 footprint hexes must land on distinct board coords. The old toOffset bug collapsed // (1,-1,0) and (1,0,-1) onto the same hex, which this guards against. assertEquals(3, new HashSet<>(buildingEntity.getCoordsList()).size(), - "Building's 3 hexes must occupy distinct board coords (no overlap)"); + "Building's 3 hexes must occupy distinct board coords (no overlap)"); for (Coords buildingCoords : buildingEntity.getCoordsList()) { Hex hex = board.getHex(buildingCoords); assertTrue(hex.containsTerrain(Terrains.BUILDING), - String.format("Hex %s should contain BUILDING terrain", buildingCoords)); + String.format("Hex %s should contain BUILDING terrain", buildingCoords)); assertEquals(BuildingType.MEDIUM.getTypeValue(), hex.terrainLevel(Terrains.BUILDING), - String.format("Hex %s BUILDING type: expected %d, got %d", - buildingCoords, BuildingType.MEDIUM.getTypeValue(), hex.terrainLevel(Terrains.BUILDING))); + String.format("Hex %s BUILDING type: expected %d, got %d", + buildingCoords, + BuildingType.MEDIUM.getTypeValue(), + hex.terrainLevel(Terrains.BUILDING))); assertEquals(IBuilding.HANGAR, hex.terrainLevel(Terrains.BLDG_CLASS), - String.format("Hex %s BLDG_CLASS: expected %d, got %d", - buildingCoords, IBuilding.HANGAR, hex.terrainLevel(Terrains.BLDG_CLASS))); + String.format("Hex %s BLDG_CLASS: expected %d, got %d", + buildingCoords, IBuilding.HANGAR, hex.terrainLevel(Terrains.BLDG_CLASS))); } } diff --git a/megamek/unittests/megamek/server/totalWarfare/OffBoardTrailerDeploymentTest.java b/megamek/unittests/megamek/server/totalWarfare/OffBoardTrailerDeploymentTest.java index 270487a1632..ef2712e0061 100644 --- a/megamek/unittests/megamek/server/totalWarfare/OffBoardTrailerDeploymentTest.java +++ b/megamek/unittests/megamek/server/totalWarfare/OffBoardTrailerDeploymentTest.java @@ -43,8 +43,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.util.List; - import megamek.common.OffBoardDirection; import megamek.common.Player; import megamek.common.enums.GamePhase; @@ -76,6 +74,7 @@ class OffBoardTrailerDeploymentTest { private Player owner; private DeploymentProcessor deploymentProcessor; private int nextId = 1; + private DeploymentServerHelper deploymentServerHelper; @BeforeEach void setUp() { @@ -94,9 +93,11 @@ void setUp() { gameManager.setGame(game); deploymentProcessor = new DeploymentProcessor(gameManager); + deploymentServerHelper = new DeploymentServerHelper(gameManager); } - private Tank buildVehicle(double tonnage, boolean isTrailer) throws Exception { + private Tank buildVehicle(double tonnage, + boolean isTrailer) throws Exception { Tank vehicle = new Tank(); vehicle.setId(nextId++); vehicle.setOwner(owner); @@ -118,7 +119,8 @@ private Tank buildTrain(int trailerCount) throws Exception { return tractor; } - private Entity trailerAt(Tank tractor, int trailerNumber) { + private Entity trailerAt(Tank tractor, + int trailerNumber) { return game.getEntity(tractor.getAllTowedUnits().get(trailerNumber)); } @@ -134,9 +136,9 @@ void aTrailerWithNoTractorDeploysOffBoardOnItsOwn() throws Exception { setOffBoard(looseTrailer); assertFalse(looseTrailer.shouldDeploy(FIRST_ROUND), - "Off board units never take a deployment turn"); + "Off board units never take a deployment turn"); assertTrue(looseTrailer.shouldOffBoardDeploy(FIRST_ROUND), - "but it does deploy itself off board, with no tractor involved"); + "but it does deploy itself off board, with no tractor involved"); } @Test @@ -145,7 +147,7 @@ void aLoneOffBoardTrailerIsUntouchedByTheTrainCode() throws Exception { Tank looseTrailer = buildVehicle(CARRIAGE_TONS, true); setOffBoard(looseTrailer); - deploymentProcessor.clearTrailerOffBoardSettings(tractor); + deploymentServerHelper.clearTrailerOffBoardSettings(tractor); assertEquals(Entity.NONE, looseTrailer.getTractor(), "It was never part of anyone's train"); assertTrue(looseTrailer.isOffBoard(), "and its off board setting is left alone"); @@ -159,10 +161,10 @@ void aHitchedTrailersOwnOffBoardSettingIsCleared() throws Exception { Entity gunTrailer = trailerAt(tractor, 1); setOffBoard(gunTrailer); - deploymentProcessor.clearTrailerOffBoardSettings(tractor); + deploymentServerHelper.clearTrailerOffBoardSettings(tractor); assertFalse(gunTrailer.isOffBoard(), - "A train deploys where its tractor does, so a trailer keeps no deployment of its own"); + "A train deploys where its tractor does, so a trailer keeps no deployment of its own"); assertEquals(2, tractor.getAllTowedUnits().size(), "and the train stays whole"); assertEquals(tractor.getId(), leadTrailer.getTractor()); }