Skip to content

Commit

Permalink
Merge pull request #4813 from MegaMek/aero_landing
Browse files Browse the repository at this point in the history
Aerospace landing fixes
  • Loading branch information
SJuliez authored Oct 3, 2023
2 parents 516a8d8 + 8f85486 commit 8ec42e4
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 52 deletions.
1 change: 1 addition & 0 deletions megamek/i18n/megamek/client/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2466,6 +2466,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
Expand Down
2 changes: 2 additions & 0 deletions megamek/i18n/megamek/common/report-messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,8 @@
#crash related
9700=<newline><font color='C00000'><data> (<data>) crashes, suffering <data> damage!</font>
9701=<newline><font color='C00000'><data> (<data>) crashes off the map!</font>
9702=<data> (<data>) is destroyed by landing in water.
9703=<data> (<data>) is immobilized by landing in water.
9705=<newline><data> (<data>) must roll a <data> or lower to avoid <data> damage, rolls a <data>: <msg:9706,9707>
9706=<font color='C00000'>hit by crash!</font>
9707=avoids crash.
Expand Down
16 changes: 16 additions & 0 deletions megamek/src/megamek/client/ui/swing/MovementDisplay.java
Original file line number Diff line number Diff line change
Expand Up @@ -1697,6 +1697,22 @@ && ce().getGame().getBoard().getHex(cmd.getFinalCoords())
}
}

if (cmd.contains(MoveStepType.LAND) || cmd.contains(MoveStepType.VLAND)) {
Set<Coords> landingPath = ((IAero) ce()).getLandingCoords(cmd.contains(MoveStepType.VLAND),
cmd.getFinalCoords(), cmd.getFinalFacing());
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"),
Messages.getString("MovementDisplay.ConfirmLandingGearDamage"),
false);
nag.setVisible(true);
if (!nag.getAnswer()) {
return;
}
}
}

if (isUsingChaff) {
addStepToMovePath(MoveStepType.CHAFF);
isUsingChaff = false;
Expand Down
12 changes: 12 additions & 0 deletions megamek/src/megamek/common/Dropship.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
*
Expand Down
105 changes: 53 additions & 52 deletions megamek/src/megamek/common/IAero.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -477,70 +473,67 @@ default PilotingRollData checkLanding(EntityMovementType moveType, int velocity,
roll.addModifier(+4, "no thrust");
}
}
// terrain mods
boolean lightWoods = false;
boolean rough = false;
boolean heavyWoods = false;

// Per TW p. 87, the modifier is added once for each terrain type
Set<List<Integer>> terrains = new HashSet<>();
Set<Coords> landingPositions = getLandingCoords(isVertical, landingPos, face);
// Any hex without terrain is clear, which is a +2 modifier.
boolean clear = false;
boolean paved = true;
for (Coords pos : landingPositions) {
Hex hex = ((Entity) this).getGame().getBoard().getHex(pos);
if ((hex == null) || 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 (clear) {
roll.addModifier(isVertical ? 1 : 2, "Clear terrain in landing path");
}
for (List<Integer> 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");
}

Set<Coords> landingPositions = new HashSet<>();
boolean isDropship = (this instanceof Dropship);
// Vertical landing just checks the landing hex
return roll;
}

default Set<Coords> getLandingCoords(boolean isVertical, Coords landingPos, int facing) {
Set<Coords> landingPositions = new HashSet<Coords>();
if (isVertical) {
landingPositions.add(landingPos);
// Dropships must also check the adjacent 6 hexes
if (isDropship) {
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(face, i);
Coords pos = landingPos.translated(facing, 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));
if (this instanceof Dropship) {
landingPositions.add(pos.translated((facing + 4) % 6));
landingPositions.add(pos.translated((facing + 2) % 6));
}
}
}

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 = false;
// Landing in other terrains isn't allowed, so if we reach here
// it must be a clear hex
clear = true;
}
}

if (heavyWoods) {
roll.addModifier(+5, "heavy woods in landing path");
}
if (lightWoods) {
roll.addModifier(+4, "light woods in landing path");
}
if (rough) {
roll.addModifier(+3, "rough/rubble in landing path");
}
if (paved) {
roll.addModifier(+0, "paved/road landing strip");
}
if (clear) {
roll.addModifier(+2, "clear hex in landing path");
}

return roll;
return landingPositions;
}

/**
Expand Down Expand Up @@ -737,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;
}
Expand Down
35 changes: 35 additions & 0 deletions megamek/src/megamek/common/Terrains.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
37 changes: 37 additions & 0 deletions megamek/src/megamek/server/GameManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -4033,6 +4033,40 @@ 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 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<Coords> 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,
Coords pos, int facing, int velocity, int altitude, int[] moveVec,
int bonus) {
Expand Down Expand Up @@ -6146,6 +6180,8 @@ private void processMovement(Entity entity, MovePath md, Map<EntityTargetPair,
rollTarget = a.checkLanding(md.getLastStepMovementType(), md.getFinalVelocity(),
md.getFinalCoords(), md.getFinalFacing(), false);
attemptLanding(entity, rollTarget);
checkLandingTerrainEffects(a, true, md.getFinalCoords(),
md.getFinalCoords().translated(md.getFinalFacing(), a.getLandingLength()), md.getFinalFacing());
a.land();
entity.setPosition(md.getFinalCoords().translated(md.getFinalFacing(),
a.getLandingLength()));
Expand All @@ -6163,6 +6199,7 @@ private void processMovement(Entity entity, MovePath md, Map<EntityTargetPair,
if (entity instanceof Dropship) {
applyDropShipLandingDamage(md.getFinalCoords(), (Dropship) a);
}
checkLandingTerrainEffects(a, true, md.getFinalCoords(), md.getFinalCoords(), md.getFinalFacing());
a.land();
entity.setPosition(md.getFinalCoords());
entity.setDone(true);
Expand Down

0 comments on commit 8ec42e4

Please sign in to comment.