mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-08-15 14:43:32 -07: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:
parent
ea1026a1cc
commit
f9de339f85
3 changed files with 517 additions and 537 deletions
|
|
@ -2,8 +2,10 @@
|
|||
* 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
|
||||
{
|
||||
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;
|
||||
|
|
@ -27,28 +29,28 @@ export function InfoMap(sharedScript, type, originalMap, actualCopy)
|
|||
this.map = originalMap;
|
||||
else
|
||||
this.map = new Uint8Array(this.length);
|
||||
}
|
||||
}
|
||||
|
||||
InfoMap.prototype.setMaxVal = function(val)
|
||||
{
|
||||
setMaxVal(val)
|
||||
{
|
||||
this.maxVal = val;
|
||||
};
|
||||
}
|
||||
|
||||
InfoMap.prototype.gamePosToMapPos = function(p)
|
||||
{
|
||||
gamePosToMapPos(p)
|
||||
{
|
||||
return [Math.floor(p[0]/this.cellSize), Math.floor(p[1]/this.cellSize)];
|
||||
};
|
||||
}
|
||||
|
||||
InfoMap.prototype.point = function(p)
|
||||
{
|
||||
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]];
|
||||
};
|
||||
}
|
||||
|
||||
InfoMap.prototype.runLoop = function(x0, x1, y0, y1, cx, cy, maxDist2, func)
|
||||
{
|
||||
runLoop(x0, x1, y0, y1, cx, cy, maxDist2, func)
|
||||
{
|
||||
for (let y = y0; y < y1; ++y)
|
||||
{
|
||||
const dy2 = (y - cy) * (y - cy);
|
||||
|
|
@ -63,10 +65,10 @@ InfoMap.prototype.runLoop = function(x0, x1, y0, y1, cx, cy, maxDist2, func)
|
|||
this.set(w, func(w, r2));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
InfoMap.prototype.addInfluence = function(cx, cy, maxDist, strength, type = "linear")
|
||||
{
|
||||
addInfluence(cx, cy, maxDist, strength, type = "linear")
|
||||
{
|
||||
strength = strength ? strength : maxDist;
|
||||
|
||||
const x0 = Math.floor(Math.max(0, cx - maxDist));
|
||||
|
|
@ -87,11 +89,10 @@ InfoMap.prototype.addInfluence = function(cx, cy, maxDist, strength, type = "lin
|
|||
}
|
||||
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")
|
||||
{
|
||||
multiplyInfluence(cx, cy, maxDist, strength, type = "constant")
|
||||
{
|
||||
strength = strength ? +strength : +maxDist;
|
||||
|
||||
const x0 = Math.max(0, cx - maxDist);
|
||||
|
|
@ -112,24 +113,24 @@ InfoMap.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type =
|
|||
}
|
||||
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)
|
||||
{
|
||||
/** add to current map by the parameter map pixelwise */
|
||||
add(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)
|
||||
{
|
||||
/** Set the value taking overflow into account */
|
||||
set(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)
|
||||
{
|
||||
/** Find the best non-obstructed tile */
|
||||
findBestTile(radius, obstruction)
|
||||
{
|
||||
let bestIdx;
|
||||
let bestVal = 0;
|
||||
for (let j = 0; j < this.length; ++j)
|
||||
|
|
@ -144,11 +145,11 @@ InfoMap.prototype.findBestTile = function(radius, obstruction)
|
|||
}
|
||||
|
||||
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)
|
||||
{
|
||||
/** return any non obstructed (small) tile inside the (big) tile i from obstruction map */
|
||||
getNonObstructedTile(i, radius, obstruction)
|
||||
{
|
||||
const ratio = this.cellSize / obstruction.cellSize;
|
||||
const ix = (i % this.width) * ratio;
|
||||
const iy = Math.floor(i / this.width) * ratio;
|
||||
|
|
@ -171,11 +172,11 @@ InfoMap.prototype.getNonObstructedTile = function(i, radius, obstruction)
|
|||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
}
|
||||
|
||||
/** return true if the area centered on tile kx-ky and with radius is obstructed */
|
||||
InfoMap.prototype.isObstructedTile = function(kx, ky, radius)
|
||||
{
|
||||
/** 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 };
|
||||
|
|
@ -204,9 +205,10 @@ InfoMap.prototype.isObstructedTile = function(kx, ky, radius)
|
|||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
InfoMap.prototype.dumpIm = function(name = "default.png", threshold = this.maxVal)
|
||||
{
|
||||
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,18 +11,11 @@ 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()
|
||||
{
|
||||
}
|
||||
|
||||
copyPrototype(TerrainAnalysis, InfoMap);
|
||||
|
||||
TerrainAnalysis.prototype.init = function(sharedScript, rawState)
|
||||
export class TerrainAnalysis extends InfoMap
|
||||
{
|
||||
constructor(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"];
|
||||
|
|
@ -48,8 +40,12 @@ TerrainAnalysis.prototype.init = function(sharedScript, rawState)
|
|||
}
|
||||
}
|
||||
|
||||
this.InfoMap(rawState, "passability", obstructionTiles);
|
||||
};
|
||||
super(rawState, "passability", obstructionTiles);
|
||||
this.width = passabilityMap.width;
|
||||
this.height = passabilityMap.height;
|
||||
this.cellSize = passabilityMap.cellSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessibility inherits from TerrainAnalysis
|
||||
|
|
@ -59,18 +55,13 @@ 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);
|
||||
constructor(rawState, terrainAnalyser)
|
||||
{
|
||||
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";
|
||||
|
|
@ -138,10 +129,10 @@ Accessibility.prototype.init = function(rawState, terrainAnalyser)
|
|||
|
||||
// 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)
|
||||
{
|
||||
getAccessValue(position, onWater)
|
||||
{
|
||||
const gamePos = this.gamePosToMapPos(position);
|
||||
if (onWater)
|
||||
return this.navalPassMap[gamePos[0] + this.width*gamePos[1]];
|
||||
|
|
@ -162,10 +153,10 @@ Accessibility.prototype.getAccessValue = function(position, onWater)
|
|||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
Accessibility.prototype.getTrajectTo = function(start, end)
|
||||
{
|
||||
getTrajectTo(start, end)
|
||||
{
|
||||
const pstart = this.gamePosToMapPos(start);
|
||||
const istart = pstart[0] + pstart[1]*this.width;
|
||||
const pend = this.gamePosToMapPos(end);
|
||||
|
|
@ -185,15 +176,15 @@ Accessibility.prototype.getTrajectTo = function(start, end)
|
|||
|
||||
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)
|
||||
{
|
||||
getTrajectToIndex(istart, iend)
|
||||
{
|
||||
if (istart === iend)
|
||||
return [istart];
|
||||
|
||||
|
|
@ -219,30 +210,30 @@ Accessibility.prototype.getTrajectToIndex = function(istart, iend)
|
|||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
Accessibility.prototype.getRegionSize = function(position, onWater)
|
||||
{
|
||||
getRegionSize(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)
|
||||
{
|
||||
getRegionSizei(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)
|
||||
{
|
||||
/** Implementation of a fast flood fill. Reasonably good performances for JS. */
|
||||
floodFill(startIndex, value, onWater)
|
||||
{
|
||||
if (value > this.maxRegions)
|
||||
{
|
||||
error("AI accessibility map: too many regions.");
|
||||
|
|
@ -397,4 +388,5 @@ Accessibility.prototype.floodFill = function(startIndex, value, onWater)
|
|||
} while (index/w < h-1); // should actually break
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue