diff --git a/binaries/data/mods/public/simulation/ai/jubot/economy.js b/binaries/data/mods/public/simulation/ai/jubot/economy.js index 145b528ff6..581d32a6ed 100644 --- a/binaries/data/mods/public/simulation/ai/jubot/economy.js +++ b/binaries/data/mods/public/simulation/ai/jubot/economy.js @@ -372,6 +372,9 @@ var EconomyManager = Class({ buildMoreBuildings: function(gameState, planGroups) { var numCCs = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("structures/{civ}_civil_centre")); + var numMills = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("structures/{civ}_mill")); + var numFarmsteads = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("structures/{civ}_farmstead")); + var defensequot = numMills + numFarmsteads if (numCCs < 1) { planGroups.economyConstruction.addPlan(1000, @@ -398,6 +401,15 @@ var EconomyManager = Class({ if (gameState.findFoundations().length > 0) return; + var numTowers = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("structures/{civ}_defense_tower")); + if (numTowers < defensequot) + { + planGroups.economyConstruction.addPlan(150, + new BuildingConstructionPlanDefensePoints(gameState, "structures/{civ}_defense_tower", 1) + ); + return; + } + // START BY GETTING ALL CCs UP TO SMALL VILLAGE LEVEL for each (var building in this.villageBuildings) { @@ -625,11 +637,11 @@ var EconomyManager = Class({ for (var type in this.gatherWeights) numGatherers[type] = 0; - gameState.getOwnEntitiesWithRole("worker").forEach(function(ent) { + gameState.getOwnRoleGroup("worker").forEach(function(ent) { if (ent.getMetadata("subrole") === "gatherer") numGatherers[ent.getMetadata("gather-type")] += 1; }); - gameState.getOwnEntitiesWithRole("militia").forEach(function(ent) { + gameState.getOwnRoleGroup("militia").forEach(function(ent) { if (ent.getMetadata("subrole") === "gatherer") numGatherers[ent.getMetadata("gather-type")] += 1; }); @@ -647,7 +659,7 @@ var EconomyManager = Class({ reassignRolelessUnits: function(gameState) { - var roleless = gameState.getOwnEntitiesWithRole(undefined); + var roleless = gameState.getOwnRoleGroup(undefined); roleless.forEach(function(ent) @@ -659,12 +671,12 @@ var EconomyManager = Class({ else if (ent.hasClass("CitizenSoldier") && ent.hasClass("Infantry")) { var currentPosition = ent.position(); - var targets = gameState.entities.filter(function(enten) { + var targets = gameState.getJustEnemies().filter(function(enten) { var foeposition = enten.position(); if (foeposition) { var dist = SquareVectorDistance(foeposition, currentPosition); - return (enten.isEnemy() && enten.owner()!= 0 && dist < 2500); + return (dist < 2500); } else { diff --git a/binaries/data/mods/public/simulation/ai/jubot/gamestate.js b/binaries/data/mods/public/simulation/ai/jubot/gamestate.js index 5864ef64f2..383274290c 100644 --- a/binaries/data/mods/public/simulation/ai/jubot/gamestate.js +++ b/binaries/data/mods/public/simulation/ai/jubot/gamestate.js @@ -69,6 +69,15 @@ var GameState = Class({ return this.entities; }, + updatingCollection: function(id, filter, collection){ + if (!this.store[id]){ + this.store[id] = collection.filter(filter); + this.store[id].registerUpdates(); + } + + return this.store[id]; + }, + getOwnEntities: function() { if (!this.store.ownEntities){ @@ -79,6 +88,69 @@ var GameState = Class({ return this.store.ownEntities; }, + getOwnRoleGroup: function(role) { + return this.updatingCollection("RoleGroup" + role, Filters.byMetadata("role", role), this.getOwnEntities()); + }, + + getOwnWithClass: function(aclass) { + return this.updatingCollection("RoleGroup" + aclass, Filters.byClass(aclass), this.getOwnEntities()); + }, + + getNotGaia: function() { + var collection = this.updatingCollection("NotGaia", Filters.byNotOwner(0), this.getEntities()); + return collection; + }, + + getJustEnemies: function() { + var collection = this.updatingCollection("JustEnemies", Filters.byNotOwner(this.player), this.getNotGaia()); + //warn(collection.length + " enemy unit objects") + return collection; + }, + + // These get enemies things are copied from qbot, to avoid duplication of a ton of effort (yay laziness) + getEnemies: function(){ + var ret = []; + for (i in this.playerData.isEnemy){ + if (this.playerData.isEnemy[i]){ + ret.push(i); + } + } + return ret; + }, + + getEnemyEntities: function() { + var diplomacyChange = false; + var enemies = this.getEnemies(); + warn(enemies.length + " enemy factions") + if (this.store.enemies){ + if (this.store.enemies.length != enemies.length){ + diplomacyChange = true; + } + else{ + for (var i = 0; i < enemies.length; i++){ + if (enemies[i] !== this.store.enemies[i]){ + diplomacyChange = true; + } + } + } + } + if (!this.store.enemyEntities){ + var filter = Filters.byOwners(enemies); + this.store.enemyEntities = this.getEntities().filter(filter); + this.store.enemyEntities.registerUpdates(); + this.store.enemies = enemies; + } + if (diplomacyChange == true){ + var filter = Filters.byOwners(enemies); + this.store.enemyEntities = this.getEntities().filter(filter); + this.store.enemyEntities.registerUpdates(); + this.store.enemies = enemies; + } + warn(this.store.enemyEntities.length + " enemy objects") + return this.store.enemyEntities; +}, +/// Laziness ends here. + getOwnEntitiesWithRole: Memoize('getOwnEntitiesWithRole', function(role) { var metas = this.ai._entityMetadata; diff --git a/binaries/data/mods/public/simulation/ai/jubot/military.js b/binaries/data/mods/public/simulation/ai/jubot/military.js index 5774a68766..1dca6f525e 100644 --- a/binaries/data/mods/public/simulation/ai/jubot/military.js +++ b/binaries/data/mods/public/simulation/ai/jubot/military.js @@ -51,43 +51,41 @@ var MilitaryAttackManager = Class({ regroup: function(gameState, planGroups) { if (gameState.getTimeElapsed() > this.changetimeReg && this.killstrat != 3){ - var regroupneeded = gameState.getOwnEntitiesWithRole("attack"); + var regroupneeded = gameState.getOwnRoleGroup("attack"); regroupneeded.forEach(function(ent) { ent.setMetadata("role", "attack-pending"); }); - var regroupneeded = gameState.getOwnEntitiesWithRole("attack_3p1"); + var regroupneeded = gameState.getOwnRoleGroup("attack_3p1"); regroupneeded.forEach(function(ent) { ent.setMetadata("role", "attack-pending"); }); - var regroupneeded = gameState.getOwnEntitiesWithRole("attack_3p2"); + var regroupneeded = gameState.getOwnRoleGroup("attack_3p2"); regroupneeded.forEach(function(ent) { ent.setMetadata("role", "attack-pending"); }); - var regroupneeded = gameState.getOwnEntitiesWithRole("attack_3p3"); + var regroupneeded = gameState.getOwnRoleGroup("attack_3p3"); regroupneeded.forEach(function(ent) { ent.setMetadata("role", "attack-pending"); }); - var regroupneeded = gameState.getOwnEntitiesWithRole("attack-pending_3p1"); + var regroupneeded = gameState.getOwnRoleGroup("attack-pending_3p1"); regroupneeded.forEach(function(ent) { ent.setMetadata("role", "attack-pending"); }); - var regroupneeded = gameState.getOwnEntitiesWithRole("attack-pending_3p2"); + var regroupneeded = gameState.getOwnRoleGroup("attack-pending_3p2"); regroupneeded.forEach(function(ent) { ent.setMetadata("role", "attack-pending"); }); - var regroupneeded = gameState.getOwnEntitiesWithRole("attack-pending_3p3"); + var regroupneeded = gameState.getOwnRoleGroup("attack-pending_3p3"); regroupneeded.forEach(function(ent) { ent.setMetadata("role", "attack-pending"); }); - var regroupneeded = gameState.getOwnEntitiesWithRole("fighting"); + var regroupneeded = gameState.getOwnRoleGroup("fighting"); regroupneeded.forEach(function(ent) { ent.setMetadata("role", "attack-pending"); }); - var regroupneededPartB = gameState.getOwnEntitiesWithRole("attack-pending"); + var regroupneededPartB = gameState.getOwnRoleGroup("attack-pending"); //Find a friendsly CC - var targets = gameState.entities.filter(function(ent) { - return (!ent.isEnemy() && ent.hasClass("CivCentre")); - }); + var targets = gameState.getOwnWithClass("CivCentre"); if (targets.length){ var target = targets.toEntityArray()[0]; var targetPos = target.position(); @@ -99,7 +97,7 @@ var MilitaryAttackManager = Class({ this.changetimeReg = this.changetimeReg + (60*4000); } else if (gameState.getTimeElapsed() > this.changetimeReg && this.killstrat == 3){ - var regroupneeded = gameState.getOwnEntitiesWithRole("attack"); + var regroupneeded = gameState.getOwnRoleGroup("attack"); regroupneeded.forEach(function(ent) { var section = Math.random(); if (section < 0.3){ @@ -112,7 +110,7 @@ var MilitaryAttackManager = Class({ ent.setMetadata("role", "attack-pending_3p3"); } }); - var regroupneeded = gameState.getOwnEntitiesWithRole("attack-pending"); + var regroupneeded = gameState.getOwnRoleGroup("attack-pending"); regroupneeded.forEach(function(ent) { var section = Math.random(); if (section < 0.3){ @@ -125,19 +123,19 @@ var MilitaryAttackManager = Class({ ent.setMetadata("role", "attack-pending_3p3"); } }); - var regroupneeded = gameState.getOwnEntitiesWithRole("attack_3p1"); + var regroupneeded = gameState.getOwnRoleGroup("attack_3p1"); regroupneeded.forEach(function(ent) { ent.setMetadata("role", "attack-pending_3p1"); }); - var regroupneeded = gameState.getOwnEntitiesWithRole("attack_3p2"); + var regroupneeded = gameState.getOwnRoleGroup("attack_3p2"); regroupneeded.forEach(function(ent) { ent.setMetadata("role", "attack-pending_3p2"); }); - var regroupneeded = gameState.getOwnEntitiesWithRole("attack_3p3"); + var regroupneeded = gameState.getOwnRoleGroup("attack_3p3"); regroupneeded.forEach(function(ent) { ent.setMetadata("role", "attack-pending_3p3"); }); - var regroupneeded = gameState.getOwnEntitiesWithRole("fighting"); + var regroupneeded = gameState.getOwnRoleGroup("fighting"); regroupneeded.forEach(function(ent) { var section = Math.random(); if (section < 0.3){ @@ -152,11 +150,9 @@ var MilitaryAttackManager = Class({ }); // MOVE THEM ALL // GROUP ONE - var regroupneededPartB = gameState.getOwnEntitiesWithRole("attack-pending_3p1"); + var regroupneededPartB = gameState.getOwnRoleGroup("attack-pending_3p1"); //Find a friendsly CC - var targets = gameState.entities.filter(function(ent) { - return (!ent.isEnemy() && ent.hasClass("CivCentre")); - }); + var targets = gameState.getOwnWithClass("CivCentre"); if (targets.length){ var target = targets.toEntityArray()[0]; var targetPos = target.position(); @@ -165,11 +161,9 @@ var MilitaryAttackManager = Class({ regroupneededPartB.move(targetPos[0], targetPos[1]); } // MOVING GROUP TWO - var regroupneededPartB = gameState.getOwnEntitiesWithRole("attack-pending_3p2"); + var regroupneededPartB = gameState.getOwnRoleGroup("attack-pending_3p2"); //Find a friendsly CC - var targets = gameState.entities.filter(function(ent) { - return (!ent.isEnemy() && ent.hasClass("CivCentre")); - }); + var targets = gameState.getOwnWithClass("CivCentre"); if (targets.length){ var target = targets.toEntityArray()[0]; var targetPos = target.position(); @@ -178,11 +172,9 @@ var MilitaryAttackManager = Class({ regroupneededPartB.move(targetPos[0], targetPos[1]); } // MOVING GROUP THREE - var regroupneededPartB = gameState.getOwnEntitiesWithRole("attack-pending_3p3"); + var regroupneededPartB = gameState.getOwnRoleGroup("attack-pending_3p3"); //Find a friendsly CC - var targets = gameState.entities.filter(function(ent) { - return (!ent.isEnemy() && ent.hasClass("CivCentre")); - }); + var targets = gameState.getOwnWithClass("CivCentre"); if (targets.length){ var target = targets.toEntityArray()[0]; var targetPos = target.position(); @@ -197,10 +189,10 @@ var MilitaryAttackManager = Class({ combatcheck: function(gameState, planGroups, assaultgroup) { - var regroupneeded = gameState.getOwnEntitiesWithRole(assaultgroup); + var regroupneeded = gameState.getOwnRoleGroup(assaultgroup); regroupneeded.forEach(function(troop) { var currentPosition = troop.position(); - var targets = gameState.entities.filter(function(ent) { + var targets = gameState.getJustEnemies().filter(function(ent) { var foeposition = ent.position(); if (foeposition){ var dist = SquareVectorDistance(foeposition, currentPosition); @@ -271,26 +263,26 @@ var MilitaryAttackManager = Class({ combatcheckMilitia: function(gameState, planGroups, assaultgroup) { - var regroupneeded = gameState.getOwnEntitiesWithRole(assaultgroup); + var regroupneeded = gameState.getOwnRoleGroup(assaultgroup); regroupneeded.forEach(function(troop) { var currentPosition = troop.position(); // Find nearby enemies - var targets = gameState.entities.filter(function(ent) { + var targets = gameState.getJustEnemies().filter(function(ent) { var foeposition = ent.position(); if (foeposition){ var dist = SquareVectorDistance(foeposition, currentPosition); - return (ent.isEnemy() && ent.owner()!= 0 && dist < 2500); + return (dist < 2500); } else { return false; } }); // Check that some of our own buildings are nearby - var ownbuildings = gameState.getOwnEntities().filter(function(ent) { + var ownbuildings = gameState.getOwnWithClass("Village").filter(function(ent) { var foeposition = ent.position(); if (foeposition){ var dist = SquareVectorDistance(foeposition, currentPosition); - return (dist < 2500 && ent.hasClass("Village")); + return (dist < 2500); } else { return false; @@ -328,7 +320,7 @@ var MilitaryAttackManager = Class({ var targetPos = target.position(); } // Change 'em back to militia - var defenseregroupers = gameState.getOwnEntitiesWithRole("militiafighter"); + var defenseregroupers = gameState.getOwnRoleGroup("militiafighter"); defenseregroupers.forEach(function(ent) { // If we have a target to go home to, move to it ent.move(targetPos[0], targetPos[1]); @@ -342,11 +334,9 @@ var MilitaryAttackManager = Class({ waitingregroup: function(gameState, planGroups) { if (gameState.getTimeElapsed() > this.changetimeRegWaiting){ - var regroupneededPartC = gameState.getOwnEntitiesWithRole("attack-pending"); + var regroupneededPartC = gameState.getOwnRoleGroup("attack-pending"); //Find a friendsly CC - var targets = gameState.entities.filter(function(ent) { - return (!ent.isEnemy() && ent.hasClass("Village")); - }); + var targets = gameState.getOwnWithClass("Village"); // If we have a target, move to it if (targets.length) { @@ -356,11 +346,9 @@ var MilitaryAttackManager = Class({ // TODO: this should be an attack-move command regroupneededPartC.move(targetPos[0], targetPos[1]); } - var regroupneededPartC = gameState.getOwnEntitiesWithRole("attack-pending_3p1"); + var regroupneededPartC = gameState.getOwnRoleGroup("attack-pending_3p1"); //Find a friendsly CC - var targets = gameState.entities.filter(function(ent) { - return (!ent.isEnemy() && ent.hasClass("Village")); - }); + var targets = gameState.getOwnWithClass("Village"); // If we have a target, move to it if (targets.length) { @@ -370,11 +358,9 @@ var MilitaryAttackManager = Class({ // TODO: this should be an attack-move command regroupneededPartC.move(targetPos[0], targetPos[1]); } - var regroupneededPartC = gameState.getOwnEntitiesWithRole("attack-pending_3p2"); + var regroupneededPartC = gameState.getOwnRoleGroup("attack-pending_3p2"); //Find a friendsly CC - var targets = gameState.entities.filter(function(ent) { - return (!ent.isEnemy() && ent.hasClass("Village")); - }); + var targets = gameState.getOwnWithClass("Village"); // If we have a target, move to it if (targets.length) { @@ -384,11 +370,9 @@ var MilitaryAttackManager = Class({ // TODO: this should be an attack-move command regroupneededPartC.move(targetPos[0], targetPos[1]); } - var regroupneededPartC = gameState.getOwnEntitiesWithRole("attack-pending_3p3"); + var regroupneededPartC = gameState.getOwnRoleGroup("attack-pending_3p3"); //Find a friendsly CC - var targets = gameState.entities.filter(function(ent) { - return (!ent.isEnemy() && ent.hasClass("Village")); - }); + var targets = gameState.getOwnWithClass("Village"); // If we have a target, move to it if (targets.length) { @@ -409,18 +393,17 @@ var MilitaryAttackManager = Class({ { var trainup = 0; var pendingdefense = gameState.countEntitiesAndQueuedWithRole("defenders"); - var targets = gameState.entities.filter(function(ent) { - return (!ent.isEnemy() && ent.hasClass("GarrisonTower")); - }); + var targets = gameState.getOwnWithClass("GarrisonTower"); if (targets.length) { targets.forEach(function(tower) { + if (tower.foundationProgress() === undefined){ var defno = tower.garrisoned().length; var defneed = 3 - defno; - warn("Need " + defneed + " men in the tower, with " + pendingdefense + " available."); + //warn("Need " + defneed + " men in the tower, with " + pendingdefense + " available."); if (defneed >= 1) { if (pendingdefense >= 1) { - gameState.getOwnEntitiesWithRole("defenders").forEach(function(ent) { + gameState.getOwnRoleGroup("defenders").forEach(function(ent) { ent.garrison(tower); ent.setMetadata("role", "towerGuard"); }); @@ -429,6 +412,29 @@ var MilitaryAttackManager = Class({ trainup = 1; } } + } + }); + } + var targetsII = gameState.getOwnWithClass("GarrisonFortress"); + if (targetsII.length) + { + targetsII.forEach(function(tower) { + if (tower.foundationProgress() === undefined){ + var defno = tower.garrisoned().length; + var defneed = 6 - defno; + //warn("Need " + defneed + " men in the tower, with " + pendingdefense + " available."); + if (defneed >= 1) { + if (pendingdefense >= 1) { + gameState.getOwnRoleGroup("defenders").forEach(function(ent) { + ent.garrison(tower); + ent.setMetadata("role", "towerGuard"); + }); + } + else { + trainup = 1; + } + } + } }); } if (trainup == 1){ diff --git a/binaries/data/mods/public/simulation/ai/jubot/plan-buildingdefensepoints.js b/binaries/data/mods/public/simulation/ai/jubot/plan-buildingdefensepoints.js new file mode 100644 index 0000000000..9c0cff92c7 --- /dev/null +++ b/binaries/data/mods/public/simulation/ai/jubot/plan-buildingdefensepoints.js @@ -0,0 +1,277 @@ +var BuildingConstructionPlanDefensePoints = Class({ + + _init: function(gameState, type, indno) + { + this.type = gameState.applyCiv(type); + + var template = gameState.getTemplate(this.type); + if (!template) + { + this.invalidTemplate = true; + return; + } + + this.cost = new Resources(template.cost()); + }, + + canExecute: function(gameState) + { + if (this.invalidTemplate) + return false; + + // TODO: verify numeric limits etc + + var builders = gameState.findBuilders(this.type); + + return (builders.length != 0); + }, + + execute: function(gameState) + { +// warn("Executing BuildingConstructionPlan "+uneval(this)); + + var builders = gameState.findBuilders(this.type).toEntityArray(); + + // We don't care which builder we assign, since they won't actually + // do the building themselves - all we care about is that there is + // some unit that can start the foundation + + var pos = this.findGoodPosition(gameState); + + builders[0].construct(this.type, pos.x, pos.z, pos.angle); + }, + + getCost: function() + { + return this.cost; + }, + + /** + * Make each cell's 16-bit value at least one greater than each of its + * neighbours' values. (If the grid is initialised with 0s and 65535s, + * the result of each cell is its Manhattan distance to the nearest 0.) + * + * TODO: maybe this should be 8-bit (and clamp at 255)? + */ + expandInfluences: function(grid, w, h) + { + for (var y = 0; y < h; ++y) + { + var min = 65535; + for (var x = 0; x < w; ++x) + { + var g = grid[x + y*w]; + if (g > min) grid[x + y*w] = min; + else if (g < min) min = g; + ++min; + } + + for (var x = w-2; x >= 0; --x) + { + var g = grid[x + y*w]; + if (g > min) grid[x + y*w] = min; + else if (g < min) min = g; + ++min; + } + } + + for (var x = 0; x < w; ++x) + { + var min = 65535; + for (var y = 0; y < h; ++y) + { + var g = grid[x + y*w]; + if (g > min) grid[x + y*w] = min; + else if (g < min) min = g; + ++min; + } + + for (var y = h-2; y >= 0; --y) + { + var g = grid[x + y*w]; + if (g > min) grid[x + y*w] = min; + else if (g < min) min = g; + ++min; + } + } + }, + + /** + * Add a circular linear-falloff shape to a grid. + */ + addInfluence: function(grid, w, h, cx, cy, maxDist) + { + var x0 = Math.max(0, cx - maxDist); + var y0 = Math.max(0, cy - maxDist); + var x1 = Math.min(w, cx + maxDist); + var y1 = Math.min(h, cy + maxDist); + for (var y = y0; y < y1; ++y) + { + for (var x = x0; x < x1; ++x) + { + var dx = x - cx; + var dy = y - cy; + var r = Math.sqrt(dx*dx + dy*dy); + if (r < maxDist) + grid[x + y*w] += maxDist - r; + } + } + }, + + /** + * Add a circular linear-falloff shape to a grid. + */ + subtractInfluence: function(grid, w, h, cx, cy, maxDist) + { + var x0 = Math.max(0, cx - maxDist); + var y0 = Math.max(0, cy - maxDist); + var x1 = Math.min(w, cx + maxDist); + var y1 = Math.min(h, cy + maxDist); + for (var y = y0; y < y1; ++y) + { + for (var x = x0; x < x1; ++x) + { + var dx = x - cx; + var dy = y - cy; + var r = Math.sqrt(dx*dx + dy*dy); + if (r < maxDist) + grid[x + y*w] -= maxDist - r; + } + } + }, + + findGoodPosition: function(gameState) + { + var self = this; + + var cellSize = 4; // size of each tile + + var template = gameState.getTemplate(this.type); + + // Find all tiles in valid territory that are far enough away from obstructions: + var passabilityMap = gameState.getPassabilityMap(); + var territoryMap = gameState.getTerritoryMap(); + const TERRITORY_PLAYER_MASK = 0x7F; + var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction"); + + if (passabilityMap.data.length != territoryMap.data.length) + error("passability and territory data are not matched!"); + + // See BuildRestrictions.js + switch(template.buildPlacementType()) + { + case "shore": + obstructionMask |= gameState.getPassabilityClassMask("building-shore"); + break; + case "land": + default: + obstructionMask |= gameState.getPassabilityClassMask("building-land"); + } + + var playerID = gameState.getPlayerID(); + var buildOwn = template.hasBuildTerritory("own"); + var buildAlly = template.hasBuildTerritory("ally"); + var buildNeutral = template.hasBuildTerritory("neutral"); + var buildEnemy = template.hasBuildTerritory("enemy"); + + var obstructionTiles = new Uint16Array(passabilityMap.data.length); + for (var i = 0; i < passabilityMap.data.length; ++i) + { + var tilePlayer = (territoryMap.data[i] & TERRITORY_PLAYER_MASK); + var invalidTerritory = ( + (!buildOwn && tilePlayer == playerID) || + (!buildAlly && gameState.isPlayerAlly(tilePlayer) && tilePlayer != playerID) || + (!buildNeutral && tilePlayer == 0) || + (!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer !=0) + ); + obstructionTiles[i] = (invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 65535; + } + +// Engine.DumpImage("tiles0.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 64); + + this.expandInfluences(obstructionTiles, passabilityMap.width, passabilityMap.height); + + // TODO: handle distance restrictions for e.g. CivCentres + + // Compute each tile's closeness to friendly structures: + + var friendlyTiles = new Uint16Array(passabilityMap.data.length); + + gameState.getOwnEntities().forEach(function(ent) { + if (ent.hasClass("Structure")) + { + var infl = 0; + if (ent.hasClass("Economic") && ent.getMetadata("tower") != 1) + { + infl = 20; + ent.setMetadata("tower", 1); + } + else + { + infl = 0; + } + + var pos = ent.position(); + var x = Math.round(pos[0] / cellSize); + var z = Math.round(pos[1] / cellSize); + self.addInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl); + } + }); + +// var foetargets = gameState.entities.filter(function(ent) { +// return (ent.isEnemy()); +// }); +// foetargets.forEach(function(ent) { +// if (ent.hasClass("CivCentre")) +// { +// var infl = 100; +// var pos = ent.position(); +// var x = Math.round(pos[0] / cellSize); +// var z = Math.round(pos[1] / cellSize); +// self.subtractInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl); +// } +// }); + + + // Find target building's approximate obstruction radius, + // and expand by a bit to make sure we're not too close + var radius = Math.ceil(template.obstructionRadius() / cellSize) + 1; + + // Find the best non-obstructed tile + var bestIdx = 0; + var bestVal = -1; + for (var i = 0; i < passabilityMap.data.length; ++i) + { + if (obstructionTiles[i] > radius) + { + var v = friendlyTiles[i]; + //var foe = enemyTiles[i]; + //JuBotAI.prototype.chat(v); + //JuBotAI.prototype.chat(i); + //JuBotAI.prototype.chat(foe); + if (v >= bestVal) + { + bestVal = v; + bestIdx = i; + //JuBotAI.prototype.chat("BestVal is " + bestVal + ", and bestIdx is " + bestIdx + "."); + } + } + } + var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize; + var z = (Math.floor(bestIdx / passabilityMap.width) + 0.5) * cellSize; + +// Engine.DumpImage("tiles1.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32); +// Engine.DumpImage("tiles2.png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256); + + // TODO: special dock placement requirements + + // Fixed angle to match fixed starting cam + var angle = 0.75*Math.PI; + + return { + "x": x, + "z": z, + "angle": angle + }; + }, +});