1
0
forked from 0ad/0ad

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.
This commit is contained in:
historic_bruno 2011-10-16 02:55:58 +00:00
parent c73c0870f1
commit 1c081135ad
8 changed files with 247 additions and 87 deletions

View File

@ -79,11 +79,12 @@ BaseAI.prototype.HandleMessage = function(state)
this.entities = new EntityCollection(this, this._rawEntities); this.entities = new EntityCollection(this, this._rawEntities);
this.events = state.events; this.events = state.events;
this.map = state.map;
this.passabilityClasses = state.passabilityClasses; this.passabilityClasses = state.passabilityClasses;
this.passabilityMap = state.passabilityMap;
this.player = this._player; this.player = this._player;
this.playerData = state.players[this._player]; this.playerData = state.players[this._player];
this.templates = this._templates; this.templates = this._templates;
this.territoryMap = state.territoryMap;
this.timeElapsed = state.timeElapsed; this.timeElapsed = state.timeElapsed;
Engine.ProfileStop(); Engine.ProfileStop();
@ -93,11 +94,12 @@ BaseAI.prototype.HandleMessage = function(state)
// Clean up temporary properties, so they don't disturb the serializer // Clean up temporary properties, so they don't disturb the serializer
delete this.entities; delete this.entities;
delete this.events; delete this.events;
delete this.map;
delete this.passabilityClasses; delete this.passabilityClasses;
delete this.passabilityMap;
delete this.player; delete this.player;
delete this.playerData; delete this.playerData;
delete this.templates; delete this.templates;
delete this.territoryMap;
delete this.timeElapsed; delete this.timeElapsed;
}; };

View File

