mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-07-27 08:52:46 +00:00
Use JS classes for Terain-Analysis and InfoMap
`TerrainAnalysis` and `Accessibility` used a costum form of inheritance. Refs: #6285
This commit is contained in:
@@ -2,211 +2,213 @@
|
||||
* The map module.
|
||||
* Copied with changes from QuantumState's original for qBot, it's a component for storing 8 bit values.
|
||||
*/
|
||||
export function InfoMap(sharedScript, type, originalMap, actualCopy)
|
||||
export class InfoMap
|
||||
{
|
||||
// get the correct dimensions according to the map type
|
||||
const map = type == "territory" || type == "resource" ? sharedScript.territoryMap : sharedScript.passabilityMap;
|
||||
this.width = map.width;
|
||||
this.height = map.height;
|
||||
this.cellSize = map.cellSize;
|
||||
this.length = this.width * this.height;
|
||||
|
||||
this.maxVal = 255;
|
||||
|
||||
// sanity check
|
||||
if (originalMap && originalMap.length != this.length)
|
||||
warn("AI map size incompatibility with type " + type + ": original " + originalMap.length + " new " + this.length);
|
||||
|
||||
if (originalMap && actualCopy)
|
||||
constructor(sharedScript, type, originalMap, actualCopy)
|
||||
{
|
||||
// get the correct dimensions according to the map type
|
||||
const map = type == "territory" || type == "resource" ? sharedScript.territoryMap : sharedScript.passabilityMap;
|
||||
this.width = map.width;
|
||||
this.height = map.height;
|
||||
this.cellSize = map.cellSize;
|
||||
this.length = this.width * this.height;
|
||||
|
||||
this.maxVal = 255;
|
||||
|
||||
// sanity check
|
||||
if (originalMap && originalMap.length != this.length)
|
||||
warn("AI map size incompatibility with type " + type + ": original " + originalMap.length + " new " + this.length);
|
||||
|
||||
if (originalMap && actualCopy)
|
||||
{
|
||||
this.map = new Uint8Array(this.length);
|
||||
for (let i = 0; i < this.length; ++i)
|
||||
this.map[i] = originalMap[i];
|
||||
}
|
||||
else if (originalMap)
|
||||
this.map = originalMap;
|
||||
else
|
||||
this.map = new Uint8Array(this.length);
|
||||
}
|
||||
|
||||
setMaxVal(val)
|
||||
{
|
||||
this.maxVal = val;
|
||||
}
|
||||
|
||||
gamePosToMapPos(p)
|
||||
{
|
||||
return [Math.floor(p[0]/this.cellSize), Math.floor(p[1]/this.cellSize)];
|
||||
}
|
||||
|
||||
point(p)
|
||||
{
|
||||
const q = this.gamePosToMapPos(p);
|
||||
q[0] = q[0] >= this.width ? this.width-1 : q[0] < 0 ? 0 : q[0];
|
||||
q[1] = q[1] >= this.width ? this.width-1 : q[1] < 0 ? 0 : q[1];
|
||||
return this.map[q[0] + this.width * q[1]];
|
||||
}
|
||||
|
||||
runLoop(x0, x1, y0, y1, cx, cy, maxDist2, func)
|
||||
{
|
||||
for (let y = y0; y < y1; ++y)
|
||||
{
|
||||
const dy2 = (y - cy) * (y - cy);
|
||||
const yw = y * this.width;
|
||||
for (let x = x0; x < x1; ++x)
|
||||
{
|
||||
const dx = x - cx;
|
||||
const r2 = dx * dx + dy2;
|
||||
if (r2 >= maxDist2)
|
||||
continue;
|
||||
const w = x + yw;
|
||||
this.set(w, func(w, r2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addInfluence(cx, cy, maxDist, strength, type = "linear")
|
||||
{
|
||||
strength = strength ? strength : maxDist;
|
||||
|
||||
const x0 = Math.floor(Math.max(0, cx - maxDist));
|
||||
const y0 = Math.floor(Math.max(0, cy - maxDist));
|
||||
const x1 = Math.floor(Math.min(this.width-1, cx + maxDist));
|
||||
const y1 = Math.floor(Math.min(this.height-1, cy + maxDist));
|
||||
const maxDist2 = maxDist * maxDist;
|
||||
|
||||
if (type == "linear")
|
||||
{
|
||||
const str = strength / maxDist;
|
||||
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => this.map[w] + str * (maxDist - Math.sqrt(r2)));
|
||||
}
|
||||
else if (type == "quadratic")
|
||||
{
|
||||
const str = strength / maxDist2;
|
||||
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => this.map[w] + str * (maxDist2 - r2));
|
||||
}
|
||||
else
|
||||
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => this.map[w] + strength);
|
||||
}
|
||||
|
||||
multiplyInfluence(cx, cy, maxDist, strength, type = "constant")
|
||||
{
|
||||
strength = strength ? +strength : +maxDist;
|
||||
|
||||
const x0 = Math.max(0, cx - maxDist);
|
||||
const y0 = Math.max(0, cy - maxDist);
|
||||
const x1 = Math.min(this.width, cx + maxDist);
|
||||
const y1 = Math.min(this.height, cy + maxDist);
|
||||
const maxDist2 = maxDist * maxDist;
|
||||
|
||||
if (type == "linear")
|
||||
{
|
||||
const str = strength / maxDist;
|
||||
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => str * (maxDist - Math.sqrt(r2)) * this.map[w]);
|
||||
}
|
||||
else if (type == "quadratic")
|
||||
{
|
||||
const str = strength / maxDist2;
|
||||
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => str * (maxDist2 - r2) * this.map[w]);
|
||||
}
|
||||
else
|
||||
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => this.map[w] * strength);
|
||||
}
|
||||
|
||||
/** add to current map by the parameter map pixelwise */
|
||||
add(map)
|
||||
{
|
||||
this.map = new Uint8Array(this.length);
|
||||
for (let i = 0; i < this.length; ++i)
|
||||
this.map[i] = originalMap[i];
|
||||
this.set(i, this.map[i] + map.map[i]);
|
||||
}
|
||||
else if (originalMap)
|
||||
this.map = originalMap;
|
||||
else
|
||||
this.map = new Uint8Array(this.length);
|
||||
}
|
||||
|
||||
InfoMap.prototype.setMaxVal = function(val)
|
||||
{
|
||||
this.maxVal = val;
|
||||
};
|
||||
|
||||
InfoMap.prototype.gamePosToMapPos = function(p)
|
||||
{
|
||||
return [Math.floor(p[0]/this.cellSize), Math.floor(p[1]/this.cellSize)];
|
||||
};
|
||||
|
||||
InfoMap.prototype.point = function(p)
|
||||
{
|
||||
const q = this.gamePosToMapPos(p);
|
||||
q[0] = q[0] >= this.width ? this.width-1 : q[0] < 0 ? 0 : q[0];
|
||||
q[1] = q[1] >= this.width ? this.width-1 : q[1] < 0 ? 0 : q[1];
|
||||
return this.map[q[0] + this.width * q[1]];
|
||||
};
|
||||
|
||||
InfoMap.prototype.runLoop = function(x0, x1, y0, y1, cx, cy, maxDist2, func)
|
||||
{
|
||||
for (let y = y0; y < y1; ++y)
|
||||
/** Set the value taking overflow into account */
|
||||
set(i, value)
|
||||
{
|
||||
const dy2 = (y - cy) * (y - cy);
|
||||
const yw = y * this.width;
|
||||
for (let x = x0; x < x1; ++x)
|
||||
this.map[i] = value < 0 ? 0 : value > this.maxVal ? this.maxVal : value;
|
||||
}
|
||||
|
||||
/** Find the best non-obstructed tile */
|
||||
findBestTile(radius, obstruction)
|
||||
{
|
||||
let bestIdx;
|
||||
let bestVal = 0;
|
||||
for (let j = 0; j < this.length; ++j)
|
||||
{
|
||||
const dx = x - cx;
|
||||
const r2 = dx * dx + dy2;
|
||||
if (r2 >= maxDist2)
|
||||
if (this.map[j] <= bestVal)
|
||||
continue;
|
||||
const w = x + yw;
|
||||
this.set(w, func(w, r2));
|
||||
const i = this.getNonObstructedTile(j, radius, obstruction);
|
||||
if (i < 0)
|
||||
continue;
|
||||
bestVal = this.map[j];
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
InfoMap.prototype.addInfluence = function(cx, cy, maxDist, strength, type = "linear")
|
||||
{
|
||||
strength = strength ? strength : maxDist;
|
||||
|
||||
const x0 = Math.floor(Math.max(0, cx - maxDist));
|
||||
const y0 = Math.floor(Math.max(0, cy - maxDist));
|
||||
const x1 = Math.floor(Math.min(this.width-1, cx + maxDist));
|
||||
const y1 = Math.floor(Math.min(this.height-1, cy + maxDist));
|
||||
const maxDist2 = maxDist * maxDist;
|
||||
|
||||
if (type == "linear")
|
||||
{
|
||||
const str = strength / maxDist;
|
||||
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => this.map[w] + str * (maxDist - Math.sqrt(r2)));
|
||||
}
|
||||
else if (type == "quadratic")
|
||||
{
|
||||
const str = strength / maxDist2;
|
||||
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => this.map[w] + str * (maxDist2 - r2));
|
||||
}
|
||||
else
|
||||
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => this.map[w] + strength);
|
||||
|
||||
};
|
||||
|
||||
InfoMap.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type = "constant")
|
||||
{
|
||||
strength = strength ? +strength : +maxDist;
|
||||
|
||||
const x0 = Math.max(0, cx - maxDist);
|
||||
const y0 = Math.max(0, cy - maxDist);
|
||||
const x1 = Math.min(this.width, cx + maxDist);
|
||||
const y1 = Math.min(this.height, cy + maxDist);
|
||||
const maxDist2 = maxDist * maxDist;
|
||||
|
||||
if (type == "linear")
|
||||
{
|
||||
const str = strength / maxDist;
|
||||
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => str * (maxDist - Math.sqrt(r2)) * this.map[w]);
|
||||
}
|
||||
else if (type == "quadratic")
|
||||
{
|
||||
const str = strength / maxDist2;
|
||||
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => str * (maxDist2 - r2) * this.map[w]);
|
||||
}
|
||||
else
|
||||
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => this.map[w] * strength);
|
||||
};
|
||||
|
||||
/** add to current map by the parameter map pixelwise */
|
||||
InfoMap.prototype.add = function(map)
|
||||
{
|
||||
for (let i = 0; i < this.length; ++i)
|
||||
this.set(i, this.map[i] + map.map[i]);
|
||||
};
|
||||
|
||||
/** Set the value taking overflow into account */
|
||||
InfoMap.prototype.set = function(i, value)
|
||||
{
|
||||
this.map[i] = value < 0 ? 0 : value > this.maxVal ? this.maxVal : value;
|
||||
};
|
||||
|
||||
/** Find the best non-obstructed tile */
|
||||
InfoMap.prototype.findBestTile = function(radius, obstruction)
|
||||
{
|
||||
let bestIdx;
|
||||
let bestVal = 0;
|
||||
for (let j = 0; j < this.length; ++j)
|
||||
{
|
||||
if (this.map[j] <= bestVal)
|
||||
continue;
|
||||
const i = this.getNonObstructedTile(j, radius, obstruction);
|
||||
if (i < 0)
|
||||
continue;
|
||||
bestVal = this.map[j];
|
||||
bestIdx = i;
|
||||
return { "idx": bestIdx, "val": bestVal };
|
||||
}
|
||||
|
||||
return { "idx": bestIdx, "val": bestVal };
|
||||
};
|
||||
|
||||
/** return any non obstructed (small) tile inside the (big) tile i from obstruction map */
|
||||
InfoMap.prototype.getNonObstructedTile = function(i, radius, obstruction)
|
||||
{
|
||||
const ratio = this.cellSize / obstruction.cellSize;
|
||||
const ix = (i % this.width) * ratio;
|
||||
const iy = Math.floor(i / this.width) * ratio;
|
||||
const w = obstruction.width;
|
||||
const r2 = radius * radius;
|
||||
let lastPoint;
|
||||
for (let kx = ix; kx < ix + ratio; ++kx)
|
||||
/** return any non obstructed (small) tile inside the (big) tile i from obstruction map */
|
||||
getNonObstructedTile(i, radius, obstruction)
|
||||
{
|
||||
if (kx < radius || kx >= w - radius)
|
||||
continue;
|
||||
for (let ky = iy; ky < iy + ratio; ++ky)
|
||||
{
|
||||
if (ky < radius || ky >= w - radius)
|
||||
continue;
|
||||
if (lastPoint && (kx - lastPoint.x)*(kx - lastPoint.x) + (ky - lastPoint.y)*(ky - lastPoint.y) < r2)
|
||||
continue;
|
||||
lastPoint = obstruction.isObstructedTile(kx, ky, radius);
|
||||
if (!lastPoint)
|
||||
return kx + ky*w;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/** return true if the area centered on tile kx-ky and with radius is obstructed */
|
||||
InfoMap.prototype.isObstructedTile = function(kx, ky, radius)
|
||||
{
|
||||
const w = this.width;
|
||||
if (kx < radius || kx >= w - radius || ky < radius || ky >= w - radius || this.map[kx+ky*w] == 0)
|
||||
return { "x": kx, "y": ky };
|
||||
if (!this.pattern || this.pattern[0] != radius)
|
||||
{
|
||||
this.pattern = [radius];
|
||||
const ratio = this.cellSize / obstruction.cellSize;
|
||||
const ix = (i % this.width) * ratio;
|
||||
const iy = Math.floor(i / this.width) * ratio;
|
||||
const w = obstruction.width;
|
||||
const r2 = radius * radius;
|
||||
for (let i = 1; i <= radius; ++i)
|
||||
this.pattern.push(Math.floor(Math.sqrt(r2 - (i-0.5)*(i-0.5)) + 0.5));
|
||||
}
|
||||
for (let dy = 0; dy <= radius; ++dy)
|
||||
{
|
||||
const dxmax = this.pattern[dy];
|
||||
const xp = kx + (ky + dy)*w;
|
||||
const xm = kx + (ky - dy)*w;
|
||||
for (let dx = 0; dx <= dxmax; ++dx)
|
||||
let lastPoint;
|
||||
for (let kx = ix; kx < ix + ratio; ++kx)
|
||||
{
|
||||
if (this.map[xp + dx] == 0)
|
||||
return { "x": kx + dx, "y": ky + dy };
|
||||
if (this.map[xm + dx] == 0)
|
||||
return { "x": kx + dx, "y": ky - dy };
|
||||
if (this.map[xp - dx] == 0)
|
||||
return { "x": kx - dx, "y": ky + dy };
|
||||
if (this.map[xm - dx] == 0)
|
||||
return { "x": kx - dx, "y": ky - dy };
|
||||
if (kx < radius || kx >= w - radius)
|
||||
continue;
|
||||
for (let ky = iy; ky < iy + ratio; ++ky)
|
||||
{
|
||||
if (ky < radius || ky >= w - radius)
|
||||
continue;
|
||||
if (lastPoint && (kx - lastPoint.x)*(kx - lastPoint.x) + (ky - lastPoint.y)*(ky - lastPoint.y) < r2)
|
||||
continue;
|
||||
lastPoint = obstruction.isObstructedTile(kx, ky, radius);
|
||||
if (!lastPoint)
|
||||
return kx + ky*w;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
InfoMap.prototype.dumpIm = function(name = "default.png", threshold = this.maxVal)
|
||||
{
|
||||
Engine.DumpImage(name, this.map, this.width, this.height, threshold);
|
||||
};
|
||||
/** return true if the area centered on tile kx-ky and with radius is obstructed */
|
||||
isObstructedTile(kx, ky, radius)
|
||||
{
|
||||
const w = this.width;
|
||||
if (kx < radius || kx >= w - radius || ky < radius || ky >= w - radius || this.map[kx+ky*w] == 0)
|
||||
return { "x": kx, "y": ky };
|
||||
if (!this.pattern || this.pattern[0] != radius)
|
||||
{
|
||||
this.pattern = [radius];
|
||||
const r2 = radius * radius;
|
||||
for (let i = 1; i <= radius; ++i)
|
||||
this.pattern.push(Math.floor(Math.sqrt(r2 - (i-0.5)*(i-0.5)) + 0.5));
|
||||
}
|
||||
for (let dy = 0; dy <= radius; ++dy)
|
||||
{
|
||||
const dxmax = this.pattern[dy];
|
||||
const xp = kx + (ky + dy)*w;
|
||||
const xm = kx + (ky - dy)*w;
|
||||
for (let dx = 0; dx <= dxmax; ++dx)
|
||||
{
|
||||
if (this.map[xp + dx] == 0)
|
||||
return { "x": kx + dx, "y": ky + dy };
|
||||
if (this.map[xm + dx] == 0)
|
||||
return { "x": kx + dx, "y": ky - dy };
|
||||
if (this.map[xp - dx] == 0)
|
||||
return { "x": kx - dx, "y": ky + dy };
|
||||
if (this.map[xm - dx] == 0)
|
||||
return { "x": kx - dx, "y": ky - dy };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
dumpIm(name = "default.png", threshold = this.maxVal)
|
||||
{
|
||||
Engine.DumpImage(name, this.map, this.width, this.height, threshold);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,10 +113,8 @@ SharedScript.prototype.init = function(state, deserialization)
|
||||
this.entities = new EntityCollection(this, this._entities);
|
||||
|
||||
// create the terrain analyzer
|
||||
this.terrainAnalyzer = new TerrainAnalysis();
|
||||
this.terrainAnalyzer.init(this, state);
|
||||
this.accessibility = new Accessibility();
|
||||
this.accessibility.init(state, this.terrainAnalyzer);
|
||||
this.terrainAnalyzer = new TerrainAnalysis(this, state);
|
||||
this.accessibility = new Accessibility(state, this.terrainAnalyzer);
|
||||
|
||||
// Resource types: ignore = not used for resource maps
|
||||
// abundant = abundant resource with small amount each
|
||||
@@ -379,18 +377,6 @@ SharedScript.prototype.deleteMetadata = function(player, ent, key)
|
||||
return true;
|
||||
};
|
||||
|
||||
export function copyPrototype(descendant, parent)
|
||||
{
|
||||
const sConstructor = parent.toString();
|
||||
const aMatch = sConstructor.match(/\s*function (.*)\(/);
|
||||
|
||||
if (aMatch != null)
|
||||
descendant.prototype[aMatch[1]] = parent;
|
||||
|
||||
for (const p in parent.prototype)
|
||||
descendant.prototype[p] = parent.prototype[p];
|
||||
}
|
||||
|
||||
/** creates a map of resource density */
|
||||
SharedScript.prototype.createResourceMaps = function()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { InfoMap } from "simulation/ai/common-api/map-module.js";
|
||||
import { copyPrototype } from "simulation/ai/common-api/shared.js";
|
||||
import * as terrainStates from "simulation/ai/common-api/terrain-states.js";
|
||||
|
||||
/**
|
||||
@@ -12,44 +11,41 @@ import * as terrainStates from "simulation/ai/common-api/terrain-states.js";
|
||||
* This is intended for use with 8 bit maps for reduced memory usage.
|
||||
* Upgraded from QuantumState's original TerrainAnalysis for qBot.
|
||||
*/
|
||||
export function TerrainAnalysis()
|
||||
export class TerrainAnalysis extends InfoMap
|
||||
{
|
||||
}
|
||||
|
||||
copyPrototype(TerrainAnalysis, InfoMap);
|
||||
|
||||
TerrainAnalysis.prototype.init = function(sharedScript, rawState)
|
||||
{
|
||||
const passabilityMap = rawState.passabilityMap;
|
||||
this.width = passabilityMap.width;
|
||||
this.height = passabilityMap.height;
|
||||
this.cellSize = passabilityMap.cellSize;
|
||||
|
||||
const obstructionMaskLand = rawState.passabilityClasses["default-terrain-only"];
|
||||
const obstructionMaskWater = rawState.passabilityClasses["ship-terrain-only"];
|
||||
|
||||
const obstructionTiles = new Uint8Array(passabilityMap.data.length);
|
||||
|
||||
|
||||
for (let i = 0; i < passabilityMap.data.length; ++i)
|
||||
constructor(sharedScript, rawState)
|
||||
{
|
||||
obstructionTiles[i] = (passabilityMap.data[i] & obstructionMaskLand) ?
|
||||
terrainStates.IMPASSABLE : terrainStates.LAND;
|
||||
const passabilityMap = rawState.passabilityMap;
|
||||
|
||||
if (!(passabilityMap.data[i] & obstructionMaskWater) &&
|
||||
obstructionTiles[i] === terrainStates.IMPASSABLE)
|
||||
const obstructionMaskLand = rawState.passabilityClasses["default-terrain-only"];
|
||||
const obstructionMaskWater = rawState.passabilityClasses["ship-terrain-only"];
|
||||
|
||||
const obstructionTiles = new Uint8Array(passabilityMap.data.length);
|
||||
|
||||
|
||||
for (let i = 0; i < passabilityMap.data.length; ++i)
|
||||
{
|
||||
obstructionTiles[i] = terrainStates.DEEP_WATER;
|
||||
}
|
||||
else if (!(passabilityMap.data[i] & obstructionMaskWater) &&
|
||||
obstructionTiles[i] === terrainStates.LAND)
|
||||
{
|
||||
obstructionTiles[i] = terrainStates.SHALLOW_WATER;
|
||||
obstructionTiles[i] = (passabilityMap.data[i] & obstructionMaskLand) ?
|
||||
terrainStates.IMPASSABLE : terrainStates.LAND;
|
||||
|
||||
if (!(passabilityMap.data[i] & obstructionMaskWater) &&
|
||||
obstructionTiles[i] === terrainStates.IMPASSABLE)
|
||||
{
|
||||
obstructionTiles[i] = terrainStates.DEEP_WATER;
|
||||
}
|
||||
else if (!(passabilityMap.data[i] & obstructionMaskWater) &&
|
||||
obstructionTiles[i] === terrainStates.LAND)
|
||||
{
|
||||
obstructionTiles[i] = terrainStates.SHALLOW_WATER;
|
||||
}
|
||||
}
|
||||
|
||||
super(rawState, "passability", obstructionTiles);
|
||||
this.width = passabilityMap.width;
|
||||
this.height = passabilityMap.height;
|
||||
this.cellSize = passabilityMap.cellSize;
|
||||
}
|
||||
|
||||
this.InfoMap(rawState, "passability", obstructionTiles);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessibility inherits from TerrainAnalysis
|
||||
@@ -59,342 +55,338 @@ TerrainAnalysis.prototype.init = function(sharedScript, rawState)
|
||||
* for optimizations it's called after the TerrainAnalyser has finished initializing his map
|
||||
* so this can use the land regions already.
|
||||
*/
|
||||
export function Accessibility()
|
||||
export class Accessibility extends InfoMap
|
||||
{
|
||||
}
|
||||
|
||||
copyPrototype(Accessibility, TerrainAnalysis);
|
||||
|
||||
Accessibility.prototype.init = function(rawState, terrainAnalyser)
|
||||
{
|
||||
this.InfoMap(rawState, "passability", terrainAnalyser.map);
|
||||
this.landPassMap = new Uint16Array(terrainAnalyser.length);
|
||||
this.navalPassMap = new Uint16Array(terrainAnalyser.length);
|
||||
|
||||
this.maxRegions = 65535;
|
||||
this.regionSize = [];
|
||||
this.regionType = []; // "inaccessible", "land" or "water";
|
||||
// ID of the region associated with an array of region IDs.
|
||||
this.regionLinks = [];
|
||||
|
||||
// initialized to 0, it's more optimized to start at 1 (I'm checking that if it's not 0, then it's already aprt of a region, don't touch);
|
||||
// However I actually store all unpassable as region 1 (because if I don't, on some maps the toal nb of region is over 256, and it crashes as the mapis 8bit.)
|
||||
// So start at 2.
|
||||
this.regionID = 2;
|
||||
|
||||
for (let i = 0; i < this.landPassMap.length; ++i)
|
||||
constructor(rawState, terrainAnalyser)
|
||||
{
|
||||
if (this.map[i] !== terrainStates.IMPASSABLE)
|
||||
{ // any non-painted, non-inacessible area.
|
||||
if (this.landPassMap[i] == 0 && this.floodFill(i, this.regionID, false))
|
||||
this.regionType[this.regionID++] = "land";
|
||||
if (this.navalPassMap[i] == 0 && this.floodFill(i, this.regionID, true))
|
||||
this.regionType[this.regionID++] = "water";
|
||||
}
|
||||
else if (this.landPassMap[i] == 0)
|
||||
{ // any non-painted, inacessible area.
|
||||
this.floodFill(i, 1, false);
|
||||
this.floodFill(i, 1, true);
|
||||
}
|
||||
}
|
||||
super(rawState, "passability", terrainAnalyser.map);
|
||||
this.landPassMap = new Uint16Array(terrainAnalyser.length);
|
||||
this.navalPassMap = new Uint16Array(terrainAnalyser.length);
|
||||
this.maxRegions = 65535;
|
||||
this.regionSize = [];
|
||||
this.regionType = []; // "inaccessible", "land" or "water";
|
||||
// ID of the region associated with an array of region IDs.
|
||||
this.regionLinks = [];
|
||||
|
||||
// calculating region links. Regions only touching diagonaly are not linked.
|
||||
// since we're checking all of them, we'll check from the top left to the bottom right
|
||||
const w = this.width;
|
||||
for (let x = 0; x < this.width-1; ++x)
|
||||
{
|
||||
for (let y = 0; y < this.height-1; ++y)
|
||||
// initialized to 0, it's more optimized to start at 1 (I'm checking that if it's not 0, then it's already aprt of a region, don't touch);
|
||||
// However I actually store all unpassable as region 1 (because if I don't, on some maps the toal nb of region is over 256, and it crashes as the mapis 8bit.)
|
||||
// So start at 2.
|
||||
this.regionID = 2;
|
||||
|
||||
for (let i = 0; i < this.landPassMap.length; ++i)
|
||||
{
|
||||
// checking right.
|
||||
const thisLID = this.landPassMap[x+y*w];
|
||||
const thisNID = this.navalPassMap[x+y*w];
|
||||
const rightLID = this.landPassMap[x+1+y*w];
|
||||
const rightNID = this.navalPassMap[x+1+y*w];
|
||||
const bottomLID = this.landPassMap[x+y*w+w];
|
||||
const bottomNID = this.navalPassMap[x+y*w+w];
|
||||
if (thisLID > 1)
|
||||
{
|
||||
if (rightNID > 1)
|
||||
if (this.regionLinks[thisLID].indexOf(rightNID) === -1)
|
||||
this.regionLinks[thisLID].push(rightNID);
|
||||
if (bottomNID > 1)
|
||||
if (this.regionLinks[thisLID].indexOf(bottomNID) === -1)
|
||||
this.regionLinks[thisLID].push(bottomNID);
|
||||
if (this.map[i] !== terrainStates.IMPASSABLE)
|
||||
{ // any non-painted, non-inacessible area.
|
||||
if (this.landPassMap[i] == 0 && this.floodFill(i, this.regionID, false))
|
||||
this.regionType[this.regionID++] = "land";
|
||||
if (this.navalPassMap[i] == 0 && this.floodFill(i, this.regionID, true))
|
||||
this.regionType[this.regionID++] = "water";
|
||||
}
|
||||
if (thisNID > 1)
|
||||
else if (this.landPassMap[i] == 0)
|
||||
{ // any non-painted, inacessible area.
|
||||
this.floodFill(i, 1, false);
|
||||
this.floodFill(i, 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
// calculating region links. Regions only touching diagonaly are not linked.
|
||||
// since we're checking all of them, we'll check from the top left to the bottom right
|
||||
const w = this.width;
|
||||
for (let x = 0; x < this.width-1; ++x)
|
||||
{
|
||||
for (let y = 0; y < this.height-1; ++y)
|
||||
{
|
||||
if (rightLID > 1)
|
||||
if (this.regionLinks[thisNID].indexOf(rightLID) === -1)
|
||||
this.regionLinks[thisNID].push(rightLID);
|
||||
if (bottomLID > 1)
|
||||
if (this.regionLinks[thisNID].indexOf(bottomLID) === -1)
|
||||
this.regionLinks[thisNID].push(bottomLID);
|
||||
// checking right.
|
||||
const thisLID = this.landPassMap[x+y*w];
|
||||
const thisNID = this.navalPassMap[x+y*w];
|
||||
const rightLID = this.landPassMap[x+1+y*w];
|
||||
const rightNID = this.navalPassMap[x+1+y*w];
|
||||
const bottomLID = this.landPassMap[x+y*w+w];
|
||||
const bottomNID = this.navalPassMap[x+y*w+w];
|
||||
if (thisLID > 1)
|
||||
if (this.regionLinks[thisNID].indexOf(thisLID) === -1)
|
||||
this.regionLinks[thisNID].push(thisLID);
|
||||
{
|
||||
if (rightNID > 1)
|
||||
if (this.regionLinks[thisLID].indexOf(rightNID) === -1)
|
||||
this.regionLinks[thisLID].push(rightNID);
|
||||
if (bottomNID > 1)
|
||||
if (this.regionLinks[thisLID].indexOf(bottomNID) === -1)
|
||||
this.regionLinks[thisLID].push(bottomNID);
|
||||
}
|
||||
if (thisNID > 1)
|
||||
{
|
||||
if (rightLID > 1)
|
||||
if (this.regionLinks[thisNID].indexOf(rightLID) === -1)
|
||||
this.regionLinks[thisNID].push(rightLID);
|
||||
if (bottomLID > 1)
|
||||
if (this.regionLinks[thisNID].indexOf(bottomLID) === -1)
|
||||
this.regionLinks[thisNID].push(bottomLID);
|
||||
if (thisLID > 1)
|
||||
if (this.regionLinks[thisNID].indexOf(thisLID) === -1)
|
||||
this.regionLinks[thisNID].push(thisLID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Engine.DumpImage("LandPassMap.png", this.landPassMap, this.width, this.height, 255);
|
||||
// Engine.DumpImage("NavalPassMap.png", this.navalPassMap, this.width, this.height, 255);
|
||||
}
|
||||
|
||||
// Engine.DumpImage("LandPassMap.png", this.landPassMap, this.width, this.height, 255);
|
||||
// Engine.DumpImage("NavalPassMap.png", this.navalPassMap, this.width, this.height, 255);
|
||||
};
|
||||
|
||||
Accessibility.prototype.getAccessValue = function(position, onWater)
|
||||
{
|
||||
const gamePos = this.gamePosToMapPos(position);
|
||||
if (onWater)
|
||||
return this.navalPassMap[gamePos[0] + this.width*gamePos[1]];
|
||||
let ret = this.landPassMap[gamePos[0] + this.width*gamePos[1]];
|
||||
if (ret === 1)
|
||||
getAccessValue(position, onWater)
|
||||
{
|
||||
// quick spiral search.
|
||||
const indx = [ [-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1]];
|
||||
for (const i of indx)
|
||||
const gamePos = this.gamePosToMapPos(position);
|
||||
if (onWater)
|
||||
return this.navalPassMap[gamePos[0] + this.width*gamePos[1]];
|
||||
let ret = this.landPassMap[gamePos[0] + this.width*gamePos[1]];
|
||||
if (ret === 1)
|
||||
{
|
||||
const id0 = gamePos[0] + i[0];
|
||||
const id1 = gamePos[1] + i[1];
|
||||
if (id0 < 0 || id0 >= this.width || id1 < 0 || id1 >= this.width)
|
||||
continue;
|
||||
ret = this.landPassMap[id0 + this.width*id1];
|
||||
if (ret !== 1)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
Accessibility.prototype.getTrajectTo = function(start, end)
|
||||
{
|
||||
const pstart = this.gamePosToMapPos(start);
|
||||
const istart = pstart[0] + pstart[1]*this.width;
|
||||
const pend = this.gamePosToMapPos(end);
|
||||
const iend = pend[0] + pend[1]*this.width;
|
||||
|
||||
let onLand = true;
|
||||
if (this.landPassMap[istart] <= 1 && this.navalPassMap[istart] > 1)
|
||||
onLand = false;
|
||||
if (this.landPassMap[istart] <= 1 && this.navalPassMap[istart] <= 1)
|
||||
return false;
|
||||
|
||||
let endRegion = this.landPassMap[iend];
|
||||
if (endRegion <= 1 && this.navalPassMap[iend] > 1)
|
||||
endRegion = this.navalPassMap[iend];
|
||||
else if (endRegion <= 1)
|
||||
return false;
|
||||
|
||||
const startRegion = onLand ? this.landPassMap[istart] : this.navalPassMap[istart];
|
||||
return this.getTrajectToIndex(startRegion, endRegion);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a "path" of accessibility indexes from one point to another, including the start and the end indexes
|
||||
* this can tell you what sea zone you need to have a dock on, for example.
|
||||
* assumes a land unit unless start point is over deep water.
|
||||
*/
|
||||
Accessibility.prototype.getTrajectToIndex = function(istart, iend)
|
||||
{
|
||||
if (istart === iend)
|
||||
return [istart];
|
||||
|
||||
const trajects = new Set();
|
||||
const explored = new Set();
|
||||
trajects.add([istart]);
|
||||
explored.add(istart);
|
||||
while (trajects.size)
|
||||
{
|
||||
for (const traj of trajects)
|
||||
{
|
||||
const ilast = traj[traj.length-1];
|
||||
for (const inew of this.regionLinks[ilast])
|
||||
// quick spiral search.
|
||||
const indx = [ [-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1]];
|
||||
for (const i of indx)
|
||||
{
|
||||
if (inew === iend)
|
||||
return traj.concat(iend);
|
||||
if (explored.has(inew))
|
||||
const id0 = gamePos[0] + i[0];
|
||||
const id1 = gamePos[1] + i[1];
|
||||
if (id0 < 0 || id0 >= this.width || id1 < 0 || id1 >= this.width)
|
||||
continue;
|
||||
trajects.add(traj.concat(inew));
|
||||
explored.add(inew);
|
||||
ret = this.landPassMap[id0 + this.width*id1];
|
||||
if (ret !== 1)
|
||||
return ret;
|
||||
}
|
||||
trajects.delete(traj);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
Accessibility.prototype.getRegionSize = function(position, onWater)
|
||||
{
|
||||
const pos = this.gamePosToMapPos(position);
|
||||
const index = pos[0] + pos[1]*this.width;
|
||||
const ID = onWater === true ? this.navalPassMap[index] : this.landPassMap[index];
|
||||
if (this.regionSize[ID] === undefined)
|
||||
return 0;
|
||||
return this.regionSize[ID];
|
||||
};
|
||||
|
||||
Accessibility.prototype.getRegionSizei = function(index, onWater)
|
||||
{
|
||||
if (this.regionSize[this.landPassMap[index]] === undefined && (!onWater || this.regionSize[this.navalPassMap[index]] === undefined))
|
||||
return 0;
|
||||
if (onWater && this.regionSize[this.navalPassMap[index]] > this.regionSize[this.landPassMap[index]])
|
||||
return this.regionSize[this.navalPassMap[index]];
|
||||
return this.regionSize[this.landPassMap[index]];
|
||||
};
|
||||
|
||||
/** Implementation of a fast flood fill. Reasonably good performances for JS. */
|
||||
Accessibility.prototype.floodFill = function(startIndex, value, onWater)
|
||||
{
|
||||
if (value > this.maxRegions)
|
||||
{
|
||||
error("AI accessibility map: too many regions.");
|
||||
this.landPassMap[startIndex] = 1;
|
||||
this.navalPassMap[startIndex] = 1;
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!onWater && this.landPassMap[startIndex] != 0 || onWater && this.navalPassMap[startIndex] != 0)
|
||||
return false; // already painted.
|
||||
|
||||
let floodFor = "land";
|
||||
if (this.map[startIndex] === terrainStates.IMPASSABLE)
|
||||
getTrajectTo(start, end)
|
||||
{
|
||||
this.landPassMap[startIndex] = 1;
|
||||
this.navalPassMap[startIndex] = 1;
|
||||
return false;
|
||||
const pstart = this.gamePosToMapPos(start);
|
||||
const istart = pstart[0] + pstart[1]*this.width;
|
||||
const pend = this.gamePosToMapPos(end);
|
||||
const iend = pend[0] + pend[1]*this.width;
|
||||
|
||||
let onLand = true;
|
||||
if (this.landPassMap[istart] <= 1 && this.navalPassMap[istart] > 1)
|
||||
onLand = false;
|
||||
if (this.landPassMap[istart] <= 1 && this.navalPassMap[istart] <= 1)
|
||||
return false;
|
||||
|
||||
let endRegion = this.landPassMap[iend];
|
||||
if (endRegion <= 1 && this.navalPassMap[iend] > 1)
|
||||
endRegion = this.navalPassMap[iend];
|
||||
else if (endRegion <= 1)
|
||||
return false;
|
||||
|
||||
const startRegion = onLand ? this.landPassMap[istart] : this.navalPassMap[istart];
|
||||
return this.getTrajectToIndex(startRegion, endRegion);
|
||||
}
|
||||
|
||||
if (onWater === true)
|
||||
/**
|
||||
* Return a "path" of accessibility indexes from one point to another, including the start and the end indexes
|
||||
* this can tell you what sea zone you need to have a dock on, for example.
|
||||
* assumes a land unit unless start point is over deep water.
|
||||
*/
|
||||
getTrajectToIndex(istart, iend)
|
||||
{
|
||||
if (this.map[startIndex] !== terrainStates.DEEP_WATER &&
|
||||
this.map[startIndex] !== terrainStates.SHALLOW_WATER)
|
||||
if (istart === iend)
|
||||
return [istart];
|
||||
|
||||
const trajects = new Set();
|
||||
const explored = new Set();
|
||||
trajects.add([istart]);
|
||||
explored.add(istart);
|
||||
while (trajects.size)
|
||||
{
|
||||
this.navalPassMap[startIndex] = 1; // impassable for naval
|
||||
return false; // do nothing
|
||||
for (const traj of trajects)
|
||||
{
|
||||
const ilast = traj[traj.length-1];
|
||||
for (const inew of this.regionLinks[ilast])
|
||||
{
|
||||
if (inew === iend)
|
||||
return traj.concat(iend);
|
||||
if (explored.has(inew))
|
||||
continue;
|
||||
trajects.add(traj.concat(inew));
|
||||
explored.add(inew);
|
||||
}
|
||||
trajects.delete(traj);
|
||||
}
|
||||
}
|
||||
floodFor = "water";
|
||||
}
|
||||
else if (this.map[startIndex] === terrainStates.DEEP_WATER)
|
||||
{
|
||||
this.landPassMap[startIndex] = 1; // impassable for land
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// here we'll be able to start.
|
||||
for (let i = this.regionSize.length; i <= value; ++i)
|
||||
getRegionSize(position, onWater)
|
||||
{
|
||||
this.regionLinks.push([]);
|
||||
this.regionSize.push(0);
|
||||
this.regionType.push("inaccessible");
|
||||
const pos = this.gamePosToMapPos(position);
|
||||
const index = pos[0] + pos[1]*this.width;
|
||||
const ID = onWater === true ? this.navalPassMap[index] : this.landPassMap[index];
|
||||
if (this.regionSize[ID] === undefined)
|
||||
return 0;
|
||||
return this.regionSize[ID];
|
||||
}
|
||||
const w = this.width;
|
||||
const h = this.height;
|
||||
|
||||
// Get x and y from index
|
||||
const IndexArray = [startIndex];
|
||||
let newIndex;
|
||||
while (IndexArray.length)
|
||||
getRegionSizei(index, onWater)
|
||||
{
|
||||
newIndex = IndexArray.pop();
|
||||
if (this.regionSize[this.landPassMap[index]] === undefined && (!onWater || this.regionSize[this.navalPassMap[index]] === undefined))
|
||||
return 0;
|
||||
if (onWater && this.regionSize[this.navalPassMap[index]] > this.regionSize[this.landPassMap[index]])
|
||||
return this.regionSize[this.navalPassMap[index]];
|
||||
return this.regionSize[this.landPassMap[index]];
|
||||
}
|
||||
|
||||
let y = 0;
|
||||
// vertical iteration
|
||||
while (true)
|
||||
/** Implementation of a fast flood fill. Reasonably good performances for JS. */
|
||||
floodFill(startIndex, value, onWater)
|
||||
{
|
||||
if (value > this.maxRegions)
|
||||
{
|
||||
--y;
|
||||
const index = newIndex + w*y;
|
||||
if (index < 0)
|
||||
break;
|
||||
if (floodFor === "land" && this.landPassMap[index] === 0 &&
|
||||
this.map[index] !== terrainStates.IMPASSABLE &&
|
||||
this.map[index] !== terrainStates.DEEP_WATER)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (floodFor === "water" && this.navalPassMap[index] === 0 &&
|
||||
(this.map[index] === terrainStates.DEEP_WATER ||
|
||||
this.map[index] === terrainStates.SHALLOW_WATER))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
} // should actually break
|
||||
++y;
|
||||
let reachLeft = false;
|
||||
let reachRight = false;
|
||||
let index;
|
||||
do
|
||||
error("AI accessibility map: too many regions.");
|
||||
this.landPassMap[startIndex] = 1;
|
||||
this.navalPassMap[startIndex] = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!onWater && this.landPassMap[startIndex] != 0 || onWater && this.navalPassMap[startIndex] != 0)
|
||||
return false; // already painted.
|
||||
|
||||
let floodFor = "land";
|
||||
if (this.map[startIndex] === terrainStates.IMPASSABLE)
|
||||
{
|
||||
index = newIndex + w*y;
|
||||
this.landPassMap[startIndex] = 1;
|
||||
this.navalPassMap[startIndex] = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (floodFor === "land" && this.landPassMap[index] === 0 &&
|
||||
this.map[index] !== terrainStates.IMPASSABLE &&
|
||||
this.map[index] !== terrainStates.DEEP_WATER)
|
||||
if (onWater === true)
|
||||
{
|
||||
if (this.map[startIndex] !== terrainStates.DEEP_WATER &&
|
||||
this.map[startIndex] !== terrainStates.SHALLOW_WATER)
|
||||
{
|
||||
this.landPassMap[index] = value;
|
||||
this.regionSize[value]++;
|
||||
this.navalPassMap[startIndex] = 1; // impassable for naval
|
||||
return false; // do nothing
|
||||
}
|
||||
else if (floodFor === "water" && this.navalPassMap[index] === 0 &&
|
||||
(this.map[index] === terrainStates.DEEP_WATER ||
|
||||
this.map[index] === terrainStates.SHALLOW_WATER))
|
||||
floodFor = "water";
|
||||
}
|
||||
else if (this.map[startIndex] === terrainStates.DEEP_WATER)
|
||||
{
|
||||
this.landPassMap[startIndex] = 1; // impassable for land
|
||||
return false;
|
||||
}
|
||||
|
||||
// here we'll be able to start.
|
||||
for (let i = this.regionSize.length; i <= value; ++i)
|
||||
{
|
||||
this.regionLinks.push([]);
|
||||
this.regionSize.push(0);
|
||||
this.regionType.push("inaccessible");
|
||||
}
|
||||
const w = this.width;
|
||||
const h = this.height;
|
||||
|
||||
// Get x and y from index
|
||||
const IndexArray = [startIndex];
|
||||
let newIndex;
|
||||
while (IndexArray.length)
|
||||
{
|
||||
newIndex = IndexArray.pop();
|
||||
|
||||
let y = 0;
|
||||
// vertical iteration
|
||||
while (true)
|
||||
{
|
||||
this.navalPassMap[index] = value;
|
||||
this.regionSize[value]++;
|
||||
}
|
||||
else
|
||||
--y;
|
||||
const index = newIndex + w*y;
|
||||
if (index < 0)
|
||||
break;
|
||||
if (floodFor === "land" && this.landPassMap[index] === 0 &&
|
||||
this.map[index] !== terrainStates.IMPASSABLE &&
|
||||
this.map[index] !== terrainStates.DEEP_WATER)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (floodFor === "water" && this.navalPassMap[index] === 0 &&
|
||||
(this.map[index] === terrainStates.DEEP_WATER ||
|
||||
this.map[index] === terrainStates.SHALLOW_WATER))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
if (index%w > 0)
|
||||
{
|
||||
if (floodFor === "land" && this.landPassMap[index -1] === 0 &&
|
||||
this.map[index -1] !== terrainStates.IMPASSABLE &&
|
||||
this.map[index -1] !== terrainStates.DEEP_WATER)
|
||||
{
|
||||
if (!reachLeft)
|
||||
{
|
||||
IndexArray.push(index -1);
|
||||
reachLeft = true;
|
||||
}
|
||||
}
|
||||
else if (floodFor === "water" && this.navalPassMap[index -1] === 0 &&
|
||||
(this.map[index -1] === terrainStates.DEEP_WATER ||
|
||||
this.map[index -1] === terrainStates.SHALLOW_WATER))
|
||||
{
|
||||
if (!reachLeft)
|
||||
{
|
||||
IndexArray.push(index -1);
|
||||
reachLeft = true;
|
||||
}
|
||||
}
|
||||
else if (reachLeft)
|
||||
reachLeft = false;
|
||||
}
|
||||
|
||||
if (index%w < w - 1)
|
||||
{
|
||||
if (floodFor === "land" && this.landPassMap[index +1] === 0 &&
|
||||
this.map[index +1] !== terrainStates.IMPASSABLE &&
|
||||
this.map[index +1] !== terrainStates.DEEP_WATER)
|
||||
{
|
||||
if (!reachRight)
|
||||
{
|
||||
IndexArray.push(index +1);
|
||||
reachRight = true;
|
||||
}
|
||||
}
|
||||
else if (floodFor === "water" && this.navalPassMap[index +1] === 0 &&
|
||||
(this.map[index +1] === terrainStates.DEEP_WATER ||
|
||||
this.map[index +1] === terrainStates.SHALLOW_WATER))
|
||||
{
|
||||
if (!reachRight)
|
||||
{
|
||||
IndexArray.push(index +1);
|
||||
reachRight = true;
|
||||
}
|
||||
}
|
||||
else if (reachRight)
|
||||
reachRight = false;
|
||||
}
|
||||
} // should actually break
|
||||
++y;
|
||||
} while (index/w < h-1); // should actually break
|
||||
let reachLeft = false;
|
||||
let reachRight = false;
|
||||
let index;
|
||||
do
|
||||
{
|
||||
index = newIndex + w*y;
|
||||
|
||||
if (floodFor === "land" && this.landPassMap[index] === 0 &&
|
||||
this.map[index] !== terrainStates.IMPASSABLE &&
|
||||
this.map[index] !== terrainStates.DEEP_WATER)
|
||||
{
|
||||
this.landPassMap[index] = value;
|
||||
this.regionSize[value]++;
|
||||
}
|
||||
else if (floodFor === "water" && this.navalPassMap[index] === 0 &&
|
||||
(this.map[index] === terrainStates.DEEP_WATER ||
|
||||
this.map[index] === terrainStates.SHALLOW_WATER))
|
||||
{
|
||||
this.navalPassMap[index] = value;
|
||||
this.regionSize[value]++;
|
||||
}
|
||||
else
|
||||
break;
|
||||
|
||||
if (index%w > 0)
|
||||
{
|
||||
if (floodFor === "land" && this.landPassMap[index -1] === 0 &&
|
||||
this.map[index -1] !== terrainStates.IMPASSABLE &&
|
||||
this.map[index -1] !== terrainStates.DEEP_WATER)
|
||||
{
|
||||
if (!reachLeft)
|
||||
{
|
||||
IndexArray.push(index -1);
|
||||
reachLeft = true;
|
||||
}
|
||||
}
|
||||
else if (floodFor === "water" && this.navalPassMap[index -1] === 0 &&
|
||||
(this.map[index -1] === terrainStates.DEEP_WATER ||
|
||||
this.map[index -1] === terrainStates.SHALLOW_WATER))
|
||||
{
|
||||
if (!reachLeft)
|
||||
{
|
||||
IndexArray.push(index -1);
|
||||
reachLeft = true;
|
||||
}
|
||||
}
|
||||
else if (reachLeft)
|
||||
reachLeft = false;
|
||||
}
|
||||
|
||||
if (index%w < w - 1)
|
||||
{
|
||||
if (floodFor === "land" && this.landPassMap[index +1] === 0 &&
|
||||
this.map[index +1] !== terrainStates.IMPASSABLE &&
|
||||
this.map[index +1] !== terrainStates.DEEP_WATER)
|
||||
{
|
||||
if (!reachRight)
|
||||
{
|
||||
IndexArray.push(index +1);
|
||||
reachRight = true;
|
||||
}
|
||||
}
|
||||
else if (floodFor === "water" && this.navalPassMap[index +1] === 0 &&
|
||||
(this.map[index +1] === terrainStates.DEEP_WATER ||
|
||||
this.map[index +1] === terrainStates.SHALLOW_WATER))
|
||||
{
|
||||
if (!reachRight)
|
||||
{
|
||||
IndexArray.push(index +1);
|
||||
reachRight = true;
|
||||
}
|
||||
}
|
||||
else if (reachRight)
|
||||
reachRight = false;
|
||||
}
|
||||
++y;
|
||||
} while (index/w < h-1); // should actually break
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user