From 1c081135ad49823a43c3565f9a558197cb363696 Mon Sep 17 00:00:00 2001 From: historic_bruno Date: Sun, 16 Oct 2011 02:55:58 +0000 Subject: [PATCH] Adds territory map to AI GameState, based on patch from quantumstate. Fixes #969. Updates testbot and Jubot to support basic build restrictions. This was SVN commit r10408. --- .../public/simulation/ai/common-api/base.js | 6 +- .../public/simulation/ai/jubot/gamestate.js | 9 +- .../simulation/ai/jubot/plan-building.js | 70 ++++++++++---- .../simulation/ai/jubot/plan-buildingecon.js | 95 ++++++++++++------- .../public/simulation/ai/testbot/gamestate.js | 9 +- .../simulation/ai/testbot/plan-building.js | 68 +++++++++---- .../simulation2/components/CCmpAIManager.cpp | 50 +++++++--- .../scripting/EngineScriptConversions.cpp | 27 ++++++ 8 files changed, 247 insertions(+), 87 deletions(-) diff --git a/binaries/data/mods/public/simulation/ai/common-api/base.js b/binaries/data/mods/public/simulation/ai/common-api/base.js index 54f87479a5..2fc4e6a614 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/base.js +++ b/binaries/data/mods/public/simulation/ai/common-api/base.js @@ -79,11 +79,12 @@ BaseAI.prototype.HandleMessage = function(state) this.entities = new EntityCollection(this, this._rawEntities); this.events = state.events; - this.map = state.map; this.passabilityClasses = state.passabilityClasses; + this.passabilityMap = state.passabilityMap; this.player = this._player; this.playerData = state.players[this._player]; this.templates = this._templates; + this.territoryMap = state.territoryMap; this.timeElapsed = state.timeElapsed; Engine.ProfileStop(); @@ -93,11 +94,12 @@ BaseAI.prototype.HandleMessage = function(state) // Clean up temporary properties, so they don't disturb the serializer delete this.entities; delete this.events; - delete this.map; delete this.passabilityClasses; + delete this.passabilityMap; delete this.player; delete this.playerData; delete this.templates; + delete this.territoryMap; delete this.timeElapsed; }; diff --git a/binaries/data/mods/public/simulation/ai/jubot/gamestate.js b/binaries/data/mods/public/simulation/ai/jubot/gamestate.js index 3676d2bff9..a34d24c847 100644 --- a/binaries/data/mods/public/simulation/ai/jubot/gamestate.js +++ b/binaries/data/mods/public/simulation/ai/jubot/gamestate.js @@ -43,9 +43,9 @@ var GameState = Class({ return new Resources(this.playerData.resourceCounts); }, - getMap: function() + getPassabilityMap: function() { - return this.ai.map; + return this.ai.passabilityMap; }, getPassabilityClassMask: function(name) @@ -55,6 +55,11 @@ var GameState = Class({ return this.ai.passabilityClasses[name]; }, + getTerritoryMap: function() + { + return this.ai.territoryMap; + }, + getOwnEntities: (function() { return new EntityCollection(this.ai, this.ai._ownEntities); diff --git a/binaries/data/mods/public/simulation/ai/jubot/plan-building.js b/binaries/data/mods/public/simulation/ai/jubot/plan-building.js index ab9ac4a7b5..f6351e7db8 100644 --- a/binaries/data/mods/public/simulation/ai/jubot/plan-building.js +++ b/binaries/data/mods/public/simulation/ai/jubot/plan-building.js @@ -146,25 +146,56 @@ var BuildingConstructionPlan = Class({ var cellSize = 4; // size of each tile - // First, find all tiles that are far enough away from obstructions: - - var map = gameState.getMap(); + 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"); - // Only accept valid land tiles (we don't handle docks yet) - obstructionMask |= gameState.getPassabilityClassMask("building-land"); - var obstructionTiles = new Uint16Array(map.data.length); - for (var i = 0; i < map.data.length; ++i) - obstructionTiles[i] = (map.data[i] & obstructionMask) ? 0 : 65535; + if (passabilityMap.data.length != territoryMap.data.length) + error("passability and territory data are not matched!"); -// Engine.DumpImage("tiles0.png", obstructionTiles, map.width, map.height, 64); + // See BuildRestrictions.js + switch(template.buildPlacementType()) + { + case "shore": + obstructionMask |= gameState.getPassabilityClassMask("building-shore"); + break; + case "land": + default: + obstructionMask |= gameState.getPassabilityClassMask("building-land"); + } - this.expandInfluences(obstructionTiles, map.width, map.height); + 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(map.data.length); + var friendlyTiles = new Uint16Array(passabilityMap.data.length); gameState.getOwnEntities().forEach(function(ent) { if (ent.hasClass("Structure")) @@ -176,7 +207,7 @@ var BuildingConstructionPlan = Class({ var pos = ent.position(); var x = Math.round(pos[0] / cellSize); var z = Math.round(pos[1] / cellSize); - self.addInfluence(friendlyTiles, map.width, map.height, x, z, infl); + self.addInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl); } }); @@ -190,20 +221,19 @@ var BuildingConstructionPlan = Class({ // var pos = ent.position(); // var x = Math.round(pos[0] / cellSize); // var z = Math.round(pos[1] / cellSize); -// self.subtractInfluence(friendlyTiles, map.width, map.height, x, z, infl); +// 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 template = gameState.getTemplate(this.type); 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 < map.data.length; ++i) + for (var i = 0; i < passabilityMap.data.length; ++i) { if (obstructionTiles[i] > radius) { @@ -220,11 +250,13 @@ var BuildingConstructionPlan = Class({ } } } - var x = ((bestIdx % map.width) + 0.5) * cellSize; - var z = (Math.floor(bestIdx / map.width) + 0.5) * cellSize; + var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize; + var z = (Math.floor(bestIdx / passabilityMap.width) + 0.5) * cellSize; -// Engine.DumpImage("tiles1.png", obstructionTiles, map.width, map.height, 32); -// Engine.DumpImage("tiles2.png", friendlyTiles, map.width, map.height, 256); +// 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; diff --git a/binaries/data/mods/public/simulation/ai/jubot/plan-buildingecon.js b/binaries/data/mods/public/simulation/ai/jubot/plan-buildingecon.js index 63f23f20ec..a7c1c016ce 100644 --- a/binaries/data/mods/public/simulation/ai/jubot/plan-buildingecon.js +++ b/binaries/data/mods/public/simulation/ai/jubot/plan-buildingecon.js @@ -148,30 +148,61 @@ var BuildingConstructionPlanEcon = Class({ var cellSize = 4; // size of each tile - // First, find all tiles that are far enough away from obstructions: - - var map = gameState.getMap(); + 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"); - // Only accept valid land tiles (we don't handle docks yet) - obstructionMask |= gameState.getPassabilityClassMask("building-land"); - var obstructionTiles = new Uint16Array(map.data.length); - for (var i = 0; i < map.data.length; ++i) - obstructionTiles[i] = (map.data[i] & obstructionMask) ? 0 : 65535; + if (passabilityMap.data.length != territoryMap.data.length) + error("passability and territory data are not matched!"); -// Engine.DumpImage("tiles0.png", obstructionTiles, map.width, map.height, 64); + // See BuildRestrictions.js + switch(template.buildPlacementType()) + { + case "shore": + obstructionMask |= gameState.getPassabilityClassMask("building-shore"); + break; + case "land": + default: + obstructionMask |= gameState.getPassabilityClassMask("building-land"); + } - this.expandInfluences(obstructionTiles, map.width, map.height); + 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(map.data.length); + var friendlyTiles = new Uint16Array(passabilityMap.data.length); var infl = 100; var pos = this.resourceposition; var x = Math.round(pos[0] / cellSize); var z = Math.round(pos[1] / cellSize); - self.addInfluence(friendlyTiles, map.width, map.height, x, z, infl); + self.addInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl); //Look at making sure we're a long way from enemy civ centres as well. @@ -184,7 +215,7 @@ var BuildingConstructionPlanEcon = Class({ // Find the best non-obstructed tile var bestIdx = 0; var bestVal = -1; - for (var i = 0; i < map.data.length; ++i) + for (var i = 0; i < passabilityMap.data.length; ++i) { if (obstructionTiles[i] > radius) { @@ -201,11 +232,11 @@ var BuildingConstructionPlanEcon = Class({ } } } - var x = ((bestIdx % map.width) + 0.5) * cellSize; - var z = (Math.floor(bestIdx / map.width) + 0.5) * cellSize; + var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize; + var z = (Math.floor(bestIdx / passabilityMap.width) + 0.5) * cellSize; -// Engine.DumpImage("tiles1.png", obstructionTiles, map.width, map.height, 32); -// Engine.DumpImage("tiles2.png", friendlyTiles, map.width, map.height, 256); +// Engine.DumpImage("tiles1.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32); +// Engine.DumpImage("tiles2.png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256); // Fixed angle to match fixed starting cam var angle = 0.75*Math.PI; @@ -225,23 +256,23 @@ var BuildingConstructionPlanEcon = Class({ // First, find all tiles that are far enough away from obstructions: - var map = gameState.getMap(); + var passabilityMap = gameState.getPassabilityMap(); var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction"); // Only accept valid land tiles (we don't handle docks yet) obstructionMask |= gameState.getPassabilityClassMask("building-land"); - var obstructionTiles = new Uint16Array(map.data.length); - for (var i = 0; i < map.data.length; ++i) - obstructionTiles[i] = (map.data[i] & obstructionMask) ? 0 : 65535; + var obstructionTiles = new Uint16Array(passabilityMap.data.length); + for (var i = 0; i < passabilityMap.data.length; ++i) + obstructionTiles[i] = (passabilityMap.data[i] & obstructionMask) ? 0 : 65535; -// Engine.DumpImage("tiles0.png", obstructionTiles, map.width, map.height, 64); +// Engine.DumpImage("tiles0.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 64); - this.expandInfluences(obstructionTiles, map.width, map.height); + this.expandInfluences(obstructionTiles, passabilityMap.width, passabilityMap.height); // Compute each tile's closeness to friendly structures: - var friendlyTiles = new Uint16Array(map.data.length); + var friendlyTiles = new Uint16Array(passabilityMap.data.length); gameState.getOwnEntities().forEach(function(ent) { if (ent.hasClass("Structure")) @@ -253,13 +284,13 @@ var BuildingConstructionPlanEcon = Class({ var pos = ent.position(); var x = Math.round(pos[0] / cellSize); var z = Math.round(pos[1] / cellSize); - self.addInfluence(friendlyTiles, map.width, map.height, x, z, infl); + self.addInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl); } }); //Look at making sure we're a long way from enemy civ centres as well. -// var enemyTiles = new Uint16Array(map.data.length); +// var enemyTiles = new Uint16Array(passabilityMap.data.length); // // var foetargets = gameState.entities.filter(function(ent) { // return (ent.isEnemy()); @@ -271,7 +302,7 @@ var BuildingConstructionPlanEcon = Class({ // var pos = ent.position(); // var x = Math.round(pos[0] / cellSize); // var z = Math.round(pos[1] / cellSize); -// self.addInfluence(enemyTiles, map.width, map.height, x, z, infl); +// self.addInfluence(enemyTiles, passabilityMap.width, passabilityMap.height, x, z, infl); // } // }); @@ -284,7 +315,7 @@ var BuildingConstructionPlanEcon = Class({ // Find the best non-obstructed tile var bestIdx = 0; var bestVal = -1; - for (var i = 0; i < map.data.length; ++i) + for (var i = 0; i < passabilityMap.data.length; ++i) { if (obstructionTiles[i] > radius) { @@ -301,11 +332,11 @@ var BuildingConstructionPlanEcon = Class({ } } } - var x = ((bestIdx % map.width) + 0.5) * cellSize; - var z = (Math.floor(bestIdx / map.width) + 0.5) * cellSize; + var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize; + var z = (Math.floor(bestIdx / passabilityMap.width) + 0.5) * cellSize; -// Engine.DumpImage("tiles1.png", obstructionTiles, map.width, map.height, 32); -// Engine.DumpImage("tiles2.png", friendlyTiles, map.width, map.height, 256); +// Engine.DumpImage("tiles1.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32); +// Engine.DumpImage("tiles2.png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256); // Randomise the angle a little, to look less artificial //var angle = Math.PI + (Math.random()*2-1) * Math.PI/24; diff --git a/binaries/data/mods/public/simulation/ai/testbot/gamestate.js b/binaries/data/mods/public/simulation/ai/testbot/gamestate.js index 6fbbfc3b25..7aeef7aa60 100644 --- a/binaries/data/mods/public/simulation/ai/testbot/gamestate.js +++ b/binaries/data/mods/public/simulation/ai/testbot/gamestate.js @@ -38,9 +38,9 @@ var GameState = Class({ return new Resources(this.playerData.resourceCounts); }, - getMap: function() + getPassabilityMap: function() { - return this.ai.map; + return this.ai.passabilityMap; }, getPassabilityClassMask: function(name) @@ -50,6 +50,11 @@ var GameState = Class({ return this.ai.passabilityClasses[name]; }, + getTerritoryMap: function() + { + return this.ai.territoryMap; + }, + getOwnEntities: (function() { return new EntityCollection(this.ai, this.ai._ownEntities); diff --git a/binaries/data/mods/public/simulation/ai/testbot/plan-building.js b/binaries/data/mods/public/simulation/ai/testbot/plan-building.js index 86440a02a5..da8e3775c6 100644 --- a/binaries/data/mods/public/simulation/ai/testbot/plan-building.js +++ b/binaries/data/mods/public/simulation/ai/testbot/plan-building.js @@ -124,25 +124,56 @@ var BuildingConstructionPlan = Class({ var cellSize = 4; // size of each tile - // First, find all tiles that are far enough away from obstructions: - - var map = gameState.getMap(); + 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"); - // Only accept valid land tiles (we don't handle docks yet) - obstructionMask |= gameState.getPassabilityClassMask("building-land"); - var obstructionTiles = new Uint16Array(map.data.length); - for (var i = 0; i < map.data.length; ++i) - obstructionTiles[i] = (map.data[i] & obstructionMask) ? 0 : 65535; + if (passabilityMap.data.length != territoryMap.data.length) + error("passability and territory data are not matched!"); -// Engine.DumpImage("tiles0.png", obstructionTiles, map.width, map.height, 64); + // See BuildRestrictions.js + switch(template.buildPlacementType()) + { + case "shore": + obstructionMask |= gameState.getPassabilityClassMask("building-shore"); + break; + case "land": + default: + obstructionMask |= gameState.getPassabilityClassMask("building-land"); + } - this.expandInfluences(obstructionTiles, map.width, map.height); + 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("obstruction"+playerID+".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(map.data.length); + var friendlyTiles = new Uint16Array(passabilityMap.data.length); gameState.getOwnEntities().forEach(function(ent) { if (ent.hasClass("Structure")) @@ -154,19 +185,18 @@ var BuildingConstructionPlan = Class({ var pos = ent.position(); var x = Math.round(pos[0] / cellSize); var z = Math.round(pos[1] / cellSize); - self.addInfluence(friendlyTiles, map.width, map.height, x, z, infl); + self.addInfluence(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 template = gameState.getTemplate(this.type); var radius = Math.ceil(template.obstructionRadius() / cellSize) + 2; // Find the best non-obstructed tile var bestIdx = 0; var bestVal = -1; - for (var i = 0; i < map.data.length; ++i) + for (var i = 0; i < passabilityMap.data.length; ++i) { if (obstructionTiles[i] > radius) { @@ -178,12 +208,14 @@ var BuildingConstructionPlan = Class({ } } } - var x = ((bestIdx % map.width) + 0.5) * cellSize; - var z = (Math.floor(bestIdx / map.width) + 0.5) * cellSize; + var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize; + var z = (Math.floor(bestIdx / passabilityMap.width) + 0.5) * cellSize; -// Engine.DumpImage("tiles1.png", obstructionTiles, map.width, map.height, 32); -// Engine.DumpImage("tiles2.png", friendlyTiles, map.width, map.height, 256); +// Engine.DumpImage("influences"+playerID+".png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32); +// Engine.DumpImage("friendly"+playerID+".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; diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp index e840fd87eb..c222bac993 100644 --- a/source/simulation2/components/CCmpAIManager.cpp +++ b/source/simulation2/components/CCmpAIManager.cpp @@ -34,6 +34,7 @@ #include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpTemplateManager.h" +#include "simulation2/components/ICmpTerritoryManager.h" #include "simulation2/helpers/Grid.h" #include "simulation2/serialization/DebugSerializer.h" #include "simulation2/serialization/StdDeserializer.h" @@ -272,7 +273,8 @@ public: m_PlayerMetadata.clear(); m_Players.clear(); m_GameState.reset(); - m_GameStateMapVal = CScriptValRooted(); + m_PassabilityMapVal = CScriptValRooted(); + m_TerritoryMapVal = CScriptValRooted(); } bool AddPlayer(const std::wstring& aiName, player_id_t player, bool callConstructor) @@ -286,18 +288,26 @@ public: return true; } - void StartComputation(const shared_ptr& gameState, const Grid& map) + void StartComputation(const shared_ptr& gameState, const Grid& passabilityMap, const Grid& territoryMap, bool territoryMapDirty) { ENSURE(m_CommandsComputed); m_GameState = gameState; - if (map.m_DirtyID != m_GameStateMap.m_DirtyID) + if (passabilityMap.m_DirtyID != m_PassabilityMap.m_DirtyID) { - m_GameStateMap = map; + m_PassabilityMap = passabilityMap; JSContext* cx = m_ScriptInterface.GetContext(); - m_GameStateMapVal = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, m_GameStateMap)); + m_PassabilityMapVal = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, m_PassabilityMap)); + } + + if (territoryMapDirty) + { + m_TerritoryMap = territoryMap; + + JSContext* cx = m_ScriptInterface.GetContext(); + m_TerritoryMapVal = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, m_TerritoryMap)); } m_CommandsComputed = false; @@ -432,7 +442,8 @@ private: { PROFILE("AI compute read state"); state = m_ScriptInterface.ReadStructuredClone(m_GameState); - m_ScriptInterface.SetProperty(state.get(), "map", m_GameStateMapVal, true); + m_ScriptInterface.SetProperty(state.get(), "passabilityMap", m_PassabilityMapVal, true); + m_ScriptInterface.SetProperty(state.get(), "territoryMap", m_TerritoryMapVal, true); } // It would be nice to do @@ -467,8 +478,10 @@ private: std::vector > m_Players; // use shared_ptr just to avoid copying shared_ptr m_GameState; - Grid m_GameStateMap; - CScriptValRooted m_GameStateMapVal; + Grid m_PassabilityMap; + CScriptValRooted m_PassabilityMapVal; + Grid m_TerritoryMap; + CScriptValRooted m_TerritoryMapVal; bool m_CommandsComputed; }; @@ -568,16 +581,28 @@ public: // Get the game state from AIInterface CScriptVal state = cmpAIInterface->GetRepresentation(); - // Get the map data + // Get the passability data Grid dummyGrid; - const Grid* map = &dummyGrid; + const Grid* passabilityMap = &dummyGrid; CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); if (!cmpPathfinder.null()) - map = &cmpPathfinder->GetPassabilityGrid(); + passabilityMap = &cmpPathfinder->GetPassabilityGrid(); + + // Get the territory data + // Since getting the territory grid can trigger a recalculation, we check NeedUpdate first + bool territoryMapDirty = false; + Grid dummyGrid2; + const Grid* territoryMap = &dummyGrid2; + CmpPtr cmpTerritoryManager(GetSimContext(), SYSTEM_ENTITY); + if (!cmpTerritoryManager.null() && cmpTerritoryManager->NeedUpdate(&m_TerritoriesDirtyID)) + { + territoryMap = &cmpTerritoryManager->GetTerritoryGrid(); + territoryMapDirty = true; + } LoadPathfinderClasses(state); - m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state.get()), *map); + m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state.get()), *passabilityMap, *territoryMap, territoryMapDirty); } virtual void PushCommands() @@ -605,6 +630,7 @@ private: std::vector m_TemplateNames; size_t m_TemplateLoadedIdx; std::vector > m_Templates; + size_t m_TerritoriesDirtyID; void StartLoadEntityTemplates() { diff --git a/source/simulation2/scripting/EngineScriptConversions.cpp b/source/simulation2/scripting/EngineScriptConversions.cpp index b7cc5124e6..467ac2d3cc 100644 --- a/source/simulation2/scripting/EngineScriptConversions.cpp +++ b/source/simulation2/scripting/EngineScriptConversions.cpp @@ -236,3 +236,30 @@ template<> jsval ScriptInterface::ToJSVal >(JSContext* cx, const Grid< return OBJECT_TO_JSVAL(obj); } + +template<> jsval ScriptInterface::ToJSVal >(JSContext* cx, const Grid& val) +{ + JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL); + if (!obj) + return JSVAL_VOID; + + jsuint len = val.m_W * val.m_H; + JSObject *darray = js_CreateTypedArray(cx, js::TypedArray::TYPE_UINT8, len); + if (!darray) + return JSVAL_VOID; + + js::TypedArray *tdest = js::TypedArray::fromJSObject(darray); + ENSURE(tdest->byteLength == len*sizeof(u8)); + + memcpy(tdest->data, val.m_Data, tdest->byteLength); + + jsval w = ToJSVal(cx, val.m_W); + jsval h = ToJSVal(cx, val.m_H); + jsval data = OBJECT_TO_JSVAL(darray); + + JS_SetProperty(cx, obj, "width", &w); + JS_SetProperty(cx, obj, "height", &h); + JS_SetProperty(cx, obj, "data", &data); + + return OBJECT_TO_JSVAL(obj); +}