From eccaafff3f20100b7ca581516f315b72cb72e8b9 Mon Sep 17 00:00:00 2001 From: cwspain Date: Tue, 26 Sep 2023 19:13:44 -0500 Subject: [PATCH 1/8] Halve terrain modifiers for vertical landing. --- megamek/src/megamek/common/IAero.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/megamek/src/megamek/common/IAero.java b/megamek/src/megamek/common/IAero.java index 3b068d163da..61b98deb20a 100644 --- a/megamek/src/megamek/common/IAero.java +++ b/megamek/src/megamek/common/IAero.java @@ -525,24 +525,32 @@ default PilotingRollData checkLanding(EntityMovementType moveType, int velocity, } if (heavyWoods) { - roll.addModifier(+5, "heavy woods in landing path"); + addLandingModifier(roll, +5, "heavy woods in landing path", isVertical); } if (lightWoods) { - roll.addModifier(+4, "light woods in landing path"); + addLandingModifier(roll, +4, "light woods in landing path", isVertical); } if (rough) { - roll.addModifier(+3, "rough/rubble in landing path"); + addLandingModifier(roll, +3, "rough/rubble in landing path", isVertical); } if (paved) { - roll.addModifier(+0, "paved/road landing strip"); + addLandingModifier(roll, +0, "paved/road landing strip", isVertical); } if (clear) { - roll.addModifier(+2, "clear hex in landing path"); + addLandingModifier(roll, +2, "clear hex in landing path", isVertical); } return roll; } + default void addLandingModifier(PilotingRollData roll, int mod, String reason, boolean isVertical) { + if (isVertical) { + // divide by 2, rounding up + mod = mod / 2 + mod % 2; + } + roll.addModifier(mod, reason); + } + /** * Checks if a maneuver requires a control roll */ From cf5b339d0c554f77d77f90cfaa9ab9a865b47441 Mon Sep 17 00:00:00 2001 From: cwspain Date: Tue, 26 Sep 2023 19:16:43 -0500 Subject: [PATCH 2/8] Remove false paved terrain entry from landing PSR. --- megamek/src/megamek/common/IAero.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/megamek/src/megamek/common/IAero.java b/megamek/src/megamek/common/IAero.java index 61b98deb20a..41e027cb3af 100644 --- a/megamek/src/megamek/common/IAero.java +++ b/megamek/src/megamek/common/IAero.java @@ -482,7 +482,7 @@ default PilotingRollData checkLanding(EntityMovementType moveType, int velocity, boolean rough = false; boolean heavyWoods = false; boolean clear = false; - boolean paved = true; + boolean paved = false; Set landingPositions = new HashSet<>(); boolean isDropship = (this instanceof Dropship); @@ -516,8 +516,9 @@ default PilotingRollData checkLanding(EntityMovementType moveType, int velocity, heavyWoods = true; } else if (hex.containsTerrain(Terrains.WOODS, 1)) { lightWoods = true; - } else if (!hex.containsTerrain(Terrains.PAVEMENT) && !hex.containsTerrain(Terrains.ROAD)) { - paved = false; + } else if (hex.containsTerrain(Terrains.PAVEMENT) || hex.containsTerrain(Terrains.ROAD)) { + paved = true; + } else { // Landing in other terrains isn't allowed, so if we reach here // it must be a clear hex clear = true; From 18dfd66da54a9aabf80d5179e4e8329dbd62bf46 Mon Sep 17 00:00:00 2001 From: cwspain Date: Tue, 26 Sep 2023 19:43:25 -0500 Subject: [PATCH 3/8] Damage landing gear when landing in a rough or rubble hex. --- megamek/src/megamek/common/IAero.java | 47 ++++++++++++--------- megamek/src/megamek/server/GameManager.java | 21 +++++++++ 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/megamek/src/megamek/common/IAero.java b/megamek/src/megamek/common/IAero.java index 41e027cb3af..e0b9d29f044 100644 --- a/megamek/src/megamek/common/IAero.java +++ b/megamek/src/megamek/common/IAero.java @@ -484,29 +484,9 @@ default PilotingRollData checkLanding(EntityMovementType moveType, int velocity, boolean clear = false; boolean paved = false; - Set landingPositions = new HashSet<>(); + Set landingPositions = getLandingCoords(isVertical, landingPos, face); boolean isDropship = (this instanceof Dropship); // Vertical landing just checks the landing hex - if (isVertical) { - landingPositions.add(landingPos); - // Dropships must also check the adjacent 6 hexes - if (isDropship) { - for (int i = 0; i < 6; i++) { - landingPositions.add(landingPos.translated(i)); - } - } - // Horizontal landing requires checking whole landing strip - } else { - for (int i = 0; i < getLandingLength(); i++) { - Coords pos = landingPos.translated(face, i); - landingPositions.add(pos); - // Dropships have to check the front adjacent hexes - if (isDropship) { - landingPositions.add(pos.translated((face + 4) % 6)); - landingPositions.add(pos.translated((face + 2) % 6)); - } - } - } for (Coords pos : landingPositions) { Hex hex = ((Entity) this).getGame().getBoard().getHex(pos); @@ -544,6 +524,31 @@ default PilotingRollData checkLanding(EntityMovementType moveType, int velocity, return roll; } + default Set getLandingCoords(boolean isVertical, Coords landingPos, int facing) { + Set landingPositions = new HashSet(); + if (isVertical) { + landingPositions.add(landingPos); + // Dropships must also check the adjacent 6 hexes + if (this instanceof Dropship) { + for (int i = 0; i < 6; i++) { + landingPositions.add(landingPos.translated(i)); + } + } + // Horizontal landing requires checking whole landing strip + } else { + for (int i = 0; i < getLandingLength(); i++) { + Coords pos = landingPos.translated(facing, i); + landingPositions.add(pos); + // Dropships have to check the front adjacent hexes + if (this instanceof Dropship) { + landingPositions.add(pos.translated((facing + 4) % 6)); + landingPositions.add(pos.translated((facing + 2) % 6)); + } + } + } + return landingPositions; + } + default void addLandingModifier(PilotingRollData roll, int mod, String reason, boolean isVertical) { if (isVertical) { // divide by 2, rounding up diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index 180c2e7ddba..076c2a404ab 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -4011,6 +4011,25 @@ private void attemptLanding(Entity entity, PilotingRollData roll) { } } + /** + * Any aerospace unit that lands in a rough or rubble hex takes landing hear damage. + * @param aero The landing unit + * @param vertical Whether the landing is vertical + * @param pos The coordinates of the hex of touchdown + * @param facing The facing of the landing unit + */ + private void checkForLandingGearDamage(IAero aero, boolean vertical, Coords pos, int facing) { + Set landingPositions = aero.getLandingCoords(vertical, pos, facing); + if (landingPositions.stream().map(c -> game.getBoard().getHex(c)) + .anyMatch(h -> h.containsTerrain(Terrains.ROUGH) || h.containsTerrain(Terrains.RUBBLE))) { + aero.setGearHit(true); + } + Report r = new Report(9125); + r.subject = ((Entity) aero).getId(); + addReport(r); + + } + private boolean launchUnit(Entity unloader, Targetable unloaded, Coords pos, int facing, int velocity, int altitude, int[] moveVec, int bonus) { @@ -6124,6 +6143,7 @@ private void processMovement(Entity entity, MovePath md, Map Date: Wed, 27 Sep 2023 17:06:13 -0500 Subject: [PATCH 4/8] Add a warning nag that landing in rough/rubble will damage landing gear. --- megamek/i18n/megamek/client/messages.properties | 1 + .../megamek/client/ui/swing/MovementDisplay.java | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/megamek/i18n/megamek/client/messages.properties b/megamek/i18n/megamek/client/messages.properties index 59c5254c1ba..dc2fa4a716f 100644 --- a/megamek/i18n/megamek/client/messages.properties +++ b/megamek/i18n/megamek/client/messages.properties @@ -2465,6 +2465,7 @@ MovementDisplay.ConfirmUnJamRACDlg.title=Unjam? MovementDisplay.ConfirmPilotingRoll=You must make the following piloting\nskill check(s) for your movement:\n MovementDisplay.ConfirmSprint=Are you sure you want to sprint? MovementDisplay.ConfirmSuperchargerRoll=The movement you have selected will require a roll of {0} or higher\nto avoid Supercharger failure. Do you wish to proceed? +MovementDisplay.ConfirmLandingGearDamage=Landing in a rough or rubble hex will damage the landing ger.\n\nAre you sure you want to land here? MovementDisplay.DFADialog.message=To Hit: {0} ({1}%) ({2})\nDamage to Target: {3} (in 5pt clusters){4}\nDamage to Self: {5} (in 5pt clusters) (using Kick table) MovementDisplay.DFADialog.title=D.F.A. {0}? MovementDisplay.Done=Done diff --git a/megamek/src/megamek/client/ui/swing/MovementDisplay.java b/megamek/src/megamek/client/ui/swing/MovementDisplay.java index be92d394ccd..7f2a2b1559d 100644 --- a/megamek/src/megamek/client/ui/swing/MovementDisplay.java +++ b/megamek/src/megamek/client/ui/swing/MovementDisplay.java @@ -1697,6 +1697,22 @@ && ce().getGame().getBoard().getHex(cmd.getFinalCoords()) } } + if (cmd.contains(MoveStepType.LAND) || cmd.contains(MoveStepType.VLAND)) { + Set landingPath = ((IAero) ce()).getLandingCoords(cmd.contains(MoveStepType.VLAND), + cmd.getFinalCoords(), cmd.getFinalFacing()); + if (landingPath.stream().map(c -> game().getBoard().getHex(c)) + .anyMatch(h -> h.containsTerrain(Terrains.ROUGH) || h.containsTerrain(Terrains.RUBBLE))) { + ConfirmDialog nag = new ConfirmDialog(clientgui.frame, + Messages.getString("MovementDisplay.areYourSure"), + Messages.getString("MovementDisplay.ConfirmLandingGearDamage"), + false); + nag.setVisible(true); + if (!nag.getAnswer()) { + return; + } + } + } + if (isUsingChaff) { addStepToMovePath(MoveStepType.CHAFF); isUsingChaff = false; From b658565271262cfc99ae3f75b82e3d58e651ad78 Mon Sep 17 00:00:00 2001 From: cwspain Date: Wed, 27 Sep 2023 17:15:06 -0500 Subject: [PATCH 5/8] Fix spurious landing gear damage message. --- megamek/src/megamek/server/GameManager.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index 076c2a404ab..f8d6c662230 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -4023,11 +4023,10 @@ private void checkForLandingGearDamage(IAero aero, boolean vertical, Coords pos, if (landingPositions.stream().map(c -> game.getBoard().getHex(c)) .anyMatch(h -> h.containsTerrain(Terrains.ROUGH) || h.containsTerrain(Terrains.RUBBLE))) { aero.setGearHit(true); + Report r = new Report(9125); + r.subject = ((Entity) aero).getId(); + addReport(r); } - Report r = new Report(9125); - r.subject = ((Entity) aero).getId(); - addReport(r); - } private boolean launchUnit(Entity unloader, Targetable unloaded, From a041fd247c3006877752a26c7f8f7571d902682b Mon Sep 17 00:00:00 2001 From: cwspain Date: Thu, 28 Sep 2023 14:55:08 -0500 Subject: [PATCH 6/8] Added landing modifiers for terrain from TacOps. --- megamek/src/megamek/common/IAero.java | 73 +++++++++--------------- megamek/src/megamek/common/Terrains.java | 35 ++++++++++++ 2 files changed, 61 insertions(+), 47 deletions(-) diff --git a/megamek/src/megamek/common/IAero.java b/megamek/src/megamek/common/IAero.java index e0b9d29f044..f9d981f22ff 100644 --- a/megamek/src/megamek/common/IAero.java +++ b/megamek/src/megamek/common/IAero.java @@ -15,11 +15,7 @@ package megamek.common; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.Vector; +import java.util.*; import megamek.common.MovePath.MoveStepType; import org.apache.logging.log4j.LogManager; @@ -477,48 +473,39 @@ default PilotingRollData checkLanding(EntityMovementType moveType, int velocity, roll.addModifier(+4, "no thrust"); } } - // terrain mods - boolean lightWoods = false; - boolean rough = false; - boolean heavyWoods = false; - boolean clear = false; - boolean paved = false; + // Per TW p. 87, the modifier is added once for each terrain type + Set> terrains = new HashSet<>(); Set landingPositions = getLandingCoords(isVertical, landingPos, face); - boolean isDropship = (this instanceof Dropship); - // Vertical landing just checks the landing hex - + // Any hex without terrain is clear, which is a +2 modifier. + boolean clear = false; for (Coords pos : landingPositions) { Hex hex = ((Entity) this).getGame().getBoard().getHex(pos); - if (hex.containsTerrain(Terrains.ROUGH) || hex.containsTerrain(Terrains.RUBBLE)) { - rough = true; - } else if (hex.containsTerrain(Terrains.WOODS, 2)) { - heavyWoods = true; - } else if (hex.containsTerrain(Terrains.WOODS, 1)) { - lightWoods = true; - } else if (hex.containsTerrain(Terrains.PAVEMENT) || hex.containsTerrain(Terrains.ROAD)) { - paved = true; - } else { - // Landing in other terrains isn't allowed, so if we reach here - // it must be a clear hex + if (hex.hasPavement()) { + continue; + } + if (hex.isClearHex()) { clear = true; + } else { + for (int terrain : hex.getTerrainTypes()) { + if ((terrain == Terrains.WATER) && hex.containsTerrain(Terrains.ICE)) { + continue; + } + if (Terrains.landingModifier(terrain, hex.terrainLevel(terrain)) > 0) { + terrains.add(List.of(terrain, hex.terrainLevel(terrain))); + } + } } } - - if (heavyWoods) { - addLandingModifier(roll, +5, "heavy woods in landing path", isVertical); - } - if (lightWoods) { - addLandingModifier(roll, +4, "light woods in landing path", isVertical); - } - if (rough) { - addLandingModifier(roll, +3, "rough/rubble in landing path", isVertical); - } - if (paved) { - addLandingModifier(roll, +0, "paved/road landing strip", isVertical); - } if (clear) { - addLandingModifier(roll, +2, "clear hex in landing path", isVertical); + roll.addModifier(isVertical ? 1 : 2, "Clear terrain in landing path"); + } + for (List terrain : terrains) { + int mod = Terrains.landingModifier(terrain.get(0), terrain.get(1)); + if (isVertical) { + mod = mod / 2 + mod % 2; + } + roll.addModifier(mod, Terrains.getDisplayName(terrain.get(0), terrain.get(1)) + " in landing path"); } return roll; @@ -549,14 +536,6 @@ default Set getLandingCoords(boolean isVertical, Coords landingPos, int return landingPositions; } - default void addLandingModifier(PilotingRollData roll, int mod, String reason, boolean isVertical) { - if (isVertical) { - // divide by 2, rounding up - mod = mod / 2 + mod % 2; - } - roll.addModifier(mod, reason); - } - /** * Checks if a maneuver requires a control roll */ diff --git a/megamek/src/megamek/common/Terrains.java b/megamek/src/megamek/common/Terrains.java index c3d0fd11020..5f0561b8b6b 100644 --- a/megamek/src/megamek/common/Terrains.java +++ b/megamek/src/megamek/common/Terrains.java @@ -500,4 +500,39 @@ public static int getTerrainElevation(int terrainType, int terrainLevel, boolean } } } + + /** + * Modifier to control roll when the terrain is in the landing path. + * @param terrainType Type of terrain + * @param terrainLevel The level of the terrain + * @return The control roll modifier + */ + public static int landingModifier(int terrainType, int terrainLevel) { + switch (terrainType) { + case WOODS: + case JUNGLE: + return (terrainLevel == 3) ? 7 : terrainLevel + 3; + case WATER: + return (terrainLevel > 0) ? 3 : 2; + case ROUGH: + return terrainLevel == 2 ? 5 : 3; + case RUBBLE: + return terrainLevel == 6 ? 5 : 3; + case SAND: + case TUNDRA: + case MAGMA: + case FIELDS: + case SWAMP: + return 2; + case BUILDING: + return terrainLevel + 1; + case SNOW: + return (terrainLevel == 2) ? 1 : 0; + case ICE: + case MUD: + return 1; + default: + return 0; + } + } } From 94737940682b14e092e84db982a536be1e136cdf Mon Sep 17 00:00:00 2001 From: cwspain Date: Sat, 30 Sep 2023 16:49:10 -0500 Subject: [PATCH 7/8] Aeros destroyed by landing in water of sufficient depth. --- .../megamek/common/report-messages.properties | 2 + .../client/ui/swing/MovementDisplay.java | 2 +- megamek/src/megamek/common/IAero.java | 10 ++++- megamek/src/megamek/server/GameManager.java | 37 ++++++++++++++----- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/megamek/i18n/megamek/common/report-messages.properties b/megamek/i18n/megamek/common/report-messages.properties index 3a347880ccd..1de020d1164 100755 --- a/megamek/i18n/megamek/common/report-messages.properties +++ b/megamek/i18n/megamek/common/report-messages.properties @@ -1182,6 +1182,8 @@ #crash related 9700= () crashes, suffering damage! 9701= () crashes off the map! +9702= () is destroyed by landing in water. +9703= () is immobilized by landing in water. 9705= () must roll a or lower to avoid damage, rolls a : 9706=hit by crash! 9707=avoids crash. diff --git a/megamek/src/megamek/client/ui/swing/MovementDisplay.java b/megamek/src/megamek/client/ui/swing/MovementDisplay.java index 7f2a2b1559d..1d047c751d0 100644 --- a/megamek/src/megamek/client/ui/swing/MovementDisplay.java +++ b/megamek/src/megamek/client/ui/swing/MovementDisplay.java @@ -1700,7 +1700,7 @@ && ce().getGame().getBoard().getHex(cmd.getFinalCoords()) if (cmd.contains(MoveStepType.LAND) || cmd.contains(MoveStepType.VLAND)) { Set landingPath = ((IAero) ce()).getLandingCoords(cmd.contains(MoveStepType.VLAND), cmd.getFinalCoords(), cmd.getFinalFacing()); - if (landingPath.stream().map(c -> game().getBoard().getHex(c)) + if (landingPath.stream().map(c -> game().getBoard().getHex(c)).filter(Objects::nonNull) .anyMatch(h -> h.containsTerrain(Terrains.ROUGH) || h.containsTerrain(Terrains.RUBBLE))) { ConfirmDialog nag = new ConfirmDialog(clientgui.frame, Messages.getString("MovementDisplay.areYourSure"), diff --git a/megamek/src/megamek/common/IAero.java b/megamek/src/megamek/common/IAero.java index f9d981f22ff..13179ec2995 100644 --- a/megamek/src/megamek/common/IAero.java +++ b/megamek/src/megamek/common/IAero.java @@ -481,7 +481,7 @@ default PilotingRollData checkLanding(EntityMovementType moveType, int velocity, boolean clear = false; for (Coords pos : landingPositions) { Hex hex = ((Entity) this).getGame().getBoard().getHex(pos); - if (hex.hasPavement()) { + if ((hex == null) || hex.hasPavement()) { continue; } if (hex.isClearHex()) { @@ -730,6 +730,14 @@ default String hasRoomForVerticalLanding() { if (!hex.isClearForLanding()) { return "Unacceptable terrain for landing"; } + // Aerospace units are destroyed by water landings except for those that have flotation hulls. + // LAMs are not. + if (hex.containsTerrain(Terrains.WATER) && !hex.containsTerrain(Terrains.ICE) + && (hex.terrainLevel(Terrains.WATER) > 0) + && (this instanceof Aero) + && !((Entity) this).hasWorkingMisc(MiscType.F_FLOTATION_HULL)) { + return "cannot land on water"; + } return null; } diff --git a/megamek/src/megamek/server/GameManager.java b/megamek/src/megamek/server/GameManager.java index f8d6c662230..42766a0e5c0 100644 --- a/megamek/src/megamek/server/GameManager.java +++ b/megamek/src/megamek/server/GameManager.java @@ -4013,20 +4013,36 @@ private void attemptLanding(Entity entity, PilotingRollData roll) { /** * Any aerospace unit that lands in a rough or rubble hex takes landing hear damage. - * @param aero The landing unit - * @param vertical Whether the landing is vertical - * @param pos The coordinates of the hex of touchdown - * @param facing The facing of the landing unit - */ - private void checkForLandingGearDamage(IAero aero, boolean vertical, Coords pos, int facing) { - Set landingPositions = aero.getLandingCoords(vertical, pos, facing); - if (landingPositions.stream().map(c -> game.getBoard().getHex(c)) + * @param aero The landing unit + * @param vertical Whether the landing is vertical + * @param touchdownPos The coordinates of the hex of touchdown + * @param finalPos The coordinates of the hex in which the unit comes to a stop + * @param facing The facing of the landing unit + * @ + */ + private void checkLandingTerrainEffects(IAero aero, boolean vertical, Coords touchdownPos, Coords finalPos, int facing) { + // Landing in a rough for rubble hex damages landing gear. + Set landingPositions = aero.getLandingCoords(vertical, touchdownPos, facing); + if (landingPositions.stream().map(c -> game.getBoard().getHex(c)).filter(Objects::nonNull) .anyMatch(h -> h.containsTerrain(Terrains.ROUGH) || h.containsTerrain(Terrains.RUBBLE))) { aero.setGearHit(true); Report r = new Report(9125); r.subject = ((Entity) aero).getId(); addReport(r); } + // Landing in water can destroy or immobilize the unit. + Hex hex = game.getBoard().getHex(finalPos); + if ((aero instanceof Aero) && hex.containsTerrain(Terrains.WATER) && !hex.containsTerrain(Terrains.ICE) + && (hex.terrainLevel(Terrains.WATER) > 0) + && !((Entity) aero).hasWorkingMisc(MiscType.F_FLOTATION_HULL)) { + if ((hex.terrainLevel(Terrains.WATER) > 1) || !(aero instanceof Dropship)) { + Report r = new Report(9702); + r.subject(((Entity) aero).getId()); + r.addDesc((Entity) aero); + addReport(r); + addReport(destroyEntity((Entity) aero, "landing in deep water")); + } + } } private boolean launchUnit(Entity unloader, Targetable unloaded, @@ -6142,7 +6158,8 @@ private void processMovement(Entity entity, MovePath md, Map Date: Sat, 30 Sep 2023 17:50:23 -0500 Subject: [PATCH 8/8] Dropships in depth 1 water (center hex) are immobile. --- megamek/src/megamek/common/Dropship.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/megamek/src/megamek/common/Dropship.java b/megamek/src/megamek/common/Dropship.java index 36b3b8bbb31..c04f709f116 100644 --- a/megamek/src/megamek/common/Dropship.java +++ b/megamek/src/megamek/common/Dropship.java @@ -474,6 +474,18 @@ public int height() { return 4; } + @Override + public int getWalkMP(MPCalculationSetting mpCalculationSetting) { + // A grounded dropship with the center hex in level 1 water is immobile. + if ((game != null) && !game.getBoard().inSpace() && !isAirborne()) { + Hex hex = game.getBoard().getHex(getPosition()); + if ((hex != null) && (hex.containsTerrain(Terrains.WATER, 1) && !hex.containsTerrain(Terrains.ICE))) { + return 0; + } + } + return super.getWalkMP(mpCalculationSetting); + } + /* * (non-Javadoc) *