AI: start switch from the internal JS pathFinder to the c++ one, thanks to Itms implementation. Still wip
This was SVN commit r16809.
This commit is contained in:
parent
d045bb87d6
commit
7ac4b53cb8
@ -213,9 +213,8 @@ m.SharedScript.prototype.onUpdate = function(state)
|
||||
for (var i in this.gameState)
|
||||
this.gameState[i].update(this,state);
|
||||
|
||||
// TODO: merge those two with "ApplyEntitiesDelta" since after all they do the same.
|
||||
// TODO: merge this with "ApplyEntitiesDelta" since after all they do the same.
|
||||
this.updateResourceMaps(this, this.events);
|
||||
this.terrainAnalyzer.updateMapWithEvents(this);
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
|
@ -1,350 +0,0 @@
|
||||
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)
|
||||
{
|
||||
// get the terrain analyzer map as a reference.
|
||||
this.Map(gameState.ai, "passability", 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 passMap = gameState.sharedScript.passabilityMap;
|
||||
var terrMap = gameState.sharedScript.territoryMap;
|
||||
var ratio = terrMap.cellSize / passMap.cellSize;
|
||||
var pathObstruction = gameState.sharedScript.passabilityClasses["default"];
|
||||
|
||||
this.widthMap = new Uint8Array(this.map.length);
|
||||
for (var i = 0; i < this.map.length; ++i)
|
||||
{
|
||||
let ix = i % passMap.width;
|
||||
let iz = Math.floor(i / passMap.width);
|
||||
ix = Math.floor(ix / ratio);
|
||||
iz = Math.floor(iz / ratio);
|
||||
let j = ix + iz*terrMap.width;
|
||||
if (this.map[i] == 0)
|
||||
this.widthMap[i] = 0;
|
||||
else if (!disregardEntities && (((terrMap.data[j] & 0x3F) !== gameState.ai.player && (terrMap.data[j] & 0x3F) !== 0
|
||||
&& (terrMap.data[j] & 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);
|
@ -4,15 +4,12 @@ var API3 = function(m)
|
||||
/*
|
||||
* TerrainAnalysis, inheriting from the Map Component.
|
||||
*
|
||||
* This creates a suitable passability map for pathfinding units and provides the findClosestPassablePoint() function.
|
||||
* This creates a suitable passability map.
|
||||
* This is part of the Shared Script, and thus should only be used for things that are non-player specific.
|
||||
* This.map is a map of the world, where particular stuffs are pointed with a value
|
||||
* For example, impassable land is 0, water is 200, areas near tree (ie forest grounds) are 41…
|
||||
* This is intended for use with 8 bit maps for reduced memory usage.
|
||||
* Upgraded from QuantumState's original TerrainAnalysis for qBot.
|
||||
* You may notice a lot of the A* star codes differ only by a few things.
|
||||
* It's wanted: each does a very slightly different things
|
||||
* But truly separating optimizes.
|
||||
*/
|
||||
|
||||
m.TerrainAnalysis = function()
|
||||
@ -21,7 +18,7 @@ m.TerrainAnalysis = function()
|
||||
|
||||
m.copyPrototype(m.TerrainAnalysis, m.Map);
|
||||
|
||||
m.TerrainAnalysis.prototype.init = function(sharedScript,rawState)
|
||||
m.TerrainAnalysis.prototype.init = function(sharedScript, rawState)
|
||||
{
|
||||
var passabilityMap = rawState.passabilityMap;
|
||||
this.width = passabilityMap.width;
|
||||
@ -38,12 +35,9 @@ m.TerrainAnalysis.prototype.init = function(sharedScript,rawState)
|
||||
200 is deep water (ie non-passable by land units)
|
||||
201 is shallow water (passable by land units and water units)
|
||||
255 is land (or extremely shallow water where ships can't go).
|
||||
40 is "tree".
|
||||
The following 41-49 range is "near a tree", with the second number showing how many trees this tile neighbors.
|
||||
30 is "geological component", such as a mine
|
||||
*/
|
||||
|
||||
for (var i = 0; i < passabilityMap.data.length; ++i)
|
||||
for (let i = 0; i < passabilityMap.data.length; ++i)
|
||||
{
|
||||
// If impassable for land units, set to 0, else to 255.
|
||||
obstructionTiles[i] = (passabilityMap.data[i] & obstructionMaskLand) ? 0 : 255;
|
||||
@ -54,95 +48,9 @@ m.TerrainAnalysis.prototype.init = function(sharedScript,rawState)
|
||||
obstructionTiles[i] = 201; // navigable and walkable.
|
||||
}
|
||||
|
||||
var square = [ [-1,-1], [-1,0], [-1, 1], [0,1], [1,1], [1,0], [1,-1], [0,-1], [0,0] ];
|
||||
var xx = 0;
|
||||
var yy = 0;
|
||||
var value = 0;
|
||||
var pos = [];
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
var radius = 0;
|
||||
for (let ent of sharedScript._entities.values())
|
||||
{
|
||||
if (ent.hasClass("ForestPlant") === true)
|
||||
{
|
||||
pos = this.gamePosToMapPos(ent.position());
|
||||
x = pos[0];
|
||||
y = pos[1];
|
||||
// unless it's impassable already, mark it as 40.
|
||||
if (obstructionTiles[x + y*this.width] !== 0)
|
||||
obstructionTiles[x + y*this.width] = 40;
|
||||
for (let sq of square)
|
||||
{
|
||||
xx = sq[0];
|
||||
yy = sq[1];
|
||||
if (x+xx >= 0 && x+xx < this.width && y+yy >= 0 && y+yy < this.height) {
|
||||
value = obstructionTiles[(x+xx) + (y+yy)*this.width];
|
||||
if (value === 255)
|
||||
obstructionTiles[(x+xx) + (y+yy)*this.width] = 41;
|
||||
else if (value < 49 && value > 40)
|
||||
obstructionTiles[(x+xx) + (y+yy)*this.width] = value + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ent.hasClass("Geology") === true)
|
||||
{
|
||||
radius = Math.floor(ent.obstructionRadius() / this.cellSize);
|
||||
pos = this.gamePosToMapPos(ent.position());
|
||||
x = pos[0];
|
||||
y = pos[1];
|
||||
// Unless it's impassable, mark as 30. This takes precedence over trees.
|
||||
obstructionTiles[x + y*this.width] = obstructionTiles[x + y*this.width] === 0 ? 0 : 30;
|
||||
for (var xx = -radius; xx <= radius;xx++)
|
||||
for (var yy = -radius; yy <= radius;yy++)
|
||||
if (x+xx >= 0 && x+xx < this.width && y+yy >= 0 && y+yy < this.height)
|
||||
obstructionTiles[(x+xx) + (y+yy)*this.width] = obstructionTiles[(x+xx) + (y+yy)*this.width] === 0 ? 0 : 30;
|
||||
}
|
||||
}
|
||||
// Okay now we have a pretty good knowledge of the map.
|
||||
this.Map(rawState, "passability", obstructionTiles);
|
||||
};
|
||||
|
||||
// Returns the (approximately) closest point which is passable by searching in a spiral pattern
|
||||
m.TerrainAnalysis.prototype.findClosestPassablePoint = function(startPoint, onLand, limitDistance, quickscope)
|
||||
{
|
||||
var w = this.width;
|
||||
var p = startPoint;
|
||||
var direction = 1;
|
||||
|
||||
if (p[0] + w*p[1] < 0 || p[0] + w*p[1] >= this.length) {
|
||||
return undefined;
|
||||
}
|
||||
// quickscope
|
||||
if (this.map[p[0] + w*p[1]] === 255)
|
||||
if (this.countConnected(p[0] + w*p[1], onLand) >= 2)
|
||||
return p;
|
||||
|
||||
var count = 0;
|
||||
// search in a spiral pattern. We require a value that is actually accessible in this case, ie 255, 201 or 41 if land, 200/201 if water.
|
||||
for (var i = 1; i < w; i++)
|
||||
{
|
||||
for (var j = 0; j < 2; j++)
|
||||
{
|
||||
for (var k = 0; k < i; k++)
|
||||
{
|
||||
p[j] += direction;
|
||||
// if the value is not markedly inaccessible
|
||||
var index = p[0] + w*p[1];
|
||||
if (this.map[index] !== 0 && this.map[index] !== 90 && this.map[index] !== 120 && this.map[index] !== 30 && this.map[index] !== 40)
|
||||
if (quickscope || this.countConnected(index, onLand) >= 2)
|
||||
return p;
|
||||
if (limitDistance !== undefined && count > limitDistance)
|
||||
return undefined;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
direction *= -1;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// Returns an estimate of a tile accessibility. It checks neighboring cells over two levels.
|
||||
// returns a count. It's not integer. About 2 should be fairly accessible already.
|
||||
m.TerrainAnalysis.prototype.countConnected = function(startIndex, byLand)
|
||||
@ -179,58 +87,6 @@ m.TerrainAnalysis.prototype.countConnected = function(startIndex, byLand)
|
||||
return count;
|
||||
};
|
||||
|
||||
// TODO: for now this resets to 255.
|
||||
m.TerrainAnalysis.prototype.updateMapWithEvents = function(sharedAI)
|
||||
{
|
||||
var events = sharedAI.events["Destroy"];
|
||||
var passabilityMap = sharedAI.passabilityMap;
|
||||
|
||||
// looking for creation or destruction of entities, and updates the map accordingly.
|
||||
for (let e of events)
|
||||
{
|
||||
if (!e.entityObj)
|
||||
continue;
|
||||
let ent = e.entityObj;
|
||||
if (ent.hasClass("Geology"))
|
||||
{
|
||||
let pos = this.gamePosToMapPos(ent.position());
|
||||
let x = pos[0];
|
||||
let y = pos[1];
|
||||
// remove it. Don't really care about surrounding and possible overlappings.
|
||||
let radius = Math.floor(ent.obstructionRadius() / this.cellSize);
|
||||
for (let xx = -radius; xx <= radius;xx++)
|
||||
for (let yy = -radius; yy <= radius;yy++)
|
||||
{
|
||||
if (x+xx >= 0 && x+xx < this.width && y+yy >= 0 && y+yy < this.height && this.map[(x+xx) + (y+yy)*this.width] === 30)
|
||||
this.map[(x+xx) + (y+yy)*this.width] = 255;
|
||||
}
|
||||
}
|
||||
else if (ent.hasClass("ForestPlant"))
|
||||
{
|
||||
let pos = this.gamePosToMapPos(ent.position());
|
||||
let x = pos[0];
|
||||
let y = pos[1];
|
||||
let nbOfNeigh = 0;
|
||||
for (let xx = -1; xx <= 1;xx++)
|
||||
for (let yy = -1; yy <= 1;yy++)
|
||||
{
|
||||
if (xx == 0 && yy == 0)
|
||||
continue;
|
||||
if (this.map[(x+xx) + (y+yy)*this.width] === 40)
|
||||
nbOfNeigh++;
|
||||
else if (this.map[(x+xx) + (y+yy)*this.width] === 41)
|
||||
this.map[(x+xx) + (y+yy)*this.width] = 255;
|
||||
else if (this.map[(x+xx) + (y+yy)*this.width] > 41 && this.map[(x+xx) + (y+yy)*this.width] < 50)
|
||||
this.map[(x+xx) + (y+yy)*this.width] = this.map[(x+xx) + (y+yy)*this.width] - 1;
|
||||
}
|
||||
if (nbOfNeigh > 0)
|
||||
this.map[x + y*this.width] = this.map[x + y*this.width] = 40 + nbOfNeigh;
|
||||
else
|
||||
this.map[x + y*this.width] = this.map[x + y*this.width] = 255;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Accessibility inherits from TerrainAnalysis
|
||||
*
|
||||
|
@ -86,7 +86,7 @@ m.AttackManager.prototype.checkEvents = function(gameState, events)
|
||||
continue;
|
||||
}
|
||||
attack.targetPos = attack.target.position();
|
||||
attack.resetPath(gameState);
|
||||
attack.resetPath();
|
||||
}
|
||||
if (attack.targetPlayer && attack.targetPlayer === targetPlayer)
|
||||
available += attack.unitCollection.length;
|
||||
|
@ -34,7 +34,7 @@ m.AttackPlan = function(gameState, Config, uniqueID, type, data)
|
||||
}
|
||||
|
||||
// get a starting rallyPoint ... will be improved later
|
||||
var rallyPoint = undefined;
|
||||
var rallyPoint;
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
{
|
||||
if (!base.anchor || !base.anchor.position())
|
||||
@ -166,13 +166,6 @@ m.AttackPlan = function(gameState, Config, uniqueID, type, data)
|
||||
this.lastPosition = [0,0];
|
||||
this.position = [0,0];
|
||||
|
||||
// get a good path to an estimated target.
|
||||
this.pathFinder = new API3.aStarPath(gameState, false, false, this.targetPlayer);
|
||||
//Engine.DumpImage("widthmap.png", this.pathFinder.widthMap, this.pathFinder.width,this.pathFinder.height,255);
|
||||
|
||||
this.pathWidth = 6; // prefer a path far from entities. This will avoid units getting stuck in trees and also results in less straight paths.
|
||||
this.pathSampling = 2;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@ -410,16 +403,13 @@ m.AttackPlan.prototype.updatePreparation = function(gameState)
|
||||
}
|
||||
}
|
||||
// reset the path so that we recompute it for this new target
|
||||
this.resetPath(gameState);
|
||||
this.resetPath();
|
||||
}
|
||||
|
||||
// when we have a target, we path to it.
|
||||
// I'd like a good high width sampling first.
|
||||
// Thus I will not do everything at once.
|
||||
// It will probably carry over a few turns but that's no issue.
|
||||
if (this.path === undefined || this.path === "toBeContinued")
|
||||
if (!this.path || this.path === "toBeContinued")
|
||||
{
|
||||
var ret = this.getPathToTarget(gameState);
|
||||
let ret = this.getPathToTarget(gameState);
|
||||
if (ret >= 0)
|
||||
return ret;
|
||||
}
|
||||
@ -540,10 +530,10 @@ m.AttackPlan.prototype.trainMoreUnits = function(gameState)
|
||||
// let's sort by training advancement, ie 'current size / target size'
|
||||
// count the number of queued units too.
|
||||
// substract priority.
|
||||
for (var i = 0; i < this.buildOrder.length; ++i)
|
||||
for (let i = 0; i < this.buildOrder.length; ++i)
|
||||
{
|
||||
var special = "Plan_" + this.name + "_" + this.buildOrder[i][4];
|
||||
var aQueued = gameState.countOwnQueuedEntitiesWithMetadata("special", special);
|
||||
let special = "Plan_" + this.name + "_" + this.buildOrder[i][4];
|
||||
let aQueued = gameState.countOwnQueuedEntitiesWithMetadata("special", special);
|
||||
aQueued += this.queue.countQueuedUnitsWithMetadata("special", special);
|
||||
aQueued += this.queueChamp.countQueuedUnitsWithMetadata("special", special);
|
||||
aQueued += this.queueSiege.countQueuedUnitsWithMetadata("special", special);
|
||||
@ -563,13 +553,13 @@ m.AttackPlan.prototype.trainMoreUnits = function(gameState)
|
||||
{
|
||||
API3.warn("====================================");
|
||||
API3.warn("======== build order for plan " + this.name);
|
||||
for (var order of this.buildOrder)
|
||||
for (let order of this.buildOrder)
|
||||
{
|
||||
var specialData = "Plan_"+this.name+"_"+order[4];
|
||||
var inTraining = gameState.countOwnQueuedEntitiesWithMetadata("special", specialData);
|
||||
var queue1 = this.queue.countQueuedUnitsWithMetadata("special", specialData);
|
||||
var queue2 = this.queueChamp.countQueuedUnitsWithMetadata("special", specialData);
|
||||
var queue3 = this.queueSiege.countQueuedUnitsWithMetadata("special", specialData);
|
||||
let specialData = "Plan_"+this.name+"_"+order[4];
|
||||
let inTraining = gameState.countOwnQueuedEntitiesWithMetadata("special", specialData);
|
||||
let queue1 = this.queue.countQueuedUnitsWithMetadata("special", specialData);
|
||||
let queue2 = this.queueChamp.countQueuedUnitsWithMetadata("special", specialData);
|
||||
let queue3 = this.queueSiege.countQueuedUnitsWithMetadata("special", specialData);
|
||||
API3.warn(" >>> " + order[4] + " done " + order[2].length + " training " + inTraining
|
||||
+ " queue " + queue1 + " champ " + queue2 + " siege " + queue3 + " >> need " + order[3].targetSize);
|
||||
}
|
||||
@ -735,8 +725,8 @@ m.AttackPlan.prototype.assignUnits = function(gameState)
|
||||
// Reassign one (at each turn) Cav unit to fasten raid preparation
|
||||
m.AttackPlan.prototype.reassignCavUnit = function(gameState)
|
||||
{
|
||||
var found = undefined;
|
||||
for (var ent of this.unitCollection.values())
|
||||
var found;
|
||||
for (let ent of this.unitCollection.values())
|
||||
{
|
||||
if (!ent.position() || ent.getMetadata(PlayerID, "transport") !== undefined)
|
||||
continue;
|
||||
@ -770,13 +760,13 @@ m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand
|
||||
// picking the nearest target
|
||||
var minDist = -1;
|
||||
var target = undefined;
|
||||
for (var ent of targets.values())
|
||||
for (let ent of targets.values())
|
||||
{
|
||||
if (!ent.position())
|
||||
continue;
|
||||
if (sameLand && gameState.ai.accessibility.getAccessValue(ent.position()) != land)
|
||||
continue;
|
||||
var dist = API3.SquareVectorDistance(ent.position(), position);
|
||||
let dist = API3.SquareVectorDistance(ent.position(), position);
|
||||
if (dist < minDist || minDist == -1)
|
||||
{
|
||||
minDist = dist;
|
||||
@ -877,9 +867,9 @@ m.AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy)
|
||||
m.AttackPlan.prototype.raidTargetFinder = function(gameState)
|
||||
{
|
||||
var targets = new API3.EntityCollection(gameState.sharedScript);
|
||||
for (var targetId of gameState.ai.HQ.defenseManager.targetList)
|
||||
for (let targetId of gameState.ai.HQ.defenseManager.targetList)
|
||||
{
|
||||
var target = gameState.getEntityById(targetId);
|
||||
let target = gameState.getEntityById(targetId);
|
||||
if (target && target.position())
|
||||
targets.addEnt(target);
|
||||
}
|
||||
@ -888,74 +878,42 @@ m.AttackPlan.prototype.raidTargetFinder = function(gameState)
|
||||
|
||||
m.AttackPlan.prototype.getPathToTarget = function(gameState)
|
||||
{
|
||||
if (this.path === undefined)
|
||||
this.path = this.pathFinder.getPath(this.rallyPoint, this.targetPos, this.pathSampling, this.pathWidth, 175);
|
||||
else if (this.path === "toBeContinued")
|
||||
this.path = this.pathFinder.continuePath();
|
||||
|
||||
if (this.path === undefined)
|
||||
if (!this.path)
|
||||
{
|
||||
if (this.pathWidth == 6)
|
||||
{
|
||||
this.pathWidth = 2;
|
||||
delete this.path;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete this.pathFinder;
|
||||
return 3; // no path.
|
||||
}
|
||||
Engine.ProfileStart("Compute path");
|
||||
let startPos = { "x": this.rallyPoint[0], "y": this.rallyPoint[1] };
|
||||
let endPos = { "x": this.targetPos[0], "y": this.targetPos[1] };
|
||||
let path = Engine.ComputePath(startPos, endPos, gameState.getPassabilityClassMask("siege-large"));
|
||||
this.path = [];
|
||||
this.path.push(this.targetPos);
|
||||
for (let p in path)
|
||||
this.path.push([path[p].x, path[p].y])
|
||||
this.path.push(this.rallyPoint);
|
||||
this.path.reverse();
|
||||
// Change the rally point to something useful (should avoid rams getting stuck in our territor)
|
||||
this.setRallyPoint(gameState);
|
||||
//API3.warn("new path computed " + uneval(this.path) + " and ralyPoint " + uneval(this.rallyPoint));
|
||||
Engine.ProfileStop();
|
||||
}
|
||||
else if (this.path === "toBeContinued")
|
||||
{
|
||||
return 1; // carry on
|
||||
}
|
||||
else if (this.path[1] === true && this.pathWidth == 2)
|
||||
{
|
||||
// okay so we need a ship.
|
||||
// Basically we'll add it as a new class to train compulsorily, and we'll recompute our path.
|
||||
if (!gameState.ai.HQ.navalMap)
|
||||
{
|
||||
gameState.ai.HQ.navalMap = true;
|
||||
return 0;
|
||||
}
|
||||
this.pathWidth = 3;
|
||||
this.pathSampling = 3;
|
||||
this.path = this.path[0].reverse();
|
||||
delete this.pathFinder;
|
||||
// Change the rally point to something useful (should avoid rams getting stuck in our territor)
|
||||
this.setRallyPoint(gameState);
|
||||
}
|
||||
else if (this.path[1] === true && this.pathWidth == 6)
|
||||
{
|
||||
// retry with a smaller pathwidth:
|
||||
this.pathWidth = 2;
|
||||
delete this.path;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.path = this.path[0].reverse();
|
||||
delete this.pathFinder;
|
||||
// Change the rally point to something useful (should avoid rams getting stuck in our territor)
|
||||
this.setRallyPoint(gameState);
|
||||
}
|
||||
return -1; // ok
|
||||
|
||||
return -1;
|
||||
};
|
||||
|
||||
m.AttackPlan.prototype.setRallyPoint = function(gameState)
|
||||
{
|
||||
for (var i = 0; i < this.path.length; ++i)
|
||||
for (let i = 0; i < this.path.length; ++i)
|
||||
{
|
||||
// my pathfinder returns arrays in arrays in arrays.
|
||||
var waypointPos = this.path[i][0];
|
||||
if (gameState.ai.HQ.territoryMap.getOwner(waypointPos) !== PlayerID || this.path[i][1] === true)
|
||||
let waypointPos = this.path[i];
|
||||
if (gameState.ai.HQ.territoryMap.getOwner(waypointPos) !== PlayerID)
|
||||
{
|
||||
// Set rally point at the border of our territory
|
||||
// or where we need to change transportation method.
|
||||
if (i !== 0)
|
||||
this.rallyPoint = this.path[i-1][0];
|
||||
this.rallyPoint = this.path[i-1];
|
||||
else
|
||||
this.rallyPoint = this.path[0][0];
|
||||
this.rallyPoint = this.path[0];
|
||||
|
||||
if (i >= 2)
|
||||
this.path.splice(0, i-1);
|
||||
@ -1019,20 +977,20 @@ m.AttackPlan.prototype.StartAttack = function(gameState)
|
||||
|
||||
var curPos = this.unitCollection.getCentrePosition();
|
||||
|
||||
for (var ent of this.unitCollection.values())
|
||||
for (let ent of this.unitCollection.values())
|
||||
ent.setMetadata(PlayerID, "subrole", "walking");
|
||||
this.unitCollection.setStance("aggressive");
|
||||
|
||||
if (gameState.ai.accessibility.getAccessValue(this.targetPos) === gameState.ai.accessibility.getAccessValue(this.rallyPoint))
|
||||
{
|
||||
if (!this.path[0][0][0] || !this.path[0][0][1])
|
||||
if (!this.path[0][0] || !this.path[0][1])
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
API3.warn("StartAttack: Problem with path " + uneval(this.path));
|
||||
return false;
|
||||
}
|
||||
this.state = "walking";
|
||||
this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]);
|
||||
this.unitCollection.move(this.path[0][0], this.path[0][1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1042,7 +1000,7 @@ m.AttackPlan.prototype.StartAttack = function(gameState)
|
||||
var endPos = this.targetPos;
|
||||
// TODO require a global transport for the collection,
|
||||
// and put back its state to "walking" when the transport is finished
|
||||
for (var ent of this.unitCollection.values())
|
||||
for (let ent of this.unitCollection.values())
|
||||
gameState.ai.HQ.navalManager.requireTransport(gameState, ent, startIndex, endIndex, endPos);
|
||||
}
|
||||
}
|
||||
@ -1073,7 +1031,7 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
if (this.state === "transporting")
|
||||
{
|
||||
var done = true;
|
||||
for (var ent of this.unitCollection.values())
|
||||
for (let ent of this.unitCollection.values())
|
||||
{
|
||||
if (this.Config.debug > 1 && ent.getMetadata(PlayerID, "transport") !== undefined)
|
||||
Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [2,2,0]});
|
||||
@ -1090,7 +1048,7 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
else
|
||||
{
|
||||
// if we are attacked while waiting the rest of the army, retaliate
|
||||
for (var evt of events["Attacked"])
|
||||
for (let evt of events["Attacked"])
|
||||
{
|
||||
if (IDs.indexOf(evt.target) == -1)
|
||||
continue;
|
||||
@ -1098,7 +1056,7 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
var ourUnit = gameState.getEntityById(evt.target);
|
||||
if (!attacker || !ourUnit)
|
||||
continue;
|
||||
for (var ent of this.unitCollection.values())
|
||||
for (let ent of this.unitCollection.values())
|
||||
{
|
||||
if (ent.getMetadata(PlayerID, "transport") !== undefined)
|
||||
continue;
|
||||
@ -1120,7 +1078,7 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
// or if we reached the enemy base. Different plans may react differently.
|
||||
var attackedNB = 0;
|
||||
var attackedUnitNB = 0;
|
||||
for (var evt of events["Attacked"])
|
||||
for (let evt of events["Attacked"])
|
||||
{
|
||||
if (IDs.indexOf(evt.target) == -1)
|
||||
continue;
|
||||
@ -1139,7 +1097,7 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
if (attackedUnitNB == 0)
|
||||
{
|
||||
var siegeNB = 0;
|
||||
for (var ent of this.unitCollection.values())
|
||||
for (let ent of this.unitCollection.values())
|
||||
if (this.isSiegeUnit(gameState, ent))
|
||||
siegeNB++;
|
||||
if (siegeNB == 0)
|
||||
@ -1158,7 +1116,7 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
var farthest = 0;
|
||||
var farthestEnt = -1;
|
||||
this.unitCollection.filter(API3.Filters.byClass("Siege")).forEach (function (ent) {
|
||||
var dist = API3.SquareVectorDistance(ent.position(), self.position);
|
||||
let dist = API3.SquareVectorDistance(ent.position(), self.position);
|
||||
if (dist < farthest)
|
||||
return;
|
||||
farthest = dist;
|
||||
@ -1172,7 +1130,7 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
|
||||
if (this.lastPosition && API3.SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0)
|
||||
{
|
||||
if (!this.path[0][0][0] || !this.path[0][0][1])
|
||||
if (!this.path[0][0] || !this.path[0][1])
|
||||
API3.warn("Start: Problem with path " + uneval(this.path));
|
||||
// We're stuck, presumably. Check if there are no walls just close to us. If so, we're arrived, and we're gonna tear down some serious stone.
|
||||
var nexttoWalls = false;
|
||||
@ -1195,8 +1153,8 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
//this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]);
|
||||
this.unitCollection.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
|
||||
//this.unitCollection.move(this.path[0][0], this.path[0][1]);
|
||||
this.unitCollection.moveIndiv(this.path[0][0], this.path[0][1]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1209,11 +1167,11 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination.");
|
||||
this.state = "arrived";
|
||||
}
|
||||
else if (this.path.length && API3.SquareVectorDistance(this.position, this.path[0][0]) < 1600)
|
||||
else if (this.path.length && API3.SquareVectorDistance(this.position, this.path[0]) < 1600)
|
||||
{
|
||||
this.path.shift();
|
||||
if (this.path.length)
|
||||
this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]);
|
||||
this.unitCollection.move(this.path[0][0], this.path[0][1]);
|
||||
else
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
@ -1719,15 +1677,9 @@ m.AttackPlan.prototype.removeUnit = function(ent, update)
|
||||
};
|
||||
|
||||
// Reset the path so that it can be recomputed for a new target
|
||||
m.AttackPlan.prototype.resetPath = function(gameState)
|
||||
m.AttackPlan.prototype.resetPath = function()
|
||||
{
|
||||
this.path = undefined;
|
||||
if (!this.pathFinder)
|
||||
{
|
||||
this.pathFinder = new API3.aStarPath(gameState, false, false, this.targetPlayer);
|
||||
this.pathWidth = 6;
|
||||
this.pathSampling = 2;
|
||||
}
|
||||
};
|
||||
|
||||
m.AttackPlan.prototype.checkEvents = function(gameState, events)
|
||||
@ -1809,16 +1761,11 @@ m.AttackPlan.prototype.Serialize = function()
|
||||
"position": this.position,
|
||||
"targetPlayer": this.targetPlayer,
|
||||
"target": ((this.target !== undefined) ? this.target.id() : undefined),
|
||||
"targetPos": this.targetPos
|
||||
"targetPos": this.targetPos,
|
||||
"path": this.path
|
||||
};
|
||||
|
||||
let path = {
|
||||
"path": this.path,
|
||||
"pathSampling": this.pathSampling,
|
||||
"pathWidth": this.pathWidth
|
||||
};
|
||||
|
||||
return { "properties": properties, "path": path };
|
||||
return { "properties": properties};
|
||||
};
|
||||
|
||||
m.AttackPlan.prototype.Deserialize = function(gameState, data)
|
||||
@ -1829,11 +1776,6 @@ m.AttackPlan.prototype.Deserialize = function(gameState, data)
|
||||
if (this.target)
|
||||
this.target = gameState.getEntityById(this.target);
|
||||
|
||||
// if the path was not fully computed, we will recompute it as it is not serialized
|
||||
if (data.path.path != "toBeContinued")
|
||||
for (let key in data.path)
|
||||
this[key] = data.path[key];
|
||||
|
||||
this.failed = undefined;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user