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
|
* 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user