diff --git a/megamek/i18n/megamek/common/options/messages.properties b/megamek/i18n/megamek/common/options/messages.properties index a1af2eaf8d..78fd3cd33c 100644 --- a/megamek/i18n/megamek/common/options/messages.properties +++ b/megamek/i18n/megamek/common/options/messages.properties @@ -149,6 +149,8 @@ GameOptionsInfo.option.hidden_units.displayableName=Hidden Units GameOptionsInfo.option.hidden_units.description=If checked, players may deploy units hidden. GameOptionsInfo.option.black_ice.displayableName=Black Ice GameOptionsInfo.option.black_ice.description=If checked, Black Ice may form on pavement if temperatures are below -30C. TO:AR p38\nDoes not affect Black Ice forming due to Ice Storms. TO:AR p58 +GameOptionsInfo.option.lightning_storm_targets_units.displayableName=Lightning Storm targets units +GameOptionsInfo.option.lightning_storm_targets_units.description=If unchecked, targets random hex on the board GameOptionsInfo.option.double_blind.displayableName=TacOps Double blind GameOptionsInfo.option.double_blind.description=If checked, enemy units will only be visible if they are in line of sight of one or more of your units, or within sensor range. \nUnchecked by default. GameOptionsInfo.option.single_blind_bots.displayableName=(Unofficial) Default bots to single blind, when using TacOps Double blind diff --git a/megamek/i18n/megamek/common/report-messages.properties b/megamek/i18n/megamek/common/report-messages.properties index 1f06e185f1..d9a11ddd3d 100755 --- a/megamek/i18n/megamek/common/report-messages.properties +++ b/megamek/i18n/megamek/common/report-messages.properties @@ -754,6 +754,9 @@ 5606=player must choose bombs to be destroyed. 5607=bot loses bombs. 5608= () Internal Bomb Bay + Cargo critical: Damage exceeds remaining bombs; all bombs destroyed. +5620=Damage from lightning storm------------------- +5621=lightning strike in hex +5622= and adjacent hex #6000's -- Damage Related 6005=\ no effect. diff --git a/megamek/src/megamek/common/Sensor.java b/megamek/src/megamek/common/Sensor.java index 69084eec96..69ed8d8e1b 100644 --- a/megamek/src/megamek/common/Sensor.java +++ b/megamek/src/megamek/common/Sensor.java @@ -180,12 +180,15 @@ public int adjustRange(int range, Game game, LosEffects los) { return 0; } + //TO:AR 6th ed. p 190 if ((type != TYPE_MEK_SEISMIC) && (type != TYPE_VEE_SEISMIC)) { PlanetaryConditions conditions = game.getPlanetaryConditions(); - if (conditions.getEMI().isEMI()) { + if (conditions.isEMI()) { range -= 4; } - // TODO: add lightning + if (conditions.getWeather().isLightningStorm()) { + range -= 1; + } } if ((type == TYPE_MEK_RADAR) || (type == TYPE_VEE_RADAR) diff --git a/megamek/src/megamek/common/actions/WeaponAttackAction.java b/megamek/src/megamek/common/actions/WeaponAttackAction.java index ca42769604..53d7fb3b13 100644 --- a/megamek/src/megamek/common/actions/WeaponAttackAction.java +++ b/megamek/src/megamek/common/actions/WeaponAttackAction.java @@ -5560,7 +5560,7 @@ public static ToHitData processAttackerSPAs(ToHitData toHit, Entity ae, Targetab toHit.addModifier(-1, Messages.getString("WeaponAttackAction.RainSpec")); } - if (conditions.getWeather().isModerateRainOrHeavyRainOrGustingRainOrDownpour()) { + if (conditions.getWeather().isModerateRainOrHeavyRainOrGustingRainOrDownpourOrLightningStorm()) { toHit.addModifier(-1, Messages.getString("WeaponAttackAction.RainSpec")); } } diff --git a/megamek/src/megamek/common/options/GameOptions.java b/megamek/src/megamek/common/options/GameOptions.java index 1b65d8a301..77fae48cbd 100755 --- a/megamek/src/megamek/common/options/GameOptions.java +++ b/megamek/src/megamek/common/options/GameOptions.java @@ -98,6 +98,7 @@ public synchronized void initialize() { addOption(advancedRules, OptionsConstants.ADVANCED_MINEFIELDS, false); addOption(advancedRules, OptionsConstants.ADVANCED_HIDDEN_UNITS, true); addOption(advancedRules, OptionsConstants.ADVANCED_BLACK_ICE, false); + addOption(advancedRules, OptionsConstants.ADVANCED_LIGHTNING_STORM_TARGETS_UNITS, false); addOption(advancedRules, OptionsConstants.ADVANCED_DOUBLE_BLIND, false); addOption(advancedRules, OptionsConstants.ADVANCED_TACOPS_SENSORS, false); addOption(advancedRules, OptionsConstants.ADVANCED_SUPRESS_ALL_DB_MESSAGES, false); diff --git a/megamek/src/megamek/common/options/OptionsConstants.java b/megamek/src/megamek/common/options/OptionsConstants.java index c69f26f322..0a46c5d48e 100644 --- a/megamek/src/megamek/common/options/OptionsConstants.java +++ b/megamek/src/megamek/common/options/OptionsConstants.java @@ -333,6 +333,7 @@ public class OptionsConstants { public static final String ADVANCED_MINEFIELDS = "minefields"; public static final String ADVANCED_HIDDEN_UNITS = "hidden_units"; public static final String ADVANCED_BLACK_ICE= "black_ice"; + public static final String ADVANCED_LIGHTNING_STORM_TARGETS_UNITS= "lightning_storm_targets_units"; public static final String ADVANCED_DOUBLE_BLIND = "double_blind"; public static final String ADVANCED_SINGLE_BLIND_BOTS = "single_blind_bots"; public static final String ADVANCED_TACOPS_SENSORS = "tacops_sensors"; diff --git a/megamek/src/megamek/common/planetaryconditions/PlanetaryConditions.java b/megamek/src/megamek/common/planetaryconditions/PlanetaryConditions.java index 9367f0552c..7e043ebd45 100644 --- a/megamek/src/megamek/common/planetaryconditions/PlanetaryConditions.java +++ b/megamek/src/megamek/common/planetaryconditions/PlanetaryConditions.java @@ -281,7 +281,7 @@ public int getWeatherHitPenalty(Entity en) { if (getWeather().isLightRainOrLightSnow() && en.isConventionalInfantry()) { return 1; - } else if (getWeather().isModerateRainOrHeavyRainOrGustingRainOrModerateSnowOrSnowFlurriesOrHeavySnowOrSleet()) { + } else if (getWeather().isModerateRainOrHeavyRainOrGustingRainOrModerateSnowOrSnowFlurriesOrHeavySnowOrSleetOrLightningStorm()) { return 1; } else if(getWeather().isDownpour()) { return 2; @@ -403,7 +403,7 @@ && getWind().isStrongerThan(Wind.STORM)) { public int getIgniteModifiers() { int mod = 0; - if (getWeather().isLightRainOrModerateRain() ) { + if (getWeather().isLightRainOrModerateRainOrLightningStorm() ) { mod += 1; } @@ -449,6 +449,7 @@ public boolean putOutFire() { case MOD_RAIN: case MOD_SNOW: case SNOW_FLURRIES: + case LIGHTNING_STORM: roll = roll + 2; break; case HEAVY_RAIN: @@ -752,7 +753,7 @@ && getWind().isStrongerThan(Wind.LIGHT_GALE)) { } else { otherRange = 8; } - } else if (getWeather().isModerateRainOrModerateSnow()) { + } else if (getWeather().isModerateRainOrModerateSnowOrLightningStorm()) { if (isMekOrVee || isLowAltitudeAero) { otherRange = 20; } else if (isAero) { @@ -885,6 +886,7 @@ public static Wind setWindFromWeather(Weather weather, Wind wind) { switch (weather) { case ICE_STORM: case SNOW_FLURRIES: + case LIGHTNING_STORM: return Wind.MOD_GALE; case GUSTING_RAIN: return Wind.STRONG_GALE; diff --git a/megamek/src/megamek/common/planetaryconditions/Weather.java b/megamek/src/megamek/common/planetaryconditions/Weather.java index 53c7e44477..7036aa7084 100644 --- a/megamek/src/megamek/common/planetaryconditions/Weather.java +++ b/megamek/src/megamek/common/planetaryconditions/Weather.java @@ -143,9 +143,10 @@ public boolean isHeavyRainOrGustingRain() { } - public boolean isLightRainOrModerateRain() { + public boolean isLightRainOrModerateRainOrLightningStorm() { return isLightRain() - || isModerateRain(); + || isModerateRain() + || isLightningStorm(); } public boolean isModerateSnowOrSnowFlurries() { @@ -153,15 +154,22 @@ public boolean isModerateSnowOrSnowFlurries() { || isSnowFlurries(); } - public boolean isModerateRainOrModerateSnow() { + public boolean isModerateRainOrLightningStorm() { + return isModerateRain() + || isLightningStorm(); + } + + public boolean isModerateRainOrModerateSnowOrLightningStorm() { return isModerateRain() - || isModerateSnow(); + || isModerateSnow() + || isLightningStorm(); } - public boolean isDownpourOrHeavySnowOrIceStorm() { + public boolean isDownpourOrHeavySnowOrIceStormOrLightningStorm() { return isDownpour() || isHeavySnow() - || isIceStorm(); + || isIceStorm() + || isLightningStorm(); } public boolean isSnowFlurriesOrSleetOrIceStorm() { @@ -220,11 +228,12 @@ public boolean isModerateSnowOrHeavySnowOrSnowFlurriesOrSleet() { || isSleet(); } - public boolean isModerateRainOrHeavyRainOrGustingRainOrDownpour() { + public boolean isModerateRainOrHeavyRainOrGustingRainOrDownpourOrLightningStorm() { return isModerateRain() || isHeavyRain() || isGustingRain() - || isDownpour(); + || isDownpour() + || isLightningStorm(); } public boolean isGustingRainOrSnowFlurriesOrIceStormOrLightningStorm() { @@ -260,14 +269,15 @@ public boolean isHeavyRainOrGustingRainOrDownpourOrLightSnowOrModerateSnowOrSnow || isSnowFlurries(); } - public boolean isModerateRainOrHeavyRainOrGustingRainOrModerateSnowOrSnowFlurriesOrHeavySnowOrSleet() { + public boolean isModerateRainOrHeavyRainOrGustingRainOrModerateSnowOrSnowFlurriesOrHeavySnowOrSleetOrLightningStorm() { return isModerateRain() || isHeavyRain() || isGustingRain() || isModerateSnow() || isSnowFlurries() || isHeavySnow() - || isSleet(); + || isSleet() + || isLightningStorm(); } public boolean isAnyRain() { diff --git a/megamek/src/megamek/common/util/BoardUtilities.java b/megamek/src/megamek/common/util/BoardUtilities.java index 315c25394b..b8adc6fa9c 100644 --- a/megamek/src/megamek/common/util/BoardUtilities.java +++ b/megamek/src/megamek/common/util/BoardUtilities.java @@ -1122,7 +1122,7 @@ public static void addWeatherConditions(Board board, Weather weatherCond, Wind w Hex hex = board.getHex(c); //moderate rain - mud in clear hexes, depth 0 water, and dirt roads (not implemented yet) - if (weatherCond.isModerateRain()) { + if (weatherCond.isModerateRainOrLightningStorm()) { if ((hex.terrainsPresent() == 0) || (hex.containsTerrain(Terrains.WATER) && (hex.depth() == 0))) { hex.addTerrain(new Terrain(Terrains.MUD, 1)); if (hex.containsTerrain(Terrains.WATER)) { diff --git a/megamek/src/megamek/server/totalwarfare/TWGameManager.java b/megamek/src/megamek/server/totalwarfare/TWGameManager.java index 1e6a227040..bce0f09ebb 100644 --- a/megamek/src/megamek/server/totalwarfare/TWGameManager.java +++ b/megamek/src/megamek/server/totalwarfare/TWGameManager.java @@ -14948,6 +14948,16 @@ private PilotingRollData getKickPushPSR(Entity psrEntity, Entity attacker, return psr; } + void resolveWeather() { + PlanetaryConditions conditions = game.getPlanetaryConditions(); + if (conditions.isBlowingSandActive()) { + addReport(resolveBlowingSandDamage()); + } + if (conditions.getWeather().isLightningStorm()) { + addReport(resolveLightningStormDamage()); + } + } + /** * Each mek sinks the amount of heat appropriate to its current heat * capacity. @@ -29704,6 +29714,7 @@ public static PilotingRollData getEjectModifiers(Game game, Entity entity, int c } Hex targetHex = game.getBoard().getHex(targetCoords); // Terrain modifiers should only apply if the unit is on the ground... + // TO:AR 6th ed p165 if (!entity.isSpaceborne() && !entity.isAirborne()) { if (targetHex != null) { if ((targetHex.terrainLevel(Terrains.WATER) > 0) @@ -29741,6 +29752,7 @@ public static PilotingRollData getEjectModifiers(Game game, Entity entity, int c // battle, but it shouldn't // That's a fix for another day, probably when I get around to space terrain and // 'weather' + // TO:AR 6th ed p165 if (conditions.getGravity() == 0) { rollTarget.addModifier(3, "Zero-G"); } else if (conditions.getGravity() < 0.8) { @@ -29752,6 +29764,7 @@ public static PilotingRollData getEjectModifiers(Game game, Entity entity, int c // Vacuum shouldn't apply to ASF ejection since they're designed for it, but the // rules don't specify // High and low pressures make more sense to apply to all + // TO:AR 6th ed p165 if (conditions.getAtmosphere().isVacuum()) { rollTarget.addModifier(3, "Vacuum"); } else if (conditions.getAtmosphere().isVeryHigh()) { @@ -29761,11 +29774,13 @@ public static PilotingRollData getEjectModifiers(Game game, Entity entity, int c } } - if (conditions.getWeather().isDownpourOrHeavySnowOrIceStorm() + // TO:AR 6th ed p165 + if (conditions.getWeather().isDownpourOrHeavySnowOrIceStormOrLightningStorm() || conditions.getWind().isStrongGale()) { rollTarget.addModifier(2, "Bad Weather"); } + // TO:AR 6th ed p165 if (conditions.getWind().isStrongerThan(Wind.STRONG_GALE) || (conditions.getWeather().isHeavySnow() && conditions.getWind().isStrongGale())) { rollTarget.addModifier(3, "Really Bad Weather"); @@ -31253,6 +31268,102 @@ public List getSmokeCloudList() { return game.getSmokeCloudList(); } + /** + * Check to see if Lightning Storm caused damage + * TO:AR 6th ed. p. 57 + * */ + private Vector resolveLightningStormDamage() { + Vector vFullReport = new Vector<>(); + Roll rollStrike = Compute.rollD6(1); + + if (rollStrike.getIntValue() > 4) { + Report.addNewline(vFullReport); + vFullReport.add(new Report(5620, Report.PUBLIC)); + + Roll rollNumber = Compute.rollD6(1); + int numberOfStrikes = Math.max(1, rollNumber.getIntValue() / 2); + + for (int i = 0; i < numberOfStrikes; i++) { + Roll rollType = Compute.rollD6(1); + int damage; + switch (rollType.getIntValue()) { + case 1: + case 2: + case 3: + damage = 5; + break; + case 4: + case 5: + damage = 10; + break; + default: + damage = 15; + } + + Coords coords; + + if (game.getOptions().booleanOption(OptionsConstants.ADVANCED_LIGHTNING_STORM_TARGETS_UNITS)) { + List entities = game.getEntitiesVector().stream() + .filter(e -> e.getPosition() != null) + .toList(); + int index = Compute.randomInt(entities.size()); + coords = entities.get(index).getPosition(); + } else { + int x = Compute.randomInt(game.getBoard().getWidth()); + int y = Compute.randomInt(game.getBoard().getHeight()); + coords = new Coords(x, y); + } + + Report r; + r = new Report(5621); + r.add(coords.getBoardNum()); + vFullReport.add(r); + + vFullReport.addAll(lightningStormDamage(coords, damage)); + + if (rollType.getIntValue() == 6) { + for (Coords locationAdjacent : coords.allAdjacent()) { + r = new Report(5622); + r.add(locationAdjacent.getBoardNum()); + vFullReport.add(r); + + vFullReport.addAll(lightningStormDamage(locationAdjacent, 5)); + } + } + } + } + + return vFullReport; + } + + private Vector lightningStormDamage(Coords coords, int damage) { + Vector vFullReport = new Vector<>(); + Vector newReports = tryClearHex(coords, damage, Entity.NONE); + vFullReport.addAll(newReports); + + Building bldg = game.getBoard().getBuildingAt(coords); + + if (bldg != null) { + Vector buildingReport = damageBuilding(bldg, damage, coords); + vFullReport.addAll(buildingReport); + } + + List hitEntities = game.getEntitiesVector().stream() + .filter(e -> coords.equals(e.getPosition()) + && !(e instanceof GunEmplacement)) + .toList(); + + for (Entity entity : hitEntities) { + ToHitData toHit = new ToHitData(); + toHit.setSideTable(ToHitData.SIDE_RANDOM); + HitData hit = entity.rollHitLocation(ToHitData.HIT_NORMAL, toHit.getSideTable()); + Vector entityReport = damageEntity(entity, hit, damage); + vFullReport.addAll(entityReport); + } + + return vFullReport; + } + /** * Check to see if blowing sand caused damage to airborne VTOL/WIGEs */ diff --git a/megamek/src/megamek/server/totalwarfare/TWPhasePreparationManager.java b/megamek/src/megamek/server/totalwarfare/TWPhasePreparationManager.java index 1c57393826..1d31708f8e 100644 --- a/megamek/src/megamek/server/totalwarfare/TWPhasePreparationManager.java +++ b/megamek/src/megamek/server/totalwarfare/TWPhasePreparationManager.java @@ -178,10 +178,7 @@ void managePhase() { gameManager.resetEntityPhase(phase); gameManager.clearReports(); gameManager.resolveHeat(); - PlanetaryConditions conditions = gameManager.getGame().getPlanetaryConditions(); - if (conditions.isBlowingSandActive()) { - gameManager.addReport(gameManager.resolveBlowingSandDamage()); - } + gameManager.resolveWeather(); gameManager.addReport(gameManager.resolveControlRolls()); gameManager.addReport(gameManager.checkForTraitors()); // write End Phase header