From 0dd6711afa2e978fa372b8eb7dbe9a9d25c21191 Mon Sep 17 00:00:00 2001 From: mimo Date: Sat, 27 Dec 2014 15:23:20 +0000 Subject: [PATCH] make Petra support passability and territory maps of different resolutions, addresses #2960 This was SVN commit r16072. --- .../simulation/ai/common-api/map-module.js | 51 +++--- .../public/simulation/ai/common-api/shared.js | 18 ++ .../ai/common-api/terrain-analysis.js | 30 ++-- .../public/simulation/ai/common-api/utils.js | 51 +++++- .../public/simulation/ai/petra/baseManager.js | 18 +- .../simulation/ai/petra/headquarters.js | 155 ++++++++++-------- .../public/simulation/ai/petra/map-module.js | 73 ++++++--- .../simulation/ai/petra/queueplan-building.js | 109 ++++++------ 8 files changed, 314 insertions(+), 191 deletions(-) diff --git a/binaries/data/mods/public/simulation/ai/common-api/map-module.js b/binaries/data/mods/public/simulation/ai/common-api/map-module.js index 89f9d15523..e02cf3af87 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/map-module.js +++ b/binaries/data/mods/public/simulation/ai/common-api/map-module.js @@ -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) { diff --git a/binaries/data/mods/public/simulation/ai/common-api/shared.js b/binaries/data/mods/public/simulation/ai/common-api/shared.js index 3d33fb7c10..ebd8204ab5 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/shared.js +++ b/binaries/data/mods/public/simulation/ai/common-api/shared.js @@ -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(); diff --git a/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis.js b/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis.js index 29eb179496..281397b275 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis.js +++ b/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis.js @@ -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]]; diff --git a/binaries/data/mods/public/simulation/ai/common-api/utils.js b/binaries/data/mods/public/simulation/ai/common-api/utils.js index 7d8e522647..a67729ccb5 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/utils.js +++ b/binaries/data/mods/public/simulation/ai/common-api/utils.js @@ -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; diff --git a/binaries/data/mods/public/simulation/ai/petra/baseManager.js b/binaries/data/mods/public/simulation/ai/petra/baseManager.js index 428125e100..350a65b7ac 100644 --- a/binaries/data/mods/public/simulation/ai/petra/baseManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/baseManager.js @@ -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]}; }; diff --git a/binaries/data/mods/public/simulation/ai/petra/headquarters.js b/binaries/data/mods/public/simulation/ai/petra/headquarters.js index 77c55b0918..3a1fc4c569 100644 --- a/binaries/data/mods/public/simulation/ai/petra/headquarters.js +++ b/binaries/data/mods/public/simulation/ai/petra/headquarters.js @@ -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()); diff --git a/binaries/data/mods/public/simulation/ai/petra/map-module.js b/binaries/data/mods/public/simulation/ai/petra/map-module.js index a3813d854a..788b991cbb 100644 --- a/binaries/data/mods/public/simulation/ai/petra/map-module.js +++ b/binaries/data/mods/public/simulation/ai/petra/map-module.js @@ -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))) { diff --git a/binaries/data/mods/public/simulation/ai/petra/queueplan-building.js b/binaries/data/mods/public/simulation/ai/petra/queueplan-building.js index 96267d5d2f..21431ca995 100644 --- a/binaries/data/mods/public/simulation/ai/petra/queueplan-building.js +++ b/binaries/data/mods/public/simulation/ai/petra/queueplan-building.js @@ -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;