From 6e828b655ce37af8cb373ccbc835db857f1fc196 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Jan 2022 19:45:45 -0500 Subject: [PATCH 1/7] Cleaning up PathRanker.java --- .../client/bot/princess/PathRanker.java | 154 ++++++++---------- 1 file changed, 65 insertions(+), 89 deletions(-) diff --git a/megamek/src/megamek/client/bot/princess/PathRanker.java b/megamek/src/megamek/client/bot/princess/PathRanker.java index 63fb7800617..3bb995148c2 100644 --- a/megamek/src/megamek/client/bot/princess/PathRanker.java +++ b/megamek/src/megamek/client/bot/princess/PathRanker.java @@ -1,15 +1,15 @@ /* * MegaMek - Copyright (C) 2000-2011 Ben Mazur (bmazur@sev.org) * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. */ package megamek.client.bot.princess; @@ -32,10 +32,9 @@ import java.util.List; public abstract class PathRanker implements IPathRanker { - - //TODO: Introduce PathRankerCacheHelper class that contains "global" path ranker state - //TODO: Introduce FireControlCacheHelper class that contains "global" Fire Control state - //PathRanker classes should be pretty stateless, except pointers to princess and such + // TODO: Introduce PathRankerCacheHelper class that contains "global" path ranker state + // TODO: Introduce FireControlCacheHelper class that contains "global" Fire Control state + // PathRanker classes should be pretty stateless, except pointers to princess and such /** * The possible path ranker types. @@ -53,28 +52,14 @@ public PathRanker(Princess princess) { owner = princess; } - /** - * Gives the "utility" of a path; a number representing how good it is. - * Rankers that extend this class should override this function - */ - RankedPath rankPath(MovePath path, Game game) { - double fallTolerance = getOwner().getBehaviorSettings().getFallShameIndex() / 10d; - Entity me = path.getEntity(); - int maxWeaponRange = me.getMaxWeaponRange(); - List enemies = getOwner().getEnemyEntities(); - List friends = getOwner().getFriendEntities(); - Coords allyCenter = calcAllyCenter(me.getId(), friends, game); - - return rankPath(path, game, maxWeaponRange, fallTolerance, enemies, allyCenter); - } - - abstract RankedPath rankPath(MovePath path, Game game, int maxRange, double fallTolerance, - List enemies, Coords friendsCoords); + protected abstract RankedPath rankPath(MovePath path, Game game, int maxRange, + double fallTolerance, List enemies, + Coords friendsCoords); @Override public ArrayList rankPaths(List movePaths, Game game, int maxRange, - double fallTolerance, - List enemies, List friends) { + double fallTolerance, List enemies, + List friends) { // No point in ranking an empty list. if (movePaths.isEmpty()) { return new ArrayList<>(); @@ -85,8 +70,7 @@ public ArrayList rankPaths(List movePaths, Game game, int // Let's try to whittle down this list. List validPaths = validatePaths(movePaths, game, maxRange, fallTolerance); - LogManager.getLogger().debug("Validated " + validPaths.size() + " out of " + - movePaths.size() + " possible paths."); + LogManager.getLogger().debug("Validated " + validPaths.size() + " out of " + movePaths.size() + " possible paths."); Coords allyCenter = calcAllyCenter(movePaths.get(0).getEntity().getId(), friends, game); @@ -100,8 +84,7 @@ public ArrayList rankPaths(List movePaths, Game game, int for (MovePath path : validPaths) { count = count.add(BigDecimal.ONE); - RankedPath rankedPath = rankPath(path, game, maxRange, fallTolerance, enemies, - allyCenter); + RankedPath rankedPath = rankPath(path, game, maxRange, fallTolerance, enemies, allyCenter); returnPaths.add(rankedPath); @@ -120,21 +103,23 @@ public ArrayList rankPaths(List movePaths, Game game, int Entity mover = movePaths.get(0).getEntity(); UnitBehavior behaviorTracker = getOwner().getUnitBehaviorTracker(); - boolean noDamageButCanDoDamage = !pathsHaveExpectedDamage && (FireControl.getMaxDamageAtRange(mover, 1, false, false) > 0); - + boolean noDamageButCanDoDamage = !pathsHaveExpectedDamage + && (FireControl.getMaxDamageAtRange(mover, 1, false, false) > 0); + // if we're trying to fight, but aren't going to be doing any damage no matter how we move // then let's try to get closer - if (noDamageButCanDoDamage && behaviorTracker.getBehaviorType(mover, getOwner()) == BehaviorType.Engaged) { - + if (noDamageButCanDoDamage + && (behaviorTracker.getBehaviorType(mover, getOwner()) == BehaviorType.Engaged)) { behaviorTracker.overrideBehaviorType(mover, BehaviorType.MoveToContact); - return rankPaths(getOwner().getMovePathsAndSetNecessaryTargets(mover, true), game, maxRange, fallTolerance, - enemies, friends); + return rankPaths(getOwner().getMovePathsAndSetNecessaryTargets(mover, true), + game, maxRange, fallTolerance, enemies, friends); } return returnPaths; } - private List validatePaths(List startingPathList, Game game, int maxRange, double fallTolerance) { + private List validatePaths(List startingPathList, Game game, int maxRange, + double fallTolerance) { if (startingPathList.isEmpty()) { // Nothing to validate here, might as well return the empty list // straight away. @@ -144,13 +129,12 @@ private List validatePaths(List startingPathList, Game game, Entity mover = startingPathList.get(0).getEntity(); Targetable closestTarget = findClosestEnemy(mover, mover.getPosition(), game); - int startingTargetDistance = (closestTarget == null ? - Integer.MAX_VALUE : - closestTarget.getPosition().distance(mover.getPosition())); + int startingTargetDistance = (closestTarget == null) ? Integer.MAX_VALUE + : closestTarget.getPosition().distance(mover.getPosition()); List returnPaths = new ArrayList<>(startingPathList.size()); - boolean inRange = (maxRange >= startingTargetDistance); - + boolean inRange = maxRange >= startingTargetDistance; + boolean isAirborneAeroOnGroundMap = mover.isAirborneAeroOnGroundMap(); boolean needToUnjamRAC = mover.canUnjamRAC(); int walkMP = mover.getWalkMP(); @@ -165,16 +149,16 @@ private List validatePaths(List startingPathList, Game game, try { // if we are an aero unit on the ground map, we want to discard paths that keep us at altitude 1 with no bombs - if (isAirborneAeroOnGroundMap) { - // if we have no bombs, we want to make sure our altitude is above 1 - // if we do have bombs, we may consider altitude bombing (in the future) - if (path.getEntity().getBombs(BombType.F_GROUND_BOMB).isEmpty() + if (isAirborneAeroOnGroundMap) { + // if we have no bombs, we want to make sure our altitude is above 1 + // if we do have bombs, we may consider altitude bombing (in the future) + if (path.getEntity().getBombs(BombType.F_GROUND_BOMB).isEmpty() && (path.getFinalAltitude() < 2)) { - msg.append("\n\tNo bombs but at altitude 1. No way."); - continue; - } - } - + msg.append("\n\tNo bombs but at altitude 1. No way."); + continue; + } + } + Coords finalCoords = path.getFinalCoords(); // Make sure I'm trying to get/stay in range of a target. @@ -231,18 +215,13 @@ private List validatePaths(List startingPathList, Game game, * @return "Best" out of those paths */ @Override - public RankedPath getBestPath(List ps) { - if (ps.size() == 0) { - return null; - } - return Collections.max(ps); + public @Nullable RankedPath getBestPath(List ps) { + return ps.isEmpty() ? null : Collections.max(ps); } - /** - * Performs initialization to help speed later calls of rankPath for this - * unit on this turn. Rankers that extend this class should override this - * function + * Performs initialization to help speed later calls of rankPath for this unit on this turn. + * Rankers that extend this class should override this function */ @Override public void initUnitTurn(Entity unit, Game game) { @@ -263,7 +242,8 @@ public Targetable findClosestEnemy(Entity me, Coords position, Game game, boolea List enemies = getOwner().getEnemyEntities(); for (Entity e : enemies) { // Skip airborne aero units as they're further away than they seem and hard to catch. - // Also, skip withdrawing enemy bot units, to avoid humping disabled tanks and ejected mechwarriors + // Also, skip withdrawing enemy bot units, to avoid humping disabled tanks and ejected + // MechWarriors if (e.isAirborneAeroOnGroundMap() || getOwner().getHonorUtil().isEnemyBroken(e.getTargetId(), e.getOwnerId(), getOwner().getForcedWithdrawal())) { continue; @@ -310,12 +290,10 @@ protected double getMovePathSuccessProbability(MovePath movePath, StringBuilder double successProbability = 1.0; msg.append("\n\tCalculating Move Path Success"); for (TargetRoll roll : pilotingRolls) { - - // Skip the getting up check. That's handled when checking for being immobile. + // Skip the getting up check. That's handled when checking for being immobile. if (roll.getDesc().toLowerCase().contains("getting up")) { continue; - } - if (roll.getDesc().toLowerCase().contains("careful stand")) { + } else if (roll.getDesc().toLowerCase().contains("careful stand")) { continue; } boolean naturalAptPilot = movePath.getEntity().hasAbility(OptionsConstants.PILOT_APTITUDE_PILOTING); @@ -323,9 +301,9 @@ protected double getMovePathSuccessProbability(MovePath movePath, StringBuilder msg.append("\n\t\tPilot has Natural Aptitude Piloting"); } - msg.append("\n\t\tRoll ").append(roll.getDesc()).append(" ").append(roll.getValue()); - double odds = Compute.oddsAbove(roll.getValue(), naturalAptPilot) / 100; - msg.append(" (").append(NumberFormat.getPercentInstance().format(odds)).append(")"); + msg.append("\n\t\tRoll ").append(roll.getDesc()).append(' ').append(roll.getValue()); + double odds = Compute.oddsAbove(roll.getValue(), naturalAptPilot) / 100d; + msg.append(" (").append(NumberFormat.getPercentInstance().format(odds)).append(')'); successProbability *= odds; } @@ -334,9 +312,9 @@ protected double getMovePathSuccessProbability(MovePath movePath, StringBuilder msg.append("\n\t\tMASC "); int target = pathCopy.getEntity().getMASCTarget(); msg.append(target); - // todo Does Natural Aptitude Piloting apply to this? I assume not. - double odds = Compute.oddsAbove(target) / 100; - msg.append(" (").append(NumberFormat.getPercentInstance().format(odds)).append(")"); + // TODO : Does Natural Aptitude Piloting apply to this? I assume not. + double odds = Compute.oddsAbove(target) / 100d; + msg.append(" (").append(NumberFormat.getPercentInstance().format(odds)).append(')'); successProbability *= odds; } msg.append("\n\t\tTotal = ").append(NumberFormat.getPercentInstance().format(successProbability)); @@ -383,7 +361,7 @@ public int distanceToHomeEdge(Coords position, CardinalEdge homeEdge, Game game) break; } default: { - LogManager.getLogger().warn("Invalid home edge. Defaulting to NORTH."); + LogManager.getLogger().warn("Invalid home edge. Defaulting to NORTH."); distance = position.getY(); } } @@ -391,8 +369,8 @@ public int distanceToHomeEdge(Coords position, CardinalEdge homeEdge, Game game) return distance; } - private String validRange(Coords finalCoords, Targetable target, int startingTargetDistance, int maxRange, - boolean inRange) { + private String validRange(Coords finalCoords, Targetable target, int startingTargetDistance, + int maxRange, boolean inRange) { if (target == null) { return null; } @@ -403,7 +381,6 @@ private String validRange(Coords finalCoords, Targetable target, int startingTar if (finalDistanceToTarget > startingTargetDistance) { return "INVALID: Not in range and moving further away."; } - } else { // If I am in range, discard any path that takes me out of range. if (finalDistanceToTarget > maxRange) { return "INVALID: In range and moving out of range."; @@ -414,12 +391,12 @@ private String validRange(Coords finalCoords, Targetable target, int startingTar } /** - * Check the path being moved to see if there is a danger of building collapse. Allows a margin of error of 10 - * tons in case someone decides to shoot at the building. If jumping, only the landing point is checked. For - * all other move types, the entire path is checked. - * todo reread the rules on basement collapse - * todo skip basement check if random basement option is turned off - * todo incorporate test for building damage just from moving through building + * Check the path being moved to see if there is a danger of building collapse. Allows a margin + * of error of 10 tons in case someone decides to shoot at the building. If jumping, only the + * landing point is checked. For all other move types, the entire path is checked. + * TODO : reread the rules on basement collapse + * TODO : skip basement check if random basement option is turned off + * TODO : incorporate test for building damage just from moving through building * * @param path The {@link MovePath} being traversed. * @param game The {@link Game} being played. @@ -468,8 +445,7 @@ private boolean willBuildingCollapse(MovePath path, Game game) { return false; } - @Nullable - Coords calcAllyCenter(int myId, List friends, Game game) { + protected @Nullable Coords calcAllyCenter(int myId, @Nullable List friends, Game game) { if ((friends == null) || friends.isEmpty()) { return null; } @@ -520,7 +496,7 @@ protected Princess getOwner() { /** * Convenience property to access bot-wide state information. - * @return + * @return the owner's path ranker state */ protected PathRankerState getPathRankerState() { return owner.getPathRankerState(); From f53ce4cd41d708675b19fd011c0b4629b53b4620 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Jan 2022 20:27:15 -0500 Subject: [PATCH 2/7] Fixing a few spacing issues --- megamek/src/megamek/client/bot/princess/PathRanker.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/megamek/src/megamek/client/bot/princess/PathRanker.java b/megamek/src/megamek/client/bot/princess/PathRanker.java index 3bb995148c2..85a574db8ad 100644 --- a/megamek/src/megamek/client/bot/princess/PathRanker.java +++ b/megamek/src/megamek/client/bot/princess/PathRanker.java @@ -92,7 +92,7 @@ public ArrayList rankPaths(List movePaths, Game game, int pathsHaveExpectedDamage |= (rankedPath.getExpectedDamage() > 0); BigDecimal percent = count.divide(numberPaths, 2, RoundingMode.DOWN).multiply(new BigDecimal(100)) - .round(new MathContext(0, RoundingMode.DOWN)); + .round(new MathContext(0, RoundingMode.DOWN)); if (percent.compareTo(interval) >= 0) { if (LogManager.getLogger().getLevel().isLessSpecificThan(Level.INFO)) { getOwner().sendChat("... " + percent.intValue() + "% complete."); @@ -236,7 +236,8 @@ public Targetable findClosestEnemy(Entity me, Coords position, Game game) { * Find the closest enemy to a unit with a path */ @Override - public Targetable findClosestEnemy(Entity me, Coords position, Game game, boolean includeStrategicTargets) { + public Targetable findClosestEnemy(Entity me, Coords position, Game game, + boolean includeStrategicTargets) { int range = 9999; Targetable closest = null; List enemies = getOwner().getEnemyEntities(); @@ -245,7 +246,8 @@ public Targetable findClosestEnemy(Entity me, Coords position, Game game, boolea // Also, skip withdrawing enemy bot units, to avoid humping disabled tanks and ejected // MechWarriors if (e.isAirborneAeroOnGroundMap() || - getOwner().getHonorUtil().isEnemyBroken(e.getTargetId(), e.getOwnerId(), getOwner().getForcedWithdrawal())) { + getOwner().getHonorUtil().isEnemyBroken(e.getTargetId(), e.getOwnerId(), + getOwner().getForcedWithdrawal())) { continue; } From 6c0a400474da3c0b2fbd57ebaad41d2fe6831a87 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Jan 2022 20:34:20 -0500 Subject: [PATCH 3/7] 3319: Adding missing nullable annotations --- .../megamek/client/bot/princess/Princess.java | 4 +- megamek/src/megamek/common/Compute.java | 11 +- megamek/src/megamek/common/MovePath.java | 188 +++++++++--------- 3 files changed, 96 insertions(+), 107 deletions(-) diff --git a/megamek/src/megamek/client/bot/princess/Princess.java b/megamek/src/megamek/client/bot/princess/Princess.java index 236ac0b4eea..19601c259b3 100644 --- a/megamek/src/megamek/client/bot/princess/Princess.java +++ b/megamek/src/megamek/client/bot/princess/Princess.java @@ -1981,8 +1981,8 @@ private void unloadTransportedInfantry(MovePath path) { return; } - Entity movingEntity = path.getEntity(); - Coords pathEndpoint = path.getFinalCoords(); + final Entity movingEntity = path.getEntity(); + final Coords pathEndpoint = path.getFinalCoords(); Targetable closestEnemy = getPathRanker(movingEntity).findClosestEnemy(movingEntity, pathEndpoint, getGame(), false); // if there are no enemies on the board, then we're not unloading anything. diff --git a/megamek/src/megamek/common/Compute.java b/megamek/src/megamek/common/Compute.java index fbc062f72e4..eb45200377a 100644 --- a/megamek/src/megamek/common/Compute.java +++ b/megamek/src/megamek/common/Compute.java @@ -6125,16 +6125,7 @@ public static Entity getSwarmMissileTarget(Game game, int aeId, return null; } - /** - * Gets a new target hex for a flight of smoke missiles fired at a hex, if - * there are remaining missiles. - */ - - /** - * * STUFF FOR VECTOR MOVEMENT CALCULATIONS ** - */ - public static Coords getFinalPosition(Coords curpos, int[] v) { - + public static @Nullable Coords getFinalPosition(Coords curpos, int... v) { if ((v == null) || (v.length != 6) || (curpos == null)) { return curpos; } diff --git a/megamek/src/megamek/common/MovePath.java b/megamek/src/megamek/common/MovePath.java index 4186e1e393c..8846c895cb3 100644 --- a/megamek/src/megamek/common/MovePath.java +++ b/megamek/src/megamek/common/MovePath.java @@ -57,12 +57,12 @@ public enum MoveStepType { SHUTDOWN, STARTUP, SELF_DESTRUCT, ACCN, DECN, ROLL, OFF, RETURN, LAUNCH, THRUST, YAW, CRASH, RECOVER, RAM, HOVER, MANEUVER, LOOP, CAREFUL_STAND, JOIN, DROP, VLAND, MOUNT, UNDOCK, TAKE_COVER, CONVERT_MODE, BOOTLEGGER, TOW, DISCONNECT, BRACE; - + /** * Whether this move step type will result in the unit entering a new hex */ public boolean entersNewHex() { - return this == FORWARDS || + return this == FORWARDS || this == BACKWARDS || this == LATERAL_LEFT || this == LATERAL_RIGHT || @@ -101,10 +101,10 @@ public int hashCode() { private transient Game game; private transient Entity entity; - - // holds the types of steps present in this movement + + // holds the types of steps present in this movement private Set containedStepTypes = new HashSet<>(); - + // whether this movePath take us directly over an enemy unit // useful for debugging aircraft on ground maps //private boolean fliesOverEnemy; @@ -125,7 +125,7 @@ public MovePath(final Game game, final Entity entity) { public Entity getEntity() { return entity; } - + public CachedEntityState getCachedEntityState() { return cachedEntityState; } @@ -143,20 +143,20 @@ public String toString() { sb.append("Length: " + this.length()); sb.append("Final Coords: " + this.getFinalCoords()); sb.append(System.lineSeparator()); - + for (final Enumeration i = steps.elements(); i.hasMoreElements(); ) { sb.append(i.nextElement().toString()); sb.append(' '); } - + if (!getGame().getBoard().contains(this.getFinalCoords())) { sb.append("OUT!"); } - + /*if (this.getFliesOverEnemy()) { sb.append("E! "); }*/ - + return sb.toString(); } @@ -299,7 +299,7 @@ protected MovePath addStep(final MoveStep step, boolean compile) { } steps.addElement(step); - + final MoveStep prev = getStep(steps.size() - 2); if (compile) { @@ -310,7 +310,7 @@ protected MovePath addStep(final MoveStep step, boolean compile) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); } } - + // jumping into heavy woods is danger if (game.getOptions().booleanOption(OptionsConstants.ADVGRNDMOV_PSR_JUMP_HEAVY_WOODS)) { Hex hex = game.getBoard().getHex(step.getPosition()); @@ -324,11 +324,11 @@ protected MovePath addStep(final MoveStep step, boolean compile) { final Coords start = getEntity().getPosition(); final Coords land = step.getPosition(); - + if (step.getMovementType(false) != EntityMovementType.MOVE_ILLEGAL) { performIllegalCheck(step, start, land); } - + // If the new step is legal and is a different position than // the previous step, then update the older steps, letting // them know that they are no longer the end of the path. @@ -342,11 +342,11 @@ protected MovePath addStep(final MoveStep step, boolean compile) { } } // End step-is-legal - + if (shouldMechanicalJumpCauseFallDamage()) { step.setDanger(true); } - + // If we are using turn modes, go back through the path and mark danger for any turn // that now exceeds turn mode requirement. We want to show danger on the previous step // so the StepSprite will show danger. Hiding the previous step instead would make turning costs @@ -363,7 +363,7 @@ protected MovePath addStep(final MoveStep step, boolean compile) { prevStep = s; } } - + // If running on pavement we don't know to mark the danger steps if we turn before expending // enough MP to require running movement. if (steps.size() > 1) { @@ -381,20 +381,20 @@ protected MovePath addStep(final MoveStep step, boolean compile) { prevStep = s; } } - + // if we're an aerospace unit on a ground map and have passed over a hostile unit // record this fact - it is useful for debugging thus we leave the commented out code here // but for performance reasons, we don't actually do it. /*if (step.useAeroAtmosphere(game, entity) - && game.getBoard().onGround() - && (step.getPosition() != null) - && (game.getFirstEnemyEntity(step.getPosition(), entity) != null)) { - fliesOverEnemy = true; + && game.getBoard().onGround() + && (step.getPosition() != null) + && (game.getFirstEnemyEntity(step.getPosition(), entity) != null)) { + fliesOverEnemy = true; }*/ // having checked for illegality and other things, add it to the set of contained step types containedStepTypes.add(step.getType()); - + return this; } @@ -422,32 +422,32 @@ private void performIllegalCheck(MoveStep step, Coords start, Coords land) { if ((isJumping() && (getEntity().getJumpType() != Mech.JUMP_BOOSTER)) || (Compute.useSpheroidAtmosphere(game, getEntity()) && (step.getType() != MoveStepType.HOVER))) { int distance = start.distance(land); - + if (step.isThisStepBackwards() || (step.getDistance() > distance)) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; } } } - + // Can't move backwards and Evade if (!entity.isAirborne() && contains(MoveStepType.BACKWARDS) && contains(MoveStepType.EVADE)) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; } - - // If jumpships turn, they can't do anything else + + // If jumpships turn, they can't do anything else if (game.getBoard().inSpace() && (entity instanceof Jumpship) && !(entity instanceof Warship) && !step.isFirstStep() - && (contains(MoveStepType.TURN_LEFT) + && (contains(MoveStepType.TURN_LEFT) || contains(MoveStepType.TURN_RIGHT))) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; } - + // Ensure we only lay one mine if ((step.getType() == MoveStepType.LAY_MINE)) { boolean containsOtherLayMineStep = false; @@ -475,7 +475,7 @@ && contains(MoveStepType.EVADE)) { return; } } - + // Make sure we are not turning or changing elevation while strafing, and that we are not // starting a second group of hexes during the same round if (step.isStrafingStep() && steps.size() > 1) { @@ -501,7 +501,7 @@ && contains(MoveStepType.EVADE)) { } } } - + // VTOLs using maneuvering ace to make lateral shifts can't flank // unless using controlled sideslip if (containsLateralShift() && getEntity().isUsingManAce() @@ -511,19 +511,19 @@ && getMpUsed() > getCachedEntityState().getWalkMP() step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; } - + // If a tractor connects a new trailer this round, it can't do anything but add more trailers // This prevents the tractor from moving before its MP, stacking limitations and prohibited terrain can be updated by its trailers - // It makes sense, too. You can't just connect a trailer and drive off with it in <10 seconds. + // It makes sense, too. You can't just connect a trailer and drive off with it in <10 seconds. if (contains(MoveStepType.TOW) && !(step.getType() == MoveStepType.TOW)) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); } - + if ((step.getType() == MoveStepType.BRACE) && !isValidPositionForBrace(step)) { step.setMovementType(EntityMovementType.MOVE_ILLEGAL); return; } - + // If using TacOps reverse gear option, cannot mix forward and backward movement // in the same round except VTOLs. if (game.getOptions().booleanOption(OptionsConstants.ADVGRNDMOV_REVERSE_GEAR) @@ -546,7 +546,7 @@ && getMpUsed() > getCachedEntityState().getWalkMP() } } } - + public void compile(final Game g, final Entity en) { compile(g, en, true); } @@ -625,7 +625,7 @@ public void compile(final Game g, final Entity en, boolean clip) { } } } - + if (getEntity() instanceof LandAirMech && !((LandAirMech) getEntity()).canConvertTo(getFinalConversionMode())) { steps.forEach(s -> { @@ -647,9 +647,9 @@ public void removeLastStep() { if (step1.getType() == MovePath.MoveStepType.START_JUMP) { getEntity().setIsJumpingNow(false); } - + steps.removeElementAt(steps.size() - 1); - + if (getEntity().isConvertingNow() && !this.contains(MovePath.MoveStepType.CONVERT_MODE)) { getEntity().setConvertingNow(false); //Mechs using tracks have the movement mode set at the beginning of the turn, so @@ -658,23 +658,23 @@ public void removeLastStep() { getEntity().toggleConversionMode(); } } - + //Treat multiple convert steps as a single command if (step1.getType() == MovePath.MoveStepType.CONVERT_MODE) while (steps.size() > 0 && steps.get(steps.size() - 1).getType() == MovePath.MoveStepType.CONVERT_MODE) { steps.removeElementAt(steps.size() - 1); } - + // if this step is part of a manuever, undo the whole manuever, all the way to the beginning. if (step1.isManeuver()) { int stepIndex = steps.size() - 1; - + while (steps.size() > 0 && steps.get(stepIndex).isManeuver()) { steps.removeElementAt(stepIndex); stepIndex--; } - + // a maneuver begins with a "maneuver" step, so get rid of that as well steps.removeElementAt(stepIndex); } @@ -686,7 +686,7 @@ public void removeLastStep() { && !getStep(index).isLegal(this)) { index--; } - + // we may have removed a lot of steps - recalculate the contained step types regenerateStepTypes(); } @@ -694,13 +694,13 @@ public void removeLastStep() { public void clear() { steps.removeAllElements(); } - + public boolean isValidPositionForBrace(MoveStep step) { return isValidPositionForBrace(step.getPosition(), step.getFacing()); } - + /** - * Given a set of coordinates and a facing, is the entity taking this path in a valid + * Given a set of coordinates and a facing, is the entity taking this path in a valid * position to execute a brace? */ public boolean isValidPositionForBrace(Coords coords, int facing) { @@ -708,7 +708,7 @@ public boolean isValidPositionForBrace(Coords coords, int facing) { if (isJumping() || contains(MoveStepType.GO_PRONE) || !getEntity().canBrace()) { return false; } - + // for mechs, the check is complicated - you have to be directly in front of a hex with either // a) level 1 level higher than your hex level // b) building/bridge ceiling 1 level higher than your hex level (?) @@ -716,22 +716,22 @@ public boolean isValidPositionForBrace(Coords coords, int facing) { boolean onBoard = getGame().getBoard().contains(coords); Coords nextPosition = coords.translated(facing); boolean nextHexOnBoard = getGame().getBoard().contains(nextPosition); - + if (!onBoard || !nextHexOnBoard) { return false; } - + Hex nextHex = getGame().getBoard().getHex(nextPosition); Hex currentHex = getGame().getBoard().getHex(coords); - + int curHexLevel = currentHex.containsAnyTerrainOf(Terrains.BLDG_ELEV, Terrains.BRIDGE_ELEV) ? currentHex.ceiling() : currentHex.floor(); int nextHexLevel = nextHex.containsAnyTerrainOf(Terrains.BLDG_ELEV, Terrains.BRIDGE_ELEV) ? nextHex.ceiling() : nextHex.floor(); - + return onBoard && nextHexOnBoard && (nextHexLevel == curHexLevel + 1); } - + return true; } @@ -756,27 +756,27 @@ private void regenerateStepTypes() { containedStepTypes.add(step.getType()); } } - + /** * Check for any of the specified type of step in the path * @param type The step type to check for - * @return Whether or not this step type is contained within this path + * @return Whether or not this step type is contained within this path */ public boolean contains(final MoveStepType type) { return containedStepTypes.contains(type); } - + /** * Convenience function to determine whether this path results in the unit explicitly moving off board * More relevant for aircraft * @return Whether or not this path will result in the unit moving off board */ public boolean fliesOffBoard() { - return contains(MoveStepType.OFF) || - contains(MoveStepType.RETURN) || - contains(MoveStepType.FLEE); + return contains(MoveStepType.OFF) || + contains(MoveStepType.RETURN) || + contains(MoveStepType.FLEE); } - + /** * Whether any of the steps in the path (except for the last one, based on experimentation) * pass over an enemy unit eligible for targeting. Useful for aerotech units. @@ -785,9 +785,9 @@ public boolean fliesOffBoard() { * @return Whether or not this flight path takes us over an enemy unit */ /*public boolean getFliesOverEnemy() { - return fliesOverEnemy; + return fliesOverEnemy; }*/ - + /** * Method that determines whether a given path goes through a given set of x/y coordinates * Useful for debugging mainly. @@ -805,7 +805,7 @@ public boolean goesThroughCoords(int x, int y) { return true; } } - + return false; } @@ -823,19 +823,17 @@ public boolean hasActiveMASC() { } /** - * Returns the final coordinates if a mech were to perform all the steps in - * this path. + * @return the final coordinates if a mech were to perform all the steps in this path, or + * null if there's an issue with determining the coords */ - public Coords getFinalCoords() { + public @Nullable Coords getFinalCoords() { if (getGame().useVectorMove()) { return Compute.getFinalPosition(getEntity().getPosition(), getFinalVectors()); - } - - if (getLastStep() != null) { + } else if (getLastStep() != null) { return getLastStep().getPosition(); + } else { + return getEntity().getPosition(); } - - return getEntity().getPosition(); } /** @@ -913,7 +911,7 @@ public int getFinalElevation() { } return getEntity().getElevation(); } - + /** * get final elevation relative to the tops of any buildings in the hex * @return @@ -962,7 +960,7 @@ public int getFinalVelocity() { } return 0; } - + public int getFinalVelocityLeft() { if (getLastStep() != null) { return getLastStep().getVelocityLeft(); @@ -980,11 +978,11 @@ public int getFinalNDown() { return 0; } - + /** * If the path contains mode conversions, this will determine the movement mode at the end * of movement. Note that LAMs converting from AirMech to Biped mode require two convert commands. - * + * * @return The movement mode resulting from any mode conversions in the path. */ public EntityMovementMode getFinalConversionMode() { @@ -1184,7 +1182,7 @@ public int getHexesMoved() { public boolean isJumping() { return contains(MoveStepType.START_JUMP); } - + /** * @return true if the entity is a QuadVee or LAM changing movement mode */ @@ -1587,7 +1585,7 @@ public MovePath clone() { copyFields(copy); return copy; } - + protected void copyFields(MovePath copy) { copy.steps = new Vector<>(steps); copy.careful = careful; @@ -1665,10 +1663,10 @@ public boolean willCrushBuildings() { * Airborne WiGEs that move less than five hexes (four for glider protomech) in a movement phase must * land unless it has taken off in the same phase or it is a LAM or glider ProtoMech that is using hover * movement. - * + * * @param includeMovePathHexes Whether to include the hexes plotted in this MovePath in the total distance * moved. This should be true when plotting movement in the client and - * false when the server checks for automatic landing at the end of movement. + * false when the server checks for automatic landing at the end of movement. * @return whether the unit is an airborne WiGE that must land at the end of movement. */ public boolean automaticWiGELanding(boolean includeMovePathHexes) { @@ -1709,7 +1707,7 @@ public boolean automaticWiGELanding(boolean includeMovePathHexes) { return getEntity().isAirborneVTOLorWIGE(); } } - + protected static class MovePathComparator implements Comparator { private final Coords destination; boolean backward; @@ -1804,26 +1802,26 @@ public void replaceSteps(Vector path) { steps.clear(); addSteps(path, true); } - + public boolean isEndStep(MoveStep step) { if (step == null) { return false; } return step.isEndPos(this); } - + /** * Convenience method to determine whether this path is happening on a ground map with an atmosphere */ public boolean isOnAtmosphericGroundMap() { - return getEntity().isOnAtmosphericGroundMap(); + return getEntity().isOnAtmosphericGroundMap(); } - + /** * Searches the movement path for the first step that has the given position and sets it as * a VTOL bombing step. If found, any previous bombing step is cleared. If the coordinates are not * part of the path nothing is changed. - * + * * @param pos The Coords of the hex to be bombed. * @return Whether the position was found in the movement path */ @@ -1847,15 +1845,15 @@ public boolean setVTOLBombStep(Coords pos) { } return foundPos; } - + /** * Searches the path for the first MoveStep that matches the given position and sets it * as a strafing step. In cases where there are multiple steps with the same coordinates, we want the * first one because it is the one that enters the hex. In the rare case where the path crosses * itself, select the one closest to the end of the path. - * + * * FIXME: this does not deal with paths that cross themselves - * + * * @param pos The Coords of the hex to be strafed * @return Whether the position was found in the path */ @@ -1875,7 +1873,7 @@ public boolean setStrafingStep(Coords pos) { } return false; } - + /** * @return A list of entity ids for all units that have previously be plotted to be dropped/launched. */ @@ -1888,16 +1886,16 @@ public Set getDroppedUnits() { } return dropped; } - + /** - * Convenience function encapsulating logic for whether, if we continue forward + * Convenience function encapsulating logic for whether, if we continue forward * along the current path in the current direction, we will run off the board * @return */ public boolean nextForwardStepOffBoard() { return !game.getBoard().contains(getFinalCoords().translated(getFinalFacing())); } - + /** * Debugging method that calculates a destruction-aware move path to the destination coordinates */ @@ -1908,16 +1906,16 @@ public MovePath calculateDestructionAwarePath(Coords dest) { // the destruction aware pathfinder takes either a CardinalEdge or an explicit set of coordinates Set destinationSet = new HashSet<>(); destinationSet.add(dest); - + // debugging code that can be used to find a path to a specific edge Princess princess = new Princess("test", "test", 2020); //Set destinationSet = princess.getClusterTracker().getDestinationCoords(entity, CardinalEdge.WEST, true); - + long marker1 = System.currentTimeMillis(); MovePath finPath = dpf.findPathToCoords(entity, destinationSet, false, princess.getClusterTracker()); long marker2 = System.currentTimeMillis(); long marker3 = marker2 - marker1; - + return finPath; } } From 7301fcba6807f1e822aa063380976dba982f8df1 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Jan 2022 20:35:58 -0500 Subject: [PATCH 4/7] 3319: Handling Princess::unloadTransportedInfantry NPE due to missing handling for Compute::getFinalPosition --- megamek/src/megamek/client/bot/princess/Princess.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/megamek/src/megamek/client/bot/princess/Princess.java b/megamek/src/megamek/client/bot/princess/Princess.java index 19601c259b3..cb19b7aefa5 100644 --- a/megamek/src/megamek/client/bot/princess/Princess.java +++ b/megamek/src/megamek/client/bot/princess/Princess.java @@ -1983,6 +1983,11 @@ private void unloadTransportedInfantry(MovePath path) { final Entity movingEntity = path.getEntity(); final Coords pathEndpoint = path.getFinalCoords(); + if (pathEndpoint == null) { + LogManager.getLogger().error("Can't unload infantry from " + movingEntity.getDisplayName() + + " because of null path endpoint."); + return; + } Targetable closestEnemy = getPathRanker(movingEntity).findClosestEnemy(movingEntity, pathEndpoint, getGame(), false); // if there are no enemies on the board, then we're not unloading anything. From bc2902cf4f95c31176d79f18ca906a3f4f669a96 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 29 Jan 2022 11:10:23 -0500 Subject: [PATCH 5/7] Defaulting to the entity's position for a null path endpoint --- .../src/megamek/client/bot/princess/Princess.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/megamek/src/megamek/client/bot/princess/Princess.java b/megamek/src/megamek/client/bot/princess/Princess.java index cb19b7aefa5..6a589d730f1 100644 --- a/megamek/src/megamek/client/bot/princess/Princess.java +++ b/megamek/src/megamek/client/bot/princess/Princess.java @@ -1980,13 +1980,12 @@ private void unloadTransportedInfantry(MovePath path) { if (getBehaviorSettings().shouldAutoFlee()) { return; } - + final Entity movingEntity = path.getEntity(); - final Coords pathEndpoint = path.getFinalCoords(); + Coords pathEndpoint = path.getFinalCoords(); if (pathEndpoint == null) { - LogManager.getLogger().error("Can't unload infantry from " + movingEntity.getDisplayName() - + " because of null path endpoint."); - return; + // The entity is stationary, so use their current position + pathEndpoint = movingEntity.getPosition(); } Targetable closestEnemy = getPathRanker(movingEntity).findClosestEnemy(movingEntity, pathEndpoint, getGame(), false); @@ -1995,9 +1994,9 @@ private void unloadTransportedInfantry(MovePath path) { if ((null == closestEnemy) || (closestEnemy.getTargetType() == Targetable.TYPE_HEX_CLEAR)) { return; } - + int distanceToClosestEnemy = pathEndpoint.distance(closestEnemy.getPosition()); - + // loop through all entities carried by the current entity for (Transporter transport : movingEntity.getTransports()) { // this operation is intended for entities on the ground From eaf4bac47721fb2f077b5d99c2d3c238f7058063 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 29 Jan 2022 13:14:46 -0500 Subject: [PATCH 6/7] Reverting unecessary null handling in Princess --- megamek/src/megamek/client/bot/princess/Princess.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/megamek/src/megamek/client/bot/princess/Princess.java b/megamek/src/megamek/client/bot/princess/Princess.java index 6a589d730f1..cf06cba0052 100644 --- a/megamek/src/megamek/client/bot/princess/Princess.java +++ b/megamek/src/megamek/client/bot/princess/Princess.java @@ -1982,11 +1982,7 @@ private void unloadTransportedInfantry(MovePath path) { } final Entity movingEntity = path.getEntity(); - Coords pathEndpoint = path.getFinalCoords(); - if (pathEndpoint == null) { - // The entity is stationary, so use their current position - pathEndpoint = movingEntity.getPosition(); - } + final Coords pathEndpoint = path.getFinalCoords(); Targetable closestEnemy = getPathRanker(movingEntity).findClosestEnemy(movingEntity, pathEndpoint, getGame(), false); // if there are no enemies on the board, then we're not unloading anything. From ede3af3fe79aaa3c6cdf5c22346f6bf9633e29d0 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 29 Jan 2022 13:25:30 -0500 Subject: [PATCH 7/7] 3319: Fixing parameters for undeployed entities setting arty auto hit hexes when using the unofficial on map predesignate option --- megamek/src/megamek/common/Entity.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/megamek/src/megamek/common/Entity.java b/megamek/src/megamek/common/Entity.java index 70849701777..0b53888194d 100644 --- a/megamek/src/megamek/common/Entity.java +++ b/megamek/src/megamek/common/Entity.java @@ -9751,9 +9751,9 @@ public int getKillerId() { */ public boolean isEligibleFor(GamePhase phase) { // only deploy in deployment phase - if ((phase == GamePhase.DEPLOYMENT) == isDeployed()) { - if (!isDeployed() && isEligibleForTargetingPhase() - && game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_ON_MAP_PREDESIGNATE)) { + if (phase.isDeployment() == isDeployed()) { + if (!isDeployed() && phase.isSetArtilleryAutohitHexes() + && isEligibleForArtyAutoHitHexes()) { LogManager.getLogger().debug("Artillery Units Present and Advanced PreDesignate option enabled"); } else { return false; @@ -10037,8 +10037,7 @@ && hasAbility(OptionsConstants.PILOT_DODGE_MANEUVER) && Compute */ public boolean isEligibleForArtyAutoHitHexes() { return isEligibleForTargetingPhase() - && (isOffBoard() || game.getOptions().booleanOption( - OptionsConstants.ADVCOMBAT_ON_MAP_PREDESIGNATE)); + && (isOffBoard() || game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_ON_MAP_PREDESIGNATE)); } public boolean isEligibleForTargetingPhase() {