make Petra support passability and territory maps of different resolutions, addresses #2960

This was SVN commit r16072.
This commit is contained in:
mimo
2014-12-27 15:23:20 +00:00
parent 2ef3c0c42e
commit 0dd6711afa
8 changed files with 314 additions and 191 deletions
@@ -9,20 +9,13 @@ var API3 = function(m)
m.Map = function Map(sharedScript, type, originalMap, actualCopy)
{
// get the correct dimensions according to the map type
if (type === "territory")
if (type === "territory" || type === "resource")
{
var map = sharedScript.territoryMap;
this.width = map.width;
this.height = map.height;
this.cellSize = map.cellSize;
}
else if (type === "resource")
{
this.cellSize = 4;
var map = sharedScript.passabilityMap;
this.width = map.width * map.cellSize / this.cellSize;
this.height = map.height * map.cellSize / this.cellSize;
}
else
{
var map = sharedScript.passabilityMap;
@@ -402,37 +395,41 @@ m.Map.prototype.findBestTile = function(radius, obstructionTiles)
let bestVal = -1;
for (let i = 0; i < this.length; ++i)
{
if (obstructionTiles.map[i] > radius)
let v = this.map[i];
if (v > bestVal)
{
let v = this.map[i];
if (v > bestVal)
{
bestVal = v;
bestIdx = i;
}
var j = API3.getMaxMapIndex(i, this, obstructionTiles);
if (obstructionTiles.map[j] <= radius)
continue;
bestVal = v;
bestIdx = j;
}
}
return [bestIdx, bestVal];
};
// returns the point with the lowest radius in the immediate vicinity
m.Map.prototype.findLowestNeighbor = function(x,y)
// returns the point with the lowest (but still > radius) point in the immediate vicinity
m.Map.prototype.findLowestNeighbor = function(x,y,radius)
{
var lowestPt = [0,0];
var lowestcoeff = 99999;
x = Math.floor(x/4);
y = Math.floor(y/4);
var lowestcoeff = undefined;
x = Math.floor(x/this.cellSize);
y = Math.floor(y/this.cellSize);
for (let xx = x-1; xx <= x+1; ++xx)
{
for (let yy = y-1; yy <= y+1; ++yy)
if (xx >= 0 && xx < this.width && yy >= 0 && yy < this.width)
if (this.map[xx+yy*this.width] <= lowestcoeff)
{
lowestcoeff = this.map[xx+yy*this.width];
lowestPt = [(xx+0.5)*4, (yy+0.5)*4];
}
{
if (xx < 0 || xx >= this.width || yy < 0 || yy >= this.width)
continue;
if (lowestcoeff && this.map[xx+yy*this.width] > lowestcoeff)
continue;
lowestcoeff = this.map[xx+yy*this.width];
lowestPt = [(xx+0.5)*4, (yy+0.5)*4];
}
}
return lowestPt;
}
};
m.Map.prototype.dumpIm = function(name, threshold)
{
@@ -159,6 +159,24 @@ m.SharedScript.prototype.init = function(state, deserialization)
this.territoryMap = state.territoryMap;
this.territoryMap.cellSize = this.sizeMap / this.territoryMap.width;
/*
var landPassMap = new Uint8Array(this.passabilityMap.data.length);
var waterPassMap = new Uint8Array(this.passabilityMap.data.length);
var obstructionMap = new Uint8Array(this.passabilityMap.data.length);
var obstructionMaskLand = this.passabilityClasses["default"];
var obstructionMaskWater = this.passabilityClasses["ship"];
var obstructionMask = this.passabilityClasses["pathfinderObstruction"];
for (var i = 0; i < this.passabilityMap.data.length; ++i)
{
landPassMap[i] = (this.passabilityMap.data[i] & obstructionMaskLand) ? 0 : 255;
waterPassMap[i] = (this.passabilityMap.data[i] & obstructionMaskWater) ? 0 : 255;
obstructionMap[i] = (this.passabilityMap.data[i] & obstructionMask) ? 0 : 255;
}
Engine.DumpImage("LandPassMap.png", landPassMap, this.passabilityMap.width, this.passabilityMap.height, 255);
Engine.DumpImage("WaterPassMap.png", waterPassMap, this.passabilityMap.width, this.passabilityMap.height, 255);
Engine.DumpImage("ObstrctionMap.png", obstructionMap, this.passabilityMap.width, this.passabilityMap.height, 255);
*/
if (!deserialization)
{
this._entities = new Map();
@@ -195,8 +195,9 @@ m.TerrainAnalysis.prototype.updateMapWithEvents = function(sharedAI)
let ent = e.entityObj;
if (ent.hasClass("Geology"))
{
let x = this.gamePosToMapPos(ent.position())[0];
let y = this.gamePosToMapPos(ent.position())[1];
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++)
@@ -208,8 +209,9 @@ m.TerrainAnalysis.prototype.updateMapWithEvents = function(sharedAI)
}
else if (ent.hasClass("ForestPlant"))
{
let x = this.gamePosToMapPos(ent.position())[0];
let y = this.gamePosToMapPos(ent.position())[1];
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++)
@@ -238,11 +240,10 @@ m.TerrainAnalysis.prototype.updateMapWithEvents = function(sharedAI)
* it can also determine if any point is "probably" reachable, assuming the unit can get close enough
* for optimizations it's called after the TerrainAnalyser has finished initializing his map
* so this can use the land regions already.
*/
m.Accessibility = function() {
}
m.Accessibility = function()
{
};
m.copyPrototype(m.Accessibility, m.TerrainAnalysis);
@@ -262,13 +263,17 @@ m.Accessibility.prototype.init = function(rawState, terrainAnalyser)
// So start at 2.
this.regionID = 2;
for (var i = 0; i < this.landPassMap.length; ++i) {
if (this.map[i] !== 0) { // any non-painted, non-inacessible area.
for (var i = 0; i < this.landPassMap.length; ++i)
{
if (this.map[i] !== 0)
{ // 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.
}
else if (this.landPassMap[i] === 0)
{ // any non-painted, inacessible area.
this.floodFill(i,1,false);
this.floodFill(i,1,true);
}
@@ -318,7 +323,8 @@ m.Accessibility.prototype.init = function(rawState, terrainAnalyser)
//Engine.DumpImage("NavalPassMap.png", this.navalPassMap, this.width, this.height, 255);
};
m.Accessibility.prototype.getAccessValue = function(position, onWater) {
m.Accessibility.prototype.getAccessValue = function(position, onWater)
{
var gamePos = this.gamePosToMapPos(position);
if (onWater === true)
return this.navalPassMap[gamePos[0] + this.width*gamePos[1]];
@@ -69,12 +69,59 @@ m.ShallowClone = function(obj)
}
// Picks a random element from an array
m.PickRandom = function(list){
m.PickRandom = function(list)
{
if (list.length === 0)
return undefined;
else
return list[Math.floor(Math.random()*list.length)];
}
};
// Utility functions for conversions of maps of different sizes
// It expects that cell size of map 1 is a multiple of cell size of map 2
// return the index of map2 with max content from indices contained inside the cell i of map1
m.getMaxMapIndex = function(i, map1, map2)
{
var ratio = map1.cellSize / map2.cellSize;
var ix = (i % map1.width) * ratio;
var iy = Math.floor(i / map1.width) * ratio;
var index = undefined;
for (var kx = 0; kx < ratio; ++kx)
for (var ky = 0; ky < ratio; ++ky)
if (!index || map2.map[ix+kx+(iy+ky)*map2.width] > map2.map[index])
index = ix+kx+(iy+ky)*map2.width;
return index;
};
// return the list of indices of map2 contained inside the cell i of map1
// map1.cellSize must be a multiple of map2.cellSize
m.getMapIndices = function(i, map1, map2)
{
var ratio = map1.cellSize / map2.cellSize; // TODO check that this is integer >= 1 ?
var ix = (i % map1.width) * ratio;
var iy = Math.floor(i / map1.width) * ratio;
var ret = [];
for (var kx = 0; kx < ratio; ++kx)
for (var ky = 0; ky < ratio; ++ky)
ret.push(ix+kx+(iy+ky)*map2.width);
return ret;
};
// return the list of points of map2 contained inside the cell i of map1
// map1.cellSize must be a multiple of map2.cellSize
m.getMapPoints = function(i, map1, map2)
{
var ratio = map1.cellSize / map2.cellSize; // TODO check that this is integer >= 1 ?
var ix = (i % map1.width) * ratio;
var iy = Math.floor(i / map1.width) * ratio;
var ret = [];
for (var kx = 0; kx < ratio; ++kx)
for (var ky = 0; ky < ratio; ++ky)
ret.push([ix+kx, iy+ky]);
return ret;
};
return m;
@@ -298,17 +298,21 @@ m.BaseManager.prototype.findBestDropsiteLocation = function(gameState, resource)
var DPFoundations = gameState.getOwnFoundations().filter(API3.Filters.byType(gameState.applyCiv("foundation|structures/{civ}_storehouse"))).toEntityArray();
var ccEnts = gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).toEntityArray();
var width = obstructions.width;
var bestIdx = undefined;
var bestVal = undefined;
var radius = Math.ceil(template.obstructionRadius() / obstructions.cellSize);
var territoryMap = gameState.sharedScript.territoryMap;
var width = territoryMap.width;
var cellSize = territoryMap.cellSize;
for (var p = 0; p < this.territoryIndices.length; ++p)
{
var j = this.territoryIndices[p];
if (obstructions.map[j] <= radius) // check room around
var i = API3.getMaxMapIndex(j, territoryMap, obstructions);
if (obstructions.map[i] <= radius) // check room around
continue;
// we add 3 times the needed resource and once the other two (not food)
var total = 0;
for (var i in gameState.sharedScript.resourceMaps)
@@ -322,8 +326,7 @@ m.BaseManager.prototype.findBestDropsiteLocation = function(gameState, resource)
total = 0.7*total; // Just a normalisation factor as the locateMap is limited to 255
var pos = [j%width+0.5, Math.floor(j/width)+0.5];
pos = [gameState.cellSize*pos[0], gameState.cellSize*pos[1]];
var pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
for (var i in this.dropsites)
{
if (!gameState.getEntityById(i))
@@ -388,8 +391,9 @@ m.BaseManager.prototype.findBestDropsiteLocation = function(gameState, resource)
if (bestVal <= 0)
return {"quality": bestVal, "pos": [0, 0]};
var x = ((bestIdx % width) + 0.5) * gameState.cellSize;
var z = (Math.floor(bestIdx / width) + 0.5) * gameState.cellSize;
var i = API3.getMaxMapIndex(bestIdx, territoryMap, obstructions);
var x = ((i % obstructions.width) + 0.5) * obstructions.cellSize;
var z = (Math.floor(i / obstructions.width) + 0.5) * obstructions.cellSize;
return {"quality": bestVal, "pos": [x, z]};
};
@@ -53,10 +53,10 @@ m.HQ.prototype.init = function(gameState, queues, deserializing)
this.territoryMap = m.createTerritoryMap(gameState);
// initialize base map. Each pixel is a base ID, or 0 if not or not accessible
this.basesMap = new API3.Map(gameState.sharedScript, "territory");
// area of 10 cells on the border of the map : 0=inside map, 1=border map, 2=outside map
// area of n cells on the border of the map : 0=inside map, 1=border map, 2=border+inaccessible
this.borderMap = m.createBorderMap(gameState);
// initialize frontier map. Each cell is 2 if on the near frontier, 1 on the frontier and 0 otherwise
this.frontierMap = m.createFrontierMap(gameState, this.borderMap);
this.frontierMap = m.createFrontierMap(gameState);
// list of allowed regions
this.allowedRegions = {};
@@ -64,14 +64,16 @@ m.HQ.prototype.init = function(gameState, queues, deserializing)
this.navalMap = false;
this.navalRegions = [];
var totalSize = gameState.getMap().width * gameState.getMap().width;
var passabilityMap = gameState.getMap();
var totalSize = passabilityMap.width * passabilityMap.width;
var minLandSize = Math.floor(0.2*totalSize);
var minWaterSize = Math.floor(0.3*totalSize);
var cellArea = passabilityMap.cellSize * passabilityMap.cellSize;
for (var i = 0; i < gameState.ai.accessibility.regionSize.length; ++i)
{
if (i == gameState.ai.myIndex)
this.allowedRegions[i] = true;
else if (gameState.ai.accessibility.regionType[i] === "land" && gameState.ai.accessibility.regionSize[i] > 20)
else if (gameState.ai.accessibility.regionType[i] === "land" && cellArea*gameState.ai.accessibility.regionSize[i] > 320)
{
var seaIndex = this.getSeaIndex(gameState, gameState.ai.myIndex, i);
if (!seaIndex)
@@ -94,7 +96,7 @@ m.HQ.prototype.init = function(gameState, queues, deserializing)
if (this.Config.debug > 2)
{
for (var region in this.allowedRegions)
API3.warn(" >>> zone " + region + " taille " + gameState.ai.accessibility.regionSize[region]);
API3.warn(" >>> zone " + region + " taille " + cellArea*gameState.ai.accessibility.regionSize[region]);
}
if (this.Config.difficulty < 2)
@@ -315,9 +317,11 @@ m.HQ.prototype.start = function(gameState, deserializing)
break;
}
}
var cell = gameState.getMap().cellSize;
startingSize = startingSize * cell * cell;
if (this.Config.debug > 1)
API3.warn("starting size " + startingSize + "(cut at 1500 for fish pushing)");
if (startingSize < 1500)
API3.warn("starting size " + startingSize + "(cut at 24000 for fish pushing)");
if (startingSize < 24000)
{
this.saveSpace = true;
this.Config.Economy.popForDock = Math.min(this.Config.Economy.popForDock, 16);
@@ -796,27 +800,29 @@ m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource,
for (var dp of dpEnts.values())
dpList.push({"pos": dp.position()});
var width = this.territoryMap.width;
var radius = Math.ceil(template.obstructionRadius() / gameState.cellSize);
var bestIdx = undefined;
var bestVal = undefined;
var radius = Math.ceil(template.obstructionRadius() / obstructions.cellSize);
var width = this.territoryMap.width;
var cellSize = this.territoryMap.cellSize;
for (var j = 0; j < this.territoryMap.length; ++j)
{
if (this.territoryMap.getOwnerIndex(j) != 0 || this.borderMap.map[j] == 2)
if (this.territoryMap.getOwnerIndex(j) != 0)
continue;
// We require that it is accessible
var index = gameState.ai.accessibility.landPassMap[j];
if (!this.allowedRegions[index])
continue;
// and with enough room around to build the cc
if (obstructions.map[j] <= radius)
var i = API3.getMaxMapIndex(j, this.territoryMap, obstructions);
if (obstructions.map[i] <= radius)
continue;
var norm = 0.5; // TODO adjust it, knowing that we will sum 5 maps
// checking distance to other cc
var pos = [j%width+0.5, Math.floor(j/width)+0.5];
pos = [gameState.cellSize*pos[0], gameState.cellSize*pos[1]];
var pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
var minDist = Math.min();
for (var cc of ccList)
@@ -873,7 +879,7 @@ m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource,
if (norm == 0)
continue;
if (this.borderMap.map[j] == 1) // disfavor the borders of the map
if (this.borderMap.map[j] > 0) // disfavor the borders of the map
norm *= 0.5;
var val = 2*gameState.sharedScript.CCResourceMaps[resource].map[j]
@@ -899,19 +905,19 @@ m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource,
if (bestVal < cut)
return false;
var x = (bestIdx%width + 0.5) * gameState.cellSize;
var z = (Math.floor(bestIdx/width) + 0.5) * gameState.cellSize;
var i = API3.getMaxMapIndex(bestIdx, this.territoryMap, obstructions);
var x = (i % obstructions.width + 0.5) * obstructions.cellSize;
var z = (Math.floor(i / obstructions.width) + 0.5) * obstructions.cellSize;
// Define a minimal number of wanted ships in the seas reaching this new base
var index = gameState.ai.accessibility.landPassMap[bestIdx];
var index = gameState.ai.accessibility.landPassMap[i];
for each (var base in this.baseManagers)
{
if (base.anchor && base.accessIndex != index)
{
var sea = this.getSeaIndex(gameState, base.accessIndex, index);
if (sea !== undefined)
this.navalManager.setMinimalTransportShips(gameState, sea, 1);
}
if (!base.anchor || base.accessIndex === index)
continue;
var sea = this.getSeaIndex(gameState, base.accessIndex, index);
if (sea !== undefined)
this.navalManager.setMinimalTransportShips(gameState, sea, 1);
}
return [x,z];
@@ -945,28 +951,30 @@ m.HQ.prototype.findStrategicCCLocation = function(gameState, template)
var obstructions = m.createObstructionMap(gameState, 0, template);
obstructions.expandInfluences();
var width = this.territoryMap.width;
var radius = Math.ceil(template.obstructionRadius() / gameState.cellSize);
var bestIdx = undefined;
var bestVal = undefined;
var radius = Math.ceil(template.obstructionRadius() / obstructions.cellSize);
var width = this.territoryMap.width;
var cellSize = this.territoryMap.cellSize;
var currentVal, delta;
var distcc0, distcc1, distcc2;
for (var j = 0; j < this.territoryMap.length; ++j)
{
if (this.territoryMap.getOwnerIndex(j) != 0 || this.borderMap.map[j] == 2)
if (this.territoryMap.getOwnerIndex(j) != 0)
continue;
// We require that it is accessible
var index = gameState.ai.accessibility.landPassMap[j];
if (!this.allowedRegions[index])
continue;
// and with enough room around to build the cc
if (obstructions.map[j] <= radius)
var i = API3.getMaxMapIndex(j, this.territoryMap, obstructions);
if (obstructions.map[i] <= radius)
continue;
// checking distances to other cc
var pos = [j%width+0.5, Math.floor(j/width)+0.5];
pos = [gameState.cellSize*pos[0], gameState.cellSize*pos[1]];
var pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
var minDist = Math.min();
distcc0 = undefined;
@@ -1015,7 +1023,7 @@ m.HQ.prototype.findStrategicCCLocation = function(gameState, template)
currentVal += delta*delta;
}
// disfavor border of the map
if (this.borderMap.map[j] == 1)
if (this.borderMap.map[j] > 0)
currentVal += 10000;
if (bestVal !== undefined && currentVal > bestVal)
@@ -1032,19 +1040,19 @@ m.HQ.prototype.findStrategicCCLocation = function(gameState, template)
if (bestVal === undefined)
return undefined;
var x = (bestIdx%width + 0.5) * gameState.cellSize;
var z = (Math.floor(bestIdx/width) + 0.5) * gameState.cellSize;
var i = API3.getMaxMapIndex(bestIdx, this.territoryMap, obstructions);
var x = (i % obstructions.width + 0.5) * obstructions.cellSize;
var z = (Math.floor(i / obstructions.width) + 0.5) * obstructions.cellSize;
// Define a minimal number of wanted ships in the seas reaching this new base
var index = gameState.ai.accessibility.landPassMap[bestIdx];
var index = gameState.ai.accessibility.landPassMap[i];
for each (var base in this.baseManagers)
{
if (base.anchor && base.accessIndex != index)
{
var sea = this.getSeaIndex(gameState, base.accessIndex, index);
if (sea !== undefined)
this.navalManager.setMinimalTransportShips(gameState, sea, 1);
}
if (!base.anchor || base.accessIndex === index)
continue;
var sea = this.getSeaIndex(gameState, base.accessIndex, index);
if (sea !== undefined)
this.navalManager.setMinimalTransportShips(gameState, sea, 1);
}
return [x,z];
@@ -1067,12 +1075,14 @@ m.HQ.prototype.findMarketLocation = function(gameState, template)
var obstructions = m.createObstructionMap(gameState, 0, template);
obstructions.expandInfluences();
var width = this.territoryMap.width;
var bestIdx = undefined;
var bestVal = undefined;
var radius = Math.ceil(template.obstructionRadius() / gameState.cellSize);
var radius = Math.ceil(template.obstructionRadius() / obstructions.cellSize);
var isNavalMarket = template.hasClass("NavalMarket");
var width = this.territoryMap.width;
var cellSize = this.territoryMap.cellSize;
for (var j = 0; j < this.territoryMap.length; ++j)
{
// do not try on the border of our territory
@@ -1080,14 +1090,15 @@ m.HQ.prototype.findMarketLocation = function(gameState, template)
continue;
if (this.basesMap.map[j] == 0) // only in our territory
continue;
if (obstructions.map[j] <= radius) // check room around
// with enough room around to build the cc
var i = API3.getMaxMapIndex(j, this.territoryMap, obstructions);
if (obstructions.map[i] <= radius)
continue;
var index = gameState.ai.accessibility.landPassMap[j];
var index = gameState.ai.accessibility.landPassMap[i];
if (!this.allowedRegions[index])
continue;
var pos = [j%width+0.5, Math.floor(j/width)+0.5];
pos = [gameState.cellSize*pos[0], gameState.cellSize*pos[1]];
var pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
// checking distances to other markets
var maxDist = 0;
for (var market of markets)
@@ -1124,8 +1135,9 @@ m.HQ.prototype.findMarketLocation = function(gameState, template)
(expectedGain < 8 && (!template.hasClass("BarterMarket") || gameState.getOwnStructures().filter(API3.Filters.byClass("BarterMarket")).length > 0)))
return false;
var x = (bestIdx%width + 0.5) * gameState.cellSize;
var z = (Math.floor(bestIdx/width) + 0.5) * gameState.cellSize;
var i = API3.getMaxMapIndex(bestIdx, this.territoryMap, obstructions);
var x = (i % obstructions.width + 0.5) * obstructions.cellSize;
var z = (Math.floor(i / obstructions.width) + 0.5) * obstructions.cellSize;
return [x, z, this.basesMap.map[bestIdx], expectedGain];
};
@@ -1154,16 +1166,17 @@ m.HQ.prototype.findDefensiveLocation = function(gameState, template)
var obstructions = m.createObstructionMap(gameState, 0, template);
obstructions.expandInfluences();
var width = this.territoryMap.width;
var bestIdx = undefined;
var bestVal = undefined;
var width = this.territoryMap.width;
var cellSize = this.territoryMap.cellSize;
var isTower = template.hasClass("Tower");
var isFortress = template.hasClass("Fortress");
if (isFortress)
var radius = Math.floor(template.obstructionRadius() / gameState.cellSize) + 2;
var radius = Math.floor((template.obstructionRadius() + 8) / obstructions.cellSize);
else
var radius = Math.ceil(template.obstructionRadius() / gameState.cellSize);
var radius = Math.ceil(template.obstructionRadius() / obstructions.cellSize);
for (var j = 0; j < this.territoryMap.length; ++j)
{
@@ -1177,11 +1190,12 @@ m.HQ.prototype.findDefensiveLocation = function(gameState, template)
}
if (this.basesMap.map[j] == 0) // inaccessible cell
continue;
if (obstructions.map[j] <= radius) // check room around
// and with enough room around to build the cc
var i = API3.getMaxMapIndex(j, this.territoryMap, obstructions);
if (obstructions.map[i] <= radius)
continue;
var pos = [j%width+0.5, Math.floor(j/width)+0.5];
pos = [gameState.cellSize*pos[0], gameState.cellSize*pos[1]];
var pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
// checking distances to other structures
var minDist = Math.min();
@@ -1240,8 +1254,9 @@ m.HQ.prototype.findDefensiveLocation = function(gameState, template)
if (bestVal === undefined)
return undefined;
var x = (bestIdx%width + 0.5) * gameState.cellSize;
var z = (Math.floor(bestIdx/width) + 0.5) * gameState.cellSize;
var i = API3.getMaxMapIndex(bestIdx, this.territoryMap, obstructions);
var x = (i % obstructions.width + 0.5) * obstructions.cellSize;
var z = (Math.floor(i / obstructions.width) + 0.5) * obstructions.cellSize;
return [x, z, this.basesMap.map[bestIdx]];
};
@@ -1774,11 +1789,13 @@ m.HQ.prototype.updateTerritories = function(gameState)
return;
this.lastTerritoryUpdate = gameState.ai.playedTurn;
var passabilityMap = gameState.getMap();
var width = this.territoryMap.width;
var cellSize = this.territoryMap.cellSize;
var expansion = 0;
for (var j = 0; j < this.territoryMap.length; ++j)
{
if (this.borderMap.map[j] == 2)
if (this.borderMap.map[j] > 1)
continue;
if (this.territoryMap.getOwnerIndex(j) != PlayerID)
{
@@ -1796,18 +1813,27 @@ m.HQ.prototype.updateTerritories = function(gameState)
}
else if (this.basesMap.map[j] == 0)
{
var index = gameState.ai.accessibility.landPassMap[j];
if (!this.allowedRegions[index])
var landPassable = false;
var ind = API3.getMapIndices(j, this.territoryMap, passabilityMap);
var access;
for (var k of ind)
{
if (!this.allowedRegions[gameState.ai.accessibility.landPassMap[k]])
continue;
landPassable = true;
access = gameState.ai.accessibility.landPassMap[k];
break;
}
if (!landPassable)
continue;
var distmin = Math.min();
var baseID = undefined;
var pos = [j%width+0.5, Math.floor(j/width)+0.5];
pos = [gameState.cellSize*pos[0], gameState.cellSize*pos[1]];
var pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
for each (var base in this.baseManagers)
{
if (!base.anchor || !base.anchor.position())
continue;
if (base.accessIndex != index)
if (base.accessIndex != access)
continue;
var dist = API3.SquareVectorDistance(base.anchor.position(), pos);
if (dist >= distmin)
@@ -1823,14 +1849,15 @@ m.HQ.prototype.updateTerritories = function(gameState)
}
}
this.frontierMap = m.createFrontierMap(gameState, this.borderMap);
this.frontierMap = m.createFrontierMap(gameState);
if (!expansion)
return;
// We've increased our territory, so we may have some new room to build
this.stopBuilding = [];
// And if sufficient expansion, check if building a new market would improve our present trade routes
if (expansion > 60)
var cellArea = this.territoryMap.cellSize * this.territoryMap.cellSize;
if (expansion * cellArea > 960)
this.tradeManager.routeProspection = true;
};
@@ -1875,7 +1902,7 @@ m.HQ.prototype.update = function(gameState, queues, events)
return;
if (!ent.position())
return;
var idlePos = ent.setMetadata(PlayerID, "idlePos");
var idlePos = ent.getMetadata(PlayerID, "idlePos");
if (idlePos === undefined || idlePos[0] !== ent.position()[0] || idlePos[1] !== ent.position()[1])
{
ent.setMetadata(PlayerID, "idlePos", ent.position());
@@ -38,7 +38,10 @@ m.createObstructionMap = function(gameState, accessIndex, template)
for (var y = 0; y < passabilityMap.height; ++y)
{
var i = x + y*passabilityMap.width;
var tilePlayer = (territoryMap.data[i] & m.TERRITORY_PLAYER_MASK);
var xter = Math.floor((x+0.5)*passabilityMap.cellSize / territoryMap.cellSize);
var yter = Math.floor((y+0.5)*passabilityMap.cellSize / territoryMap.cellSize);
var iter = xter + yter*territoryMap.width;
var tilePlayer = (territoryMap.data[iter] & m.TERRITORY_PLAYER_MASK);
//if (gameState.ai.myIndex !== gameState.ai.accessibility.landPassMap[i])
//{
@@ -100,7 +103,12 @@ m.createObstructionMap = function(gameState, accessIndex, template)
for (var i = 0; i < passabilityMap.data.length; ++i)
{
var tilePlayer = (territoryMap.data[i] & m.TERRITORY_PLAYER_MASK);
var x = i % passabilityMap.width;
var y = Math.floor(i / passabilityMap.width);
var xter = Math.floor((x+0.5)*passabilityMap.cellSize / territoryMap.cellSize);
var yter = Math.floor((y+0.5)*passabilityMap.cellSize / territoryMap.cellSize);
var iter = xter + yter*territoryMap.width;
var tilePlayer = (territoryMap.data[iter] & m.TERRITORY_PLAYER_MASK);
var invalidTerritory = (
(!buildOwn && tilePlayer == playerID) ||
(!buildAlly && gameState.isPlayerAlly(tilePlayer) && tilePlayer != playerID) ||
@@ -150,41 +158,56 @@ m.createTerritoryMap = function(gameState)
return ret;
};
// TODO check if not already done in obstruction maps
// flag cells around the border of the map (2 if all points into that cell are inaccessible, 1 otherwise)
m.createBorderMap = function(gameState)
{
var map = new API3.Map(gameState.sharedScript, "territory");
var width = map.width;
var border = Math.round(60 / map.cellSize);
var passabilityMap = gameState.sharedScript.passabilityMap;
var obstructionLandMask = gameState.getPassabilityClassMask("default");
var obstructionWaterMask = gameState.getPassabilityClassMask("ship");
if (gameState.ai.circularMap)
{
var ic = (width - 1) / 2;
var radmax = (ic-3)*(ic-3); // we assume three inaccessible cells all around
var radcut = (ic - border) * (ic - border);
for (var j = 0; j < map.length; ++j)
{
var dx = j%width - ic;
var dy = Math.floor(j/width) - ic;
var radius = dx*dx + dy*dy;
if (radius > radmax)
map.map[j] = 2;
else if (radius > (ic - border)*(ic - border))
map.map[j] = 1;
if (radius < radcut)
continue;
map.map[j] = 2;
var ind = API3.getMapIndices(j, map, passabilityMap);
for (var k of ind)
{
if ((passabilityMap.data[j] & obstructionLandMask) && (passabilityMap.data[j] & obstructionWaterMask))
continue;
map.map[j] = 1;
break;
}
}
}
else
{
var borderCut = width - border;
for (var j = 0; j < map.length; ++j)
{
var ix = j%width;
var iy = Math.floor(j/width);
if (ix < border || ix >= width - border)
map.map[j] = 1;
if (iy < border || iy >= width - border)
map.map[j] = 1;
if (ix < 3 || ix >= width - 3) // we assume three inaccessible cells all around
map.map[j] = 2;
if (iy < 3 || iy >= width - 3)
if (ix < border || ix >= borderCut || iy < border || iy >= borderCut)
{
map.map[j] = 2;
var ind = API3.getMapIndices(j, map, passabilityMap);
for (var k of ind)
{
if ((passabilityMap.data[j] & obstructionLandMask) && (passabilityMap.data[j] & obstructionWaterMask))
continue;
map.map[j] = 1;
break;
}
}
}
}
@@ -193,9 +216,10 @@ m.createBorderMap = function(gameState)
};
// map of our frontier : 2 means narrow border, 1 means large border
m.createFrontierMap = function(gameState, borderMap)
m.createFrontierMap = function(gameState)
{
var territoryMap = gameState.ai.HQ.territoryMap;
var borderMap = gameState.ai.HQ.borderMap;
const around = [ [-0.7,0.7], [0,1], [0.7,0.7], [1,0], [0.7,-0.7], [0,-1], [-0.7,-0.7], [-1,0] ];
var map = new API3.Map(gameState.sharedScript, "territory");
@@ -205,7 +229,7 @@ m.createFrontierMap = function(gameState, borderMap)
for (var j = 0; j < territoryMap.length; ++j)
{
if (territoryMap.getOwnerIndex(j) !== PlayerID || (borderMap && borderMap.map[j] > 1))
if (territoryMap.getOwnerIndex(j) !== PlayerID || borderMap.map[j] > 1)
continue;
var ix = j%width;
var iz = Math.floor(j/width);
@@ -217,7 +241,7 @@ m.createFrontierMap = function(gameState, borderMap)
var jz = iz + Math.round(insideSmall*a[1]);
if (jz < 0 || jz >= width)
continue;
if (borderMap && borderMap.map[jx+width*jz] > 1)
if (borderMap.map[jx+width*jz] > 1)
continue;
if (!gameState.isPlayerAlly(territoryMap.getOwnerIndex(jx+width*jz)))
{
@@ -230,7 +254,7 @@ m.createFrontierMap = function(gameState, borderMap)
jz = iz + Math.round(insideLarge*a[1]);
if (jz < 0 || jz >= width)
continue;
if (borderMap && borderMap.map[jx+width*jz] > 1)
if (borderMap.map[jx+width*jz] > 1)
continue;
if (!gameState.isPlayerAlly(territoryMap.getOwnerIndex(jx+width*jz)))
map.map[j] = 1;
@@ -241,11 +265,12 @@ m.createFrontierMap = function(gameState, borderMap)
return map;
};
// return an measure of the proximity to our frontier (including our allies)
// 0=inside, 1=less than 3 tiles, 2= less than 6 tiles, 3= less than 9 tiles, 4=less than 12 tiles, 5=above 12 tiles
m.getFrontierProximity = function(gameState, j, borderMap)
// return a measure of the proximity to our frontier (including our allies)
// 0=inside, 1=less than 16m, 2= less than 32m, 3= less than 48m, 4=less than 64m, 5=above 64m
m.getFrontierProximity = function(gameState, j)
{
var territoryMap = gameState.ai.HQ.territoryMap;
var territoryMap = gameState.ai.HQ.territoryMap;
var borderMap = gameState.ai.HQ.borderMap;
const around = [ [-0.7,0.7], [0,1], [0.7,0.7], [1,0], [0.7,-0.7], [0,-1], [-0.7,-0.7], [-1,0] ];
var width = territoryMap.width;
@@ -267,7 +292,7 @@ m.getFrontierProximity = function(gameState, j, borderMap)
var jz = iz + Math.round(i*step*a[1]);
if (jz < 0 || jz >= width)
continue;
if (borderMap && borderMap.map[jx+width*jz] > 1)
if (borderMap.map[jx+width*jz] > 1)
continue;
if (gameState.isPlayerAlly(territoryMap.getOwnerIndex(jx+width*jz)))
{
@@ -72,7 +72,7 @@ m.ConstructionPlan.prototype.start = function(gameState)
var radius = (+this.template.get("Obstruction/Static/@depth"))/2;
else
var radius = 0;
for (let step = 0; step < radius; step += gameState.cellSize)
for (let step = 0; step < radius; step += 4)
builders[0].construct(this.type, pos.x+step*sinang, pos.z+step*cosang,
pos.angle, this.metadata);
}
@@ -135,18 +135,17 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
}
}
var cellSize = gameState.cellSize; // size of each tile
// First, find all tiles that are far enough away from obstructions:
var obstructionMap = m.createObstructionMap(gameState, 0, template);
obstructionMap.expandInfluences();
var obstructions = m.createObstructionMap(gameState, 0, template);
obstructions.expandInfluences();
//obstructionMap.dumpIm(template.buildCategory() + "_obstructions.png");
//obstructions.dumpIm(template.buildCategory() + "_obstructions.png");
// Compute each tile's closeness to friendly structures:
var friendlyTiles = new API3.Map(gameState.sharedScript, "passability");
var placement = new API3.Map(gameState.sharedScript, "territory");
var cellSize = placement.cellSize; // size of each tile
var alreadyHasHouses = false;
@@ -154,7 +153,7 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
{
var x = Math.floor(this.position[0] / cellSize);
var z = Math.floor(this.position[1] / cellSize);
friendlyTiles.addInfluence(x, z, 255);
placement.addInfluence(x, z, 255);
}
else // No position was specified so try and find a sensible place to build
{
@@ -163,15 +162,15 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
if (this.metadata && this.metadata.base !== undefined)
{
var base = this.metadata.base;
for (var j = 0; j < friendlyTiles.map.length; ++j)
for (var j = 0; j < placement.map.length; ++j)
if (gameState.ai.HQ.basesMap.map[j] == base)
friendlyTiles.map[j] = 45;
placement.map[j] = 45;
}
else
{
for (var j = 0; j < friendlyTiles.map.length; ++j)
for (var j = 0; j < placement.map.length; ++j)
if (gameState.ai.HQ.basesMap.map[j] != 0)
friendlyTiles.map[j] = 45;
placement.map[j] = 45;
}
if (!gameState.ai.HQ.requireHouses || !template.hasClass("House"))
@@ -184,38 +183,38 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf("food") !== -1)
{
if (template.hasClass("Field"))
friendlyTiles.addInfluence(x, z, 20, 50);
placement.addInfluence(x, z, 20, 50);
else // If this is not a field add a negative influence because we want to leave this area for fields
friendlyTiles.addInfluence(x, z, 20, -20);
placement.addInfluence(x, z, 20, -20);
}
else if (template.hasClass("House"))
{
if (ent.hasClass("House"))
{
friendlyTiles.addInfluence(x, z, 15, 40); // houses are close to other houses
placement.addInfluence(x, z, 15, 40); // houses are close to other houses
alreadyHasHouses = true;
}
else
friendlyTiles.addInfluence(x, z, 15, -40); // and further away from other stuffs
placement.addInfluence(x, z, 15, -40); // and further away from other stuffs
}
else if (template.hasClass("Farmstead") && (!ent.hasClass("Field")
&& (!ent.hasClass("StoneWall") || ent.hasClass("Gates"))))
friendlyTiles.addInfluence(x, z, 25, -25); // move farmsteads away to make room (StoneWall test needed for iber)
placement.addInfluence(x, z, 25, -25); // move farmsteads away to make room (StoneWall test needed for iber)
else if (template.hasClass("GarrisonFortress") && ent.genericName() == "House")
friendlyTiles.addInfluence(x, z, 30, -50);
placement.addInfluence(x, z, 30, -50);
else if (template.hasClass("Military"))
friendlyTiles.addInfluence(x, z, 10, -40);
placement.addInfluence(x, z, 10, -40);
});
}
if (template.hasClass("Farmstead"))
{
for (var j = 0; j < friendlyTiles.map.length; ++j)
for (var j = 0; j < placement.map.length; ++j)
{
var value = friendlyTiles.map[j] - (gameState.sharedScript.resourceMaps["wood"].map[j])/3;
friendlyTiles.map[j] = value >= 0 ? value : 0;
var value = placement.map[j] - (gameState.sharedScript.resourceMaps["wood"].map[j])/3;
placement.map[j] = value >= 0 ? value : 0;
if (gameState.ai.HQ.borderMap.map[j] > 0)
friendlyTiles.map[j] /= 2; // we need space around farmstead, so disfavor map border
placement.map[j] /= 2; // we need space around farmstead, so disfavor map border
}
}
}
@@ -228,44 +227,44 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
if (this.metadata && this.metadata.base !== undefined)
{
var base = this.metadata.base;
for (var j = 0; j < friendlyTiles.map.length; ++j)
for (var j = 0; j < placement.map.length; ++j)
{
if (gameState.ai.HQ.basesMap.map[j] != base)
friendlyTiles.map[j] = 0;
placement.map[j] = 0;
else if (favorBorder && gameState.ai.HQ.borderMap.map[j] > 0)
friendlyTiles.map[j] += 50;
else if (disfavorBorder && gameState.ai.HQ.borderMap.map[j] == 0 && friendlyTiles.map[j] > 0)
friendlyTiles.map[j] += 10;
placement.map[j] += 50;
else if (disfavorBorder && gameState.ai.HQ.borderMap.map[j] == 0 && placement.map[j] > 0)
placement.map[j] += 10;
if (friendlyTiles.map[j] > 0)
if (placement.map[j] > 0)
{
var x = (j % friendlyTiles.width + 0.5) * cellSize;
var z = (Math.floor(j / friendlyTiles.width) + 0.5) * cellSize;
var x = (j % placement.width + 0.5) * cellSize;
var z = (Math.floor(j / placement.width) + 0.5) * cellSize;
if (gameState.ai.HQ.isDangerousLocation([x, z]))
friendlyTiles.map[j] = 0;
placement.map[j] = 0;
}
}
}
else
{
for (var j = 0; j < friendlyTiles.map.length; ++j)
for (var j = 0; j < placement.map.length; ++j)
{
if (gameState.ai.HQ.basesMap.map[j] == 0)
friendlyTiles.map[j] = 0;
placement.map[j] = 0;
else if (favorBorder && gameState.ai.HQ.borderMap.map[j] > 0)
friendlyTiles.map[j] += 50;
else if (disfavorBorder && gameState.ai.HQ.borderMap.map[j] == 0 && friendlyTiles.map[j] > 0)
friendlyTiles.map[j] += 10;
placement.map[j] += 50;
else if (disfavorBorder && gameState.ai.HQ.borderMap.map[j] == 0 && placement.map[j] > 0)
placement.map[j] += 10;
if (preferredBase && gameState.ai.HQ.basesMap.map[j] == this.metadata.preferredBase)
friendlyTiles.map[j] += 200;
placement.map[j] += 200;
if (friendlyTiles.map[j] > 0)
if (placement.map[j] > 0)
{
var x = (j % friendlyTiles.width + 0.5) * cellSize;
var z = (Math.floor(j / friendlyTiles.width) + 0.5) * cellSize;
var x = (j % placement.width + 0.5) * cellSize;
var z = (Math.floor(j / placement.width) + 0.5) * cellSize;
if (gameState.ai.HQ.isDangerousLocation([x, z]))
friendlyTiles.map[j] = 0;
placement.map[j] = 0;
}
}
}
@@ -278,24 +277,24 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
var radius = 0;
if (template.hasClass("Fortress") || this.type === gameState.applyCiv("structures/{civ}_siege_workshop")
|| this.type === gameState.applyCiv("structures/{civ}_elephant_stables"))
radius = Math.floor(template.obstructionRadius() / cellSize) + 3;
radius = Math.floor((template.obstructionRadius() + 12) / obstructions.cellSize);
else if (template.resourceDropsiteTypes() === undefined && !template.hasClass("House") && !template.hasClass("Field"))
radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
radius = Math.ceil((template.obstructionRadius() + 4) / obstructions.cellSize);
else
radius = Math.ceil(template.obstructionRadius() / cellSize);
radius = Math.ceil(template.obstructionRadius() / obstructions.cellSize);
// Find the best non-obstructed
if (template.hasClass("House") && !alreadyHasHouses)
{
// try to get some space first
var bestTile = friendlyTiles.findBestTile(10, obstructionMap);
// try to get some space to place several houses first
var bestTile = placement.findBestTile(3*radius, obstructions);
var bestIdx = bestTile[0];
var bestVal = bestTile[1];
}
if (bestVal === undefined || bestVal == -1)
{
var bestTile = friendlyTiles.findBestTile(radius, obstructionMap);
var bestTile = placement.findBestTile(radius, obstructions);
var bestIdx = bestTile[0];
var bestVal = bestTile[1];
}
@@ -303,11 +302,11 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
if (bestVal <= 0)
return false;
var x = ((bestIdx % friendlyTiles.width) + 0.5) * cellSize;
var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * cellSize;
var x = ((bestIdx % obstructions.width) + 0.5) * obstructions.cellSize;
var z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
if (template.hasClass("House") || template.hasClass("Field") || template.resourceDropsiteTypes() !== undefined)
var secondBest = obstructionMap.findLowestNeighbor(x,z);
var secondBest = obstructions.findLowestNeighbor(x,z);
else
var secondBest = [x,z];
@@ -324,8 +323,8 @@ m.ConstructionPlan.prototype.findDockPosition = function(gameState)
var cellSize = gameState.cellSize; // size of each tile
var territoryMap = gameState.ai.HQ.territoryMap;
var obstructionMap = m.createObstructionMap(gameState, 0, template);
//obstructionMap.dumpIm(template.buildCategory() + "_obstructions.png");
var obstructions = m.createObstructionMap(gameState, 0, template);
//obstructions.dumpIm(template.buildCategory() + "_obstructions.png");
var bestIdx = undefined;
var bestVal = 0;
@@ -333,7 +332,7 @@ m.ConstructionPlan.prototype.findDockPosition = function(gameState)
var navalPassMap = gameState.ai.accessibility.navalPassMap;
for (let j = 0; j < territoryMap.length; ++j)
{
if (obstructionMap.map[j] <= 0)
if (obstructions.map[j] <= 0)
continue;
if (this.metadata)
{
@@ -347,7 +346,7 @@ m.ConstructionPlan.prototype.findDockPosition = function(gameState)
continue;
// if not in our (or allied) territory, we do not want it too far to be able to defend it
let nearby = m.getFrontierProximity(gameState, j, gameState.ai.HQ.borderMap);
let nearby = m.getFrontierProximity(gameState, j);
if (nearby > 4)
continue;