Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aerospace landing fixes #4813

Merged
merged 8 commits into from
Oct 3, 2023
1 change: 1 addition & 0 deletions megamek/i18n/megamek/client/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
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 @@ -4011,6 +4011,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 @@ -6124,6 +6158,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 @@ -6141,6 +6177,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
Loading