mirror of
https://github.com/Retera/WarsmashModEngine.git
synced 2022-07-31 17:38:59 +02:00
Change pathfinding processor to have a per player per frame cycle limit
This commit is contained in:
parent
cacdf7f266
commit
d4245e2e65
@ -20,7 +20,7 @@ import com.etheller.warsmash.viewer5.ViewerTextureRenderable;
|
||||
* move around the unit selection circles without memory allocations? For now I
|
||||
* 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 float NO_ABS_HEIGHT = -257f;
|
||||
private final ViewerTextureRenderable texture;
|
||||
@ -514,4 +514,24 @@ public class SplatModel {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import java.nio.Buffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
@ -120,6 +121,7 @@ public class Terrain {
|
||||
public final DataTable uberSplatTable;
|
||||
|
||||
private final Map<String, SplatModel> uberSplatModels;
|
||||
private final List<SplatModel> uberSplatModelsList;
|
||||
private int shadowMap;
|
||||
public final Map<String, Splat> splats = new HashMap<>();
|
||||
public final Map<String, List<float[]>> shadows = new HashMap<>();
|
||||
@ -414,6 +416,7 @@ public class Terrain {
|
||||
|
||||
this.centerOffset = w3eFile.getCenterOffset();
|
||||
this.uberSplatModels = new LinkedHashMap<>();
|
||||
this.uberSplatModelsList = new ArrayList<>();
|
||||
this.mapBounds = w3iFile.getCameraBoundsComplements();
|
||||
this.shaderMapBounds = new float[] { (this.mapBounds[0] * 128.0f) + this.centerOffset[0],
|
||||
(this.mapBounds[2] * 128.0f) + this.centerOffset[1],
|
||||
@ -1040,7 +1043,7 @@ public class Terrain {
|
||||
gl.glUniform1f(shader.getUniformLocation("u_lightTextureHeight"), terrainLightsTexture.getHeight());
|
||||
|
||||
// Render the cliffs
|
||||
for (final SplatModel splat : this.uberSplatModels.values()) {
|
||||
for (final SplatModel splat : this.uberSplatModelsList) {
|
||||
if (splat.isNoDepthTest() == onTopLayer) {
|
||||
splat.render(gl, shader);
|
||||
}
|
||||
@ -1432,16 +1435,18 @@ public class Terrain {
|
||||
(Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset,
|
||||
splat.unitMapping.isEmpty() ? null : splat.unitMapping, false, false);
|
||||
splatModel.color[3] = splat.opacity;
|
||||
this.uberSplatModels.put(path, splatModel);
|
||||
this.addSplatBatchModel(path, splatModel);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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,
|
||||
@ -1450,7 +1455,7 @@ public class Terrain {
|
||||
if (splatModel == null) {
|
||||
splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null),
|
||||
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);
|
||||
}
|
||||
@ -1462,7 +1467,7 @@ public class Terrain {
|
||||
splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(texture, PathSolver.DEFAULT, null),
|
||||
new ArrayList<>(), this.centerOffset, new ArrayList<>(), false, false);
|
||||
splatModel.color[3] = opacity;
|
||||
this.uberSplatModels.put(texture, splatModel);
|
||||
this.addSplatBatchModel(texture, splatModel);
|
||||
}
|
||||
return splatModel.add(x, y, x2, y2, zDepthUpward, this.centerOffset);
|
||||
}
|
||||
|
@ -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.simulation.abilities.CAbility;
|
||||
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.CUnitAttackMissile;
|
||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile;
|
||||
@ -48,7 +49,7 @@ public class CSimulation {
|
||||
private int gameTurnTick = 0;
|
||||
private final PathingGrid pathingGrid;
|
||||
private final CWorldCollision worldCollision;
|
||||
private final CPathfindingProcessor pathfindingProcessor;
|
||||
private final CPathfindingProcessor[] pathfindingProcessors;
|
||||
private final CGameplayConstants gameplayConstants;
|
||||
private final Random seededRandom;
|
||||
private float currentGameDayTimeElapsed;
|
||||
@ -75,7 +76,10 @@ public class CSimulation {
|
||||
this.newProjectiles = new ArrayList<>();
|
||||
this.handleIdAllocator = new HandleIdAllocator();
|
||||
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.players = new ArrayList<>();
|
||||
for (int i = 0; i < (WarsmashConstants.MAX_PLAYERS - 4); i++) {
|
||||
@ -175,13 +179,19 @@ public class CSimulation {
|
||||
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 Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize,
|
||||
final boolean allowSmoothing) {
|
||||
return this.pathfindingProcessor.findNaiveSlowPath(ignoreIntersectionsWithThisUnit,
|
||||
final boolean allowSmoothing, final CBehaviorMove queueItem) {
|
||||
final int playerIndex = queueItem.getUnit().getPlayerIndex();
|
||||
this.pathfindingProcessors[playerIndex].findNaiveSlowPath(ignoreIntersectionsWithThisUnit,
|
||||
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() {
|
||||
@ -207,6 +217,9 @@ public class CSimulation {
|
||||
}
|
||||
this.projectiles.addAll(this.newProjectiles);
|
||||
this.newProjectiles.clear();
|
||||
for (final CPathfindingProcessor pathfindingProcessor : this.pathfindingProcessors) {
|
||||
pathfindingProcessor.update(this);
|
||||
}
|
||||
this.gameTurnTick++;
|
||||
this.currentGameDayTimeElapsed = (this.currentGameDayTimeElapsed + WarsmashConstants.SIMULATION_STEP_TIME)
|
||||
% this.gameplayConstants.getGameDayLength();
|
||||
|
@ -317,8 +317,6 @@ public class CUnit extends CWidget {
|
||||
this.currentBehavior.begin(game);
|
||||
}
|
||||
if (this.currentBehavior.getHighlightOrderId() != lastBehaviorHighlightOrderId) {
|
||||
System.out.println("order ID change detected from "
|
||||
+ this.currentBehavior.getHighlightOrderId() + " to " + lastBehaviorHighlightOrderId);
|
||||
this.stateNotifier.ordersChanged();
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,8 @@ public class CUnitType {
|
||||
private final int buildTime;
|
||||
private final EnumSet<CBuildingPathingType> preventedPathingTypes;
|
||||
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,
|
||||
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 int goldCost, final int lumberCost, final int foodUsed, final int foodMade, final int buildTime,
|
||||
final EnumSet<CBuildingPathingType> preventedPathingTypes,
|
||||
final EnumSet<CBuildingPathingType> requiredPathingTypes) {
|
||||
final EnumSet<CBuildingPathingType> requiredPathingTypes, final float propWindow, final float turnRate) {
|
||||
this.name = name;
|
||||
this.life = life;
|
||||
this.manaInitial = manaInitial;
|
||||
@ -101,6 +103,8 @@ public class CUnitType {
|
||||
this.buildTime = buildTime;
|
||||
this.preventedPathingTypes = preventedPathingTypes;
|
||||
this.requiredPathingTypes = requiredPathingTypes;
|
||||
this.propWindow = propWindow;
|
||||
this.turnRate = turnRate;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@ -238,4 +242,12 @@ public class CUnitType {
|
||||
public EnumSet<CBuildingPathingType> getRequiredPathingTypes() {
|
||||
return this.requiredPathingTypes;
|
||||
}
|
||||
|
||||
public float getPropWindow() {
|
||||
return this.propWindow;
|
||||
}
|
||||
|
||||
public float getTurnRate() {
|
||||
return this.turnRate;
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting
|
||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor;
|
||||
|
||||
public class CBehaviorMove implements CBehavior {
|
||||
private static boolean ALWAYS_INTERRUPT_MOVE = false;
|
||||
|
||||
private static final Rectangle tempRect = new Rectangle();
|
||||
private final CUnit unit;
|
||||
@ -41,6 +42,9 @@ public class CBehaviorMove implements CBehavior {
|
||||
private CRangedBehavior rangedBehavior;
|
||||
private boolean firstUpdate = true;
|
||||
private boolean disableCollision = false;
|
||||
private boolean pathfindingActive = false;
|
||||
private boolean firstPathfindJob = false;
|
||||
private boolean pathfindingFailedGiveUp;
|
||||
|
||||
public CBehaviorMove reset(final int highlightOrderId, final AbilityTarget target) {
|
||||
target.visit(this.targetVisitingResetter.reset(highlightOrderId));
|
||||
@ -69,6 +73,7 @@ public class CBehaviorMove implements CBehavior {
|
||||
this.searchCycles = 0;
|
||||
this.followUnit = null;
|
||||
this.firstUpdate = true;
|
||||
this.pathfindingFailedGiveUp = false;
|
||||
}
|
||||
|
||||
private void internalResetMove(final int highlightOrderId, final CUnit followUnit) {
|
||||
@ -82,6 +87,7 @@ public class CBehaviorMove implements CBehavior {
|
||||
this.searchCycles = 0;
|
||||
this.followUnit = followUnit;
|
||||
this.firstUpdate = true;
|
||||
this.pathfindingFailedGiveUp = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -100,6 +106,9 @@ public class CBehaviorMove implements CBehavior {
|
||||
this.unit.setPointAndCheckUnstuck(this.unit.getX(), this.unit.getY(), simulation);
|
||||
this.firstUpdate = false;
|
||||
}
|
||||
if (this.pathfindingFailedGiveUp) {
|
||||
return this.unit.pollNextOrderBehavior(simulation);
|
||||
}
|
||||
final float prevX = this.unit.getX();
|
||||
final float prevY = this.unit.getY();
|
||||
|
||||
@ -116,72 +125,31 @@ public class CBehaviorMove implements CBehavior {
|
||||
final float startFloatingX = prevX;
|
||||
final float startFloatingY = prevY;
|
||||
if (this.path == null) {
|
||||
if (this.followUnit != null) {
|
||||
this.target.x = this.followUnit.getX();
|
||||
this.target.y = this.followUnit.getY();
|
||||
}
|
||||
this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY,
|
||||
this.target, movementType, collisionSize, true);
|
||||
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);
|
||||
}
|
||||
if (!this.pathfindingActive) {
|
||||
if (this.followUnit != null) {
|
||||
this.target.x = this.followUnit.getX();
|
||||
this.target.y = this.followUnit.getY();
|
||||
}
|
||||
simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, this.target,
|
||||
movementType, collisionSize, true, this);
|
||||
this.pathfindingActive = true;
|
||||
this.firstPathfindJob = true;
|
||||
}
|
||||
}
|
||||
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.target.x = this.followUnit.getX();
|
||||
this.target.y = this.followUnit.getY();
|
||||
this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY,
|
||||
this.target, movementType, collisionSize, this.searchCycles < 4);
|
||||
System.out.println("new path (for target) " + this.path);
|
||||
if (this.path.isEmpty()) {
|
||||
return this.unit.pollNextOrderBehavior(simulation);
|
||||
if (this.pathfindingActive) {
|
||||
simulation.removeFromPathfindingQueue(this);
|
||||
}
|
||||
simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, this.target,
|
||||
movementType, collisionSize, this.searchCycles < 4, this);
|
||||
this.pathfindingActive = true;
|
||||
}
|
||||
float currentTargetX;
|
||||
float currentTargetY;
|
||||
if (this.path.isEmpty()) {
|
||||
if ((this.path == null) || this.path.isEmpty()) {
|
||||
if (this.followUnit != null) {
|
||||
currentTargetX = this.followUnit.getX();
|
||||
currentTargetY = this.followUnit.getY();
|
||||
@ -212,8 +180,8 @@ public class CBehaviorMove implements CBehavior {
|
||||
}
|
||||
float facing = this.unit.getFacing();
|
||||
float delta = goalAngle - facing;
|
||||
final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId());
|
||||
final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId());
|
||||
final float propulsionWindow = this.unit.getUnitType().getPropWindow();
|
||||
final float turnRate = this.unit.getUnitType().getTurnRate();
|
||||
final int speed = this.unit.getSpeed();
|
||||
|
||||
if (delta < -180) {
|
||||
@ -235,7 +203,7 @@ public class CBehaviorMove implements CBehavior {
|
||||
facing += angleToAdd;
|
||||
this.unit.setFacing(facing);
|
||||
}
|
||||
if (absDelta < propulsionWindow) {
|
||||
if ((this.path != null) && !this.pathfindingActive && (absDelta < propulsionWindow)) {
|
||||
final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME;
|
||||
double continueDistance = speedTick;
|
||||
do {
|
||||
@ -262,9 +230,9 @@ public class CBehaviorMove implements CBehavior {
|
||||
tempRect.set(this.unit.getCollisionRectangle());
|
||||
tempRect.setCenter(nextX, nextY);
|
||||
if ((movementType == null) || (pathingGrid.isPathable(nextX, nextY, movementType, collisionSize)// ((int)
|
||||
// collisionSize
|
||||
// / 16)
|
||||
// * 16
|
||||
// collisionSize
|
||||
// / 16)
|
||||
// * 16
|
||||
&& !worldCollision.intersectsAnythingOtherThan(tempRect, this.unit, movementType))) {
|
||||
this.unit.setPoint(nextX, nextY, worldCollision);
|
||||
if (done) {
|
||||
@ -341,12 +309,12 @@ public class CBehaviorMove implements CBehavior {
|
||||
this.target.x = this.followUnit.getX();
|
||||
this.target.y = this.followUnit.getY();
|
||||
}
|
||||
this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY,
|
||||
this.target, movementType, collisionSize, this.searchCycles < 4);
|
||||
this.searchCycles++;
|
||||
System.out.println("new path " + this.path);
|
||||
if (this.path.isEmpty() || (this.searchCycles > 5)) {
|
||||
return this.unit.pollNextOrderBehavior(simulation);
|
||||
if (!this.pathfindingActive) {
|
||||
simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY,
|
||||
this.target, movementType, collisionSize, this.searchCycles < 4, this);
|
||||
this.pathfindingActive = true;
|
||||
this.searchCycles++;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.WALK, SequenceUtils.EMPTY, 1.0f,
|
||||
@ -408,6 +376,86 @@ public class CBehaviorMove implements CBehavior {
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,6 +238,9 @@ public class CUnitData {
|
||||
final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0);
|
||||
final String movetp = unitType.getFieldAsString(MOVE_TYPE, 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 PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp);
|
||||
final String unitName = unitType.getFieldAsString(NAME, 0);
|
||||
@ -425,7 +428,8 @@ public class CUnitData {
|
||||
isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay,
|
||||
defenseType, impactZ, buildingPathingPixelMap, deathTime, targetedAs, acquisitionRange,
|
||||
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);
|
||||
}
|
||||
return unitTypeInstance;
|
||||
|
@ -4,19 +4,24 @@ import java.awt.geom.Point2D;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
import com.badlogic.gdx.math.Rectangle;
|
||||
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.CWorldCollision;
|
||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove;
|
||||
|
||||
public class CPathfindingProcessor {
|
||||
private static final Rectangle tempRect = new Rectangle();
|
||||
private final PathingGrid pathingGrid;
|
||||
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[][] cornerNodes;
|
||||
private final Node[] goalSet = new Node[4];
|
||||
@ -52,217 +57,29 @@ public class CPathfindingProcessor {
|
||||
*
|
||||
* @param start
|
||||
* @param goal
|
||||
* @param playerIndex
|
||||
* @param queueItem
|
||||
* @return
|
||||
*/
|
||||
public List<Point2D.Float> findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX,
|
||||
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,
|
||||
public void findNaiveSlowPath(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 float goalX = goal.x;
|
||||
final float goalY = goal.y;
|
||||
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 boolean allowSmoothing, final CBehaviorMove queueItem) {
|
||||
this.moveQueue.offer(new PathfindingJob(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
|
||||
startX, startY, goal, movementType, collisionSize, allowSmoothing, queueItem));
|
||||
}
|
||||
|
||||
final Node start = searchGraph[startGridY][startGridX];
|
||||
int startGridMinX;
|
||||
int startGridMinY;
|
||||
int startGridMaxX;
|
||||
int startGridMaxY;
|
||||
if (startX > start.point.x) {
|
||||
startGridMinX = startGridX;
|
||||
startGridMaxX = startGridX + 1;
|
||||
}
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
public void removeFromPathfindingQueue(final CBehaviorMove behaviorMove) {
|
||||
// TODO because of silly java things, this remove is O(N) for now,
|
||||
// we could do some refactors to make it O(1) but do we care?
|
||||
final Iterator<PathfindingJob> iterator = this.moveQueue.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final PathfindingJob job = iterator.next();
|
||||
if (job.queueItem == behaviorMove) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user