Change pathfinding processor to have a per player per frame cycle limit

This commit is contained in:
Retera 2021-01-24 14:37:12 -05:00
parent cacdf7f266
commit d4245e2e65
8 changed files with 463 additions and 287 deletions

View File

@ -20,7 +20,7 @@ import com.etheller.warsmash.viewer5.ViewerTextureRenderable;
* move around the unit selection circles without memory allocations? For now I * move around the unit selection circles without memory allocations? For now I
* plan to simply port the RivSoft stuff, and come back later. * plan to simply port the RivSoft stuff, and come back later.
*/ */
public class SplatModel { public class SplatModel implements Comparable<SplatModel> {
private static final int MAX_VERTICES = 65000; private static final int MAX_VERTICES = 65000;
private static final float NO_ABS_HEIGHT = -257f; private static final float NO_ABS_HEIGHT = -257f;
private final ViewerTextureRenderable texture; private final ViewerTextureRenderable texture;
@ -514,4 +514,24 @@ public class SplatModel {
this.absoluteHeights.size() * 4, RenderMathUtils.wrap(this.absoluteHeights)); this.absoluteHeights.size() * 4, RenderMathUtils.wrap(this.absoluteHeights));
} }
} }
@Override
public int compareTo(final SplatModel other) {
if (this.locations.isEmpty()) {
if (other.locations.isEmpty()) {
return 0;
}
else {
return 1;
}
}
else {
if (other.locations.isEmpty()) {
return -1;
}
else {
return Float.compare(this.locations.get(0)[4], other.locations.get(0)[4]);
}
}
}
} }

View File

@ -7,6 +7,7 @@ import java.nio.Buffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -120,6 +121,7 @@ public class Terrain {
public final DataTable uberSplatTable; public final DataTable uberSplatTable;
private final Map<String, SplatModel> uberSplatModels; private final Map<String, SplatModel> uberSplatModels;
private final List<SplatModel> uberSplatModelsList;
private int shadowMap; private int shadowMap;
public final Map<String, Splat> splats = new HashMap<>(); public final Map<String, Splat> splats = new HashMap<>();
public final Map<String, List<float[]>> shadows = new HashMap<>(); public final Map<String, List<float[]>> shadows = new HashMap<>();
@ -414,6 +416,7 @@ public class Terrain {
this.centerOffset = w3eFile.getCenterOffset(); this.centerOffset = w3eFile.getCenterOffset();
this.uberSplatModels = new LinkedHashMap<>(); this.uberSplatModels = new LinkedHashMap<>();
this.uberSplatModelsList = new ArrayList<>();
this.mapBounds = w3iFile.getCameraBoundsComplements(); this.mapBounds = w3iFile.getCameraBoundsComplements();
this.shaderMapBounds = new float[] { (this.mapBounds[0] * 128.0f) + this.centerOffset[0], this.shaderMapBounds = new float[] { (this.mapBounds[0] * 128.0f) + this.centerOffset[0],
(this.mapBounds[2] * 128.0f) + this.centerOffset[1], (this.mapBounds[2] * 128.0f) + this.centerOffset[1],
@ -1040,7 +1043,7 @@ public class Terrain {
gl.glUniform1f(shader.getUniformLocation("u_lightTextureHeight"), terrainLightsTexture.getHeight()); gl.glUniform1f(shader.getUniformLocation("u_lightTextureHeight"), terrainLightsTexture.getHeight());
// Render the cliffs // Render the cliffs
for (final SplatModel splat : this.uberSplatModels.values()) { for (final SplatModel splat : this.uberSplatModelsList) {
if (splat.isNoDepthTest() == onTopLayer) { if (splat.isNoDepthTest() == onTopLayer) {
splat.render(gl, shader); splat.render(gl, shader);
} }
@ -1432,16 +1435,18 @@ public class Terrain {
(Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset,
splat.unitMapping.isEmpty() ? null : splat.unitMapping, false, false); splat.unitMapping.isEmpty() ? null : splat.unitMapping, false, false);
splatModel.color[3] = splat.opacity; splatModel.color[3] = splat.opacity;
this.uberSplatModels.put(path, splatModel); this.addSplatBatchModel(path, splatModel);
} }
} }
public void removeSplatBatchModel(final String path) { public void removeSplatBatchModel(final String path) {
this.uberSplatModels.remove(path); this.uberSplatModelsList.remove(this.uberSplatModels.remove(path));
} }
public void addSplatBatchModel(final String path, final SplatModel model) { public void addSplatBatchModel(final String path, final SplatModel model) {
this.uberSplatModels.put(path, model); this.uberSplatModels.put(path, model);
this.uberSplatModelsList.add(model);
Collections.sort(this.uberSplatModelsList);
} }
public SplatMover addUberSplat(final String path, final float x, final float y, final float z, final float scale, public SplatMover addUberSplat(final String path, final float x, final float y, final float z, final float scale,
@ -1450,7 +1455,7 @@ public class Terrain {
if (splatModel == null) { if (splatModel == null) {
splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null),
new ArrayList<>(), this.centerOffset, new ArrayList<>(), unshaded, noDepthTest); new ArrayList<>(), this.centerOffset, new ArrayList<>(), unshaded, noDepthTest);
this.uberSplatModels.put(path, splatModel); this.addSplatBatchModel(path, splatModel);
} }
return splatModel.add(x - scale, y - scale, x + scale, y + scale, z, this.centerOffset); return splatModel.add(x - scale, y - scale, x + scale, y + scale, z, this.centerOffset);
} }
@ -1462,7 +1467,7 @@ public class Terrain {
splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(texture, PathSolver.DEFAULT, null), splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(texture, PathSolver.DEFAULT, null),
new ArrayList<>(), this.centerOffset, new ArrayList<>(), false, false); new ArrayList<>(), this.centerOffset, new ArrayList<>(), false, false);
splatModel.color[3] = opacity; splatModel.color[3] = opacity;
this.uberSplatModels.put(texture, splatModel); this.addSplatBatchModel(texture, splatModel);
} }
return splatModel.add(x, y, x2, y2, zDepthUpward, this.centerOffset); return splatModel.add(x, y, x2, y2, zDepthUpward, this.centerOffset);
} }

