forked from 0ad/0ad
wraitii
a26ab7b1e7
Slight configuration changes to improve the early-game slightly, WIP. This was SVN commit r14578.
335 lines
12 KiB
JavaScript
335 lines
12 KiB
JavaScript
var API3 = function(m)
|
|
{
|
|
|
|
// An implementation of A* as a pathfinder.
|
|
// It's oversamplable, and has a specific "distance from block"
|
|
// variable to avoid narrow passages if wanted.
|
|
// It can carry a calculation over multiple turns.
|
|
// It can work over water, or land.
|
|
// Note: while theoretically possible, when this goes from land to water
|
|
// It will return the path to the "boarding" point and
|
|
// a new path will need to be created.
|
|
|
|
// this should only be called by an AI player after setting gamestate.ai
|
|
|
|
// The initializer creates an expanded influence map for checking.
|
|
// It's not extraordinarily slow, but it's not exactly fast either.
|
|
m.aStarPath = function(gameState, onWater, disregardEntities, targetTerritory) {
|
|
var self = this;
|
|
|
|
// get the terrain analyzer map as a reference.
|
|
this.Map(gameState.ai, gameState.ai.terrainAnalyzer.map);
|
|
// get the accessibility as a reference
|
|
this.accessibility = gameState.ai.accessibility;
|
|
this.terrainAnalyzer = gameState.ai.terrainAnalyzer;
|
|
|
|
if (onWater) {
|
|
this.waterPathfinder = true;
|
|
} else
|
|
this.waterPathfinder = false;
|
|
|
|
var pathObstruction = gameState.sharedScript.passabilityClasses["pathfinderObstruction"];
|
|
var passMap = gameState.sharedScript.passabilityMap;
|
|
var terrMap = gameState.sharedScript.territoryMap;
|
|
|
|
|
|
this.widthMap = new Uint8Array(this.map.length);
|
|
for (var i = 0; i < this.map.length; ++i) {
|
|
if (this.map[i] === 0)
|
|
this.widthMap[i] = 0;
|
|
else if (!disregardEntities && (((terrMap.data[i] & 0x3F) !== gameState.ai.player && (terrMap.data[i] & 0x3F) !== 0 && (terrMap.data[i] & 0x3F) !== targetTerritory)
|
|
|| (passMap.data[i] & pathObstruction)))
|
|
this.widthMap[i] = 1; // we try to avoid enemy territory and pathfinder obstructions.
|
|
else if (!disregardEntities && this.map[i] === 30)
|
|
this.widthMap[i] = 0;
|
|
else if (!disregardEntities && this.map[i] === 40)
|
|
this.widthMap[i] = 0;
|
|
else if (!disregardEntities && this.map[i] === 41)
|
|
this.widthMap[i] = 2;
|
|
else if (!disregardEntities && this.map[i] === 42)
|
|
this.widthMap[i] = 1;
|
|
else if (!onWater && this.map[i] === 201)
|
|
this.widthMap[i] = 1;
|
|
else
|
|
this.widthMap[i] = 255;
|
|
}
|
|
this.expandInfluences(255,this.widthMap);
|
|
}
|
|
m.copyPrototype(m.aStarPath, m.Map);
|
|
|
|
// marks some points of the map as impassable. This can be used to create different paths, or to avoid going through some areas.
|
|
m.aStarPath.prototype.markImpassableArea = function(cx, cy, Distance) {
|
|
[cx,cy] = this.gamePosToMapPos([cx,cy]);
|
|
var x0 = Math.max(0, cx - Distance);
|
|
var y0 = Math.max(0, cy - Distance);
|
|
var x1 = Math.min(this.width, cx + Distance);
|
|
var y1 = Math.min(this.height, cy + Distance);
|
|
var maxDist2 = Distance * Distance;
|
|
|
|
for ( var y = y0; y < y1; ++y) {
|
|
for ( var x = x0; x < x1; ++x) {
|
|
var dx = x - cx;
|
|
var dy = y - cy;
|
|
var r2 = dx*dx + dy*dy;
|
|
if (r2 < maxDist2){
|
|
this.widthMap[x + y * this.width] = 0;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
// sending gamestate creates a map
|
|
// (you run the risk of "jumping" over obstacles or weird behavior.
|
|
m.aStarPath.prototype.getPath = function(start, end, Sampling, preferredWidth, iterationLimit, gamestate)
|
|
{
|
|
this.Sampling = Sampling >= 1 ? Sampling : 1;
|
|
this.minWidth = 1;
|
|
this.preferredWidth = (preferredWidth !== undefined && preferredWidth >= this.Sampling) ? preferredWidth : this.Sampling;
|
|
|
|
if (start[0] < 0 || this.gamePosToMapPos(start)[0] >= this.width || start[1] < 0 || this.gamePosToMapPos(start)[1] >= this.height)
|
|
return undefined;
|
|
|
|
var s = this.terrainAnalyzer.findClosestPassablePoint(this.gamePosToMapPos(start), !this.waterPathfinder,500,false);
|
|
var e = this.terrainAnalyzer.findClosestPassablePoint(this.gamePosToMapPos(end), !this.waterPathfinder,500,true);
|
|
|
|
var w = this.width;
|
|
|
|
if (!s || !e) {
|
|
return undefined;
|
|
}
|
|
if (gamestate !== undefined)
|
|
{
|
|
this.TotorMap = new m.Map(gamestate);
|
|
this.TotorMap.addInfluence(s[0],s[1],1,200,'constant');
|
|
this.TotorMap.addInfluence(e[0],e[1],1,200,'constant');
|
|
}
|
|
this.iterationLimit = 9000000000;
|
|
if (iterationLimit !== undefined)
|
|
this.iterationLimit = iterationLimit;
|
|
|
|
this.s = s[0] + w*s[1];
|
|
this.e = e[0] + w*e[1];
|
|
|
|
if (this.waterPathfinder && this.map[this.s] !== 200 && this.map[this.s] !== 201)
|
|
{
|
|
m.debug ("Trying a path over water, but we are on land, aborting");
|
|
return undefined;
|
|
} else if (!this.waterPathfinder && this.map[this.s] === 200)
|
|
{
|
|
m.debug ("Trying a path over land, but we are over water, aborting");
|
|
return undefined;
|
|
}
|
|
|
|
this.onWater = this.waterPathfinder;
|
|
this.pathChangesTransport = false;
|
|
|
|
// We are going to create a map, it's going to take memory. To avoid OOM errors, GC before we do so.
|
|
//Engine.ForceGC();
|
|
|
|
this.openList = [];
|
|
this.parentSquare = new Uint32Array(this.map.length);
|
|
this.isOpened = new Boolean(this.map.length);
|
|
this.fCostArray = new Uint32Array(this.map.length);
|
|
this.gCostArray = new Uint32Array(this.map.length);
|
|
this.currentSquare = this.s;
|
|
|
|
this.totalIteration = 0;
|
|
|
|
this.isOpened[this.s] = true;
|
|
this.openList.push(this.s);
|
|
this.fCostArray[this.s] = m.SquareVectorDistance([this.s%w, Math.floor(this.s/w)], [this.e%w, Math.floor(this.e/w)]);
|
|
this.gCostArray[this.s] = 0;
|
|
this.parentSquare[this.s] = this.s;
|
|
|
|
return this.continuePath(gamestate);
|
|
|
|
}
|
|
// in case it's not over yet, this can carry on the calculation of a path over multiple turn until it's over
|
|
m.aStarPath.prototype.continuePath = function(gamestate)
|
|
{
|
|
var w = this.width;
|
|
var h = this.height;
|
|
var positions = [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1]];
|
|
var cost = [100,100,100,100,150,150,150,150];
|
|
|
|
//creation of variables used in the loop
|
|
var found = false;
|
|
var shortcut = false;
|
|
|
|
var infinity = Math.min();
|
|
var currentDist = infinity;
|
|
|
|
var e = this.e;
|
|
var s = this.s;
|
|
|
|
var iteration = 0;
|
|
|
|
var target = [this.e%w, Math.floor(this.e/w)];
|
|
|
|
var changes = {};
|
|
|
|
var tIndex = 0;
|
|
// on to A*
|
|
while (found === false && this.openList.length !== 0 && iteration < this.iterationLimit) {
|
|
currentDist = infinity;
|
|
|
|
if (shortcut === true) {
|
|
this.currentSquare = this.openList.shift();
|
|
} else {
|
|
for (var i in this.openList)
|
|
{
|
|
var sum = this.fCostArray[this.openList[i]] + this.gCostArray[this.openList[i]];
|
|
if (sum < currentDist)
|
|
{
|
|
this.currentSquare = this.openList[i];
|
|
tIndex = i;
|
|
currentDist = sum;
|
|
}
|
|
}
|
|
this.openList.splice(tIndex,1);
|
|
}
|
|
if (!this.onWater && this.map[this.currentSquare] === 200) {
|
|
this.onWater = true;
|
|
} else if (this.onWater && (this.map[this.currentSquare] !== 200 && this.map[this.currentSquare] !== 201)) {
|
|
this.onWater = false;
|
|
}
|
|
|
|
shortcut = false;
|
|
this.isOpened[this.currentSquare] = false;
|
|
|
|
if (gamestate !== undefined)
|
|
this.TotorMap.addInfluence(this.currentSquare % w, Math.floor(this.currentSquare / w),1,40,'constant');
|
|
|
|
for (var i in positions)
|
|
{
|
|
var index = 0 + this.currentSquare +positions[i][0]*this.Sampling +w*this.Sampling*positions[i][1];
|
|
if (this.widthMap[index] >= this.minWidth || (this.onWater && this.map[index] > 0 && this.map[index] !== 200 && this.map[index] !== 201)
|
|
|| (!this.onWater && this.map[index] === 200))
|
|
{
|
|
if(this.isOpened[index] === undefined)
|
|
{
|
|
this.parentSquare[index] = this.currentSquare;
|
|
|
|
this.fCostArray[index] = m.SquareVectorDistance([index%w, Math.floor(index/w)], target);// * cost[i];
|
|
this.gCostArray[index] = this.gCostArray[this.currentSquare] + cost[i] * this.Sampling;// - this.map[index];
|
|
|
|
if (!this.onWater && this.map[index] === 200) {
|
|
this.gCostArray[index] += 10000;
|
|
} else if (this.onWater && this.map[index] !== 200) {
|
|
this.gCostArray[index] += 10000;
|
|
}
|
|
|
|
if (this.widthMap[index] < this.preferredWidth)
|
|
this.gCostArray[index] += 200 * (this.preferredWidth-this.widthMap[index]);
|
|
|
|
if (this.map[index] === 200 || (this.map[index] === 201 && this.onWater))
|
|
this.gCostArray[index] += 1000;
|
|
|
|
if (this.openList[0] !== undefined && this.fCostArray[this.openList[0]] + this.gCostArray[this.openList[0]] > this.fCostArray[index] + this.gCostArray[index])
|
|
{
|
|
this.openList.unshift(index);
|
|
shortcut = true;
|
|
} else {
|
|
this.openList.push(index);
|
|
}
|
|
this.isOpened[index] = true;
|
|
if (m.SquareVectorDistance( [index%w, Math.floor(index/w)] , target) <= this.Sampling*this.Sampling-1) {
|
|
if (this.e != index)
|
|
this.parentSquare[this.e] = index;
|
|
found = true;
|
|
break;
|
|
}
|
|
} else {
|
|
var addCost = 0;
|
|
if (!this.onWater && this.map[index] === 200) {
|
|
addCost += 10000;
|
|
} else if (this.onWater && this.map[index] !== 200) {
|
|
addCost += 10000;
|
|
}
|
|
if (this.widthMap[index] < this.preferredWidth)
|
|
addCost += 200 * (this.preferredWidth-this.widthMap[index]);
|
|
|
|
if (this.map[index] === 200 || (this.map[index] === 201 && this.onWater))
|
|
addCost += 1000;
|
|
|
|
// already on the Open or closed list
|
|
if (this.gCostArray[index] > cost[i] * this.Sampling + addCost + this.gCostArray[this.currentSquare])
|
|
{
|
|
this.parentSquare[index] = this.currentSquare;
|
|
this.gCostArray[index] = cost[i] * this.Sampling + addCost + this.gCostArray[this.currentSquare];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
iteration++;
|
|
}
|
|
this.totalIteration += iteration;
|
|
if (iteration === this.iterationLimit && found === false && this.openList.length !== 0)
|
|
{
|
|
// we've got to assume that we stopped because we reached the upper limit of iterations
|
|
return "toBeContinued";
|
|
}
|
|
//m.debug (this.totalIteration);
|
|
var paths = [];
|
|
if (found) {
|
|
this.currentSquare = e;
|
|
var lastPosx = 0;
|
|
var lastPosy = 0;
|
|
while (this.parentSquare[this.currentSquare] !== s)
|
|
{
|
|
this.currentSquare = this.parentSquare[this.currentSquare];
|
|
|
|
if (!this.onWater && this.map[this.currentSquare] === 200) {
|
|
//m.debug ("We must cross water, going " +this.currentSquare + " from parent " + this.parentSquare[this.currentSquare]);
|
|
this.pathChangesTransport = true;
|
|
changes[this.currentSquare] = true;
|
|
this.onWater = true;
|
|
} else if (this.onWater && (this.map[this.currentSquare] !== 200 && this.map[this.currentSquare] !== 201)) {
|
|
//m.debug ("We must cross to the ground, going " +this.currentSquare + " from parent " + this.parentSquare[this.currentSquare]);
|
|
this.pathChangesTransport = true;
|
|
changes[this.currentSquare] = true;
|
|
this.onWater = false;
|
|
}
|
|
|
|
if (gamestate !== undefined && changes[this.currentSquare])
|
|
this.TotorMap.addInfluence(this.currentSquare % w, Math.floor(this.currentSquare / w),2,200,'constant');
|
|
if (gamestate !== undefined)
|
|
this.TotorMap.addInfluence(this.currentSquare % w, Math.floor(this.currentSquare / w),1,50,'constant');
|
|
|
|
if (m.SquareVectorDistance([lastPosx,lastPosy],[this.currentSquare % w, Math.floor(this.currentSquare / w)]) > 300 || changes[this.currentSquare])
|
|
{
|
|
lastPosx = (this.currentSquare % w);
|
|
lastPosy = Math.floor(this.currentSquare / w);
|
|
paths.push([ [lastPosx*this.cellSize,lastPosy*this.cellSize], changes[this.currentSquare] ]);
|
|
if (gamestate !== undefined)
|
|
this.TotorMap.addInfluence(this.currentSquare % w, Math.floor(this.currentSquare / w),1,50 + paths.length,'constant');
|
|
}
|
|
}
|
|
} else {
|
|
// we have not found a path.
|
|
// what do we do then?
|
|
}
|
|
|
|
if (gamestate !== undefined)
|
|
this.TotorMap.dumpIm("Path From " +s +" to " +e +".png",255);
|
|
|
|
delete this.parentSquare;
|
|
delete this.isOpened;
|
|
delete this.fCostArray;
|
|
delete this.gCostArray;
|
|
|
|
|
|
// the return, if defined is [ [path, each waypoint being [position, mustchangeTransport] ], is there any transport change, ]
|
|
if (paths.length > 0) {
|
|
return [paths, this.pathChangesTransport];
|
|
} else {
|
|
return undefined;
|
|
}
|
|
|
|
}
|
|
|
|
return m;
|
|
|
|
}(API3);
|