diff --git a/megamek/i18n/megamek/client/messages.properties b/megamek/i18n/megamek/client/messages.properties index 84f059eb3d4..b71c76573e6 100644 --- a/megamek/i18n/megamek/client/messages.properties +++ b/megamek/i18n/megamek/client/messages.properties @@ -4823,4 +4823,8 @@ Bot.commands.herding=Herding Bot.commands.aggression=Aggression Bot.commands.bravery=Bravery Bot.commands.avoid=Self-Preservation -Bot.commands.caution=Piloting Caution \ No newline at end of file +Bot.commands.caution=Piloting Caution + +#### TacOps movement and damage descriptions +TacOps.leaping.leg_damage=leaping (leg damage) +TacOps.leaping.fall_damage=leaping (fall) diff --git a/megamek/src/megamek/client/bot/princess/BasicPathRanker.java b/megamek/src/megamek/client/bot/princess/BasicPathRanker.java index 4c3d1c4e3c3..4debedbdc36 100644 --- a/megamek/src/megamek/client/bot/princess/BasicPathRanker.java +++ b/megamek/src/megamek/client/bot/princess/BasicPathRanker.java @@ -503,13 +503,14 @@ protected RankedPath rankPath(MovePath path, Game game, int maxRange, double fal double successProbability = getMovePathSuccessProbability(pathCopy, formula); double utility = -calculateFallMod(successProbability, formula); + // Worry about how badly we can damage ourselves on this path! + double expectedDamageTaken = calculateMovePathPSRDamage(movingUnit, pathCopy, formula); + expectedDamageTaken += checkPathForHazards(pathCopy, movingUnit, game); + expectedDamageTaken += MinefieldUtil.checkPathForMinefieldHazards(pathCopy); + // look at all of my enemies FiringPhysicalDamage damageEstimate = new FiringPhysicalDamage(); - double expectedDamageTaken = checkPathForHazards(pathCopy, movingUnit, game); - - expectedDamageTaken += MinefieldUtil.checkPathForMinefieldHazards(pathCopy); - boolean extremeRange = game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_RANGE); boolean losRange = game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_LOS_RANGE); for (Entity enemy : enemies) { @@ -589,7 +590,7 @@ protected RankedPath rankPath(MovePath path, Game game, int maxRange, double fal // firing position (successProbability), how much damage I can do // (weighted by bravery), less the damage I might take. double braveryValue = getOwner().getBehaviorSettings().getBraveryValue(); - double braveryMod = successProbability * ((maximumDamageDone * braveryValue) - expectedDamageTaken); + double braveryMod = (successProbability * (maximumDamageDone * braveryValue)) - expectedDamageTaken; formula.append(" + braveryMod [") .append(LOG_DECIMAL.format(braveryMod)).append(" = ") .append(LOG_PERCENT.format(successProbability)) diff --git a/megamek/src/megamek/client/bot/princess/PathEnumerator.java b/megamek/src/megamek/client/bot/princess/PathEnumerator.java index 3acd5bd9e6a..54b18a23917 100644 --- a/megamek/src/megamek/client/bot/princess/PathEnumerator.java +++ b/megamek/src/megamek/client/bot/princess/PathEnumerator.java @@ -548,4 +548,40 @@ private boolean worryAboutBridges() { return mapHasBridges.get(); } + + /** + * Find paths with a similar direction and step count to the provided path, within the selected unit's + * already-computed unit paths. + * @param moverId + * @param prunedPath + * @return + */ + protected List getSimilarUnitPaths(int moverId, BulldozerMovePath prunedPath) { + int mpDelta = 2; + int distanceDelta = 2; + + List paths = new ArrayList<>(); + if (!getUnitPaths().containsKey(moverId)) { + return paths; + } + List unitPaths = getUnitPaths().get(moverId); + + Coords target = prunedPath.getDestination(); + int prunedDistance = target.distance(prunedPath.getFinalCoords()); + + for (MovePath movePath: unitPaths) { + // We want unit paths that use similar amounts of MP to get similarly close to the BMP's destination + if (Math.abs((target.distance(movePath.getFinalCoords()) - prunedDistance)) > distanceDelta) { + continue; + } + + if (Math.abs(movePath.getMpUsed() - prunedPath.getMpUsed()) > mpDelta ) { + continue; + } + + paths.add(movePath); + } + + return paths; + } } diff --git a/megamek/src/megamek/client/bot/princess/PathRanker.java b/megamek/src/megamek/client/bot/princess/PathRanker.java index 8eda61b67a0..bc3c8062378 100644 --- a/megamek/src/megamek/client/bot/princess/PathRanker.java +++ b/megamek/src/megamek/client/bot/princess/PathRanker.java @@ -28,6 +28,7 @@ import java.util.Enumeration; import java.util.List; +import megamek.client.ui.Messages; import org.apache.logging.log4j.Level; import megamek.client.bot.princess.UnitBehavior.BehaviorType; @@ -38,6 +39,9 @@ import megamek.common.options.OptionsConstants; import megamek.logging.MMLogger; +import static megamek.client.ui.SharedUtility.predictLeapDamage; +import static megamek.client.ui.SharedUtility.predictLeapFallDamage; + public abstract class PathRanker implements IPathRanker { private final static MMLogger logger = MMLogger.create(PathRanker.class); @@ -372,6 +376,38 @@ protected double getMovePathSuccessProbability(MovePath movePath, StringBuilder return successProbability; } + /** + * Estimates the most expected damage that a path could cause, given the pilot skill of the path ranker + * and various conditions. + * + * XXX Sleet01: add fall pilot damage, skid damage, and low-gravity overspeed damage calcs + * + * @param movingEntity + * @param path + * @param msg + * @return + */ + protected double calculateMovePathPSRDamage(Entity movingEntity, MovePath path, StringBuilder msg) { + double damage = 0.0; + + List pilotingRolls = getPSRList(path); + for (TargetRoll roll : pilotingRolls) { + // Have to use if/else as switch/case won't allow runtime loading of strings without SDK 17 LTS support + String description = roll.getLastPlainDesc().toLowerCase(); + if ( + description.contains(Messages.getString("TacOps.leaping.leg_damage")) + ) { + damage += predictLeapDamage(movingEntity, roll, msg); + } else if ( + description.contains(Messages.getString("TacOps.leaping.fall_damage")) + ) { + damage += predictLeapFallDamage(movingEntity, roll, msg); + } + } + + return damage; + } + protected List getPSRList(MovePath path) { return SharedUtility.getPSRList(path); } diff --git a/megamek/src/megamek/client/bot/princess/Princess.java b/megamek/src/megamek/client/bot/princess/Princess.java index bf4d3b86fa7..68f26861ba8 100644 --- a/megamek/src/megamek/client/bot/princess/Princess.java +++ b/megamek/src/megamek/client/bot/princess/Princess.java @@ -2490,6 +2490,8 @@ public List getMovePathsAndSetNecessaryTargets(Entity mover, boolean f // also return some paths that go a little slower than max speed // in case the faster path would force an unwanted PSR or MASC check prunedPaths.addAll(PathDecorator.decoratePath(prunedPath)); + // Return some of the already-computed unit paths as well. + prunedPaths.addAll(getPrecognition().getPathEnumerator().getSimilarUnitPaths(mover.getId(), prunedPath)); } return prunedPaths; } diff --git a/megamek/src/megamek/client/ui/SharedUtility.java b/megamek/src/megamek/client/ui/SharedUtility.java index 081fa6b1d7e..a1aa6ad3b32 100644 --- a/megamek/src/megamek/client/ui/SharedUtility.java +++ b/megamek/src/megamek/client/ui/SharedUtility.java @@ -25,8 +25,12 @@ import megamek.common.options.OptionsConstants; import megamek.server.totalwarfare.TWGameManager; +import static java.util.List.of; + public class SharedUtility { + private static int CRIT_VALUE = 100; + public static String doPSRCheck(MovePath md) { return (String) doPSRCheck(md, true); } @@ -46,7 +50,7 @@ public static List getPSRList(MovePath md) { /** * Function that carries out PSR checks specific only to airborne aero units - * + * * @param md The path to check * @param stringResult Whether to return the report as a string * @return Collection of PSRs that will be required for this activity @@ -256,11 +260,22 @@ private static Object doPSRCheck(MovePath md, boolean stringResult) { if (leapDistance > 2) { rollTarget = entity.getBasePilotingRoll(moveType); entity.addPilotingModifierForTerrain(rollTarget, curPos); - rollTarget.append(new PilotingRollData(entity.getId(), 2 * leapDistance, "leaping (leg damage)")); + rollTarget.append( + new PilotingRollData( + entity.getId(), + 2 * leapDistance, + Messages.getString("TacOps.leaping.leg_damage") + ) + ); SharedUtility.checkNag(rollTarget, nagReport, psrList); rollTarget = entity.getBasePilotingRoll(moveType); entity.addPilotingModifierForTerrain(rollTarget, curPos); - rollTarget.append(new PilotingRollData(entity.getId(), leapDistance, "leaping (fall)")); + rollTarget.append( + new PilotingRollData( + entity.getId(), + leapDistance, + Messages.getString("TacOps.leaping.fall_damage")) + ); SharedUtility.checkNag(rollTarget, nagReport, psrList); } } @@ -928,4 +943,57 @@ public static String[] getDisplayArray(List entities) { return targets.stream().filter(t -> chosenDisplayName.equals(t.getDisplayName())).findAny().orElse(null); } } + + public static double predictLeapFallDamage(Entity movingEntity, TargetRoll data, StringBuilder msg) { + // Rough guess based on normal pilots + double odds = Compute.oddsAbove(data.getValue(), false) / 100d; + int fallHeight = data.getModifiers().get(data.getModifiers().size()-1).getValue(); + double fallDamage = Math.round(movingEntity.getWeight() / 10.0) + * (fallHeight + 1); + msg.append("\nPredicting expected Leap fall damage:") + .append(String.format("\n\tFall height: %d", fallHeight)) + .append(String.format("\n\tChance of taking damage: %.2f", ((1-odds)*100d))).append('%') + .append(String.format("\n\tExpected total damage from fall: %.2f", fallDamage * (1 - odds) )); + return fallDamage * (1 - odds); + } + + /** Per TacOps p 20, a leap carries the following risks: + * 1. risk of damaging each leg by distance leaped (3 or more per leg); mod is 2 x distance leaped. + * 1.a 1 critical roll _per leg_. + * 1.b 1 _additional_ critical per leg that takes internal structure damage due to leaping damage. + * 2. risk of falling; mod is distance leaped. + * @param movingEntity + * @param data + * @param msg + * @return + */ + public static double predictLeapDamage(Entity movingEntity, TargetRoll data, StringBuilder msg) { + int legMultiplier = (movingEntity.isQuadMek()) ? 4 : 2; + double odds = Compute.oddsAbove(data.getValue(), false) / 100d; + int fallHeight = data.getModifiers().get(data.getModifiers().size()-1).getValue() / 2; + double legDamage = fallHeight * (legMultiplier); + msg.append("\nPredicting expected Leap damage:") + .append(String.format("\n\tFall height: %d", fallHeight)) + .append(String.format("\n\tChance of taking damage: %.2f", ((1-odds)*100d))).append('%'); + + int[] legLocations = {BipedMek.LOC_LLEG, BipedMek.LOC_RLEG, QuadMek.LOC_LARM, QuadMek.LOC_RARM}; + + // Add required crits; say the effective leg "damage" from a crit is 20 for now. + legDamage += legMultiplier * CRIT_VALUE; + msg.append( + String.format("\n\tAdding %d leg critical chances as %d additional damage", legMultiplier, legMultiplier * CRIT_VALUE) + ); + + // Add additional crits for each leg that would take internal damage + for (int i=0;i additionalCosts = new HashMap<>(); List coordsToLevel = new ArrayList<>(); double maxPointBlankDamage = -1; + Coords destination = null; public BulldozerMovePath(Game game, Entity entity) { super(game, entity); @@ -174,6 +175,7 @@ public BulldozerMovePath clone() { copy.additionalCosts = new HashMap<>(additionalCosts); copy.coordsToLevel = new ArrayList<>(coordsToLevel); copy.maxPointBlankDamage = maxPointBlankDamage; + copy.destination = (destination == null) ? destination : new Coords(destination.getX(), destination.getY()); return copy; } @@ -288,6 +290,14 @@ public List getCoordsToLevel() { return coordsToLevel; } + public Coords getDestination() { + return destination; + } + + public void setDestination(Coords d) { + destination = d; + } + @Override public String toString() { return super.toString() + " Leveling Cost: " + getLevelingCost() + " Additional Cost: " + getAdditionalCost(); @@ -297,7 +307,7 @@ public String toString() { * Comparator implementation useful in comparing two bulldozer move paths by * how many MP it'll take to accomplish that path, including time wasted * leveling any obstacles - * + * * @author NickAragua * */ diff --git a/megamek/src/megamek/common/pathfinder/DestructionAwareDestinationPathfinder.java b/megamek/src/megamek/common/pathfinder/DestructionAwareDestinationPathfinder.java index d0add807cf0..f96a8738c98 100644 --- a/megamek/src/megamek/common/pathfinder/DestructionAwareDestinationPathfinder.java +++ b/megamek/src/megamek/common/pathfinder/DestructionAwareDestinationPathfinder.java @@ -116,6 +116,8 @@ public BulldozerMovePath findPathToCoords(Entity entity, Set destination if (destinationCoords.contains(currentPath.getFinalCoords()) && ((bestPath == null) || (movePathComparator.compare(bestPath, currentPath) > 0))) { bestPath = currentPath; + // Keep a record of where this path is headed + bestPath.setDestination(closest); maximumCost = bestPath.getMpUsed() + bestPath.getLevelingCost(); } } @@ -124,8 +126,8 @@ public BulldozerMovePath findPathToCoords(Entity entity, Set destination } /** - * Calculates the closest coordinates to the given entity Coordinates which you - * have to blow up to get into are considered to be further + * Calculates the closest coordinates to the given entity + * Coordinates which you have to blow up to get into are considered to be further */ public static Coords getClosestCoords(Set destinationRegion, Entity entity) { Coords bestCoords = null; diff --git a/megamek/src/megamek/server/totalwarfare/MovePathHandler.java b/megamek/src/megamek/server/totalwarfare/MovePathHandler.java index e7ff875e51d..75879e9729b 100644 --- a/megamek/src/megamek/server/totalwarfare/MovePathHandler.java +++ b/megamek/src/megamek/server/totalwarfare/MovePathHandler.java @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import megamek.MMConstants; +import megamek.client.ui.Messages; import megamek.common.*; import megamek.common.actions.AirMekRamAttackAction; import megamek.common.actions.AttackAction; @@ -2275,7 +2276,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVGRNDMOV_TACOPS_LEAPI rollTarget = entity.getBasePilotingRoll(stepMoveType); entity.addPilotingModifierForTerrain(rollTarget, curPos); rollTarget.append(new PilotingRollData(entity.getId(), - 2 * leapDistance, "leaping (leg damage)")); + 2 * leapDistance, Messages.getString("TacOps.leaping.leg_damage"))); if (0 < gameManager.doSkillCheckWhileMoving(entity, lastElevation, lastPos, curPos, rollTarget, false)) { // do leg damage @@ -2298,7 +2299,7 @@ && getGame().getOptions().booleanOption(OptionsConstants.ADVGRNDMOV_TACOPS_LEAPI rollTarget = entity.getBasePilotingRoll(stepMoveType); entity.addPilotingModifierForTerrain(rollTarget, curPos); rollTarget.append(new PilotingRollData(entity.getId(), - leapDistance, "leaping (fall)")); + leapDistance, Messages.getString("TacOps.leaping.fall_damage"))); if (0 < gameManager.doSkillCheckWhileMoving(entity, lastElevation, lastPos, curPos, rollTarget, false)) { entity.setElevation(lastElevation); diff --git a/megamek/unittests/megamek/client/bot/princess/BasicPathRankerTest.java b/megamek/unittests/megamek/client/bot/princess/BasicPathRankerTest.java index 893032031a2..c25880d0d0d 100644 --- a/megamek/unittests/megamek/client/bot/princess/BasicPathRankerTest.java +++ b/megamek/unittests/megamek/client/bot/princess/BasicPathRankerTest.java @@ -439,6 +439,12 @@ void testRankPath() { when(mockPath.getLastStep()).thenReturn(mockLastStep); when(mockPath.getStepVector()).thenReturn(new Vector<>()); + final TargetRoll mockTargetRoll = MockGenerators.mockTargetRoll(8); + final TargetRoll mockTargetRollTwo = MockGenerators.mockTargetRoll(5); + final List testRollList = List.of(mockTargetRoll, mockTargetRollTwo); + + doReturn(testRollList).when(testRanker).getPSRList(eq(mockPath)); + final Board mockBoard = mock(Board.class); when(mockBoard.contains(any(Coords.class))).thenReturn(true); final Coords boardCenter = spy(new Coords(8, 8)); @@ -461,6 +467,7 @@ void testRankPath() { when(mockGame.getArtilleryAttacks()).thenReturn(Collections.emptyEnumeration()); when(mockGame.getPlanetaryConditions()).thenReturn(mockPC); when(mockPrincess.getGame()).thenReturn(mockGame); + when(mockMover.getGame()).thenReturn(mockGame); final List testEnemies = new ArrayList<>(); @@ -529,10 +536,10 @@ void testRankPath() { doReturn(0.5) .when(testRanker) .getMovePathSuccessProbability(any(MovePath.class), any(StringBuilder.class)); - expected = new RankedPath(-298.125, mockPath, "Calculation: {fall mod [" + expected = new RankedPath(-318.125, mockPath, "Calculation: {fall mod [" + LOG_DECIMAL.format(250) + " = " + LOG_DECIMAL.format(0.5) + " * " + LOG_DECIMAL.format(500) + "] + braveryMod [" - + LOG_DECIMAL.format(-3.12) + " = " + LOG_PERCENT.format(0.5) + + LOG_DECIMAL.format(-23.12) + " = " + LOG_PERCENT.format(0.5) + " * ((" + LOG_DECIMAL.format(22.5) + " * " + LOG_DECIMAL.format(1.5) + ") - " + LOG_DECIMAL.format(40) + "] - aggressionMod [" + LOG_DECIMAL.format(30) + " = " + LOG_DECIMAL.format(12) + " * " @@ -550,10 +557,10 @@ void testRankPath() { doReturn(0.75) .when(testRanker) .getMovePathSuccessProbability(any(MovePath.class), any(StringBuilder.class)); - expected = new RankedPath(-174.6875, mockPath, "Calculation: {fall mod [" + expected = new RankedPath(-184.6875, mockPath, "Calculation: {fall mod [" + LOG_DECIMAL.format(125) + " = " + LOG_DECIMAL.format(0.25) + " * " + LOG_DECIMAL.format(500) + "] + braveryMod [" - + LOG_DECIMAL.format(-4.69) + " = " + LOG_PERCENT.format(0.75) + + LOG_DECIMAL.format(-14.69) + " = " + LOG_PERCENT.format(0.75) + " * ((" + LOG_DECIMAL.format(22.5) + " * " + LOG_DECIMAL.format(1.5) + ") - " + LOG_DECIMAL.format(40) + "] - aggressionMod [" + LOG_DECIMAL.format(30) + " = " + LOG_DECIMAL.format(12) + " * " diff --git a/megamek/unittests/megamek/client/ui/SharedUtilityTest.java b/megamek/unittests/megamek/client/ui/SharedUtilityTest.java new file mode 100644 index 00000000000..cb6984e2d48 --- /dev/null +++ b/megamek/unittests/megamek/client/ui/SharedUtilityTest.java @@ -0,0 +1,145 @@ +package megamek.client.ui; + +import megamek.client.Client; +import megamek.client.ui.swing.ClientGUI; +import megamek.common.*; +import megamek.common.options.GameOptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static megamek.client.ui.SharedUtility.predictLeapDamage; +import static megamek.client.ui.SharedUtility.predictLeapFallDamage; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class SharedUtilityTest { + static GameOptions mockGameOptions = mock(GameOptions.class); + static ClientGUI cg = mock(ClientGUI.class); + static Client client = mock(Client.class); + static Game game = new Game(); + + static Mek bipedMek; + static Crew bipedPilot; + static Mek quadMek; + static Crew quadPilot; + static EntityMovementType moveType = EntityMovementType.MOVE_RUN; + + @BeforeAll + static void setUpAll() { + // Need equipment initialized + EquipmentType.initializeTypes(); + when(cg.getClient()).thenReturn(client); + when(cg.getClient().getGame()).thenReturn(game); + game.setOptions(mockGameOptions); + + bipedMek = new BipedMek(); + bipedMek.setGame(game); + bipedPilot = new Crew(CrewType.SINGLE); + quadMek = new QuadMek(); + quadMek.setGame(game); + quadPilot = new Crew(CrewType.SINGLE); + bipedPilot.setPiloting(5); + bipedMek.setCrew(bipedPilot); + bipedMek.setId(1); + quadPilot.setPiloting(5); + quadMek.setCrew(quadPilot); + quadMek.setId(2); + } + + @BeforeEach + void setUp() { + bipedMek.setArmor(10, Mek.LOC_LLEG); + bipedMek.setArmor(10, Mek.LOC_RLEG); + bipedMek.setWeight(50.0); + + quadMek.setArmor(10, Mek.LOC_LLEG); + quadMek.setArmor(10, Mek.LOC_RLEG); + quadMek.setArmor(10, Mek.LOC_LARM); + quadMek.setArmor(10, Mek.LOC_RARM); + quadMek.setWeight(85.0); + } + + TargetRoll generateLeapRoll(Entity entity, int leapDistance) { + TargetRoll rollTarget = entity.getBasePilotingRoll(moveType); + rollTarget.append(new PilotingRollData(entity.getId(), + 2 * leapDistance, Messages.getString("TacOps.leaping.leg_damage"))); + return rollTarget; + } + + TargetRoll generateLeapFallRoll(Entity entity, int leapDistance) { + TargetRoll rollTarget = entity.getBasePilotingRoll(moveType); + rollTarget.append(new PilotingRollData(entity.getId(), + leapDistance, Messages.getString("TacOps.leaping.fall_damage"))); + return rollTarget; + } + + @Test + void testPredictLeapDamageBipedLeap3AvgPilot() { + TargetRoll data = generateLeapRoll(bipedMek, 3); + StringBuilder msg = new StringBuilder(); + + // Rough math: + // Chance of Pilot Skill 5 pilot to successfully Leap down 3 levels: + // 1 / 12 = 8.3% = 0.083 + // Base biped mek damage from leap 3: + // + 2 x 3 = 6 + // Estimate each crit chance expected damage at 100 + // + 2 x 100 = 200 + // Chance of extra crits if leg armor is greater than base damage is 0: + // + 0 * 100 = 0 + // Times chance of this happening + // * (1 - 0.083) + // Approximately 188 damage expected. + double expectedDamage = 188.0; + double predictedDamage = predictLeapDamage(bipedMek, data, msg); + + assertEquals(expectedDamage, predictedDamage, 1.0); + } + + @Test + void testPredictLeapDamageQuadLeap3AvgPilot() { + TargetRoll data = generateLeapRoll(quadMek, 3); + StringBuilder msg = new StringBuilder(); + + // Rough math: + // Chance of Pilot Skill 5 pilot to successfully Leap down 3 levels: + // 3 / 12 = 0.277 + // Base biped mek damage from leap 3: + // + 4 x 3 = 12 + // Estimate each crit chance expected damage at 100 + // + 4 x 100 = 400 + // Chance of extra crits if leg armor is greater than base damage is 0: + // + 0 * 100 = 0 + // Times chance of this happening + // * (1 - 0.277) + // Approximately 298 damage expected. + double expectedDamage = 298.0; + double predictedDamage = predictLeapDamage(quadMek, data, msg); + + assertEquals(expectedDamage, predictedDamage, 1.0); + } + + @Test + void testPredictLeapFallDamageBipedLeap3AvgPilot() { + TargetRoll data = generateLeapFallRoll(bipedMek, 3); + StringBuilder msg = new StringBuilder(); + + double expectedDamage = 12.0; + double predictedDamage = predictLeapFallDamage(bipedMek, data, msg); + + assertEquals(expectedDamage, predictedDamage, 1.0); + } + + @Test + void testPredictLeapFallDamageQuadLeap3AvgPilot() { + TargetRoll data = generateLeapFallRoll(quadMek, 3); + StringBuilder msg = new StringBuilder(); + + double expectedDamage = 10.0; + double predictedDamage = predictLeapFallDamage(quadMek, data, msg); + + assertEquals(expectedDamage, predictedDamage, 1.0); + } +} diff --git a/megamek/unittests/megamek/utils/MockGenerators.java b/megamek/unittests/megamek/utils/MockGenerators.java index 3f94f85d17e..ff30d8bfb0a 100644 --- a/megamek/unittests/megamek/utils/MockGenerators.java +++ b/megamek/unittests/megamek/utils/MockGenerators.java @@ -190,6 +190,7 @@ public static TargetRoll mockTargetRoll(int value) { final TargetRoll mockTargetRoll = mock(TargetRoll.class); when(mockTargetRoll.getValue()).thenReturn(value); when(mockTargetRoll.getDesc()).thenReturn("mock"); + when(mockTargetRoll.getLastPlainDesc()).thenReturn("mock"); return mockTargetRoll; }