View File

@ -19,6 +19,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile;
@ -48,7 +49,7 @@ public class CSimulation {
private int gameTurnTick = 0; private int gameTurnTick = 0;
private final PathingGrid pathingGrid; private final PathingGrid pathingGrid;
private final CWorldCollision worldCollision; private final CWorldCollision worldCollision;
private final CPathfindingProcessor pathfindingProcessor; private final CPathfindingProcessor[] pathfindingProcessors;
private final CGameplayConstants gameplayConstants; private final CGameplayConstants gameplayConstants;
private final Random seededRandom; private final Random seededRandom;
private float currentGameDayTimeElapsed; private float currentGameDayTimeElapsed;
@ -75,7 +76,10 @@ public class CSimulation {
this.newProjectiles = new ArrayList<>(); this.newProjectiles = new ArrayList<>();
this.handleIdAllocator = new HandleIdAllocator(); this.handleIdAllocator = new HandleIdAllocator();
this.worldCollision = new CWorldCollision(entireMapBounds, this.gameplayConstants.getMaxCollisionRadius()); this.worldCollision = new CWorldCollision(entireMapBounds, this.gameplayConstants.getMaxCollisionRadius());
this.pathfindingProcessor = new CPathfindingProcessor(pathingGrid, this.worldCollision); this.pathfindingProcessors = new CPathfindingProcessor[WarsmashConstants.MAX_PLAYERS];
for (int i = 0; i < this.pathfindingProcessors.length; i++) {
this.pathfindingProcessors[i] = new CPathfindingProcessor(pathingGrid, this.worldCollision);
}
this.seededRandom = seededRandom; this.seededRandom = seededRandom;
this.players = new ArrayList<>(); this.players = new ArrayList<>();
for (int i = 0; i < (WarsmashConstants.MAX_PLAYERS - 4); i++) { for (int i = 0; i < (WarsmashConstants.MAX_PLAYERS - 4); i++) {
@ -175,13 +179,19 @@ public class CSimulation {
return this.pathingGrid; return this.pathingGrid;
} }
public List<Point2D.Float> findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, public void findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit,
final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY, final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY,
final Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize, final Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize,
final boolean allowSmoothing) { final boolean allowSmoothing, final CBehaviorMove queueItem) {
return this.pathfindingProcessor.findNaiveSlowPath(ignoreIntersectionsWithThisUnit, final int playerIndex = queueItem.getUnit().getPlayerIndex();
this.pathfindingProcessors[playerIndex].findNaiveSlowPath(ignoreIntersectionsWithThisUnit,
ignoreIntersectionsWithThisSecondUnit, startX, startY, goal, movementType, collisionSize, ignoreIntersectionsWithThisSecondUnit, startX, startY, goal, movementType, collisionSize,
allowSmoothing); allowSmoothing, queueItem);
}
public void removeFromPathfindingQueue(final CBehaviorMove behaviorMove) {
final int playerIndex = behaviorMove.getUnit().getPlayerIndex();
this.pathfindingProcessors[playerIndex].removeFromPathfindingQueue(behaviorMove);
} }
public void update() { public void update() {
@ -207,6 +217,9 @@ public class CSimulation {
} }
this.projectiles.addAll(this.newProjectiles); this.projectiles.addAll(this.newProjectiles);
this.newProjectiles.clear(); this.newProjectiles.clear();
for (final CPathfindingProcessor pathfindingProcessor : this.pathfindingProcessors) {
pathfindingProcessor.update(this);
}
this.gameTurnTick++; this.gameTurnTick++;
this.currentGameDayTimeElapsed = (this.currentGameDayTimeElapsed + WarsmashConstants.SIMULATION_STEP_TIME) this.currentGameDayTimeElapsed = (this.currentGameDayTimeElapsed + WarsmashConstants.SIMULATION_STEP_TIME)
% this.gameplayConstants.getGameDayLength(); % this.gameplayConstants.getGameDayLength();

View File

@ -317,8 +317,6 @@ public class CUnit extends CWidget {
this.currentBehavior.begin(game); this.currentBehavior.begin(game);
} }
if (this.currentBehavior.getHighlightOrderId() != lastBehaviorHighlightOrderId) { if (this.currentBehavior.getHighlightOrderId() != lastBehaviorHighlightOrderId) {
System.out.println("order ID change detected from "
+ this.currentBehavior.getHighlightOrderId() + " to " + lastBehaviorHighlightOrderId);
this.stateNotifier.ordersChanged(); this.stateNotifier.ordersChanged();
} }
} }

View File