@ -43,9 +43,9 @@ var GameState = Class({
return new Resources(this.playerData.resourceCounts); return new Resources(this.playerData.resourceCounts);
}, },
getMap: function() getPassabilityMap: function()
{ {
return this.ai.map; return this.ai.passabilityMap;
}, },
getPassabilityClassMask: function(name) getPassabilityClassMask: function(name)
@ -55,6 +55,11 @@ var GameState = Class({
return this.ai.passabilityClasses[name]; return this.ai.passabilityClasses[name];
}, },
getTerritoryMap: function()
{
return this.ai.territoryMap;
},
getOwnEntities: (function() getOwnEntities: (function()
{ {
return new EntityCollection(this.ai, this.ai._ownEntities); return new EntityCollection(this.ai, this.ai._ownEntities);

View File

@ -146,25 +146,56 @@ var BuildingConstructionPlan = Class({
var cellSize = 4; // size of each tile var cellSize = 4; // size of each tile
// First, find all tiles that are far enough away from obstructions: var template = gameState.getTemplate(this.type);
var map = gameState.getMap();
// 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"); 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); if (passabilityMap.data.length != territoryMap.data.length)
for (var i = 0; i < map.data.length; ++i) error("passability and territory data are not matched!");
obstructionTiles[i] = (map.data[i] & obstructionMask) ? 0 : 65535;
// 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: // 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) { gameState.getOwnEntities().forEach(function(ent) {
if (ent.hasClass("Structure")) if (ent.hasClass("Structure"))
@ -176,7 +207,7 @@ var BuildingConstructionPlan = Class({
var pos = ent.position(); var pos = ent.position();
var x = Math.round(pos[0] / cellSize); var x = Math.round(pos[0] / cellSize);
var z = Math.round(pos[1] / 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 pos = ent.position();
// var x = Math.round(pos[0] / cellSize); // var x = Math.round(pos[0] / cellSize);
// var z = Math.round(pos[1] / 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, // Find target building's approximate obstruction radius,
// and expand by a bit to make sure we're not too close // 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; var radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
// Find the best non-obstructed tile // Find the best non-obstructed tile
var bestIdx = 0; var bestIdx = 0;
var bestVal = -1; 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) if (obstructionTiles[i] > radius)
{ {
@ -220,11 +250,13 @@ var BuildingConstructionPlan = Class({
} }
} }
} }
var x = ((bestIdx % map.width) + 0.5) * cellSize; var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize;
var z = (Math.floor(bestIdx / map.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("tiles1.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32);
// Engine.DumpImage("tiles2.png", friendlyTiles, map.width, map.height, 256); // Engine.DumpImage("tiles2.png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256);
// TODO: special dock placement requirements
// Fixed angle to match fixed starting cam // Fixed angle to match fixed starting cam
var angle = 0.75*Math.PI; var angle = 0.75*Math.PI;

View File

@ -148,30 +148,61 @@ var BuildingConstructionPlanEcon = Class({
var cellSize = 4; // size of each tile var cellSize = 4; // size of each tile
// First, find all tiles that are far enough away from obstructions: var template = gameState.getTemplate(this.type);
var map = gameState.getMap();
// 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"); 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); if (passabilityMap.data.length != territoryMap.data.length)
for (var i = 0; i < map.data.length; ++i) error("passability and territory data are not matched!");
obstructionTiles[i] = (map.data[i] & obstructionMask) ? 0 : 65535;
// 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: // 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 infl = 100;
var pos = this.resourceposition; var pos = this.resourceposition;
var x = Math.round(pos[0] / cellSize); var x = Math.round(pos[0] / cellSize);
var z = Math.round(pos[1] / 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. //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 // Find the best non-obstructed tile
var bestIdx = 0; var bestIdx = 0;
var bestVal = -1; 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) if (obstructionTiles[i] > radius)
{ {
@ -201,11 +232,11 @@ var BuildingConstructionPlanEcon = Class({
} }
} }
} }
var x = ((bestIdx % map.width) + 0.5) * cellSize; var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize;
var z = (Math.floor(bestIdx / map.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("tiles1.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32);
// Engine.DumpImage("tiles2.png", friendlyTiles, map.width, map.height, 256); // Engine.DumpImage("tiles2.png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256);
// Fixed angle to match fixed starting cam // Fixed angle to match fixed starting cam
var angle = 0.75*Math.PI; var angle = 0.75*Math.PI;
@ -225,23 +256,23 @@ var BuildingConstructionPlanEcon = Class({
// First, find all tiles that are far enough away from obstructions: // First, find all tiles that are far enough away from obstructions:
var map = gameState.getMap(); var passabilityMap = gameState.getPassabilityMap();
var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction"); var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction");
// Only accept valid land tiles (we don't handle docks yet) // Only accept valid land tiles (we don't handle docks yet)
obstructionMask |= gameState.getPassabilityClassMask("building-land"); obstructionMask |= gameState.getPassabilityClassMask("building-land");
var obstructionTiles = new Uint16Array(map.data.length); var obstructionTiles = new Uint16Array(passabilityMap.data.length);
for (var i = 0; i < map.data.length; ++i) for (var i = 0; i < passabilityMap.data.length; ++i)
obstructionTiles[i] = (map.data[i] & obstructionMask) ? 0 : 65535; 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: // 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) { gameState.getOwnEntities().forEach(function(ent) {
if (ent.hasClass("Structure")) if (ent.hasClass("Structure"))
@ -253,13 +284,13 @@ var BuildingConstructionPlanEcon = Class({
var pos = ent.position(); var pos = ent.position();
var x = Math.round(pos[0] / cellSize); var x = Math.round(pos[0] / cellSize);
var z = Math.round(pos[1] / 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. //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) { // var foetargets = gameState.entities.filter(function(ent) {
// return (ent.isEnemy()); // return (ent.isEnemy());
@ -271,7 +302,7 @@ var BuildingConstructionPlanEcon = Class({
// var pos = ent.position(); // var pos = ent.position();
// var x = Math.round(pos[0] / cellSize); // var x = Math.round(pos[0] / cellSize);
// var z = Math.round(pos[1] / 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 // Find the best non-obstructed tile
var bestIdx = 0; var bestIdx = 0;
var bestVal = -1; 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) if (obstructionTiles[i] > radius)
{ {
@ -301,11 +332,11 @@ var BuildingConstructionPlanEcon = Class({
} }
} }
} }
var x = ((bestIdx % map.width) + 0.5) * cellSize; var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize;
var z = (Math.floor(bestIdx / map.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("tiles1.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32);
// Engine.DumpImage("tiles2.png", friendlyTiles, map.width, map.height, 256); // Engine.DumpImage("tiles2.png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256);
// Randomise the angle a little, to look less artificial // Randomise the angle a little, to look less artificial
//var angle = Math.PI + (Math.random()*2-1) * Math.PI/24; //var angle = Math.PI + (Math.random()*2-1) * Math.PI/24;

View File

@ -38,9 +38,9 @@ var GameState = Class({
return new Resources(this.playerData.resourceCounts); return new Resources(this.playerData.resourceCounts);
}, },
getMap: function() getPassabilityMap: function()
{ {
return this.ai.map; return this.ai.passabilityMap;
}, },
getPassabilityClassMask: function(name) getPassabilityClassMask: function(name)
@ -50,6 +50,11 @@ var GameState = Class({
return this.ai.passabilityClasses[name]; return this.ai.passabilityClasses[name];
}, },
getTerritoryMap: function()
{
return this.ai.territoryMap;
},
getOwnEntities: (function() getOwnEntities: (function()
{ {
return new EntityCollection(this.ai, this.ai._ownEntities); return new EntityCollection(this.ai, this.ai._ownEntities);

View File

@ -124,25 +124,56 @@ var BuildingConstructionPlan = Class({
var cellSize = 4; // size of each tile var cellSize = 4; // size of each tile
// First, find all tiles that are far enough away from obstructions: var template = gameState.getTemplate(this.type);
var map = gameState.getMap();
// 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"); 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); if (passabilityMap.data.length != territoryMap.data.length)
for (var i = 0; i < map.data.length; ++i) error("passability and territory data are not matched!");
obstructionTiles[i] = (map.data[i] & obstructionMask) ? 0 : 65535;
// 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: // 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) { gameState.getOwnEntities().forEach(function(ent) {
if (ent.hasClass("Structure")) if (ent.hasClass("Structure"))
@ -154,19 +185,18 @@ var BuildingConstructionPlan = Class({
var pos = ent.position(); var pos = ent.position();
var x = Math.round(pos[0] / cellSize); var x = Math.round(pos[0] / cellSize);
var z = Math.round(pos[1] / 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, // Find target building's approximate obstruction radius,
// and expand by a bit to make sure we're not too close // 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; var radius = Math.ceil(template.obstructionRadius() / cellSize) + 2;
// Find the best non-obstructed tile // Find the best non-obstructed tile
var bestIdx = 0; var bestIdx = 0;
var bestVal = -1; 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) if (obstructionTiles[i] > radius)
{ {
@ -178,12 +208,14 @@ var BuildingConstructionPlan = Class({
} }
} }
} }
var x = ((bestIdx % map.width) + 0.5) * cellSize; var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize;
var z = (Math.floor(bestIdx / map.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("influences"+playerID+".png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32);
// Engine.DumpImage("tiles2.png", friendlyTiles, map.width, map.height, 256); // Engine.DumpImage("friendly"+playerID+".png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256);
// TODO: special dock placement requirements
// Fixed angle to match fixed starting cam // Fixed angle to match fixed starting cam
var angle = 0.75*Math.PI; var angle = 0.75*Math.PI;

View File

@ -34,6 +34,7 @@
#include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/components/ICmpObstructionManager.h"
#include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/components/ICmpTerritoryManager.h"
#include "simulation2/helpers/Grid.h" #include "simulation2/helpers/Grid.h"
#include "simulation2/serialization/DebugSerializer.h" #include "simulation2/serialization/DebugSerializer.h"
#include "simulation2/serialization/StdDeserializer.h" #include "simulation2/serialization/StdDeserializer.h"
@ -272,7 +273,8 @@ public:
m_PlayerMetadata.clear(); m_PlayerMetadata.clear();
m_Players.clear(); m_Players.clear();
m_GameState.reset(); m_GameState.reset();
m_GameStateMapVal = CScriptValRooted(); m_PassabilityMapVal = CScriptValRooted();
m_TerritoryMapVal = CScriptValRooted();
} }
bool AddPlayer(const std::wstring& aiName, player_id_t player, bool callConstructor) bool AddPlayer(const std::wstring& aiName, player_id_t player, bool callConstructor)
@ -286,18 +288,26 @@ public:
return true; return true;
} }
void StartComputation(const shared_ptr<ScriptInterface::StructuredClone>& gameState, const Grid<u16>& map) void StartComputation(const shared_ptr<ScriptInterface::StructuredClone>& gameState, const Grid<u16>& passabilityMap, const Grid<u8>& territoryMap, bool territoryMapDirty)
{ {
ENSURE(m_CommandsComputed); ENSURE(m_CommandsComputed);
m_GameState = gameState; 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(); 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; m_CommandsComputed = false;
@ -432,7 +442,8 @@ private:
{ {
PROFILE("AI compute read state"); PROFILE("AI compute read state");
state = m_ScriptInterface.ReadStructuredClone(m_GameState); 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 // It would be nice to do
@ -467,8 +478,10 @@ private:
std::vector<shared_ptr<CAIPlayer> > m_Players; // use shared_ptr just to avoid copying std::vector<shared_ptr<CAIPlayer> > m_Players; // use shared_ptr just to avoid copying
shared_ptr<ScriptInterface::StructuredClone> m_GameState; shared_ptr<ScriptInterface::StructuredClone> m_GameState;
Grid<u16> m_GameStateMap; Grid<u16> m_PassabilityMap;
CScriptValRooted m_GameStateMapVal; CScriptValRooted m_PassabilityMapVal;
Grid<u8> m_TerritoryMap;
CScriptValRooted m_TerritoryMapVal;
bool m_CommandsComputed; bool m_CommandsComputed;
}; };
@ -568,16 +581,28 @@ public:
// Get the game state from AIInterface // Get the game state from AIInterface
CScriptVal state = cmpAIInterface->GetRepresentation(); CScriptVal state = cmpAIInterface->GetRepresentation();
// Get the map data // Get the passability data
Grid<u16> dummyGrid; Grid<u16> dummyGrid;
const Grid<u16>* map = &dummyGrid; const Grid<u16>* passabilityMap = &dummyGrid;
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
if (!cmpPathfinder.null()) 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<u8> dummyGrid2;
const Grid<u8>* territoryMap = &dummyGrid2;
CmpPtr<ICmpTerritoryManager> cmpTerritoryManager(GetSimContext(), SYSTEM_ENTITY);
if (!cmpTerritoryManager.null() && cmpTerritoryManager->NeedUpdate(&m_TerritoriesDirtyID))
{
territoryMap = &cmpTerritoryManager->GetTerritoryGrid();
territoryMapDirty = true;
}
LoadPathfinderClasses(state); LoadPathfinderClasses(state);
m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state.get()), *map); m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state.get()), *passabilityMap, *territoryMap, territoryMapDirty);
} }
virtual void PushCommands() virtual void PushCommands()
@ -605,6 +630,7 @@ private:
std::vector<std::string> m_TemplateNames; std::vector<std::string> m_TemplateNames;
size_t m_TemplateLoadedIdx; size_t m_TemplateLoadedIdx;
std::vector<std::pair<std::string, const CParamNode*> > m_Templates; std::vector<std::pair<std::string, const CParamNode*> > m_Templates;
size_t m_TerritoriesDirtyID;
void StartLoadEntityTemplates() void StartLoadEntityTemplates()
{ {

View File

@ -236,3 +236,30 @@ template<> jsval ScriptInterface::ToJSVal<Grid<u16> >(JSContext* cx, const Grid<
return OBJECT_TO_JSVAL(obj); return OBJECT_TO_JSVAL(obj);
} }
template<> jsval ScriptInterface::ToJSVal<Grid<u8> >(JSContext* cx, const Grid<u8>& 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);
}