@ -55,6 +55,8 @@ public class CUnitType {
private final int buildTime; private final int buildTime;
private final EnumSet<CBuildingPathingType> preventedPathingTypes; private final EnumSet<CBuildingPathingType> preventedPathingTypes;
private final EnumSet<CBuildingPathingType> requiredPathingTypes; private final EnumSet<CBuildingPathingType> requiredPathingTypes;
private final float propWindow;
private final float turnRate;
public CUnitType(final String name, final int life, final int manaInitial, final int manaMaximum, final int speed, public CUnitType(final String name, final int life, final int manaInitial, final int manaMaximum, final int speed,
final int defense, final String abilityList, final boolean isBldg, final MovementType movementType, final int defense, final String abilityList, final boolean isBldg, final MovementType movementType,
@ -66,7 +68,7 @@ public class CUnitType {
final List<War3ID> unitsTrained, final List<War3ID> researchesAvailable, final CUnitRace unitRace, final List<War3ID> unitsTrained, final List<War3ID> researchesAvailable, final CUnitRace unitRace,
final int goldCost, final int lumberCost, final int foodUsed, final int foodMade, final int buildTime, final int goldCost, final int lumberCost, final int foodUsed, final int foodMade, final int buildTime,
final EnumSet<CBuildingPathingType> preventedPathingTypes, final EnumSet<CBuildingPathingType> preventedPathingTypes,
final EnumSet<CBuildingPathingType> requiredPathingTypes) { final EnumSet<CBuildingPathingType> requiredPathingTypes, final float propWindow, final float turnRate) {
this.name = name; this.name = name;
this.life = life; this.life = life;
this.manaInitial = manaInitial; this.manaInitial = manaInitial;
@ -101,6 +103,8 @@ public class CUnitType {
this.buildTime = buildTime; this.buildTime = buildTime;
this.preventedPathingTypes = preventedPathingTypes; this.preventedPathingTypes = preventedPathingTypes;
this.requiredPathingTypes = requiredPathingTypes; this.requiredPathingTypes = requiredPathingTypes;
this.propWindow = propWindow;
this.turnRate = turnRate;
} }
public String getName() { public String getName() {
@ -238,4 +242,12 @@ public class CUnitType {
public EnumSet<CBuildingPathingType> getRequiredPathingTypes() { public EnumSet<CBuildingPathingType> getRequiredPathingTypes() {
return this.requiredPathingTypes; return this.requiredPathingTypes;
} }
public float getPropWindow() {
return this.propWindow;
}
public float getTurnRate() {
return this.turnRate;
}
} }

View File

@ -21,6 +21,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor;
public class CBehaviorMove implements CBehavior { public class CBehaviorMove implements CBehavior {
private static boolean ALWAYS_INTERRUPT_MOVE = false;
private static final Rectangle tempRect = new Rectangle(); private static final Rectangle tempRect = new Rectangle();
private final CUnit unit; private final CUnit unit;
@ -41,6 +42,9 @@ public class CBehaviorMove implements CBehavior {
private CRangedBehavior rangedBehavior; private CRangedBehavior rangedBehavior;
private boolean firstUpdate = true; private boolean firstUpdate = true;
private boolean disableCollision = false; private boolean disableCollision = false;
private boolean pathfindingActive = false;
private boolean firstPathfindJob = false;
private boolean pathfindingFailedGiveUp;
public CBehaviorMove reset(final int highlightOrderId, final AbilityTarget target) { public CBehaviorMove reset(final int highlightOrderId, final AbilityTarget target) {
target.visit(this.targetVisitingResetter.reset(highlightOrderId)); target.visit(this.targetVisitingResetter.reset(highlightOrderId));
@ -69,6 +73,7 @@ public class CBehaviorMove implements CBehavior {
this.searchCycles = 0; this.searchCycles = 0;
this.followUnit = null; this.followUnit = null;
this.firstUpdate = true; this.firstUpdate = true;
this.pathfindingFailedGiveUp = false;
} }
private void internalResetMove(final int highlightOrderId, final CUnit followUnit) { private void internalResetMove(final int highlightOrderId, final CUnit followUnit) {
@ -82,6 +87,7 @@ public class CBehaviorMove implements CBehavior {
this.searchCycles = 0; this.searchCycles = 0;
this.followUnit = followUnit; this.followUnit = followUnit;
this.firstUpdate = true; this.firstUpdate = true;
this.pathfindingFailedGiveUp = false;
} }
@Override @Override
@ -100,6 +106,9 @@ public class CBehaviorMove implements CBehavior {
this.unit.setPointAndCheckUnstuck(this.unit.getX(), this.unit.getY(), simulation); this.unit.setPointAndCheckUnstuck(this.unit.getX(), this.unit.getY(), simulation);
this.firstUpdate = false; this.firstUpdate = false;
} }
if (this.pathfindingFailedGiveUp) {
return this.unit.pollNextOrderBehavior(simulation);
}
final float prevX = this.unit.getX(); final float prevX = this.unit.getX();
final float prevY = this.unit.getY(); final float prevY = this.unit.getY();
@ -116,72 +125,31 @@ public class CBehaviorMove implements CBehavior {
final float startFloatingX = prevX; final float startFloatingX = prevX;
final float startFloatingY = prevY; final float startFloatingY = prevY;
if (this.path == null) { if (this.path == null) {
if (!this.pathfindingActive) {
if (this.followUnit != null) { if (this.followUnit != null) {
this.target.x = this.followUnit.getX(); this.target.x = this.followUnit.getX();
this.target.y = this.followUnit.getY(); this.target.y = this.followUnit.getY();
} }
this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, this.target,
this.target, movementType, collisionSize, true); movementType, collisionSize, true, this);
System.out.println("init path " + this.path); this.pathfindingActive = true;
// check for smoothing this.firstPathfindJob = true;
if (!this.path.isEmpty()) {
float lastX = startFloatingX;
float lastY = startFloatingY;
float smoothingGroupStartX = startFloatingX;
float smoothingGroupStartY = startFloatingY;
final Point2D.Float firstPathElement = this.path.get(0);
double totalPathDistance = firstPathElement.distance(lastX, lastY);
lastX = firstPathElement.x;
lastY = firstPathElement.y;
int smoothingStartIndex = -1;
for (int i = 0; i < (this.path.size() - 1); i++) {
final Point2D.Float nextPossiblePathElement = this.path.get(i + 1);
totalPathDistance += nextPossiblePathElement.distance(lastX, lastY);
if ((totalPathDistance < (1.15
* nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY)))
&& pathingGrid.isPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2,
(smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) {
if (smoothingStartIndex == -1) {
smoothingStartIndex = i;
}
}
else {
if (smoothingStartIndex != -1) {
for (int j = i - 1; j >= smoothingStartIndex; j--) {
this.path.remove(j);
}
i = smoothingStartIndex;
}
smoothingStartIndex = -1;
final Point2D.Float smoothGroupNext = this.path.get(i);
smoothingGroupStartX = smoothGroupNext.x;
smoothingGroupStartY = smoothGroupNext.y;
totalPathDistance = nextPossiblePathElement.distance(smoothGroupNext);
}
lastX = nextPossiblePathElement.x;
lastY = nextPossiblePathElement.y;
}
if (smoothingStartIndex != -1) {
for (int j = smoothingStartIndex; j < (this.path.size() - 1); j++) {
final Point2D.Float removed = this.path.remove(j);
}
}
} }
} }
else if ((this.followUnit != null) && (this.path.size() > 1) && (this.target.distance(this.followUnit.getX(), else if ((this.followUnit != null) && (this.path.size() > 1) && (this.target.distance(this.followUnit.getX(),
this.followUnit.getY()) > (0.1 * this.target.distance(this.unit.getX(), this.unit.getY())))) { this.followUnit.getY()) > (0.1 * this.target.distance(this.unit.getX(), this.unit.getY())))) {
this.target.x = this.followUnit.getX(); this.target.x = this.followUnit.getX();
this.target.y = this.followUnit.getY(); this.target.y = this.followUnit.getY();
this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, if (this.pathfindingActive) {
this.target, movementType, collisionSize, this.searchCycles < 4); simulation.removeFromPathfindingQueue(this);
System.out.println("new path (for target) " + this.path);
if (this.path.isEmpty()) {
return this.unit.pollNextOrderBehavior(simulation);
} }
simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, this.target,
movementType, collisionSize, this.searchCycles < 4, this);
this.pathfindingActive = true;
} }
float currentTargetX; float currentTargetX;
float currentTargetY; float currentTargetY;
if (this.path.isEmpty()) { if ((this.path == null) || this.path.isEmpty()) {
if (this.followUnit != null) { if (this.followUnit != null) {
currentTargetX = this.followUnit.getX(); currentTargetX = this.followUnit.getX();
currentTargetY = this.followUnit.getY(); currentTargetY = this.followUnit.getY();
@ -212,8 +180,8 @@ public class CBehaviorMove implements CBehavior {
} }
float facing = this.unit.getFacing(); float facing = this.unit.getFacing();
float delta = goalAngle - facing; float delta = goalAngle - facing;
final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); final float propulsionWindow = this.unit.getUnitType().getPropWindow();
final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); final float turnRate = this.unit.getUnitType().getTurnRate();
final int speed = this.unit.getSpeed(); final int speed = this.unit.getSpeed();
if (delta < -180) { if (delta < -180) {
@ -235,7 +203,7 @@ public class CBehaviorMove implements CBehavior {
facing += angleToAdd; facing += angleToAdd;
this.unit.setFacing(facing); this.unit.setFacing(facing);
} }
if (absDelta < propulsionWindow) { if ((this.path != null) && !this.pathfindingActive && (absDelta < propulsionWindow)) {
final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME;
double continueDistance = speedTick; double continueDistance = speedTick;
do { do {
@ -341,12 +309,12 @@ public class CBehaviorMove implements CBehavior {
this.target.x = this.followUnit.getX(); this.target.x = this.followUnit.getX();
this.target.y = this.followUnit.getY(); this.target.y = this.followUnit.getY();
} }
this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, if (!this.pathfindingActive) {
this.target, movementType, collisionSize, this.searchCycles < 4); simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY,
this.target, movementType, collisionSize, this.searchCycles < 4, this);
this.pathfindingActive = true;
this.searchCycles++; this.searchCycles++;
System.out.println("new path " + this.path); return this;
if (this.path.isEmpty() || (this.searchCycles > 5)) {
return this.unit.pollNextOrderBehavior(simulation);
} }
} }
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.WALK, SequenceUtils.EMPTY, 1.0f, this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.WALK, SequenceUtils.EMPTY, 1.0f,
@ -408,6 +376,86 @@ public class CBehaviorMove implements CBehavior {
@Override @Override
public void end(final CSimulation game) { public void end(final CSimulation game) {
if (ALWAYS_INTERRUPT_MOVE) {
game.removeFromPathfindingQueue(this);
this.pathfindingActive = false;
}
}
public CUnit getUnit() {
return this.unit;
}
public void pathFound(final List<Point2D.Float> waypoints, final CSimulation simulation) {
this.pathfindingActive = false;
final float prevX = this.unit.getX();
final float prevY = this.unit.getY();
MovementType movementType = this.unit.getUnitType().getMovementType();
if (movementType == null) {
movementType = MovementType.DISABLED;
}
else if ((movementType == MovementType.FOOT) && this.disableCollision) {
movementType = MovementType.FOOT_NO_COLLISION;
}
final PathingGrid pathingGrid = simulation.getPathingGrid();
final CWorldCollision worldCollision = simulation.getWorldCollision();
final float collisionSize = this.unit.getUnitType().getCollisionSize();
final float startFloatingX = prevX;
final float startFloatingY = prevY;
this.path = waypoints;
if (this.firstPathfindJob) {
this.firstPathfindJob = false;
System.out.println("init path " + this.path);
// check for smoothing
if (!this.path.isEmpty()) {
float lastX = startFloatingX;
float lastY = startFloatingY;
float smoothingGroupStartX = startFloatingX;
float smoothingGroupStartY = startFloatingY;
final Point2D.Float firstPathElement = this.path.get(0);
double totalPathDistance = firstPathElement.distance(lastX, lastY);
lastX = firstPathElement.x;
lastY = firstPathElement.y;
int smoothingStartIndex = -1;
for (int i = 0; i < (this.path.size() - 1); i++) {
final Point2D.Float nextPossiblePathElement = this.path.get(i + 1);
totalPathDistance += nextPossiblePathElement.distance(lastX, lastY);
if ((totalPathDistance < (1.15
* nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY)))
&& pathingGrid.isPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2,
(smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) {
if (smoothingStartIndex == -1) {
smoothingStartIndex = i;
}
}
else {
if (smoothingStartIndex != -1) {
for (int j = i - 1; j >= smoothingStartIndex; j--) {
this.path.remove(j);
}
i = smoothingStartIndex;
}
smoothingStartIndex = -1;
final Point2D.Float smoothGroupNext = this.path.get(i);
smoothingGroupStartX = smoothGroupNext.x;
smoothingGroupStartY = smoothGroupNext.y;
totalPathDistance = nextPossiblePathElement.distance(smoothGroupNext);
}
lastX = nextPossiblePathElement.x;
lastY = nextPossiblePathElement.y;
}
if (smoothingStartIndex != -1) {
for (int j = smoothingStartIndex; j < (this.path.size() - 1); j++) {
final Point2D.Float removed = this.path.remove(j);
}
}
}
}
else if (this.path.isEmpty() || (this.searchCycles > 6)) {
this.pathfindingFailedGiveUp = true;
}
} }
} }

View File

@ -238,6 +238,9 @@ public class CUnitData {
final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0);
final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0);
final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0);
final float propWindow = unitType.getFieldAsFloat(PROPULSION_WINDOW, 0);
final float turnRate = unitType.getFieldAsFloat(TURN_RATE, 0);
final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0);
final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp);
final String unitName = unitType.getFieldAsString(NAME, 0); final String unitName = unitType.getFieldAsString(NAME, 0);
@ -425,7 +428,8 @@ public class CUnitData {
isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay, isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay,
defenseType, impactZ, buildingPathingPixelMap, deathTime, targetedAs, acquisitionRange, defenseType, impactZ, buildingPathingPixelMap, deathTime, targetedAs, acquisitionRange,
minimumAttackRange, structuresBuilt, unitsTrained, researchesAvailable, unitRace, goldCost, minimumAttackRange, structuresBuilt, unitsTrained, researchesAvailable, unitRace, goldCost,
lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes); lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes, propWindow,
turnRate);
this.unitIdToUnitType.put(typeId, unitTypeInstance); this.unitIdToUnitType.put(typeId, unitTypeInstance);
} }
return unitTypeInstance; return unitTypeInstance;

View File

@ -4,19 +4,24 @@ import java.awt.geom.Point2D;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue; import java.util.PriorityQueue;
import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Rectangle;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove;
public class CPathfindingProcessor { public class CPathfindingProcessor {
private static final Rectangle tempRect = new Rectangle(); private static final Rectangle tempRect = new Rectangle();
private final PathingGrid pathingGrid; private final PathingGrid pathingGrid;
private final CWorldCollision worldCollision; private final CWorldCollision worldCollision;
private final LinkedList<PathfindingJob> moveQueue = new LinkedList<>();
// things with modified state per current job:
private final Node[][] nodes; private final Node[][] nodes;
private final Node[][] cornerNodes; private final Node[][] cornerNodes;
private final Node[] goalSet = new Node[4]; private final Node[] goalSet = new Node[4];
@ -52,217 +57,29 @@ public class CPathfindingProcessor {
* *
* @param start * @param start
* @param goal * @param goal
* @param playerIndex
* @param queueItem
* @return * @return
*/ */
public List<Point2D.Float> findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX, public void findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit,
final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType,
final float collisionSize, final boolean allowSmoothing) {
return findNaiveSlowPath(ignoreIntersectionsWithThisUnit, null, startX, startY, goal, movementType,
collisionSize, allowSmoothing);
}
public List<Point2D.Float> findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit,
final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY, final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY,
final Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize, final Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize,
final boolean allowSmoothing) { final boolean allowSmoothing, final CBehaviorMove queueItem) {
final float goalX = goal.x; this.moveQueue.offer(new PathfindingJob(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
final float goalY = goal.y; startX, startY, goal, movementType, collisionSize, allowSmoothing, queueItem));
float weightForHittingWalls = 1E9f;
if (!this.pathingGrid.isPathable(goalX, goalY, movementType, collisionSize) || !isPathableDynamically(goalX,
goalY, ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, movementType)) {
weightForHittingWalls = 5E2f;
} }
System.out.println("beginning findNaiveSlowPath for " + startX + "," + startY + "," + goalX + "," + goalY);
if ((startX == goalX) && (startY == goalY)) {
return Collections.emptyList();
}
tempRect.set(0, 0, collisionSize * 2, collisionSize * 2);
Node[][] searchGraph;
GridMapping gridMapping;
if (isCollisionSizeBetterSuitedForCorners(collisionSize)) {
searchGraph = this.cornerNodes;
gridMapping = GridMapping.CORNERS;
System.out.println("using corners");
}
else {
searchGraph = this.nodes;
gridMapping = GridMapping.CELLS;
System.out.println("using cells");
}
final int goalCellY = gridMapping.getY(this.pathingGrid, goalY);
final int goalCellX = gridMapping.getX(this.pathingGrid, goalX);
final Node mostLikelyGoal = searchGraph[goalCellY][goalCellX];
final double bestGoalDistance = mostLikelyGoal.point.distance(goalX, goalY);
Arrays.fill(this.goalSet, null);
this.goals = 0;
for (int i = goalCellX - 1; i <= (goalCellX + 1); i++) {
for (int j = goalCellY - 1; j <= (goalCellY + 1); j++) {
final Node possibleGoal = searchGraph[j][i];
if (possibleGoal.point.distance(goalX, goalY) <= bestGoalDistance) {
this.goalSet[this.goals++] = possibleGoal;
}
}
}
final int startGridY = gridMapping.getY(this.pathingGrid, startY);
final int startGridX = gridMapping.getX(this.pathingGrid, startX);
for (int i = 0; i < searchGraph.length; i++) {
for (int j = 0; j < searchGraph[i].length; j++) {
final Node node = searchGraph[i][j];
node.g = Float.POSITIVE_INFINITY;
node.f = Float.POSITIVE_INFINITY;
node.cameFrom = null;
node.cameFromDirection = null;
}
}
final PriorityQueue<Node> openSet = new PriorityQueue<>(new Comparator<Node>() {
@Override
public int compare(final Node a, final Node b) {
return Double.compare(f(a), f(b));
}
});
final Node start = searchGraph[startGridY][startGridX]; public void removeFromPathfindingQueue(final CBehaviorMove behaviorMove) {
int startGridMinX; // TODO because of silly java things, this remove is O(N) for now,
int startGridMinY; // we could do some refactors to make it O(1) but do we care?
int startGridMaxX; final Iterator<PathfindingJob> iterator = this.moveQueue.iterator();
int startGridMaxY; while (iterator.hasNext()) {
if (startX > start.point.x) { final PathfindingJob job = iterator.next();
startGridMinX = startGridX; if (job.queueItem == behaviorMove) {
startGridMaxX = startGridX + 1; iterator.remove();
}
else if (startX < start.point.x) {
startGridMinX = startGridX - 1;
startGridMaxX = startGridX;
}
else {
startGridMinX = startGridX;
startGridMaxX = startGridX;
}
if (startY > start.point.y) {
startGridMinY = startGridY;
startGridMaxY = startGridY + 1;
}
else if (startY < start.point.y) {
startGridMinY = startGridY - 1;
startGridMaxY = startGridY;
}
else {
startGridMinY = startGridY;
startGridMaxY = startGridY;
}
for (int cellX = startGridMinX; cellX <= startGridMaxX; cellX++) {
for (int cellY = startGridMinY; cellY <= startGridMaxY; cellY++) {
if ((cellX >= 0) && (cellX < this.pathingGrid.getWidth()) && (cellY >= 0)
&& (cellY < this.pathingGrid.getHeight())) {
final Node possibleNode = searchGraph[cellY][cellX];
final float x = possibleNode.point.x;
final float y = possibleNode.point.y;
if (pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, startX,
startY, movementType, collisionSize, x, y)) {
final double tentativeScore = possibleNode.point.distance(startX, startY);
possibleNode.g = tentativeScore;
possibleNode.f = tentativeScore + h(possibleNode);
openSet.add(possibleNode);
}
else {
final double tentativeScore = weightForHittingWalls;
possibleNode.g = tentativeScore;
possibleNode.f = tentativeScore + h(possibleNode);
openSet.add(possibleNode);
}
}
} }
} }
int searchIterations = 0;
while (!openSet.isEmpty() && searchIterations < 150000) {
Node current = openSet.poll();
if (isGoal(current)) {
final LinkedList<Point2D.Float> totalPath = new LinkedList<>();
Direction lastCameFromDirection = null;
if ((current.cameFrom != null)
&& pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
current.point.x, current.point.y, movementType, collisionSize, goalX, goalY)
&& pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
current.cameFrom.point.x, current.cameFrom.point.y, movementType, collisionSize,
current.point.x, current.point.y)
&& pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
current.cameFrom.point.x, current.cameFrom.point.y, movementType, collisionSize, goalX,
goalY)
&& allowSmoothing) {
// do some basic smoothing to walk straight to the goal if it is not obstructed,
// skipping the last grid location
totalPath.addFirst(goal);
current = current.cameFrom;
}
else {
totalPath.addFirst(goal);
totalPath.addFirst(current.point);
}
lastCameFromDirection = current.cameFromDirection;
Node lastNode = null;
while (current.cameFrom != null) {
lastNode = current;
current = current.cameFrom;
if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection)
|| (current.cameFromDirection == null)) {
if ((current.cameFromDirection != null) || (lastNode == null)
|| !pathableBetween(ignoreIntersectionsWithThisUnit,
ignoreIntersectionsWithThisSecondUnit, startX, startY, movementType,
collisionSize, current.point.x, current.point.y)
|| !pathableBetween(ignoreIntersectionsWithThisUnit,
ignoreIntersectionsWithThisSecondUnit, current.point.x, current.point.y,
movementType, collisionSize, lastNode.point.x, lastNode.point.y)
|| !pathableBetween(ignoreIntersectionsWithThisUnit,
ignoreIntersectionsWithThisSecondUnit, startX, startY, movementType,
collisionSize, lastNode.point.x, lastNode.point.y)
|| !allowSmoothing) {
// Add the point if it's not the first one, or if we can only complete
// the journey by specifically walking to the first one
totalPath.addFirst(current.point);
lastCameFromDirection = current.cameFromDirection;
}
}
}
return totalPath;
}
for (final Direction direction : Direction.VALUES) {
final float x = current.point.x + (direction.xOffset * 32);
final float y = current.point.y + (direction.yOffset * 32);
if (this.pathingGrid.contains(x, y)) {
double turnCost;
if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) {
turnCost = 0.25;
}
else {
turnCost = 0;
}
double tentativeScore = current.g + ((direction.length + turnCost) * 32);
if (!pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
current.point.x, current.point.y, movementType, collisionSize, x, y)) {
tentativeScore += (direction.length) * weightForHittingWalls;
}
final Node neighbor = searchGraph[gridMapping.getY(this.pathingGrid, y)][gridMapping
.getX(this.pathingGrid, x)];
if (tentativeScore < neighbor.g) {
neighbor.cameFrom = current;
neighbor.cameFromDirection = direction;
neighbor.g = tentativeScore;
neighbor.f = tentativeScore + h(neighbor);
if (!openSet.contains(neighbor)) {
openSet.add(neighbor);
}
}
}
}
searchIterations++;
}
return Collections.emptyList();
} }
private boolean pathableBetween(final CUnit ignoreIntersectionsWithThisUnit, private boolean pathableBetween(final CUnit ignoreIntersectionsWithThisUnit,
@ -384,4 +201,263 @@ public class CPathfindingProcessor {
}; };
} }
public void update(final CSimulation simulation) {
int workIterations = 0;
JobsLoop: while (!this.moveQueue.isEmpty()) {
final PathfindingJob job = this.moveQueue.peek();
if (!job.jobStarted) {
job.jobStarted = true;
System.out.println("starting job with smoothing=" + job.allowSmoothing);
workIterations += 5; // setup of job predicted cost
job.goalX = job.goal.x;
job.goalY = job.goal.y;
job.weightForHittingWalls = 1E9f;
if (!this.pathingGrid.isPathable(job.goalX, job.goalY, job.movementType, job.collisionSize)
|| !isPathableDynamically(job.goalX, job.goalY, job.ignoreIntersectionsWithThisUnit,
job.ignoreIntersectionsWithThisSecondUnit, job.movementType)) {
job.weightForHittingWalls = 5E2f;
}
System.out.println("beginning findNaiveSlowPath for " + job.startX + "," + job.startY + "," + job.goalX
+ "," + job.goalY);
if ((job.startX == job.goalX) && (job.startY == job.goalY)) {
job.queueItem.pathFound(Collections.emptyList(), simulation);
this.moveQueue.poll();
continue JobsLoop;
}
tempRect.set(0, 0, job.collisionSize * 2, job.collisionSize * 2);
if (isCollisionSizeBetterSuitedForCorners(job.collisionSize)) {
job.searchGraph = this.cornerNodes;
job.gridMapping = GridMapping.CORNERS;
System.out.println("using corners");
}
else {
job.searchGraph = this.nodes;
job.gridMapping = GridMapping.CELLS;
System.out.println("using cells");
}
final int goalCellY = job.gridMapping.getY(this.pathingGrid, job.goalY);
final int goalCellX = job.gridMapping.getX(this.pathingGrid, job.goalX);
final Node mostLikelyGoal = job.searchGraph[goalCellY][goalCellX];
final double bestGoalDistance = mostLikelyGoal.point.distance(job.goalX, job.goalY);
Arrays.fill(this.goalSet, null);
this.goals = 0;
for (int i = goalCellX - 1; i <= (goalCellX + 1); i++) {
for (int j = goalCellY - 1; j <= (goalCellY + 1); j++) {
final Node possibleGoal = job.searchGraph[j][i];
if (possibleGoal.point.distance(job.goalX, job.goalY) <= bestGoalDistance) {
this.goalSet[this.goals++] = possibleGoal;
}
}
}
final int startGridY = job.gridMapping.getY(this.pathingGrid, job.startY);
final int startGridX = job.gridMapping.getX(this.pathingGrid, job.startX);
for (int i = 0; i < job.searchGraph.length; i++) {
for (int j = 0; j < job.searchGraph[i].length; j++) {
final Node node = job.searchGraph[i][j];
node.g = Float.POSITIVE_INFINITY;
node.f = Float.POSITIVE_INFINITY;
node.cameFrom = null;
node.cameFromDirection = null;
workIterations++;
}
}
job.openSet = new PriorityQueue<>(new Comparator<Node>() {
@Override
public int compare(final Node a, final Node b) {
return Double.compare(f(a), f(b));
}
});
job.start = job.searchGraph[startGridY][startGridX];
if (job.startX > job.start.point.x) {
job.startGridMinX = startGridX;
job.startGridMaxX = startGridX + 1;
}
else if (job.startX < job.start.point.x) {
job.startGridMinX = startGridX - 1;
job.startGridMaxX = startGridX;
}
else {
job.startGridMinX = startGridX;
job.startGridMaxX = startGridX;
}
if (job.startY > job.start.point.y) {
job.startGridMinY = startGridY;
job.startGridMaxY = startGridY + 1;
}
else if (job.startY < job.start.point.y) {
job.startGridMinY = startGridY - 1;
job.startGridMaxY = startGridY;
}
else {
job.startGridMinY = startGridY;
job.startGridMaxY = startGridY;
}
for (int cellX = job.startGridMinX; cellX <= job.startGridMaxX; cellX++) {
for (int cellY = job.startGridMinY; cellY <= job.startGridMaxY; cellY++) {
if ((cellX >= 0) && (cellX < this.pathingGrid.getWidth()) && (cellY >= 0)
&& (cellY < this.pathingGrid.getHeight())) {
final Node possibleNode = job.searchGraph[cellY][cellX];
final float x = possibleNode.point.x;
final float y = possibleNode.point.y;
if (pathableBetween(job.ignoreIntersectionsWithThisUnit,
job.ignoreIntersectionsWithThisSecondUnit, job.startX, job.startY, job.movementType,
job.collisionSize, x, y)) {
final double tentativeScore = possibleNode.point.distance(job.startX, job.startY);
possibleNode.g = tentativeScore;
possibleNode.f = tentativeScore + h(possibleNode);
job.openSet.add(possibleNode);
}
else {
final double tentativeScore = job.weightForHittingWalls;
possibleNode.g = tentativeScore;
possibleNode.f = tentativeScore + h(possibleNode);
job.openSet.add(possibleNode);
}
}
}
}
}
while (!job.openSet.isEmpty()) {
Node current = job.openSet.poll();
if (isGoal(current)) {
final LinkedList<Point2D.Float> totalPath = new LinkedList<>();
Direction lastCameFromDirection = null;
if ((current.cameFrom != null)
&& pathableBetween(job.ignoreIntersectionsWithThisUnit,
job.ignoreIntersectionsWithThisSecondUnit, current.point.x, current.point.y,
job.movementType, job.collisionSize, job.goalX, job.goalY)
&& pathableBetween(job.ignoreIntersectionsWithThisUnit,
job.ignoreIntersectionsWithThisSecondUnit, current.cameFrom.point.x,
current.cameFrom.point.y, job.movementType, job.collisionSize, current.point.x,
current.point.y)
&& pathableBetween(job.ignoreIntersectionsWithThisUnit,
job.ignoreIntersectionsWithThisSecondUnit, current.cameFrom.point.x,
current.cameFrom.point.y, job.movementType, job.collisionSize, job.goalX, job.goalY)
&& job.allowSmoothing) {
// do some basic smoothing to walk straight to the goal if it is not obstructed,
// skipping the last grid location
totalPath.addFirst(job.goal);
current = current.cameFrom;
}
else {
totalPath.addFirst(job.goal);
totalPath.addFirst(current.point);
}
lastCameFromDirection = current.cameFromDirection;
Node lastNode = null;
while (current.cameFrom != null) {
lastNode = current;
current = current.cameFrom;
if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection)
|| (current.cameFromDirection == null)) {
if ((current.cameFromDirection != null) || (lastNode == null)
|| !pathableBetween(job.ignoreIntersectionsWithThisUnit,
job.ignoreIntersectionsWithThisSecondUnit, job.startX, job.startY,
job.movementType, job.collisionSize, current.point.x, current.point.y)
|| !pathableBetween(job.ignoreIntersectionsWithThisUnit,
job.ignoreIntersectionsWithThisSecondUnit, current.point.x, current.point.y,
job.movementType, job.collisionSize, lastNode.point.x, lastNode.point.y)
|| !pathableBetween(job.ignoreIntersectionsWithThisUnit,
job.ignoreIntersectionsWithThisSecondUnit, job.startX, job.startY,
job.movementType, job.collisionSize, lastNode.point.x, lastNode.point.y)
|| !job.allowSmoothing) {
// Add the point if it's not the first one, or if we can only complete
// the journey by specifically walking to the first one
totalPath.addFirst(current.point);
lastCameFromDirection = current.cameFromDirection;
}
}
}
job.queueItem.pathFound(totalPath, simulation);
this.moveQueue.poll();
continue JobsLoop;
}
for (final Direction direction : Direction.VALUES) {
final float x = current.point.x + (direction.xOffset * 32);
final float y = current.point.y + (direction.yOffset * 32);
if (this.pathingGrid.contains(x, y)) {
double turnCost;
if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) {
turnCost = 0.25;
}
else {
turnCost = 0;
}
double tentativeScore = current.g + ((direction.length + turnCost) * 32);
if (!pathableBetween(job.ignoreIntersectionsWithThisUnit,
job.ignoreIntersectionsWithThisSecondUnit, current.point.x, current.point.y,
job.movementType, job.collisionSize, x, y)) {
tentativeScore += (direction.length) * job.weightForHittingWalls;
}
final Node neighbor = job.searchGraph[job.gridMapping.getY(this.pathingGrid, y)][job.gridMapping
.getX(this.pathingGrid, x)];
if (tentativeScore < neighbor.g) {
neighbor.cameFrom = current;
neighbor.cameFromDirection = direction;
neighbor.g = tentativeScore;
neighbor.f = tentativeScore + h(neighbor);
if (!job.openSet.contains(neighbor)) {
job.openSet.add(neighbor);
}
}
}
}
workIterations++;
if (workIterations >= 15000) {
// breaking jobs loop will implicitly exit without calling pathFound() below
break JobsLoop;
}
}
job.queueItem.pathFound(Collections.emptyList(), simulation);
this.moveQueue.poll();
}
}
public static final class PathfindingJob {
private final CUnit ignoreIntersectionsWithThisUnit;
private final CUnit ignoreIntersectionsWithThisSecondUnit;
private final float startX;
private final float startY;
private final Point2D.Float goal;
private final MovementType movementType;
private final float collisionSize;
private final boolean allowSmoothing;
private final CBehaviorMove queueItem;
private boolean jobStarted;
public float goalY;
public float goalX;
public float weightForHittingWalls;
Node[][] searchGraph;
GridMapping gridMapping;
PriorityQueue<Node> openSet;
Node start;
int startGridMinX;
int startGridMinY;
int startGridMaxX;
int startGridMaxY;
public PathfindingJob(final CUnit ignoreIntersectionsWithThisUnit,
final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY,
final Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize,
final boolean allowSmoothing, final CBehaviorMove queueItem) {
this.ignoreIntersectionsWithThisUnit = ignoreIntersectionsWithThisUnit;
this.ignoreIntersectionsWithThisSecondUnit = ignoreIntersectionsWithThisSecondUnit;
this.startX = startX;
this.startY = startY;
this.goal = goal;
this.movementType = movementType;
this.collisionSize = collisionSize;
this.allowSmoothing = allowSmoothing;
this.queueItem = queueItem;
this.jobStarted = false;
}
}
} }