Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/walkToCC.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/walkToCC.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/walkToCC.js (nonexistent)
@@ -1,85 +0,0 @@
-var WalkToCC = function(gameState, militaryManager){
- this.minAttackSize = 20;
- this.maxAttackSize = 60;
- this.idList=[];
-};
-
-// Returns true if the attack can be executed at the current time
-WalkToCC.prototype.canExecute = function(gameState, militaryManager){
- var enemyStrength = militaryManager.measureEnemyStrength(gameState);
- var enemyCount = militaryManager.measureEnemyCount(gameState);
-
- // We require our army to be >= this strength
- var targetStrength = enemyStrength * 1.5;
-
- var availableCount = militaryManager.countAvailableUnits();
- var availableStrength = militaryManager.measureAvailableStrength();
-
- debug("Troops needed for attack: " + this.minAttackSize + " Have: " + availableCount);
- debug("Troops strength for attack: " + targetStrength + " Have: " + availableStrength);
-
- return ((availableStrength >= targetStrength && availableCount >= this.minAttackSize)
- || availableCount >= this.maxAttackSize);
-};
-
-// Executes the attack plan, after this is executed the update function will be run every turn
-WalkToCC.prototype.execute = function(gameState, militaryManager){
- var availableCount = militaryManager.countAvailableUnits();
- this.idList = militaryManager.getAvailableUnits(availableCount);
-
- var pending = EntityCollectionFromIds(gameState, this.idList);
-
- // Find the critical enemy buildings we could attack
- var targets = militaryManager.getEnemyBuildings(gameState,"ConquestCritical");
- // If there are no critical structures, attack anything else that's critical
- if (targets.length == 0) {
- targets = gameState.entities.filter(function(ent) {
- return (gameState.isEntityEnemy(ent) && ent.hasClass("ConquestCritical") && ent.owner() !== 0);
- });
- }
- // If there's nothing, attack anything else that's less critical
- if (targets.length == 0) {
- targets = militaryManager.getEnemyBuildings(gameState,"Town");
- }
- if (targets.length == 0) {
- targets = militaryManager.getEnemyBuildings(gameState,"Village");
- }
-
- // If we have a target, move to it
- if (targets.length) {
- // Remove the pending role
- pending.forEach(function(ent) {
- ent.setMetadata("role", "attack");
- });
-
- var target = targets.toEntityArray()[0];
- var targetPos = target.position();
-
- // TODO: this should be an attack-move command
- pending.move(targetPos[0], targetPos[1]);
- } else if (targets.length == 0 ) {
- gameState.ai.gameFinished = true;
- }
-};
-
-// Runs every turn after the attack is executed
-// This removes idle units from the attack
-WalkToCC.prototype.update = function(gameState, militaryManager){
- var removeList = [];
- for (var idKey in this.idList){
- var id = this.idList[idKey];
- var ent = militaryManager.entity(id);
- if(ent)
- {
- if(ent.isIdle()) {
- militaryManager.unassignUnit(id);
- removeList.push(id);
- }
- } else {
- removeList.push(id);
- }
- }
- for (var i in removeList){
- this.idList.splice(this.idList.indexOf(removeList[i]),1);
- }
-};
\ No newline at end of file
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/walkToCC.js
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/terrain-analysis.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/terrain-analysis.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/terrain-analysis.js (nonexistent)
@@ -1,720 +0,0 @@
-/*
- * TerrainAnalysis inherits from Map
- *
- * This creates a suitable passability map for pathfinding units and provides the findClosestPassablePoint() function.
- * This is intended to be a base object for the terrain analysis modules to inherit from.
- */
-
-function TerrainAnalysis(gameState){
- var passabilityMap = gameState.getMap();
-
- var obstructionMask = gameState.getPassabilityClassMask("pathfinderObstruction");
- obstructionMask |= gameState.getPassabilityClassMask("default");
-
- var obstructionTiles = new Uint16Array(passabilityMap.data.length);
- for (var i = 0; i < passabilityMap.data.length; ++i)
- {
- obstructionTiles[i] = (passabilityMap.data[i] & obstructionMask) ? 0 : 65535;
- }
-
- this.Map(gameState, obstructionTiles);
-};
-
-copyPrototype(TerrainAnalysis, Map);
-
-// Returns the (approximately) closest point which is passable by searching in a spiral pattern
-TerrainAnalysis.prototype.findClosestPassablePoint = function(startPoint, quick, limitDistance){
- var w = this.width;
- var p = startPoint;
- var direction = 1;
-
- if (p[0] + w*p[1] > 0 && p[0] + w*p[1] < this.length &&
- this.map[p[0] + w*p[1]] != 0){
- if (this.countConnected(p, 10) >= 10){
- return p;
- }
- }
-
- var count = 0;
- // search in a spiral pattern.
- for (var i = 1; i < w; i++){
- for (var j = 0; j < 2; j++){
- for (var k = 0; k < i; k++){
- p[j] += direction;
- if (p[0] + w*p[1] > 0 && p[0] + w*p[1] < this.length &&
- this.map[p[0] + w*p[1]] != 0){
- if (quick || this.countConnected(p, 10) >= 10){
- return p;
- }
- }
- if (limitDistance && count > 40){
- return undefined;
- }
- count += 1;
- }
- }
- direction *= -1;
- }
-
- return undefined;
-};
-
-// Counts how many accessible tiles there are connected to the start Point. If there are >= maxCount then it stops.
-// This is inefficient for large areas so maxCount should be kept small for efficiency.
-TerrainAnalysis.prototype.countConnected = function(startPoint, maxCount, curCount, checked){
- curCount = curCount || 0;
- checked = checked || [];
-
- var w = this.width;
-
- var positions = [[0,1], [0,-1], [1,0], [-1,0]];
-
- curCount += 1; // add 1 for the current point
- checked.push(startPoint);
- if (curCount >= maxCount){
- return curCount;
- }
-
- for (var i in positions){
- var p = [startPoint[0] + positions[i][0], startPoint[1] + positions[i][1]];
- if (p[0] + w*p[1] > 0 && p[0] + w*p[1] < this.length &&
- this.map[p[0] + w*p[1]] != 0 && !(p in checked)){
- curCount += this.countConnected(p, maxCount, curCount, checked);
- }
- }
- return curCount;
-};
-
-/*
- * PathFinder inherits from TerrainAnalysis
- *
- * Used to create a list of distinct paths between two points.
- *
- * Currently it works with a basic implementation which should be improved.
- *
- * TODO: Make this use territories.
- */
-
-
-function PathFinder(gameState){
- this.TerrainAnalysis(gameState);
-}
-
-copyPrototype(PathFinder, TerrainAnalysis);
-
-/*
- * Returns a list of distinct paths to the destination. Currently paths are distinct if they are more than
- * blockRadius apart at a distance of blockPlacementRadius from the destination. Where blockRadius and
- * blockPlacementRadius are defined in walkGradient
- */
-PathFinder.prototype.getPaths = function(start, end, mode){
- var s = this.findClosestPassablePoint(this.gamePosToMapPos(start));
- var e = this.findClosestPassablePoint(this.gamePosToMapPos(end));
-
- if (!s || !e){
- return undefined;
- }
-
- var paths = [];
- var i = 0;
- while (true){
- i++;
- //this.dumpIm("terrainanalysis_"+i+".png", 511);
- this.makeGradient(s,e);
- var curPath = this.walkGradient(e, mode);
-
- if (curPath !== undefined){
- paths.push(curPath);
- }else{
- break;
- }
-
- this.wipeGradient();
- }
-
- //this.dumpIm("terrainanalysis.png", 511);
-
- if (paths.length > 0){
- return paths;
- }else{
- return [];
- }
-};
-
-// Creates a potential gradient with the start point having the lowest potential
-PathFinder.prototype.makeGradient = function(start, end){
- var w = this.width;
- var map = this.map;
-
- // Holds the list of current points to work outwards from
- var stack = [];
- // We store the next level in its own stack
- var newStack = [];
- // Relative positions or new cells from the current one. We alternate between the adjacent 4 and 8 cells
- // so that there is an average 1.5 distance for diagonals which is close to the actual sqrt(2) ~ 1.41
- var positions = [[[0,1], [0,-1], [1,0], [-1,0]],
- [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1]]];
-
- //Set the distance of the start point to be 1 to distinguish it from the impassable areas
- map[start[0] + w*(start[1])] = 1;
- stack.push(start);
-
- // while there are new points being added to the stack
- while (stack.length > 0){
- //run through the current stack
- while (stack.length > 0){
- var cur = stack.pop();
- // stop when we reach the end point
- if (cur[0] == end[0] && cur[1] == end[1]){
- return;
- }
-
- var dist = map[cur[0] + w*(cur[1])] + 1;
- // Check the positions adjacent to the current cell
- for (var i = 0; i < positions[dist % 2].length; i++){
- var pos = positions[dist % 2][i];
- var cell = cur[0]+pos[0] + w*(cur[1]+pos[1]);
- if (cell >= 0 && cell < this.length && map[cell] > dist){
- map[cell] = dist;
- newStack.push([cur[0]+pos[0], cur[1]+pos[1]]);
- }
- }
- }
- // Replace the old empty stack with the newly filled one.
- stack = newStack;
- newStack = [];
- }
-
-};
-
-// Clears the map to just have the obstructions marked on it.
-PathFinder.prototype.wipeGradient = function(){
- for (var i = 0; i < this.length; i++){
- if (this.map[i] > 0){
- this.map[i] = 65535;
- }
- }
-};
-
-// Returns the path down a gradient from the start to the bottom of the gradient, returns a point for every 20 cells in normal mode
-// in entryPoints mode this returns the point where the path enters the region near the destination, currently defined
-// by blockPlacementRadius. Note doesn't return a path when the destination is within the blockpoint radius.
-PathFinder.prototype.walkGradient = function(start, mode){
- var positions = [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1]];
-
- var path = [[start[0]*this.cellSize, start[1]*this.cellSize]];
-
- var blockPoint = undefined;
- var blockPlacementRadius = 45;
- var blockRadius = 23;
- var count = 0;
-
- var cur = start;
- var w = this.width;
- var dist = this.map[cur[0] + w*cur[1]];
- var moved = false;
- while (this.map[cur[0] + w*cur[1]] !== 0){
- for (var i = 0; i < positions.length; i++){
- var pos = positions[i];
- var cell = cur[0]+pos[0] + w*(cur[1]+pos[1]);
- if (cell >= 0 && cell < this.length && this.map[cell] > 0 && this.map[cell] < dist){
- dist = this.map[cell];
- cur = [cur[0]+pos[0], cur[1]+pos[1]];
- moved = true;
- count++;
- // Mark the point to put an obstruction at before calculating the next path
- if (count === blockPlacementRadius){
- blockPoint = cur;
- }
- // Add waypoints to the path, fairly well spaced apart.
- if (count % 40 === 0){
- path.unshift([cur[0]*this.cellSize, cur[1]*this.cellSize]);
- }
- break;
- }
- }
- if (!moved){
- break;
- }
- moved = false;
- }
- if (blockPoint === undefined){
- return undefined;
- }
-
- // Add an obstruction to the map at the blockpoint so the next path will take a different route.
- this.addInfluence(blockPoint[0], blockPoint[1], blockRadius, -1000000, 'constant');
- if (mode === 'entryPoints'){
- // returns the point where the path enters the blockPlacementRadius
- return [blockPoint[0] * this.cellSize, blockPoint[1] * this.cellSize];
- }else{
- // return a path of points 20 squares apart on the route
- return path;
- }
-};
-
-// Would be used to calculate the width of a chokepoint
-// NOTE: Doesn't currently work.
-PathFinder.prototype.countAttached = function(pos){
- var positions = [[0,1], [0,-1], [1,0], [-1,0]];
- var w = this.width;
- var val = this.map[pos[0] + w*pos[1]];
-
- var stack = [pos];
- var used = {};
-
- while (stack.length > 0){
- var cur = stack.pop();
- used[cur[0] + " " + cur[1]] = true;
- for (var i = 0; i < positions.length; i++){
- var p = positions[i];
- var cell = cur[0]+p[0] + w*(cur[1]+p[1]);
-
- }
- }
-};
-
-/*
- * Accessibility inherits from TerrainAnalysis
- *
- * Determines whether there is a path from one point to another. It is initialised with a single point (p1) and then
- * can efficiently determine if another point is reachable from p1. Initialising the object is costly so it should be
- * cached.
- */
-
-function Accessibility(gameState, location){
- this.TerrainAnalysis(gameState);
-
- var start = this.findClosestPassablePoint(this.gamePosToMapPos(location));
-
- // Check that the accessible region is a decent size, otherwise obstacles close to the start point can create
- // tiny accessible areas which makes the rest of the map inaceesible.
- var iterations = 0;
- while (this.floodFill(start) < 20 && iterations < 30){
- this.map[start[0] + this.width*(start[1])] = 0;
- start = this.findClosestPassablePoint(this.gamePosToMapPos(location));
- iterations += 1;
- }
- //this.dumpIm("accessibility.png");
-}
-
-copyPrototype(Accessibility, TerrainAnalysis);
-
-// Return true if the given point is accessible from the point given when initialising the Accessibility object. #
-// If the given point is impassable the closest passable point is used.
-Accessibility.prototype.isAccessible = function(position){
- var s = this.findClosestPassablePoint(this.gamePosToMapPos(position), true, true);
- if (!s)
- return false;
-
- return this.map[s[0] + this.width * s[1]] === 1;
-};
-
-// fill all of the accessible areas with value 1
-Accessibility.prototype.floodFill = function(start){
- var w = this.width;
- var map = this.map;
-
- // Holds the list of current points to work outwards from
- var stack = [];
- // We store new points to be added to the stack temporarily in here while we run through the current stack
- var newStack = [];
- // Relative positions or new cells from the current one.
- var positions = [[0,1], [0,-1], [1,0], [-1,0]];
-
- // Set the start point to be accessible
- map[start[0] + w*(start[1])] = 1;
- stack.push(start);
-
- var count = 0;
-
- // while there are new points being added to the stack
- while (stack.length > 0){
- //run through the current stack
- while (stack.length > 0){
- var cur = stack.pop();
-
- // Check the positions adjacent to the current cell
- for (var i = 0; i < positions.length; i++){
- var pos = positions[i];
- var cell = cur[0]+pos[0] + w*(cur[1]+pos[1]);
- if (cell >= 0 && cell < this.length && map[cell] > 1){
- map[cell] = 1;
- newStack.push([cur[0]+pos[0], cur[1]+pos[1]]);
- count += 1;
- }
- }
- }
- // Replace the old empty stack with the newly filled one.
- stack = newStack;
- newStack = [];
- }
- return count;
-};
-
-
-
-
-// Some different take on the idea of Quantumstate... What I'll do is make a list of any terrain obstruction...
-
-function aStarPath(gameState, onWater){
- var self = this;
-
- this.passabilityMap = gameState.getMap();
-
- var obstructionMaskLand = gameState.getPassabilityClassMask("default");
- var obstructionMaskWater = gameState.getPassabilityClassMask("ship");
-
- var obstructionTiles = new Uint16Array(this.passabilityMap.data.length);
- for (var i = 0; i < this.passabilityMap.data.length; ++i)
- {
- if (onWater) {
- obstructionTiles[i] = (this.passabilityMap.data[i] & obstructionMaskWater) ? 0 : 255;
- } else {
- obstructionTiles[i] = (this.passabilityMap.data[i] & obstructionMaskLand) ? 0 : 255;
- // We allow water, but we set it at a different index.
- if (!(this.passabilityMap.data[i] & obstructionMaskWater) && obstructionTiles[i] === 0)
- obstructionTiles[i] = 200;
- }
- }
- if (onWater)
- this.onWater = true;
- else
- this.onWater = false;
- this.pathRequiresWater = this.onWater;
-
- this.cellSize = gameState.cellSize;
-
- this.Map(gameState, obstructionTiles);
- this.passabilityMap = new Map(gameState, obstructionTiles, true);
-
- var type = ["wood","stone", "metal"];
- if (onWater) // trees can perhaps be put into water, I'd doubt so about the rest.
- type = ["wood"];
- for (o in type) {
- var entities = gameState.getResourceSupplies(type[o]);
- entities.forEach(function (supply) { //}){
- var radius = Math.floor(supply.obstructionRadius() / self.cellSize);
- if (type[o] === "wood") {
- for (var xx = -1; xx <= 1;xx++)
- for (var yy = -1; yy <= 1;yy++)
- {
- var x = self.gamePosToMapPos(supply.position())[0];
- var y = self.gamePosToMapPos(supply.position())[1];
- if (x+xx >= 0 && x+xx < self.width && y+yy >= 0 && y+yy < self.height)
- {
- self.map[x+xx + (y+yy)*self.width] = 0;
- self.passabilityMap.map[x+xx + (y+yy)*self.width] = 100; // tree
- }
- }
- self.map[x + y*self.width] = 0;
- self.passabilityMap.map[x + y*self.width] = 0;
- } else {
- for (var xx = -radius; xx <= radius;xx++)
- for (var yy = -radius; yy <= radius;yy++)
- {
- var x = self.gamePosToMapPos(supply.position())[0];
- var y = self.gamePosToMapPos(supply.position())[1];
- if (x+xx >= 0 && x+xx < self.width && y+yy >= 0 && y+yy < self.height)
- {
- self.map[x+xx + (y+yy)*self.width] = 0;
- self.passabilityMap.map[x+xx + (y+yy)*self.width] = 0;
- }
- }
- }
- });
- }
- //this.dumpIm("Non-Expanded Obstructions.png",255);
- this.expandInfluences();
- //this.dumpIm("Expanded Obstructions.png",10);
- //this.BluringRadius = 10;
- //this.Blur(this.BluringRadius); // first steop of bluring
-}
-copyPrototype(aStarPath, TerrainAnalysis);
-
-aStarPath.prototype.getPath = function(start,end,optimized, minSampling, iterationLimit , gamestate)
-{
- if (minSampling === undefined)
- this.minSampling = 2;
- else this.minSampling = minSampling;
-
- if (start[0] < 0 || this.gamePosToMapPos(start)[0] >= this.width || start[1] < 0 || this.gamePosToMapPos(start)[1] >= this.height)
- return undefined;
-
- var s = this.findClosestPassablePoint(this.gamePosToMapPos(start));
- var e = this.findClosestPassablePoint(this.gamePosToMapPos(end));
-
- if (!s || !e){
- return undefined;
- }
-
- var w = this.width;
- var h = this.height;
-
- this.optimized = optimized;
- if (this.minSampling < 1)
- this.minSampling = 1;
-
- if (gamestate !== undefined)
- {
- this.TotorMap = new Map(gamestate);
- this.TotorMap.addInfluence(s[0],s[1],1,200,'constant');
- this.TotorMap.addInfluence(e[0],e[1],1,200,'constant');
- }
- this.iterationLimit = 65500;
- if (iterationLimit !== undefined)
- this.iterationLimit = iterationLimit;
-
- this.s = s[0] + w*s[1];
- this.e = e[0] + w*e[1];
-
- // I was using incredibly slow associative arrays beforeā¦
- this.openList = [];
- this.parentSquare = new Uint32Array(this.map.length);
- this.isOpened = new Boolean(this.map.length);
- this.fCostArray = new Uint32Array(this.map.length);
- this.gCostArray = new Uint32Array(this.map.length);
- this.currentSquare = this.s;
-
- this.totalIteration = 0;
-
- this.isOpened[this.s] = true;
- this.openList.push(this.s);
- this.fCostArray[this.s] = SquareVectorDistance([this.s%w, Math.floor(this.s/w)], [this.e%w, Math.floor(this.e/w)]);
- this.gCostArray[this.s] = 0;
- this.parentSquare[this.s] = this.s;
- //debug ("Initialized okay");
- return this.continuePath(gamestate);
-
-}
-// in case it's not over yet, this can carry on the calculation of a path over multiple turn until it's over
-aStarPath.prototype.continuePath = function(gamestate)
-{
- var w = this.width;
- var h = this.height;
- var positions = [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1]];
- var cost = [100,100,100,100,150,150,150,150];
- var invCost = [1,1,1,1,0.8,0.8,0.8,0.8];
- //creation of variables used in the loop
- var found = false;
- var nouveau = false;
- var shortcut = false;
- var Sampling = this.minSampling;
- var closeToEnd = false;
- var infinity = Math.min();
- var currentDist = infinity;
- var e = this.e;
- var s = this.s;
-
- var iteration = 0;
- // on to A*
- while (found === false && this.openList.length !== 0 && iteration < this.iterationLimit){
- currentDist = infinity;
-
- if (shortcut === true) {
- this.currentSquare = this.openList.shift();
- } else {
- for (i in this.openList)
- {
- var sum = this.fCostArray[this.openList[i]] + this.gCostArray[this.openList[i]];
- if (sum < currentDist)
- {
- this.currentSquare = this.openList[i];
- currentDist = sum;
- }
- }
- this.openList.splice(this.openList.indexOf(this.currentSquare),1);
- }
- if (!this.onWater && this.passabilityMap.map[this.currentSquare] === 200) {
- this.onWater = true;
- this.pathRequiresWater = true;
- } else if (this.onWater && this.passabilityMap.map[this.currentSquare] !== 200)
- this.onWater = false;
-
- shortcut = false;
- this.isOpened[this.currentSquare] = false;
-
- // optimizaiton: can make huge jumps if I know there's nothing in the way
- Sampling = this.minSampling;
- if (this.optimized === true) {
- Sampling = Math.floor( (+this.map[this.currentSquare]-this.minSampling)/Sampling )*Sampling;
- if (Sampling < this.minSampling)
- Sampling = this.minSampling;
- }
- /*
- var diagSampling = Math.floor(Sampling / 1.5);
- if (diagSampling < this.minSampling)
- diagSampling = this.minSampling;
- */
- var target = [this.e%w, Math.floor(this.e/w)];
- closeToEnd = false;
- if (SquareVectorDistance([this.currentSquare%w, Math.floor(this.currentSquare/w)], target) <= Sampling*Sampling)
- {
- closeToEnd = true;
- Sampling = 1;
- }
- if (gamestate !== undefined)
- this.TotorMap.addInfluence(this.currentSquare % w, Math.floor(this.currentSquare / w),1,40,'constant');
-
- for (i in positions)
- {
- //var hereSampling = cost[i] == 1 ? Sampling : diagSampling;
- var index = 0 + this.currentSquare +positions[i][0]*Sampling +w*Sampling*positions[i][1];
- if (this.map[index] >= Sampling)
- {
- if(this.isOpened[index] === undefined)
- {
- this.parentSquare[index] = this.currentSquare;
-
- this.fCostArray[index] = SquareVectorDistance([index%w, Math.floor(index/w)], target);// * cost[i];
- this.gCostArray[index] = this.gCostArray[this.currentSquare] + cost[i] * Sampling;// - this.map[index];
-
- if (!this.onWater && this.passabilityMap.map[index] === 200) {
- this.gCostArray[index] += this.width*this.width*3;
- } else if (this.onWater && this.passabilityMap.map[index] !== 200) {
- this.gCostArray[index] += this.fCostArray[index];
- } else if (!this.onWater && this.passabilityMap.map[index] === 100) {
- this.gCostArray[index] += 100;
- }
-
- if (this.openList[0] !== undefined && this.fCostArray[this.openList[0]] + this.gCostArray[this.openList[0]] > this.fCostArray[index] + this.gCostArray[index])
- {
- this.openList.unshift(index);
- shortcut = true;
- } else {
- this.openList.push(index);
- }
- this.isOpened[index] = true;
- if (closeToEnd === true && (index === e || index - 1 === e || index + 1 === e || index - w === e || index + w === e
- || index + 1 + w === e || index + 1 - w === e || index - 1 + w === e|| index - 1 - w === e)) {
- this.parentSquare[this.e] = this.currentSquare;
- found = true;
- break;
- }
- } else {
- var addCost = 0;
- if (!this.onWater && this.passabilityMap.map[index] === 200) {
- addCost = this.width*this.width*3;
- } else if (this.onWater && this.passabilityMap.map[index] !== 200) {
- addCost = this.fCostArray[index];
- } else if (!this.onWater && this.passabilityMap.map[index] === 100) {
- addCost += 100;
- }
- //addCost -= this.map[index];
- // already on the Open or closed list
- if (this.gCostArray[index] > cost[i] * Sampling + addCost + this.gCostArray[this.currentSquare])
- {
- this.parentSquare[index] = this.currentSquare;
- this.gCostArray[index] = cost[i] * Sampling + addCost + this.gCostArray[this.currentSquare];
- }
- }
- }
- }
- iteration++;
- }
- this.totalIteration += iteration;
- if (iteration === this.iterationLimit && found === false && this.openList.length !== 0)
- {
-
- // we've got to assume that we stopped because we reached the upper limit of iterations
- return "toBeContinued";
- }
-
- //debug (this.totalIteration);
- var paths = [];
- if (found) {
- this.currentSquare = e;
- var lastPos = [0,0];
- while (this.parentSquare[this.currentSquare] !== s)
- {
- this.currentSquare = this.parentSquare[this.currentSquare];
- if (gamestate !== undefined)
- this.TotorMap.addInfluence(this.currentSquare % w, Math.floor(this.currentSquare / w),1,50,'constant');
- if (SquareVectorDistance(lastPos,[this.currentSquare % w, Math.floor(this.currentSquare / w)]) > 300)
- {
- lastPos = [ (this.currentSquare % w) * this.cellSize, Math.floor(this.currentSquare / w) * this.cellSize];
- paths.push(lastPos);
- if (gamestate !== undefined)
- this.TotorMap.addInfluence(this.currentSquare % w, Math.floor(this.currentSquare / w),1,100,'constant');
- }
- }
- } else {
- // we have not found a path.
- // what do we do then?
- }
-
- if (gamestate !== undefined)
- this.TotorMap.dumpIm("Path From " +s +" to " +e +".png",255);
-
- delete this.parentSquare;
- delete this.isOpened;
- delete this.fCostArray;
- delete this.gCostArray;
-
- if (paths.length > 0) {
- return [paths, this.pathRequiresWater];
- } else {
- return undefined;
- }
-
-}
-
-/**
- * Make each cell's 8-bit value at least one greater than each of its
- * neighbours' values. (If the grid is initialised with 0s and things high enough (> 100 on most maps), the
- * result of each cell is its Manhattan distance to the nearest 0.)
- */
-aStarPath.prototype.expandInfluences = function() {
- var w = this.width;
- var h = this.height;
- var grid = this.map;
- for ( var y = 0; y < h; ++y) {
- var min = 8;
- 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;
- if (min > 8)
- min = 8;
- }
-
- 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;
- if (min > 8)
- min = 8;
- }
- }
-
- for ( var x = 0; x < w; ++x) {
- var min = 8;
- 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;
- if (min > 8)
- min = 8;
- }
-
- 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;
- if (min > 8)
- min = 8;
- }
- }
-};
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/terrain-analysis.js
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/filters-extend.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/filters-extend.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/filters-extend.js (nonexistent)
@@ -1,42 +0,0 @@
-// Some new filters I use in entity Collections
-
-Filters["byID"] =
- function(id){
- return {"func": function(ent){
- return (ent.id() == id);
- },
- "dynamicProperties": ['id']};
-};
-Filters["byTargetedEntity"] =
-function(targetID){
- return {"func": function(ent){
- return (ent.unitAIOrderData() && ent.unitAIOrderData()["target"] && ent.unitAIOrderData()["target"] == targetID);
- },
- "dynamicProperties": ['unitAIOrderData']};
-};
-Filters["byHasMetadata"] =
-function(key){
- return {"func" : function(ent){
- return (ent.getMetadata(key) != undefined);
- },
- "dynamicProperties": ['metadata.' + key]};
-};
-Filters["byTerritory"] = function(Map, territoryIndex){
- return {"func": function(ent){
- if (Map.point(ent.position()) == territoryIndex) {
- return true;
- } else {
- return false;
- }
- },
- "dynamicProperties": ['position']};
-};
-Filters["isDropsite"] = function(resourceType){
- return {"func": function(ent){
- return (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resourceType) !== -1
- && ent.foundationProgress() === undefined);
- },
- "dynamicProperties": []};
-};
-
-
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/filters-extend.js
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/attackMoveToCC.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/attackMoveToCC.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/attackMoveToCC.js (nonexistent)
@@ -1,205 +0,0 @@
-function AttackMoveToCC(gameState, militaryManager){
- this.minAttackSize = 20;
- this.maxAttackSize = 60;
- this.idList=[];
-
- this.previousTime = 0;
- this.state = "unexecuted";
-
- this.healthRecord = [];
-};
-
-// Returns true if the attack can be executed at the current time
-AttackMoveToCC.prototype.canExecute = function(gameState, militaryManager){
- var enemyStrength = militaryManager.measureEnemyStrength(gameState);
- var enemyCount = militaryManager.measureEnemyCount(gameState);
-
- // We require our army to be >= this strength
- var targetStrength = enemyStrength * 1.5;
-
- var availableCount = militaryManager.countAvailableUnits();
- var availableStrength = militaryManager.measureAvailableStrength();
-
- debug("Troops needed for attack: " + this.minAttackSize + " Have: " + availableCount);
- debug("Troops strength for attack: " + targetStrength + " Have: " + availableStrength);
-
- return ((availableStrength >= targetStrength && availableCount >= this.minAttackSize)
- || availableCount >= this.maxAttackSize);
-};
-
-// Executes the attack plan, after this is executed the update function will be run every turn
-AttackMoveToCC.prototype.execute = function(gameState, militaryManager){
- var availableCount = militaryManager.countAvailableUnits();
- this.idList = militaryManager.getAvailableUnits(availableCount);
-
- var pending = EntityCollectionFromIds(gameState, this.idList);
-
- // Find the critical enemy buildings we could attack
- var targets = militaryManager.getEnemyBuildings(gameState,"ConquestCritical");
- // If there are no critical structures, attack anything else that's critical
- if (targets.length == 0) {
- targets = gameState.entities.filter(function(ent) {
- return (gameState.isEntityEnemy(ent) && ent.hasClass("ConquestCritical") && ent.owner() !== 0 && ent.position());
- });
- }
- // If there's nothing, attack anything else that's less critical
- if (targets.length == 0) {
- targets = militaryManager.getEnemyBuildings(gameState,"Town");
- }
- if (targets.length == 0) {
- targets = militaryManager.getEnemyBuildings(gameState,"Village");
- }
-
- // If we have a target, move to it
- if (targets.length) {
- // Add an attack role so the economic manager doesn't try and use them
- pending.forEach(function(ent) {
- ent.setMetadata("role", "attack");
- });
-
- var curPos = pending.getCentrePosition();
-
- var target = targets.toEntityArray()[0];
- this.targetPos = target.position();
-
- // Find possible distinct paths to the enemy
- var pathFinder = new PathFinder(gameState);
- var pathsToEnemy = pathFinder.getPaths(curPos, this.targetPos);
- if (! pathsToEnemy){
- pathsToEnemy = [this.targetPos];
- }
-
- var rand = Math.floor(Math.random() * pathsToEnemy.length);
- this.path = pathsToEnemy[rand];
-
- pending.move(this.path[0][0], this.path[0][1]);
- } else if (targets.length == 0 ) {
- gameState.ai.gameFinished = true;
- }
-
- this.state = "walking";
-};
-
-// Runs every turn after the attack is executed
-// This removes idle units from the attack
-AttackMoveToCC.prototype.update = function(gameState, militaryManager, events){
-
- // keep the list of units in good order by pruning ids with no corresponding entities (i.e. dead units)
- var removeList = [];
- var totalHealth = 0;
- for (var idKey in this.idList){
- var id = this.idList[idKey];
- var ent = militaryManager.entity(id);
- if (ent === undefined){
- removeList.push(id);
- }else{
- if (ent.hitpoints()){
- totalHealth += ent.hitpoints();
- }
- }
- }
- for (var i in removeList){
- this.idList.splice(this.idList.indexOf(removeList[i]),1);
- }
-
- var units = EntityCollectionFromIds(gameState, this.idList);
-
- if (this.path.length === 0){
- var idleCount = 0;
- var self = this;
- units.forEach(function(ent){
- if (ent.isIdle()){
- if (ent.position() && VectorDistance(ent.position(), self.targetPos) > 30){
- ent.move(self.targetPos[0], self.targetPos[1]);
- }else{
- militaryManager.unassignUnit(ent.id());
- }
- }
- });
- return;
- }
-
- var deltaHealth = 0;
- var deltaTime = 1;
- var time = gameState.getTimeElapsed();
- this.healthRecord.push([totalHealth, time]);
- if (this.healthRecord.length > 1){
- for (var i = this.healthRecord.length - 1; i >= 0; i--){
- deltaHealth = totalHealth - this.healthRecord[i][0];
- deltaTime = time - this.healthRecord[i][1];
- if (this.healthRecord[i][1] < time - 5*1000){
- break;
- }
- }
- }
-
- var numUnits = this.idList.length;
- if (numUnits < 1) return;
- var damageRate = -deltaHealth / deltaTime * 1000;
- var centrePos = units.getCentrePosition();
- if (! centrePos) return;
-
- var idleCount = 0;
- // Looks for idle units away from the formations centre
- for (var idKey in this.idList){
- var id = this.idList[idKey];
- var ent = militaryManager.entity(id);
- if (ent.isIdle()){
- if (ent.position() && VectorDistance(ent.position(), centrePos) > 20){
- var dist = VectorDistance(ent.position(), centrePos);
- var vector = [centrePos[0] - ent.position()[0], centrePos[1] - ent.position()[1]];
- vector[0] *= 10/dist;
- vector[1] *= 10/dist;
- ent.move(centrePos[0] + vector[0], centrePos[1] + vector[1]);
- }else{
- idleCount++;
- }
- }
- }
-
- if ((damageRate / Math.sqrt(numUnits)) > 2){
- if (this.state === "walking"){
- var sumAttackerPos = [0,0];
- var numAttackers = 0;
-
- for (var key in events){
- var e = events[key];
- //{type:"Attacked", msg:{attacker:736, target:1133, type:"Melee"}}
- if (e.type === "Attacked" && e.msg){
- if (this.idList.indexOf(e.msg.target) !== -1){
- var attacker = militaryManager.entity(e.msg.attacker);
- if (attacker && attacker.position()){
- sumAttackerPos[0] += attacker.position()[0];
- sumAttackerPos[1] += attacker.position()[1];
- numAttackers += 1;
- }
- }
- }
- }
- if (numAttackers > 0){
- var avgAttackerPos = [sumAttackerPos[0]/numAttackers, sumAttackerPos[1]/numAttackers];
- // Stop moving
- units.move(centrePos[0], centrePos[1]);
- this.state = "attacking";
- }
- }
- }else{
- if (this.state === "attacking"){
- units.move(this.path[0][0], this.path[0][1]);
- this.state = "walking";
- }
- }
-
- if (this.state === "walking"){
- if (VectorDistance(centrePos, this.path[0]) < 20 || idleCount/numUnits > 0.8){
- this.path.shift();
- if (this.path.length > 0){
- units.move(this.path[0][0], this.path[0][1]);
- }
- }
- }
-
- this.previousTime = time;
- this.previousHealth = totalHealth;
-};
-
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/attackMoveToCC.js
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/readme.txt
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/readme.txt (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/readme.txt (nonexistent)
@@ -1,7 +0,0 @@
-This is an AI for 0 A.D. (http://play0ad.com/) based on the testBot.
-
-Install by placing the files into the data/mods/public/simulation/ai/qbot folder.
-
-If you are developing you might find it helpful to change the debugOn line in qBot.js. This will make it spew random warnings depending on what I have been working on. Use the debug() function to make your own warnings.
-
-Addendum for the experimental version: this is the regular qBot, with improvements here and there. It's "experimental" as it is currently in "test" phase, and if it proves to work and be more efficient than the normal qBot, will replace it in the next alpha. Please report any error to the wildfire games forum ( http://www.wildfiregames.com/forum/index.php?act=idx ), thanks for playing the bot!
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/readme.txt
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/attackMoveToLocation.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/attackMoveToLocation.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/attackMoveToLocation.js (nonexistent)
@@ -1,238 +0,0 @@
-function AttackMoveToLocation(gameState, militaryManager, minAttackSize, maxAttackSize, targetFinder){
- this.minAttackSize = minAttackSize || Config.attack.minAttackSize;
- this.maxAttackSize = maxAttackSize || Config.attack.maxAttackSize;
- this.idList=[];
-
- this.previousTime = 0;
- this.state = "unexecuted";
-
- this.targetFinder = targetFinder || this.defaultTargetFinder;
-
- this.healthRecord = [];
-};
-
-// Returns true if the attack can be executed at the current time
-AttackMoveToLocation.prototype.canExecute = function(gameState, militaryManager){
- var enemyStrength = militaryManager.measureEnemyStrength(gameState);
- var enemyCount = militaryManager.measureEnemyCount(gameState);
-
- // We require our army to be >= this strength
- var targetStrength = enemyStrength * Config.attack.enemyRatio;
-
- var availableCount = militaryManager.countAvailableUnits();
- var availableStrength = militaryManager.measureAvailableStrength();
-
- debug("Troops needed for attack: " + this.minAttackSize + " Have: " + availableCount);
- debug("Troops strength for attack: " + targetStrength + " Have: " + availableStrength);
-
- return ((availableStrength >= targetStrength && availableCount >= this.minAttackSize)
- || availableCount >= this.maxAttackSize);
-};
-
-// Default target finder aims for conquest critical targets
-AttackMoveToLocation.prototype.defaultTargetFinder = function(gameState, militaryManager){
- // Find the critical enemy buildings we could attack
- var targets = militaryManager.getEnemyBuildings(gameState,"ConquestCritical");
- // If there are no critical structures, attack anything else that's critical
- if (targets.length == 0) {
- targets = gameState.entities.filter(function(ent) {
- return (gameState.isEntityEnemy(ent) && ent.hasClass("ConquestCritical") && ent.owner() !== 0 && ent.position());
- });
- }
- // If there's nothing, attack anything else that's less critical
- if (targets.length == 0) {
- targets = militaryManager.getEnemyBuildings(gameState,"Town");
- }
- if (targets.length == 0) {
- targets = militaryManager.getEnemyBuildings(gameState,"Village");
- }
- return targets;
-};
-
-// Executes the attack plan, after this is executed the update function will be run every turn
-AttackMoveToLocation.prototype.execute = function(gameState, militaryManager){
- var availableCount = militaryManager.countAvailableUnits();
- var numWanted = Math.min(availableCount, this.maxAttackSize);
- this.idList = militaryManager.getAvailableUnits(numWanted);
-
- var pending = EntityCollectionFromIds(gameState, this.idList);
-
- var targets = this.targetFinder(gameState, militaryManager);
-
- if (targets.length === 0){
- targets = this.defaultTargetFinder(gameState, militaryManager);
- }
-
- // If we have a target, move to it
- if (targets.length) {
- // Add an attack role so the economic manager doesn't try and use them
- pending.forEach(function(ent) {
- ent.setMetadata("role", "attack");
- });
-
- var curPos = pending.getCentrePosition();
-
- // pick a random target from the list
- var rand = Math.floor((Math.random()*targets.length));
- this.targetPos = undefined;
- var count = 0;
- while (!this.targetPos){
- var target = targets.toEntityArray()[rand];
- this.targetPos = target.position();
- count++;
- if (count > 1000){
- warn("No target with a valid position found");
- return;
- }
- }
-
- // Find possible distinct paths to the enemy
- var pathFinder = new PathFinder(gameState);
- var pathsToEnemy = pathFinder.getPaths(curPos, this.targetPos);
- if (!pathsToEnemy || !pathsToEnemy[0] || pathsToEnemy[0][0] === undefined || pathsToEnemy[0][1] === undefined){
- pathsToEnemy = [[this.targetPos]];
- }
-
- var rand = Math.floor(Math.random() * pathsToEnemy.length);
- this.path = pathsToEnemy[rand];
-
- pending.move(this.path[0][0], this.path[0][1]);
- } else if (targets.length == 0 ) {
- gameState.ai.gameFinished = true;
- return;
- }
-
- this.state = "walking";
-};
-
-// Runs every turn after the attack is executed
-// This removes idle units from the attack
-AttackMoveToLocation.prototype.update = function(gameState, militaryManager, events){
-
- if (!this.targetPos){
- for (var idKey in this.idList){
- var id = this.idList[idKey];
- militaryManager.unassignUnit(id);
- }
- this.idList = [];
- }
-
- // keep the list of units in good order by pruning ids with no corresponding entities (i.e. dead units)
- var removeList = [];
- var totalHealth = 0;
- for (var idKey in this.idList){
- var id = this.idList[idKey];
- var ent = militaryManager.entity(id);
- if (ent === undefined){
- removeList.push(id);
- }else{
- if (ent.hitpoints()){
- totalHealth += ent.hitpoints();
- }
- }
- }
- for (var i in removeList){
- this.idList.splice(this.idList.indexOf(removeList[i]),1);
- }
-
- var units = EntityCollectionFromIds(gameState, this.idList);
-
- if (!this.path || this.path.length === 0){
- var idleCount = 0;
- var self = this;
- units.forEach(function(ent){
- if (ent.isIdle()){
- if (ent.position() && VectorDistance(ent.position(), self.targetPos) > 30){
- ent.move(self.targetPos[0], self.targetPos[1]);
- }else{
- militaryManager.unassignUnit(ent.id());
- }
- }
- });
- return;
- }
-
- var deltaHealth = 0;
- var deltaTime = 1;
- var time = gameState.getTimeElapsed();
- this.healthRecord.push([totalHealth, time]);
- if (this.healthRecord.length > 1){
- for (var i = this.healthRecord.length - 1; i >= 0; i--){
- deltaHealth = totalHealth - this.healthRecord[i][0];
- deltaTime = time - this.healthRecord[i][1];
- if (this.healthRecord[i][1] < time - 5*1000){
- break;
- }
- }
- }
-
- var numUnits = this.idList.length;
- if (numUnits < 1) return;
- var damageRate = -deltaHealth / deltaTime * 1000;
- var centrePos = units.getCentrePosition();
- if (! centrePos) return;
-
- var idleCount = 0;
- // Looks for idle units away from the formations centre
- for (var idKey in this.idList){
- var id = this.idList[idKey];
- var ent = militaryManager.entity(id);
- if (ent.isIdle()){
- if (ent.position() && VectorDistance(ent.position(), centrePos) > 20){
- var dist = VectorDistance(ent.position(), centrePos);
- var vector = [centrePos[0] - ent.position()[0], centrePos[1] - ent.position()[1]];
- vector[0] *= 10/dist;
- vector[1] *= 10/dist;
- ent.move(centrePos[0] + vector[0], centrePos[1] + vector[1]);
- }else{
- idleCount++;
- }
- }
- }
-
- if ((damageRate / Math.sqrt(numUnits)) > 2){
- if (this.state === "walking"){
- var sumAttackerPos = [0,0];
- var numAttackers = 0;
-
- for (var key in events){
- var e = events[key];
- //{type:"Attacked", msg:{attacker:736, target:1133, type:"Melee"}}
- if (e.type === "Attacked" && e.msg){
- if (this.idList.indexOf(e.msg.target) !== -1){
- var attacker = militaryManager.entity(e.msg.attacker);
- if (attacker && attacker.position()){
- sumAttackerPos[0] += attacker.position()[0];
- sumAttackerPos[1] += attacker.position()[1];
- numAttackers += 1;
- }
- }
- }
- }
- if (numAttackers > 0){
- var avgAttackerPos = [sumAttackerPos[0]/numAttackers, sumAttackerPos[1]/numAttackers];
- // Stop moving
- units.move(centrePos[0], centrePos[1]);
- this.state = "attacking";
- }
- }
- }else{
- if (this.state === "attacking"){
- units.move(this.path[0][0], this.path[0][1]);
- this.state = "walking";
- }
- }
-
- if (this.state === "walking"){
- if (VectorDistance(centrePos, this.path[0]) < 20 || idleCount/numUnits > 0.8){
- this.path.shift();
- if (this.path.length > 0){
- units.move(this.path[0][0], this.path[0][1]);
- }
- }
- }
-
- this.previousTime = time;
- this.previousHealth = totalHealth;
-};
-
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/attackMoveToLocation.js
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/resources.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/resources.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/resources.js (nonexistent)
@@ -1,66 +0,0 @@
-function Resources(amounts, population) {
- if (amounts === undefined) {
- amounts = {
- food : 0,
- wood : 0,
- stone : 0,
- metal : 0
- };
- }
- for ( var tKey in this.types) {
- var t = this.types[tKey];
- this[t] = amounts[t] || 0;
- }
-
- if (population > 0) {
- this.population = parseInt(population);
- } else {
- this.population = 0;
- }
-}
-
-Resources.prototype.types = [ "food", "wood", "stone", "metal" ];
-
-Resources.prototype.canAfford = function(that) {
- for ( var tKey in this.types) {
- var t = this.types[tKey];
- if (this[t] < that[t]) {
- return false;
- }
- }
- return true;
-};
-
-Resources.prototype.add = function(that) {
- for ( var tKey in this.types) {
- var t = this.types[tKey];
- this[t] += that[t];
- }
- this.population += that.population;
-};
-
-Resources.prototype.subtract = function(that) {
- for ( var tKey in this.types) {
- var t = this.types[tKey];
- this[t] -= that[t];
- }
- this.population += that.population;
-};
-
-Resources.prototype.multiply = function(n) {
- for ( var tKey in this.types) {
- var t = this.types[tKey];
- this[t] *= n;
- }
- this.population *= n;
-};
-
-Resources.prototype.toInt = function() {
- var sum = 0;
- for ( var tKey in this.types) {
- var t = this.types[tKey];
- sum += this[t];
- }
- sum += this.population * 50; // based on typical unit costs
- return sum;
-};
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/resources.js
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/gamestate.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/gamestate.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/gamestate.js (nonexistent)
@@ -1,376 +0,0 @@
-/**
- * Provides an API for the rest of the AI scripts to query the world state at a
- * higher level than the raw data.
- */
-var GameState = function(ai) {
- MemoizeInit(this);
-
- this.ai = ai;
- this.timeElapsed = ai.timeElapsed;
- this.templates = ai.templates;
- this.entities = ai.entities;
- this.player = ai.player;
- this.playerData = ai.playerData;
- this.buildingsBuilt = 0;
-
- if (!this.ai._gameStateStore){
- this.ai._gameStateStore = {};
- }
- this.store = this.ai._gameStateStore;
-
- this.cellSize = 4; // Size of each map tile
-
- this.turnCache = {};
-};
-
-GameState.prototype.updatingCollection = function(id, filter, collection){
- if (!this.store[id]){
- this.store[id] = collection.filter(filter);
- this.store[id].registerUpdates();
- }
-
- return this.store[id];
-};
-
-GameState.prototype.getTimeElapsed = function() {
- return this.timeElapsed;
-};
-
-GameState.prototype.getTemplate = function(type) {
- if (!this.templates[type]){
- return null;
- }
-
- return new EntityTemplate(this.templates[type]);
-};
-
-GameState.prototype.applyCiv = function(str) {
- return str.replace(/\{civ\}/g, this.playerData.civ);
-};
-
-/**
- * @returns {Resources}
- */
-GameState.prototype.getResources = function() {
- return new Resources(this.playerData.resourceCounts);
-};
-
-GameState.prototype.getMap = function() {
- return this.ai.passabilityMap;
-};
-
-GameState.prototype.getTerritoryMap = function() {
- return Map.createTerritoryMap(this);
-};
-
-GameState.prototype.getPopulation = function() {
- return this.playerData.popCount;
-};
-
-GameState.prototype.getPopulationLimit = function() {
- return this.playerData.popLimit;
-};
-
-GameState.prototype.getPopulationMax = function() {
- return this.playerData.popMax;
-};
-
-GameState.prototype.getPassabilityClassMask = function(name) {
- if (!(name in this.ai.passabilityClasses)){
- error("Tried to use invalid passability class name '" + name + "'");
- }
- return this.ai.passabilityClasses[name];
-};
-
-GameState.prototype.getPlayerID = function() {
- return this.player;
-};
-
-GameState.prototype.isPlayerAlly = function(id) {
- return this.playerData.isAlly[id];
-};
-
-GameState.prototype.isPlayerEnemy = function(id) {
- return this.playerData.isEnemy[id];
-};
-
-GameState.prototype.getEnemies = function(){
- var ret = [];
- for (var i in this.playerData.isEnemy){
- if (this.playerData.isEnemy[i]){
- ret.push(i);
- }
- }
- return ret;
-};
-
-GameState.prototype.isEntityAlly = function(ent) {
- if (ent && ent.owner && (typeof ent.owner) === "function"){
- return this.playerData.isAlly[ent.owner()];
- } else if (ent && ent.owner){
- return this.playerData.isAlly[ent.owner];
- }
- return false;
-};
-
-GameState.prototype.isEntityEnemy = function(ent) {
- if (ent && ent.owner && (typeof ent.owner) === "function"){
- return this.playerData.isEnemy[ent.owner()];
- } else if (ent && ent.owner){
- return this.playerData.isEnemy[ent.owner];
- }
- return false;
-};
-
-GameState.prototype.isEntityOwn = function(ent) {
- if (ent && ent.owner && (typeof ent.owner) === "function"){
- return ent.owner() == this.player;
- } else if (ent && ent.owner){
- return ent.owner == this.player;
- }
- return false;
-};
-
-GameState.prototype.getOwnEntities = function() {
- if (!this.store.ownEntities){
- this.store.ownEntities = this.getEntities().filter(Filters.byOwner(this.player));
- this.store.ownEntities.registerUpdates();
- }
-
- return this.store.ownEntities;
-};
-
-GameState.prototype.getEnemyEntities = function() {
- var diplomacyChange = false;
- var enemies = this.getEnemies();
- 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 (diplomacyChange || !this.store.enemyEntities){
- var filter = Filters.byOwners(enemies);
- this.store.enemyEntities = this.getEntities().filter(filter);
- this.store.enemyEntities.registerUpdates();
- this.store.enemies = enemies;
- }
-
- return this.store.enemyEntities;
-};
-
-GameState.prototype.getEntities = function() {
- return this.entities;
-};
-
-GameState.prototype.getEntityById = function(id){
- if (this.entities._entities[id]) {
- return this.entities._entities[id];
- }else{
- //debug("Entity " + id + " requested does not exist");
- }
- return undefined;
-};
-
-GameState.prototype.getOwnEntitiesByMetadata = function(key, value){
- if (!this.store[key + "-" + value]){
- var filter = Filters.byMetadata(key, value);
- this.store[key + "-" + value] = this.getOwnEntities().filter(filter);
- this.store[key + "-" + value].registerUpdates();
- }
-
- return this.store[key + "-" + value];
-};
-
-GameState.prototype.getOwnEntitiesByRole = function(role){
- return this.getOwnEntitiesByMetadata("role", role);
-};
-
-// TODO: fix this so it picks up not in use training stuff
-GameState.prototype.getOwnTrainingFacilities = function(){
- return this.updatingCollection("own-training-facilities", Filters.byTrainingQueue(), this.getOwnEntities());
-};
-
-GameState.prototype.getOwnEntitiesByType = function(type){
- var filter = Filters.byType(type);
- return this.updatingCollection("own-by-type-" + type, filter, this.getOwnEntities());
-};
-
-GameState.prototype.countEntitiesByType = function(type) {
- return this.getOwnEntitiesByType(type).length;
-};
-
-GameState.prototype.countEntitiesAndQueuedByType = function(type) {
- var count = this.countEntitiesByType(type);
-
- // Count building foundations
- count += this.countEntitiesByType("foundation|" + type);
-
- // Count animal resources
- count += this.countEntitiesByType("resource|" + type);
-
- // Count entities in building production queues
- this.getOwnTrainingFacilities().forEach(function(ent){
- ent.trainingQueue().forEach(function(item) {
- if (item.template == type){
- count += item.count;
- }
- });
- });
-
- return count;
-};
-
-GameState.prototype.countFoundationsWithType = function(type) {
- var foundationType = "foundation|" + type;
- var count = 0;
- this.getOwnEntities().forEach(function(ent) {
- var t = ent.templateName();
- if (t == foundationType)
- ++count;
- });
- return count;
-};
-
-GameState.prototype.countOwnEntitiesByRole = function(role) {
- return this.getOwnEntitiesByRole(role).length;
-};
-
-GameState.prototype.countOwnEntitiesAndQueuedWithRole = function(role) {
- var count = this.countOwnEntitiesByRole(role);
-
- // Count entities in building production queues
- this.getOwnTrainingFacilities().forEach(function(ent) {
- ent.trainingQueue().forEach(function(item) {
- if (item.metadata && item.metadata.role == role)
- count += item.count;
- });
- });
- return count;
-};
-
-GameState.prototype.countOwnQueuedEntitiesWithMetadata = function(data, value) {
- // Count entities in building production queues
- var count = 0;
- this.getOwnTrainingFacilities().forEach(function(ent) {
- ent.trainingQueue().forEach(function(item) {
- if (item.metadata && item.metadata[data] && item.metadata[data] == value)
- count += item.count;
- });
- });
- return count;
-};
-
-/**
- * Find buildings that are capable of training the given unit type, and aren't
- * already too busy.
- */
-GameState.prototype.findTrainers = function(template) {
- var maxQueueLength = 2; // avoid tying up resources in giant training queues
-
- return this.getOwnTrainingFacilities().filter(function(ent) {
-
- var trainable = ent.trainableEntities();
- if (!trainable || trainable.indexOf(template) == -1)
- return false;
-
- var queue = ent.trainingQueue();
- if (queue) {
- if (queue.length >= maxQueueLength)
- return false;
- }
-
- return true;
- });
-};
-
-/**
- * Find units that are capable of constructing the given building type.
- */
-GameState.prototype.findBuilders = function(template) {
- return this.getOwnEntities().filter(function(ent) {
-
- var buildable = ent.buildableEntities();
- if (!buildable || buildable.indexOf(template) == -1)
- return false;
-
- return true;
- });
-};
-
-GameState.prototype.getOwnFoundations = function() {
- return this.updatingCollection("ownFoundations", Filters.isFoundation(), this.getOwnEntities());
-};
-
-GameState.prototype.getOwnDropsites = function(resource){
- return this.updatingCollection("dropsite-own-" + resource, Filters.isDropsite(resource), this.getOwnEntities());
-};
-
-GameState.prototype.getResourceSupplies = function(resource){
- return this.updatingCollection("resource-" + resource, Filters.byResource(resource), this.getEntities());
-};
-
-GameState.prototype.getEntityLimits = function() {
- return this.playerData.entityLimits;
-};
-
-GameState.prototype.getEntityCounts = function() {
- return this.playerData.entityCounts;
-};
-
-// Checks whether the maximum number of buildings have been constructed for a certain catergory
-GameState.prototype.isEntityLimitReached = function(category) {
- if(this.playerData.entityLimits[category] === undefined || this.playerData.entityCounts[category] === undefined)
- return false;
- if(this.playerData.entityLimits[category].LimitsPerCivCentre != undefined)
- return (this.playerData.entityCounts[category] >=
- this.playerData.entityCounts["CivilCentre"] * this.playerData.entityLimits[category].LimitPerCivCentre);
- else
- return (this.playerData.entityCounts[category] >= this.playerData.entityLimits[category]);
-};
-
-GameState.prototype.findTrainableUnits = function(classes){
- var allTrainable = [];
- this.getOwnEntities().forEach(function(ent) {
- var trainable = ent.trainableEntities();
- for (var i in trainable){
- if (allTrainable.indexOf(trainable[i]) === -1){
- allTrainable.push(trainable[i]);
- }
- }
- });
- var ret = [];
- for (var i in allTrainable) {
- var template = this.getTemplate(allTrainable[i]);
- var okay = true;
-
- for (o in classes)
- if (!template.hasClass(classes[o]))
- okay = false;
-
- if (template.hasClass("Hero")) // disabling heroes for now
- okay = false;
-
- if (okay)
- ret.push( [allTrainable[i], template] );
- }
- return ret;
-};
-// defcon utilities
-GameState.prototype.timeSinceDefconChange = function() {
- return this.getTimeElapsed()-this.ai.defconChangeTime;
-};
-GameState.prototype.setDefcon = function(level,force) {
- if (this.ai.defcon >= level || force) {
- this.ai.defcon = level;
- this.ai.defconChangeTime = this.getTimeElapsed();
- }
-};
-GameState.prototype.defcon = function() {
- return this.ai.defcon;
-};
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/gamestate.js
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/housing.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/housing.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/housing.js (nonexistent)
@@ -1,31 +0,0 @@
-// Decides when to a new house needs to be built
-var HousingManager = function() {
-
-};
-
-HousingManager.prototype.buildMoreHouses = function(gameState, queues) {
- if (gameState.getTimeElapsed() < 25000)
- return;
- // temporary 'remaining population space' based check, need to do
- // predictive in future
- if (gameState.getPopulationLimit() - gameState.getPopulation() < 20
- && gameState.getPopulationLimit() < gameState.getPopulationMax()) {
- var numConstructing = gameState.countEntitiesByType(gameState.applyCiv("foundation|structures/{civ}_house"));
- var numPlanned = queues.house.totalLength();
-
- var additional = Math.ceil((20 - (gameState.getPopulationLimit() - gameState.getPopulation())) / 10)
- - numConstructing - numPlanned;
-
- for ( var i = 0; i < additional; i++) {
- queues.house.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_house"));
- }
- }
-};
-
-HousingManager.prototype.update = function(gameState, queues) {
- Engine.ProfileStart("housing update");
-
- this.buildMoreHouses(gameState, queues);
-
- Engine.ProfileStop();
-};
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/housing.js
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/TechnologyTemplateManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/TechnologyTemplateManager.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/TechnologyTemplateManager.js (nonexistent)
@@ -1 +0,0 @@
-Engine.RegisterInterface("TechnologyTemplateManager");
\ No newline at end of file
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/base.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/base.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/base.js (revision 13225)
@@ -0,0 +1,296 @@
+var PlayerID = -1;
+
+function BaseAI(settings)
+{
+
+ if (!settings)
+ return;
+
+ // Make some properties non-enumerable, so they won't be serialised
+ Object.defineProperty(this, "_player", {value: settings.player, enumerable: false});
+ PlayerID = this._player;
+ /*
+ Object.defineProperty(this, "_templates", {value: settings.templates, enumerable: false});
+ Object.defineProperty(this, "_derivedTemplates", {value: {}, enumerable: false});
+
+ this._entityMetadata = {};
+
+ this._entityCollections = [];
+ this._entityCollectionsByDynProp = {};
+ this._entityCollectionsUID = 0;
+ */
+
+ this.turn = 0;
+}
+
+//Return a simple object (using no classes etc) that will be serialized
+//into saved games
+BaseAI.prototype.Serialize = function()
+{
+ return {};
+ // TODO: ought to get the AI script subclass to serialize its own state
+};
+
+//Called after the constructor when loading a saved game, with 'data' being
+//whatever Serialize() returned
+BaseAI.prototype.Deserialize = function(data)
+{
+ /*
+ var rawEntities = data._rawEntities;
+ this._entityMetadata = data._entityMetadata;
+ this._entities = {}
+
+ for (var id in rawEntities)
+ {
+ this._entities[id] = new Entity(this, rawEntities[id]);
+ }
+*/
+ // TODO: ought to get the AI script subclass to deserialize its own state
+};
+
+/*BaseAI.prototype.GetTemplate = function(name)
+{
+ if (this._templates[name])
+ return this._templates[name];
+
+ if (this._derivedTemplates[name])
+ return this._derivedTemplates[name];
+
+ // If this is a foundation template, construct it automatically
+ if (name.substr(0, 11) === "foundation|")
+ {
+ var base = this.GetTemplate(name.substr(11));
+
+ var foundation = {};
+ for (var key in base)
+ if (!g_FoundationForbiddenComponents[key])
+ foundation[key] = base[key];
+
+ this._derivedTemplates[name] = foundation;
+ return foundation;
+ }
+
+ error("Tried to retrieve invalid template '"+name+"'");
+ return null;
+};
+*/
+BaseAI.prototype.InitWithSharedScript = function(state, sharedAI)
+{
+ this.accessibility = sharedAI.accessibility;
+ this.terrainAnalyzer = sharedAI.terrainAnalyzer;
+ this.passabilityClasses = sharedAI.passabilityClasses;
+ this.passabilityMap = sharedAI.passabilityMap;
+
+ var gameState = sharedAI.gameState[PlayerID];
+ gameState.ai = this;
+
+ this.InitShared(gameState, sharedAI);
+
+ delete gameState.ai;
+}
+
+BaseAI.prototype.HandleMessage = function(state, sharedAI)
+{
+ /*
+ if (!this._entities)
+ {
+ // Do a (shallow) clone of all the initial entity properties (in order
+ // to copy into our own script context and minimise cross-context
+ // weirdness)
+ this._entities = {};
+ for (var id in state.entities)
+ {
+ this._entities[id] = new Entity(this, state.entities[id]);
+ }
+ }
+ else
+ {
+ this.ApplyEntitiesDelta(state);
+ }
+*/
+ Engine.ProfileStart("HandleMessage setup");
+
+ this.entities = sharedAI.entities;
+ this.events = sharedAI.events;
+ this.passabilityClasses = sharedAI.passabilityClasses;
+ this.passabilityMap = sharedAI.passabilityMap;
+ this.player = this._player;
+ this.playerData = sharedAI.playersData[this._player];
+ this.templates = sharedAI.templates;
+ this.territoryMap = sharedAI.territoryMap;
+ this.timeElapsed = sharedAI.timeElapsed;
+ this.accessibility = sharedAI.accessibility;
+ this.terrainAnalyzer = sharedAI.terrainAnalyzer;
+ this.techModifications = sharedAI.techModifications[this._player];
+
+ Engine.ProfileStop();
+
+ this.OnUpdate(sharedAI);
+
+ // Clean up temporary properties, so they don't disturb the serializer
+ delete this.entities;
+ delete this.events;
+ delete this.passabilityClasses;
+ delete this.passabilityMap;
+ delete this.player;
+ delete this.playerData;
+ delete this.templates;
+ delete this.territoryMap;
+ delete this.timeElapsed;
+};
+
+BaseAI.prototype.ApplyEntitiesDelta = function(state)
+{
+ Engine.ProfileStart("ApplyEntitiesDelta");
+
+ for each (var evt in state.events)
+ {
+ if (evt.type == "Create")
+ {
+ if (! state.entities[evt.msg.entity])
+ {
+ continue; // Sometimes there are things like foundations which get destroyed too fast
+ }
+
+ this._entities[evt.msg.entity] = new Entity(this, state.entities[evt.msg.entity]);
+
+ // Update all the entity collections since the create operation affects static properties as well as dynamic
+ for each (var entCollection in this._entityCollections)
+ {
+ entCollection.updateEnt(this._entities[evt.msg.entity]);
+ }
+
+ }
+ else if (evt.type == "Destroy")
+ {
+ if (!this._entities[evt.msg.entity])
+ {
+ continue;
+ }
+ // The entity was destroyed but its data may still be useful, so
+ // remember the entity and this AI's metadata concerning it
+ evt.msg.metadata = (evt.msg.metadata || []);
+ evt.msg.entityObj = (evt.msg.entityObj || this._entities[evt.msg.entity]);
+ evt.msg.metadata[this._player] = this._entityMetadata[evt.msg.entity];
+
+ for each (var entCol in this._entityCollections)
+ {
+ entCol.removeEnt(this._entities[evt.msg.entity]);
+ }
+
+ delete this._entities[evt.msg.entity];
+ delete this._entityMetadata[evt.msg.entity];
+ }
+ else if (evt.type == "TrainingFinished")
+ {
+ // Apply metadata stored in training queues, but only if they
+ // look like they were added by us
+ if (evt.msg.owner === this._player)
+ {
+ for each (var ent in evt.msg.entities)
+ {
+ for (key in evt.msg.metadata)
+ {
+ this.setMetadata(this._entities[ent], key, evt.msg.metadata[key])
+ }
+ }
+ }
+ }
+ }
+
+ for (var id in state.entities)
+ {
+ var changes = state.entities[id];
+
+ for (var prop in changes)
+ {
+ if (prop == "position" || prop == "resourceSupplyAmount") {
+ if (this.turn % 10 === 0) {
+ this._entities[id]._entity[prop] = changes[prop];
+ this.updateEntityCollections(prop, this._entities[id]);
+ }
+ } else {
+ this._entities[id]._entity[prop] = changes[prop];
+ this.updateEntityCollections(prop, this._entities[id]);
+ }
+ }
+ }
+ Engine.ProfileStop();
+};
+
+BaseAI.prototype.OnUpdate = function()
+{ // AIs override this function
+ // They should do at least this.turn++;
+};
+
+BaseAI.prototype.chat = function(message)
+{
+ Engine.PostCommand({"type": "chat", "message": message});
+};
+
+BaseAI.prototype.registerUpdatingEntityCollection = function(entCollection)
+{
+ entCollection.setUID(this._entityCollectionsUID);
+ this._entityCollections.push(entCollection);
+
+ for each (var prop in entCollection.dynamicProperties())
+ {
+ this._entityCollectionsByDynProp[prop] = this._entityCollectionsByDynProp[prop] || [];
+ this._entityCollectionsByDynProp[prop].push(entCollection);
+ }
+
+ this._entityCollectionsUID++;
+};
+
+BaseAI.prototype.removeUpdatingEntityCollection = function(entCollection)
+{
+ for (var i in this._entityCollections)
+ {
+ if (this._entityCollections[i].getUID() === entCollection.getUID())
+ {
+ this._entityCollections.splice(i, 1);
+ }
+ }
+
+ for each (var prop in entCollection.dynamicProperties())
+ {
+ for (var i in this._entityCollectionsByDynProp[prop])
+ {
+ if (this._entityCollectionsByDynProp[prop][i].getUID() === entCollection.getUID())
+ {
+ this._entityCollectionsByDynProp[prop].splice(i, 1);
+ }
+ }
+ }
+};
+
+BaseAI.prototype.updateEntityCollections = function(property, ent)
+{
+ if (this._entityCollectionsByDynProp[property] !== undefined)
+ {
+ for each (var entCollection in this._entityCollectionsByDynProp[property])
+ {
+ entCollection.updateEnt(ent);
+ }
+ }
+}
+
+BaseAI.prototype.setMetadata = function(ent, key, value)
+{
+ var metadata = this._entityMetadata[ent.id()];
+ if (!metadata)
+ metadata = this._entityMetadata[ent.id()] = {};
+ metadata[key] = value;
+
+ this.updateEntityCollections('metadata', ent);
+ this.updateEntityCollections('metadata.' + key, ent);
+}
+
+BaseAI.prototype.getMetadata = function(ent, key)
+{
+ var metadata = this._entityMetadata[ent.id()];
+
+ if (!metadata || !(key in metadata))
+ return undefined;
+ return metadata[key];
+}
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/base.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/class.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/class.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/class.js (revision 13225)
@@ -0,0 +1,36 @@
+/**
+ * Provides a nicer syntax for defining classes,
+ * with support for OO-style inheritance.
+ */
+function Class(data)
+{
+ var ctor;
+ if (data._init)
+ ctor = data._init;
+ else
+ ctor = function() { };
+
+ if (data._super)
+ {
+ ctor.prototype = { "__proto__": data._super.prototype };
+ }
+
+ for (var key in data)
+ {
+ ctor.prototype[key] = data[key];
+ }
+
+ return ctor;
+}
+
+/* Test inheritance:
+var A = Class({foo:1, bar:10});
+print((new A).foo+" "+(new A).bar+"\n");
+var B = Class({foo:2, bar:20});
+print((new A).foo+" "+(new A).bar+"\n");
+print((new B).foo+" "+(new B).bar+"\n");
+var C = Class({_super:A, foo:3});
+print((new A).foo+" "+(new A).bar+"\n");
+print((new B).foo+" "+(new B).bar+"\n");
+print((new C).foo+" "+(new C).bar+"\n");
+//*/
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/class.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js (revision 13225)
@@ -0,0 +1,653 @@
+var EntityTemplate = Class({
+
+ // techModifications should be the tech modifications of only one player.
+ // gamestates handle "GetTemplate" and should push the player's
+ // while entities should push the owner's
+ _init: function(template, techModifications)
+ {
+ this._techModifications = techModifications;
+ this._template = template;
+ },
+
+ genericName: function() {
+ if (!this._template.Identity || !this._template.Identity.GenericName)
+ return undefined;
+ return this._template.Identity.GenericName;
+ },
+
+ rank: function() {
+ if (!this._template.Identity)
+ return undefined;
+ return this._template.Identity.Rank;
+ },
+
+ classes: function() {
+ if (!this._template.Identity || !this._template.Identity.Classes)
+ return undefined;
+ return this._template.Identity.Classes._string.split(/\s+/);
+ },
+
+ requiredTech: function() {
+ if (!this._template.Identity || !this._template.Identity.RequiredTechnology)
+ return undefined;
+ return this._template.Identity.RequiredTechnology;
+ },
+
+ phase: function() {
+ if (!this._template.Identity || !this._template.Identity.RequiredTechnology)
+ return 0;
+ if (this.template.Identity.RequiredTechnology == "phase_village")
+ return 1;
+ if (this.template.Identity.RequiredTechnology == "phase_town")
+ return 2;
+ if (this.template.Identity.RequiredTechnology == "phase_city")
+ return 3;
+ return 0;
+ },
+
+ hasClass: function(name) {
+ var classes = this.classes();
+ return (classes && classes.indexOf(name) != -1);
+ },
+
+ hasClasses: function(array) {
+ var classes = this.classes();
+ if (!classes)
+ return false;
+
+ for (i in array)
+ if (classes.indexOf(array[i]) === -1)
+ return false;
+ return true;
+ },
+
+ civ: function() {
+ if (!this._template.Identity)
+ return undefined;
+ return this._template.Identity.Civ;
+ },
+
+ cost: function() {
+ if (!this._template.Cost)
+ return undefined;
+
+ var ret = {};
+ for (var type in this._template.Cost.Resources)
+ ret[type] = GetTechModifiedProperty(this._techModifications, this._template, "Cost/Resources/"+type, +this._template.Cost.Resources[type]);
+ return ret;
+ },
+
+ costSum: function() {
+ if (!this._template.Cost)
+ return undefined;
+
+ var ret = 0;
+ for (var type in this._template.Cost.Resources)
+ ret += +this._template.Cost.Resources[type];
+ return ret;
+ },
+
+ /**
+ * Returns the radius of a circle surrounding this entity's
+ * obstruction shape, or undefined if no obstruction.
+ */
+ obstructionRadius: function() {
+ if (!this._template.Obstruction)
+ return undefined;
+
+ if (this._template.Obstruction.Static)
+ {
+ var w = +this._template.Obstruction.Static["@width"];
+ var h = +this._template.Obstruction.Static["@depth"];
+ return Math.sqrt(w*w + h*h) / 2;
+ }
+
+ if (this._template.Obstruction.Unit)
+ return +this._template.Obstruction.Unit["@radius"];
+
+ return 0; // this should never happen
+ },
+
+ /**
+ * Returns the radius of a circle surrounding this entity's
+ * footprint.
+ */
+ footprintRadius: function() {
+ if (!this._template.Footprint)
+ return undefined;
+
+ if (this._template.Footprint.Square)
+ {
+ var w = +this._template.Footprint.Square["@width"];
+ var h = +this._template.Footprint.Square["@depth"];
+ return Math.sqrt(w*w + h*h) / 2;
+ }
+
+ if (this._template.Footprint.Circle)
+ return +this._template.Footprint.Circle["@radius"];
+
+ return 0; // this should never happen
+ },
+
+ maxHitpoints: function()
+ {
+ if (this._template.Health !== undefined)
+ return this._template.Health.Max;
+ return 0;
+ },
+ isHealable: function()
+ {
+ if (this._template.Health !== undefined)
+ return this._template.Health.Unhealable !== "true";
+ return false;
+ },
+ isRepairable: function()
+ {
+ if (this._template.Health !== undefined)
+ return this._template.Health.Repairable === "true";
+ return false;
+ },
+
+ getPopulationBonus: function() {
+ if (!this._template.Cost || !this._template.Cost.PopulationBonus)
+ return undefined;
+ return this._template.Cost.PopulationBonus;
+ },
+
+ armourStrengths: function() {
+ if (!this._template.Armour)
+ return undefined;
+
+ return {
+ hack: GetTechModifiedProperty(this._techModifications, this._template, "Armour/Hack", +this._template.Armour.Hack),
+ pierce: GetTechModifiedProperty(this._techModifications, this._template, "Armour/Pierce", +this._template.Armour.Pierce),
+ crush: GetTechModifiedProperty(this._techModifications, this._template, "Armour/Crush", +this._template.Armour.Crush)
+ };
+ },
+
+ attackTypes: function() {
+ if (!this._template.Attack)
+ return undefined;
+
+ var ret = [];
+ for (var type in this._template.Attack)
+ ret.push(type);
+
+ return ret;
+ },
+
+ attackRange: function(type) {
+ if (!this._template.Attack || !this._template.Attack[type])
+ return undefined;
+
+ return {
+ max: GetTechModifiedProperty(this._techModifications, this._template, "Attack/MaxRange", +this._template.Attack[type].MaxRange),
+ min: GetTechModifiedProperty(this._techModifications, this._template, "Attack/MinRange", +(this._template.Attack[type].MinRange || 0))
+ };
+ },
+
+ attackStrengths: function(type) {
+ if (!this._template.Attack || !this._template.Attack[type])
+ return undefined;
+
+ return {
+ hack: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/Hack", +(this._template.Attack[type].Hack || 0)),
+ pierce: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/Pierce", +(this._template.Attack[type].Pierce || 0)),
+ crush: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/Crush", +(this._template.Attack[type].Crush || 0))
+ };
+ },
+
+ attackTimes: function(type) {
+ if (!this._template.Attack || !this._template.Attack[type])
+ return undefined;
+
+ return {
+ prepare: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/PrepareTime", +(this._template.Attack[type].PrepareTime || 0)),
+ repeat: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/RepeatTime", +(this._template.Attack[type].RepeatTime || 1000))
+ };
+ },
+
+ // returns the classes this templates counters:
+ // Return type is [ [-neededClasses-] , multiplier ].
+ getCounteredClasses: function() {
+ if (!this._template.Attack)
+ return undefined;
+
+ var Classes = [];
+ for (i in this._template.Attack) {
+ if (!this._template.Attack[i].Bonuses)
+ continue;
+ for (o in this._template.Attack[i].Bonuses) {
+ if (this._template.Attack[i].Bonuses[o].Classes == undefined)
+ continue;
+ Classes.push([this._template.Attack[i].Bonuses[o].Classes.split(" "), +this._template.Attack[i].Bonuses[o].Multiplier]);
+ }
+ }
+ return Classes;
+ },
+
+ // returns true if the entity counters those classes.
+ // TODO: refine using the multiplier
+ countersClasses: function(classes) {
+ if (!this._template.Attack)
+ return false;
+ var mcounter = [];
+ for (i in this._template.Attack) {
+ if (!this._template.Attack[i].Bonuses)
+ continue;
+ for (o in this._template.Attack[i].Bonuses) {
+ if (this._template.Attack[i].Bonuses[o].Classes == undefined)
+ continue;
+ mcounter.concat(this._template.Attack[i].Bonuses[o].Classes.split(" "));
+ }
+ }
+ for (i in classes)
+ {
+ if (mcounter.indexOf(classes[i]) !== -1)
+ return true;
+ }
+ return false;
+ },
+
+ // returns, if it exists, the multiplier from each attack against a given class
+ getMultiplierAgainst: function(type, againstClass) {
+ if (!this._template.Attack || !this._template.Attack[type])
+ return undefined;
+
+ if (this._template.Attack[type].Bonuses)
+ for (o in this._template.Attack[type].Bonuses)
+ if (this._template.Attack[type].Bonuses[o].Classes !== undefined)
+ {
+ var total = this._template.Attack[type].Bonuses[o].Classes.split(" ");
+ for (j in total)
+ if (total[j] === againstClass)
+ return this._template.Attack[type].Bonuses[o].Multiplier;
+ }
+ return 1;
+ },
+
+ buildableEntities: function() {
+ if (!this._template.Builder || !this._template.Builder.Entities._string)
+ return undefined;
+ var civ = this.civ();
+ var templates = this._template.Builder.Entities._string.replace(/\{civ\}/g, civ).split(/\s+/);
+ return templates; // TODO: map to Entity?
+ },
+
+ trainableEntities: function() {
+ if (!this._template.ProductionQueue || !this._template.ProductionQueue.Entities)
+ return undefined;
+ var civ = this.civ();
+ var templates = this._template.ProductionQueue.Entities._string.replace(/\{civ\}/g, civ).split(/\s+/);
+ return templates;
+ },
+
+ researchableTechs: function() {
+ if (!this._template.ProductionQueue || !this._template.ProductionQueue.Technologies)
+ return undefined;
+ var templates = this._template.ProductionQueue.Technologies._string.split(/\s+/);
+ return templates;
+ },
+
+ resourceSupplyType: function() {
+ if (!this._template.ResourceSupply)
+ return undefined;
+ var [type, subtype] = this._template.ResourceSupply.Type.split('.');
+ return { "generic": type, "specific": subtype };
+ },
+ // will return either "food", "wood", "stone", "metal" and not treasure.
+ getResourceType: function() {
+ if (!this._template.ResourceSupply)
+ return undefined;
+ var [type, subtype] = this._template.ResourceSupply.Type.split('.');
+ if (type == "treasure")
+ return subtype;
+ return type;
+ },
+
+ resourceSupplyMax: function() {
+ if (!this._template.ResourceSupply)
+ return undefined;
+ return +this._template.ResourceSupply.Amount;
+ },
+
+ resourceGatherRates: function() {
+ if (!this._template.ResourceGatherer)
+ return undefined;
+ var ret = {};
+ var baseSpeed = GetTechModifiedProperty(this._techModifications, this._template, "ResourceGatherer/BaseSpeed", +this._template.ResourceGatherer.BaseSpeed);
+ for (var r in this._template.ResourceGatherer.Rates)
+ ret[r] = GetTechModifiedProperty(this._techModifications, this._template, "ResourceGatherer/Rates/"+r, +this._template.ResourceGatherer.Rates[r]) * baseSpeed;
+ return ret;
+ },
+
+ resourceDropsiteTypes: function() {
+ if (!this._template.ResourceDropsite)
+ return undefined;
+ return this._template.ResourceDropsite.Types.split(/\s+/);
+ },
+
+
+ garrisonableClasses: function() {
+ if (!this._template.GarrisonHolder)
+ return undefined;
+ return this._template.GarrisonHolder.List._string.split(/\s+/);
+ },
+
+ garrisonMax: function() {
+ if (!this._template.GarrisonHolder)
+ return undefined;
+ return this._template.GarrisonHolder.Max;
+ },
+
+ /**
+ * Returns whether this is an animal that is too difficult to hunt.
+ * (Currently this just includes skittish animals, which are probably
+ * too fast to chase.)
+ */
+ isUnhuntable: function() {
+ if (!this._template.UnitAI || !this._template.UnitAI.NaturalBehaviour)
+ return false;
+
+ // return (this._template.UnitAI.NaturalBehaviour == "skittish");
+ // Actually, since the AI is currently rubbish at hunting, skip all animals
+ // that aren't really weak:
+ return this._template.Health.Max >= 10;
+ },
+
+ walkSpeed: function() {
+ if (!this._template.UnitMotion || !this._template.UnitMotion.WalkSpeed)
+ return undefined;
+ return this._template.UnitMotion.WalkSpeed;
+ },
+
+ buildCategory: function() {
+ if (!this._template.BuildRestrictions || !this._template.BuildRestrictions.Category)
+ return undefined;
+ return this._template.BuildRestrictions.Category;
+ },
+
+ buildTime: function() {
+ if (!this._template.Cost || !this._template.Cost.buildTime)
+ return undefined;
+ return this._template.Cost.buildTime;
+ },
+
+ buildDistance: function() {
+ if (!this._template.BuildRestrictions || !this._template.BuildRestrictions.Distance)
+ return undefined;
+ return this._template.BuildRestrictions.Distance;
+ },
+
+ buildPlacementType: function() {
+ if (!this._template.BuildRestrictions || !this._template.BuildRestrictions.PlacementType)
+ return undefined;
+ return this._template.BuildRestrictions.PlacementType;
+ },
+
+ buildTerritories: function() {
+ if (!this._template.BuildRestrictions || !this._template.BuildRestrictions.Territory)
+ return undefined;
+ return this._template.BuildRestrictions.Territory.split(/\s+/);
+ },
+
+ hasBuildTerritory: function(territory) {
+ var territories = this.buildTerritories();
+ return (territories && territories.indexOf(territory) != -1);
+ },
+
+ visionRange: function() {
+ if (!this._template.Vision)
+ return undefined;
+ return this._template.Vision.Range;
+ }
+});
+
+
+
+var Entity = Class({
+ _super: EntityTemplate,
+
+ _init: function(sharedAI, entity)
+ {
+ this._super.call(this, sharedAI.GetTemplate(entity.template), sharedAI.techModifications[entity.owner]);
+
+ this._ai = sharedAI;
+ this._templateName = entity.template;
+ this._entity = entity;
+ },
+
+ toString: function() {
+ return "[Entity " + this.id() + " " + this.templateName() + "]";
+ },
+
+ id: function() {
+ return this._entity.id;
+ },
+
+ templateName: function() {
+ return this._templateName;
+ },
+
+ /**
+ * Returns extra data that the AI scripts have associated with this entity,
+ * for arbitrary local annotations.
+ * (This data is not shared with any other AI scripts.)
+ */
+ getMetadata: function(player, key) {
+ return this._ai.getMetadata(player, this, key);
+ },
+
+ /**
+ * Sets extra data to be associated with this entity.
+ */
+ setMetadata: function(player, key, value) {
+ this._ai.setMetadata(player, this, key, value);
+ },
+
+ deleteMetadata: function(player) {
+ delete this._ai._entityMetadata[player][this.id()];
+ },
+
+ position: function() { return this._entity.position; },
+
+ isIdle: function() {
+ if (typeof this._entity.idle === "undefined")
+ return undefined;
+ return this._entity.idle;
+ },
+
+ unitAIState: function() { return this._entity.unitAIState; },
+ unitAIOrderData: function() { return this._entity.unitAIOrderData; },
+ hitpoints: function() { if (this._entity.hitpoints !== undefined) return this._entity.hitpoints; return undefined; },
+ isHurt: function() { return this.hitpoints() < this.maxHitpoints(); },
+ healthLevel: function() { return (this.hitpoints() / this.maxHitpoints()); },
+ needsHeal: function() { return this.isHurt() && this.isHealable(); },
+ needsRepair: function() { return this.isHurt() && this.isRepairable(); },
+
+ /**
+ * Returns the current training queue state, of the form
+ * [ { "id": 0, "template": "...", "count": 1, "progress": 0.5, "metadata": ... }, ... ]
+ */
+ trainingQueue: function() {
+ var queue = this._entity.trainingQueue;
+ return queue;
+ },
+
+ trainingQueueTime: function() {
+ var queue = this._entity.trainingQueue;
+ if (!queue)
+ return undefined;
+ // TODO: compute total time for units in training queue
+ return queue.length;
+ },
+
+ foundationProgress: function() {
+ if (typeof this._entity.foundationProgress === "undefined")
+ return undefined;
+ return this._entity.foundationProgress;
+ },
+
+ owner: function() {
+ return this._entity.owner;
+ },
+ isOwn: function(player) {
+ if (typeof(this._entity.owner) === "undefined")
+ return false;
+ return this._entity.owner === player;
+ },
+ isFriendly: function(player) {
+ return this.isOwn(player); // TODO: diplomacy
+ },
+ isEnemy: function(player) {
+ return !this.isOwn(player); // TODO: diplomacy
+ },
+
+ resourceSupplyAmount: function() {
+ if(this._entity.resourceSupplyAmount === undefined)
+ return undefined;
+ return this._entity.resourceSupplyAmount;
+ },
+
+ resourceCarrying: function() {
+ if(this._entity.resourceCarrying === undefined)
+ return undefined;
+ return this._entity.resourceCarrying;
+ },
+
+ garrisoned: function() { return new EntityCollection(this._ai, this._entity.garrisoned); },
+
+ // TODO: visibility
+
+ move: function(x, z, queued) {
+ queued = queued || false;
+ Engine.PostCommand({"type": "walk", "entities": [this.id()], "x": x, "z": z, "queued": queued });
+ return this;
+ },
+
+ // violent, aggressive, defensive, passive, standground
+ setStance: function(stance,queued){
+ Engine.PostCommand({"type": "stance", "entities": [this.id()], "name" : stance, "queued": queued });
+ return this;
+ },
+
+ // TODO: replace this with the proper "STOP" command
+ stopMoving: function() {
+ if (this.position() !== undefined)
+ Engine.PostCommand({"type": "walk", "entities": [this.id()], "x": this.position()[0], "z": this.position()[1], "queued": false});
+ },
+
+ unload: function(id) {
+ if (!this._template.GarrisonHolder)
+ return undefined;
+ Engine.PostCommand({"type": "unload", "garrisonHolder": this.id(), "entity": id});
+ return this;
+ },
+
+ unloadAll: function() {
+ if (!this._template.GarrisonHolder)
+ return undefined;
+ Engine.PostCommand({"type": "unload-all", "garrisonHolders": [this.id()]});
+ return this;
+ },
+
+ garrison: function(target) {
+ Engine.PostCommand({"type": "garrison", "entities": [this.id()], "target": target.id(),"queued": false});
+ return this;
+ },
+
+ attack: function(unitId) {
+ Engine.PostCommand({"type": "attack", "entities": [this.id()], "target": unitId, "queued": false});
+ return this;
+ },
+
+ // Flees from a unit in the opposite direction.
+ flee: function(unitToFleeFrom) {
+ if (this.position() !== undefined && unitToFleeFrom.position() !== undefined) {
+ var FleeDirection = [unitToFleeFrom.position()[0] - this.position()[0],unitToFleeFrom.position()[1] - this.position()[1]];
+ var dist = VectorDistance(unitToFleeFrom.position(), this.position() );
+ FleeDirection[0] = (FleeDirection[0]/dist) * 5;
+ FleeDirection[1] = (FleeDirection[1]/dist) * 5;
+
+ Engine.PostCommand({"type": "walk", "entities": [this.id()], "x": this.position()[0] + FleeDirection[0]*5, "z": this.position()[1] + FleeDirection[1]*5, "queued": false});
+ }
+ return this;
+ },
+
+ gather: function(target, queued) {
+ queued = queued || false;
+ Engine.PostCommand({"type": "gather", "entities": [this.id()], "target": target.id(), "queued": queued});
+ return this;
+ },
+
+ repair: function(target, queued) {
+ queued = queued || false;
+ Engine.PostCommand({"type": "repair", "entities": [this.id()], "target": target.id(), "autocontinue": false, "queued": queued});
+ return this;
+ },
+
+ returnResources: function(target, queued) {
+ queued = queued || false;
+ Engine.PostCommand({"type": "returnresource", "entities": [this.id()], "target": target.id(), "queued": queued});
+ return this;
+ },
+
+ destroy: function() {
+ Engine.PostCommand({"type": "delete-entities", "entities": [this.id()] });
+ return this;
+ },
+
+ barter: function(buyType, sellType, amount) {
+ Engine.PostCommand({"type": "barter", "sell" : sellType, "buy" : buyType, "amount" : amount });
+ return this;
+ },
+
+ train: function(type, count, metadata)
+ {
+ var trainable = this.trainableEntities();
+ if (!trainable)
+ {
+ error("Called train("+type+", "+count+") on non-training entity "+this);
+ return this;
+ }
+ if (trainable.indexOf(type) === -1)
+ {
+ error("Called train("+type+", "+count+") on entity "+this+" which can't train that");
+ return this;
+ }
+
+ Engine.PostCommand({
+ "type": "train",
+ "entities": [this.id()],
+ "template": type,
+ "count": count,
+ "metadata": metadata
+ });
+ return this;
+ },
+
+ construct: function(template, x, z, angle) {
+ // TODO: verify this unit can construct this, just for internal
+ // sanity-checking and error reporting
+
+ Engine.PostCommand({
+ "type": "construct",
+ "entities": [this.id()],
+ "template": template,
+ "x": x,
+ "z": z,
+ "angle": angle,
+ "autorepair": false,
+ "autocontinue": false,
+ "queued": false
+ });
+ return this;
+ },
+
+ research: function(template) {
+ Engine.PostCommand({ "type": "research", "entity": this.id(), "template": template });
+ return this;
+ },
+
+});
+
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/entitycollection.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/entitycollection.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/entitycollection.js (revision 13225)
@@ -0,0 +1,319 @@
+function EntityCollection(sharedAI, entities, filters)
+{
+ this._ai = sharedAI;
+ this._entities = entities || {};
+ this._filters = filters || [];
+
+ // Compute length lazily on demand, since it can be
+ // expensive for large collections
+ this._length = undefined;
+ Object.defineProperty(this, "length", {
+ get: function () {
+ if (this._length === undefined)
+ {
+ this._length = 0;
+ for (var id in entities)
+ ++this._length;
+ }
+ return this._length;
+ }
+ });
+ this.frozen = false;
+}
+
+// If an entitycollection is frozen, it will never automatically add a unit.
+// But can remove one.
+EntityCollection.prototype.freeze = function()
+{
+ this.frozen = true;
+};
+EntityCollection.prototype.defreeze = function()
+{
+ this.frozen = false;
+};
+
+EntityCollection.prototype.toIdArray = function()
+{
+ var ret = [];
+ for (var id in this._entities)
+ ret.push(+id);
+ return ret;
+};
+
+EntityCollection.prototype.toEntityArray = function()
+{
+ var ret = [];
+ for each (var ent in this._entities)
+ ret.push(ent);
+ return ret;
+};
+
+EntityCollection.prototype.toString = function()
+{
+ return "[EntityCollection " + this.toEntityArray().join(" ") + "]";
+};
+
+/**
+ * Returns the (at most) n entities nearest to targetPos.
+ */
+EntityCollection.prototype.filterNearest = function(targetPos, n)
+{
+ // Compute the distance of each entity
+ var data = []; // [ [id, ent, distance], ... ]
+ for (var id in this._entities)
+ {
+ var ent = this._entities[id];
+ if (ent.position())
+ data.push([id, ent, VectorDistance(targetPos, ent.position())]);
+ }
+
+ // Sort by increasing distance
+ data.sort(function (a, b) { return (a[2] - b[2]); });
+
+ // Extract the first n
+ var ret = {};
+ for each (var val in data.slice(0, n))
+ ret[val[0]] = val[1];
+
+ return new EntityCollection(this._ai, ret);
+};
+
+EntityCollection.prototype.filter = function(filter, thisp)
+{
+ if (typeof(filter) == "function")
+ filter = {"func": filter, "dynamicProperties": []};
+
+ var ret = {};
+ for (var id in this._entities)
+ {
+ var ent = this._entities[id];
+ if (filter.func.call(thisp, ent, id, this))
+ ret[id] = ent;
+ }
+
+ return new EntityCollection(this._ai, ret, this._filters.concat([filter]));
+};
+
+EntityCollection.prototype.filter_raw = function(callback, thisp)
+{
+ var ret = {};
+ for (var id in this._entities)
+ {
+ var ent = this._entities[id];
+ var val = this._entities[id]._entity;
+ if (callback.call(thisp, val, id, this))
+ ret[id] = ent;
+ }
+ return new EntityCollection(this._ai, ret);
+};
+
+EntityCollection.prototype.filterNearest = function(targetPos, n)
+{
+ // Compute the distance of each entity
+ var data = []; // [ [id, ent, distance], ... ]
+ for (var id in this._entities)
+ {
+ var ent = this._entities[id];
+ if (ent.position())
+ data.push([id, ent, SquareVectorDistance(targetPos, ent.position())]);
+ }
+
+ // Sort by increasing distance
+ data.sort(function (a, b) { return (a[2] - b[2]); });
+ if (n === undefined)
+ n = this._length;
+ // Extract the first n
+ var ret = {};
+ for each (var val in data.slice(0, n))
+ ret[val[0]] = val[1];
+
+ return new EntityCollection(this._ai, ret);
+};
+
+EntityCollection.prototype.forEach = function(callback, thisp)
+{
+ for (var id in this._entities)
+ {
+ var ent = this._entities[id];
+ callback.call(thisp, ent, id, this);
+ }
+ return this;
+};
+
+EntityCollection.prototype.move = function(x, z, queued)
+{
+ queued = queued || false;
+ Engine.PostCommand({"type": "walk", "entities": this.toIdArray(), "x": x, "z": z, "queued": queued});
+ return this;
+};
+EntityCollection.prototype.moveIndiv = function(x, z, queued)
+{
+ queued = queued || false;
+ for (var id in this._entities)
+ Engine.PostCommand({"type": "walk", "entities": [this._entities[id].id()], "x": x, "z": z, "queued": queued});
+ return this;
+};
+EntityCollection.prototype.destroy = function()
+{
+ Engine.PostCommand({"type": "delete-entities", "entities": this.toIdArray()});
+ return this;
+};
+EntityCollection.prototype.attack = function(unit)
+{
+ var unitId;
+ if (typeof(unit) === "Entity")
+ {
+ unitId = unit.id();
+ }
+ else
+ {
+ unitId = unit;
+ }
+ Engine.PostCommand({"type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false});
+ return this;
+};
+// violent, aggressive, defensive, passive, standground
+EntityCollection.prototype.setStance = function(stance)
+{
+ Engine.PostCommand({"type": "stance", "entities": this.toIdArray(), "name" : stance, "queued": false});
+ return this;
+};
+
+// Returns the average position of all units
+EntityCollection.prototype.getCentrePosition = function()
+{
+ var sumPos = [0, 0];
+ var count = 0;
+ this.forEach(function(ent)
+ {
+ if (ent.position())
+ {
+ sumPos[0] += ent.position()[0];
+ sumPos[1] += ent.position()[1];
+ count ++;
+ }
+ });
+ if (count === 0)
+ {
+ return undefined;
+ }
+ else
+ {
+ return [sumPos[0]/count, sumPos[1]/count];
+ }
+};
+
+// returns the average position from the sample first units.
+// This might be faster for huge collections, but there's
+// always a risk that it'll be unprecise.
+EntityCollection.prototype.getApproximatePosition = function(sample)
+{
+ var sumPos = [0, 0];
+ var i = 0;
+ for (var id in this._entities)
+ {
+ var ent = this._entities[id];
+ if (ent.position())
+ {
+ sumPos[0] += ent.position()[0];
+ sumPos[1] += ent.position()[1];
+ i++;
+ }
+ if (i === sample)
+ break;
+ }
+ if (sample === 0)
+ {
+ return undefined;
+ }
+ else
+ {
+ return [sumPos[0]/i, sumPos[1]/i];
+ }
+};
+
+
+// Removes an entity from the collection, returns true if the entity was a member, false otherwise
+EntityCollection.prototype.removeEnt = function(ent)
+{
+ if (this._entities[ent.id()])
+ {
+ // Checking length may initialize it, so do it before deleting.
+ if (this.length !== undefined)
+ this._length--;
+ delete this._entities[ent.id()];
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+};
+
+// Adds an entity to the collection, returns true if the entity was not member, false otherwise
+EntityCollection.prototype.addEnt = function(ent)
+{
+ if (this._entities[ent.id()])
+ {
+ return false;
+ }
+ else
+ {
+ // Checking length may initialize it, so do it before adding.
+ if (this.length !== undefined)
+ this._length++;
+ this._entities[ent.id()] = ent;
+ return true;
+ }
+};
+
+// Checks the entity against the filters, and adds or removes it appropriately, returns true if the
+// entity collection was modified.
+// Force can add a unit despite a freezing.
+// If an entitycollection is frozen, it will never automatically add a unit.
+// But can remove one.
+EntityCollection.prototype.updateEnt = function(ent, force)
+{
+ var passesFilters = true;
+ for each (var filter in this._filters)
+ {
+ passesFilters = passesFilters && filter.func(ent);
+ }
+
+ if (passesFilters)
+ {
+ if (!force && this.frozen)
+ return false;
+ return this.addEnt(ent);
+ }
+ else
+ {
+ return this.removeEnt(ent);
+ }
+};
+
+EntityCollection.prototype.registerUpdates = function(noPush)
+{
+ this._ai.registerUpdatingEntityCollection(this,noPush);
+};
+
+EntityCollection.prototype.dynamicProperties = function()
+{
+ var ret = [];
+ for each (var filter in this._filters)
+ {
+ ret = ret.concat(filter.dynamicProperties);
+ }
+
+ return ret;
+};
+
+EntityCollection.prototype.setUID = function(id)
+{
+ this._UID = id;
+};
+
+EntityCollection.prototype.getUID = function()
+{
+ return this._UID;
+};
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/entitycollection.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/filters.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/filters.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/filters.js (revision 13225)
@@ -0,0 +1,211 @@
+var Filters = {
+ byType: function(type){
+ return {"func" : function(ent){
+ return ent.templateName() === type;
+ },
+ "dynamicProperties": []};
+ },
+
+ byClass: function(cls){
+ return {"func" : function(ent){
+ return ent.hasClass(cls);
+ },
+ "dynamicProperties": []};
+ },
+
+ byClassesAnd: function(clsList){
+ return {"func" : function(ent){
+ var ret = true;
+ for (var i in clsList){
+ ret = ret && ent.hasClass(clsList[i]);
+ }
+ return ret;
+ },
+ "dynamicProperties": []};
+ },
+
+ byClassesOr: function(clsList){
+ return {"func" : function(ent){
+ var ret = false;
+ for (var i in clsList){
+ ret = ret || ent.hasClass(clsList[i]);
+ }
+ return ret;
+ },
+ "dynamicProperties": []};
+ },
+
+ byMetadata: function(player, key, value){
+ return {"func" : function(ent){
+ return (ent.getMetadata(player, key) == value);
+ },
+ "dynamicProperties": ['metadata.' + key]};
+ },
+ byHasMetadata: function(key){
+ return {"func" : function(ent){
+ return (ent.getMetadata(PlayerID, key) != undefined);
+ },
+ "dynamicProperties": ['metadata.' + key]};
+ },
+
+ and: function(filter1, filter2){
+ return {"func": function(ent){
+ return filter1.func(ent) && filter2.func(ent);
+ },
+ "dynamicProperties": filter1.dynamicProperties.concat(filter2.dynamicProperties)};
+ },
+
+ or: function(filter1, filter2){
+ return {"func" : function(ent){
+ return filter1.func(ent) || filter2.func(ent);
+ },
+ "dynamicProperties": filter1.dynamicProperties.concat(filter2.dynamicProperties)};
+ },
+
+ not: function(filter){
+ return {"func": function(ent){
+ return !filter.func(ent);
+ },
+ "dynamicProperties": filter.dynamicProperties};
+ },
+
+ byOwner: function(owner){
+ return {"func" : function(ent){
+ return (ent.owner() === owner);
+ },
+ "dynamicProperties": ['owner']};
+ },
+
+ byNotOwner: function(owner){
+ return {"func" : function(ent){
+ return (ent.owner() !== owner);
+ },
+ "dynamicProperties": ['owner']};
+ },
+
+ byOwners: function(owners){
+ return {"func" : function(ent){
+ for (var i in owners){
+ if (ent.owner() == +owners[i]){
+ return true;
+ }
+ }
+ return false;
+ },
+ "dynamicProperties": ['owner']};
+ },
+
+ byTrainingQueue: function(){
+ return {"func" : function(ent){
+ return ent.trainingQueue();
+ },
+ "dynamicProperties": ['trainingQueue']};
+ },
+ byResearchAvailable: function(){
+ return {"func" : function(ent){
+ return ent.researchableTechs() !== undefined;
+ },
+ "dynamicProperties": []};
+ },
+ byTargetedEntity: function(targetID){
+ return {"func": function(ent) {
+ return (ent.unitAIOrderData().length && ent.unitAIOrderData()[0]["target"] && ent.unitAIOrderData()[0]["target"] == targetID);
+ },
+ "dynamicProperties": ['unitAIOrderData']};
+ },
+
+ isSoldier: function(){
+ return {"func" : function(ent){
+ return Filters.byClassesOr(["CitizenSoldier", "Super"])(ent);
+ },
+ "dynamicProperties": []};
+ },
+
+ isIdle: function(){
+ return {"func" : function(ent){
+ return ent.isIdle();
+ },
+ "dynamicProperties": ['idle']};
+ },
+
+ isFoundation: function(){
+ return {"func": function(ent){
+ return ent.foundationProgress() !== undefined;
+ },
+ "dynamicProperties": []};
+ },
+
+ byDistance: function(startPoint, dist){
+ return {"func": function(ent){
+ if (ent.position() === undefined){
+ return false;
+ }else{
+ return (SquareVectorDistance(startPoint, ent.position()) < dist*dist);
+ }
+ },
+ "dynamicProperties": ['position']};
+ },
+
+ // Distance filter with no auto updating, use with care
+ byStaticDistance: function(startPoint, dist){
+ return {"func": function(ent){
+ if (!ent.position()){
+ return false;
+ }else{
+ return (SquareVectorDistance(startPoint, ent.position()) < dist*dist);
+ }
+ },
+ "dynamicProperties": []};
+ },
+
+ byTerritory: function(Map, territoryIndex){
+ return {"func": function(ent){
+ if (Map.point(ent.position()) == territoryIndex) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ "dynamicProperties": ['position']};
+ },
+
+ isDropsite: function(resourceType){
+ return {"func": function(ent){
+ return (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resourceType) !== -1);
+ },
+ "dynamicProperties": []};
+ },
+
+ byResource: function(resourceType){
+ return {"func" : function(ent){
+ var type = ent.resourceSupplyType();
+ if (!type)
+ return false;
+ var amount = ent.resourceSupplyMax();
+ if (!amount)
+ return false;
+
+ // Skip targets that are too hard to hunt
+ if (ent.isUnhuntable())
+ return false;
+
+ // And don't go for the bloody fish! TODO: better accessibility checks
+ if (ent.hasClass("SeaCreature")){
+ return false;
+ }
+
+ // Don't go for floating treasures since we won't be able to reach them and it kills the pathfinder.
+ if (ent.templateName() == "other/special_treasure_shipwreck_debris" ||
+ ent.templateName() == "other/special_treasure_shipwreck" ){
+ return false;
+ }
+
+ if (type.generic == "treasure"){
+ return (resourceType == type.specific);
+ } else {
+ return (resourceType == type.generic);
+ }
+ },
+ "dynamicProperties": []};
+ }
+};
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/filters.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/gamestate.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/gamestate.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/gamestate.js (revision 13225)
@@ -0,0 +1,595 @@
+/**
+ * Provides an API for the rest of the AI scripts to query the world state at a
+ * higher level than the raw data.
+ */
+var GameState = function(SharedScript, state, player) {
+ this.sharedScript = SharedScript;
+ this.EntCollecNames = SharedScript._entityCollectionsName;
+ this.EntCollec = SharedScript._entityCollections;
+ this.timeElapsed = state.timeElapsed;
+ this.templates = SharedScript._templates;
+ this.techTemplates = SharedScript._techTemplates;
+ this.entities = SharedScript.entities;
+ this.player = player;
+ this.playerData = state.players[player];
+ this.techModifications = SharedScript.techModifications[player];
+ this.buildingsBuilt = 0;
+
+ this.ai = null; // must be updated by the AIs.
+
+ this.cellSize = 4; // Size of each map tile
+
+ this.turnCache = {};
+};
+GameState.prototype.update = function(SharedScript, state) {
+ this.sharedScript = SharedScript;
+ this.EntCollecNames = SharedScript._entityCollectionsName;
+ this.EntCollec = SharedScript._entityCollections;
+ this.timeElapsed = state.timeElapsed;
+ this.templates = SharedScript._templates;
+ this.techTemplates = SharedScript._techTemplates;
+ this.entities = SharedScript.entities;
+ this.playerData = state.players[this.player];
+ this.techModifications = SharedScript.techModifications[this.player];
+
+ this.buildingsBuilt = 0;
+ this.turnCache = {};
+};
+
+GameState.prototype.updatingCollection = function(id, filter, collection){
+ // automatically add the player ID
+ id = this.player + "-" + id;
+
+ if (!this.EntCollecNames[id]){
+ if (collection !== undefined)
+ this.EntCollecNames[id] = collection.filter(filter);
+ else {
+ this.EntCollecNames[id] = this.entities.filter(filter);
+ }
+ this.EntCollecNames[id].registerUpdates();
+ }
+
+ return this.EntCollecNames[id];
+};
+GameState.prototype.destroyCollection = function(id){
+ // automatically add the player ID
+ id = this.player + "-" + id;
+
+ if (this.EntCollecNames[id] !== undefined){
+ this.sharedScript.removeUpdatingEntityCollection(this.EntCollecNames[id]);
+ delete this.EntCollecNames[id];
+ }
+};
+GameState.prototype.getEC = function(id){
+ // automatically add the player ID
+ id = this.player + "-" + id;
+
+ if (this.EntCollecNames[id] !== undefined)
+ return this.EntCollecNames[id];
+ return undefined;
+};
+
+GameState.prototype.updatingGlobalCollection = function(id, filter, collection) {
+ if (!this.EntCollecNames[id]){
+ if (collection !== undefined)
+ this.EntCollecNames[id] = collection.filter(filter);
+ else
+ this.EntCollecNames[id] = this.entities.filter(filter);
+ this.EntCollecNames[id].registerUpdates();
+ }
+
+ return this.EntCollecNames[id];
+};
+GameState.prototype.destroyGlobalCollection = function(id)
+{
+ if (this.EntCollecNames[id] !== undefined){
+ this.sharedScript.removeUpdatingEntityCollection(this.EntCollecNames[id]);
+ delete this.EntCollecNames[id];
+ }
+};
+GameState.prototype.getGEC = function(id)
+{
+ if (this.EntCollecNames[id] !== undefined)
+ return this.EntCollecNames[id];
+ return undefined;
+};
+
+GameState.prototype.currentPhase = function()
+{
+ if (this.isResearched("phase_city"))
+ return 3;
+ if (this.isResearched("phase_town"))
+ return 2;
+ if (this.isResearched("phase_village"))
+ return 1;
+ return 0;
+};
+
+GameState.prototype.isResearched = function(template)
+{
+ return this.playerData.researchedTechs[template] !== undefined;
+};
+
+// this is an absolute check that doesn't check if we have a building to research from.
+GameState.prototype.canResearch = function(techTemplateName, noRequirementCheck)
+{
+ var template = this.getTemplate(techTemplateName);
+ if (!template)
+ return false;
+
+ // researching or already researched: NOO.
+ if (this.playerData.researchQueued[techTemplateName] || this.playerData.researchStarted[techTemplateName] || this.playerData.researchedTechs[techTemplateName])
+ return false;
+
+ if (noRequirementCheck === false)
+ return true;
+
+ // not already researched, check if we can.
+ // basically a copy of the function in technologyManager since we can't use it.
+ // Checks the requirements for a technology to see if it can be researched at the current time
+
+ // The technology which this technology supersedes is required
+ if (template.supersedes() && !this.playerData.researchedTechs[template.supersedes()])
+ return false;
+
+ // if this is a pair, we must check that the paire tech is not being researched
+ if (template.pair())
+ {
+ var other = template.pairedWith();
+ if (this.playerData.researchQueued[other] || this.playerData.researchStarted[other] || this.playerData.researchedTechs[other])
+ return false;
+ }
+
+ return this.checkTechRequirements(template.requirements());
+}
+
+// Private function for checking a set of requirements is met
+// basically copies TechnologyManager's
+GameState.prototype.checkTechRequirements = function (reqs)
+{
+ // If there are no requirements then all requirements are met
+ if (!reqs)
+ return true;
+
+ if (reqs.tech)
+ {
+ return (this.playerData.researchedTechs[reqs.tech] !== undefined && this.playerData.researchedTechs[reqs.tech]);
+ }
+ else if (reqs.all)
+ {
+ for (var i = 0; i < reqs.all.length; i++)
+ {
+ if (!this.checkTechRequirements(reqs.all[i]))
+ return false;
+ }
+ return true;
+ }
+ else if (reqs.any)
+ {
+ for (var i = 0; i < reqs.any.length; i++)
+ {
+ if (this.checkTechRequirements(reqs.any[i]))
+ return true;
+ }
+ return false;
+ }
+ else if (reqs.class)
+ {
+ if (reqs.numberOfTypes)
+ {
+ if (this.playerData.typeCountsByClass[reqs.class])
+ return (reqs.numberOfTypes <= Object.keys(this.playerData.typeCountsByClass[reqs.class]).length);
+ else
+ return false;
+ }
+ else if (reqs.number)
+ {
+ if (this.playerData.classCounts[reqs.class])
+ return (reqs.number <= this.playerData.classCounts[reqs.class]);
+ else
+ return false;
+ }
+ }
+ else if (reqs.civ)
+ {
+ if (this.playerData.civ == reqs.civ)
+ return true;
+ else
+ return false;
+ }
+
+ // The technologies requirements are not a recognised format
+ error("Bad requirements " + uneval(reqs));
+ return false;
+};
+
+
+GameState.prototype.getTimeElapsed = function()
+{
+ return this.timeElapsed;
+};
+
+GameState.prototype.getTemplate = function(type)
+{
+ if (this.techTemplates[type] !== undefined)
+ return new Technology(this.techTemplates, type);
+
+ if (!this.templates[type])
+ return null;
+
+ return new EntityTemplate(this.templates[type], this.techModifications);
+};
+
+GameState.prototype.applyCiv = function(str) {
+ return str.replace(/\{civ\}/g, this.playerData.civ);
+};
+GameState.prototype.civ = function() {
+ return this.playerData.civ;
+};
+
+/**
+ * @returns {Resources}
+ */
+GameState.prototype.getResources = function() {
+ return new Resources(this.playerData.resourceCounts);
+};
+
+GameState.prototype.getMap = function() {
+ return this.sharedScript.passabilityMap;
+};
+
+GameState.prototype.getPopulation = function() {
+ return this.playerData.popCount;
+};
+
+GameState.prototype.getPopulationLimit = function() {
+ return this.playerData.popLimit;
+};
+
+GameState.prototype.getPopulationMax = function() {
+ return this.playerData.popMax;
+};
+
+GameState.prototype.getPassabilityClassMask = function(name) {
+ if (!(name in this.sharedScript.passabilityClasses)){
+ error("Tried to use invalid passability class name '" + name + "'");
+ }
+ return this.sharedScript.passabilityClasses[name];
+};
+
+GameState.prototype.getPlayerID = function() {
+ return this.player;
+};
+
+GameState.prototype.isPlayerAlly = function(id) {
+ return this.playerData.isAlly[id];
+};
+
+GameState.prototype.isPlayerEnemy = function(id) {
+ return this.playerData.isEnemy[id];
+};
+
+GameState.prototype.getEnemies = function(){
+ var ret = [];
+ for (var i in this.playerData.isEnemy){
+ if (this.playerData.isEnemy[i]){
+ ret.push(i);
+ }
+ }
+ return ret;
+};
+
+GameState.prototype.isEntityAlly = function(ent) {
+ if (ent && ent.owner && (typeof ent.owner) === "function"){
+ return this.playerData.isAlly[ent.owner()];
+ } else if (ent && ent.owner){
+ return this.playerData.isAlly[ent.owner];
+ }
+ return false;
+};
+
+GameState.prototype.isEntityEnemy = function(ent) {
+ if (ent && ent.owner && (typeof ent.owner) === "function"){
+ return this.playerData.isEnemy[ent.owner()];
+ } else if (ent && ent.owner){
+ return this.playerData.isEnemy[ent.owner];
+ }
+ return false;
+};
+
+GameState.prototype.isEntityOwn = function(ent) {
+ if (ent && ent.owner && (typeof ent.owner) === "function"){
+ return ent.owner() == this.player;
+ } else if (ent && ent.owner){
+ return ent.owner == this.player;
+ }
+ return false;
+};
+
+GameState.prototype.getOwnEntities = function() {
+ return this.updatingCollection("own-entities", Filters.byOwner(this.player));
+};
+
+GameState.prototype.getEnemyEntities = function() {
+ var diplomacyChange = false;
+ var enemies = this.getEnemies();
+ if (this.enemies){
+ if (this.enemies.length != enemies.length){
+ diplomacyChange = true;
+ }else{
+ for (var i = 0; i < enemies.length; i++){
+ if (enemies[i] !== this.enemies[i]){
+ diplomacyChange = true;
+ }
+ }
+ }
+ }
+ if (diplomacyChange || !this.enemies){
+ return this.updatingCollection("enemy-entities", Filters.byOwners(enemies));
+ this.enemies = enemies;
+ }
+ return this.getEC("enemy-entities");
+};
+
+GameState.prototype.getEntities = function() {
+ return this.entities;
+};
+
+GameState.prototype.getEntityById = function(id){
+ if (this.entities._entities[id]) {
+ return this.entities._entities[id];
+ }else{
+ //debug("Entity " + id + " requested does not exist");
+ }
+ return undefined;
+};
+
+GameState.prototype.getOwnEntitiesByMetadata = function(key, value){
+ return this.updatingCollection(key + "-" + value, Filters.byMetadata(this.player, key, value),this.getOwnEntities());
+};
+
+GameState.prototype.getOwnEntitiesByRole = function(role){
+ return this.getOwnEntitiesByMetadata("role", role);
+};
+
+GameState.prototype.getOwnTrainingFacilities = function(){
+ return this.updatingCollection("own-training-facilities", Filters.byTrainingQueue(), this.getOwnEntities());
+};
+
+GameState.prototype.getOwnResearchFacilities = function(){
+ return this.updatingCollection("own-research-facilities", Filters.byResearchAvailable(), this.getOwnEntities());
+};
+
+GameState.prototype.getOwnEntitiesByType = function(type){
+ var filter = Filters.byType(type);
+ return this.updatingCollection("own-by-type-" + type, filter, this.getOwnEntities());
+};
+
+GameState.prototype.countEntitiesByType = function(type) {
+ return this.getOwnEntitiesByType(type).length;
+};
+
+GameState.prototype.countEntitiesAndQueuedByType = function(type) {
+ var count = this.countEntitiesByType(type);
+
+ // Count building foundations
+ count += this.countEntitiesByType("foundation|" + type);
+
+ // Count animal resources
+ count += this.countEntitiesByType("resource|" + type);
+
+ // Count entities in building production queues
+ this.getOwnTrainingFacilities().forEach(function(ent){
+ ent.trainingQueue().forEach(function(item) {
+ if (item.template == type){
+ count += item.count;
+ }
+ });
+ });
+
+ return count;
+};
+
+GameState.prototype.countFoundationsWithType = function(type) {
+ var foundationType = "foundation|" + type;
+ var count = 0;
+ this.getOwnEntities().forEach(function(ent) {
+ var t = ent.templateName();
+ if (t == foundationType)
+ ++count;
+ });
+ return count;
+};
+
+GameState.prototype.countOwnEntitiesByRole = function(role) {
+ return this.getOwnEntitiesByRole(role).length;
+};
+
+GameState.prototype.countOwnEntitiesAndQueuedWithRole = function(role) {
+ var count = this.countOwnEntitiesByRole(role);
+
+ // Count entities in building production queues
+ this.getOwnTrainingFacilities().forEach(function(ent) {
+ ent.trainingQueue().forEach(function(item) {
+ if (item.metadata && item.metadata.role == role)
+ count += item.count;
+ });
+ });
+ return count;
+};
+
+GameState.prototype.countOwnQueuedEntitiesWithMetadata = function(data, value) {
+ // Count entities in building production queues
+ var count = 0;
+ this.getOwnTrainingFacilities().forEach(function(ent) {
+ ent.trainingQueue().forEach(function(item) {
+ if (item.metadata && item.metadata[data] && item.metadata[data] == value)
+ count += item.count;
+ });
+ });
+ return count;
+};
+
+/**
+ * Find buildings that are capable of training the given unit type, and aren't
+ * already too busy.
+ */
+GameState.prototype.findTrainers = function(template) {
+ var maxQueueLength = 3; // avoid tying up resources in giant training queues
+
+ return this.getOwnTrainingFacilities().filter(function(ent) {
+
+ var trainable = ent.trainableEntities();
+ if (!trainable || trainable.indexOf(template) == -1)
+ return false;
+
+ var queue = ent.trainingQueue();
+ if (queue) {
+ if (queue.length >= maxQueueLength)
+ return false;
+ }
+
+ return true;
+ });
+};
+
+/**
+ * Find units that are capable of constructing the given building type.
+ */
+GameState.prototype.findBuilders = function(template) {
+ return this.getOwnEntities().filter(function(ent) {
+
+ var buildable = ent.buildableEntities();
+ if (!buildable || buildable.indexOf(template) == -1)
+ return false;
+
+ return true;
+ });
+};
+
+/**
+ * Find buildings that are capable of researching the given tech, and aren't
+ * already too busy.
+ */
+GameState.prototype.findResearchers = function(templateName) {
+ // let's check we can research the tech.
+ if (!this.canResearch(templateName))
+ return [];
+
+ var template = this.getTemplate(templateName);
+
+ return this.getOwnResearchFacilities().filter(function(ent) { //}){
+ var techs = ent.researchableTechs();
+ if (!techs || (template.pair() && techs.indexOf(template.pair()) === -1) || (!template.pair() && techs.indexOf(templateName) === -1))
+ return false;
+ return true;
+ });
+};
+
+GameState.prototype.getOwnFoundations = function() {
+ return this.updatingCollection("ownFoundations", Filters.isFoundation(), this.getOwnEntities());
+};
+
+GameState.prototype.getOwnDropsites = function(resource){
+ return this.updatingCollection("dropsite-own-" + resource, Filters.isDropsite(resource), this.getOwnEntities());
+};
+
+GameState.prototype.getResourceSupplies = function(resource){
+ return this.updatingGlobalCollection("resource-" + resource, Filters.byResource(resource), this.getEntities());
+};
+
+GameState.prototype.getEntityLimits = function() {
+ return this.playerData.entityLimits;
+};
+
+GameState.prototype.getEntityCounts = function() {
+ return this.playerData.entityCounts;
+};
+
+// Checks whether the maximum number of buildings have been cnstructed for a certain catergory
+GameState.prototype.isEntityLimitReached = function(category) {
+ if(this.playerData.entityLimits[category] === undefined || this.playerData.entityCounts[category] === undefined)
+ return false;
+ if(this.playerData.entityLimits[category].LimitsPerCivCentre != undefined)
+ return (this.playerData.entityCounts[category] >= this.playerData.entityCounts["CivilCentre"]*this.playerData.entityLimits[category].LimitPerCivCentre);
+ else
+ return (this.playerData.entityCounts[category] >= this.playerData.entityLimits[category]);
+};
+
+GameState.prototype.findTrainableUnits = function(classes){
+ var allTrainable = [];
+ this.getOwnEntities().forEach(function(ent) {
+ var trainable = ent.trainableEntities();
+ if (ent.hasClass("Structure"))
+ for (var i in trainable){
+ if (allTrainable.indexOf(trainable[i]) === -1){
+ allTrainable.push(trainable[i]);
+ }
+ }
+ });
+ var ret = [];
+ for (var i in allTrainable) {
+ var template = this.getTemplate(allTrainable[i]);
+ var okay = true;
+
+ for (o in classes)
+ if (!template.hasClass(classes[o]))
+ okay = false;
+
+ if (template.hasClass("Hero")) // disabling heroes for now
+ okay = false;
+
+ if (okay)
+ ret.push( [allTrainable[i], template] );
+ }
+ return ret;
+};
+
+// Return all techs which can currently be researched
+// Does not factor cost.
+// If there are pairs, both techs are returned.
+GameState.prototype.findAvailableTech = function() {
+
+ var allResearchable = [];
+ this.getOwnEntities().forEach(function(ent) {
+ var searchable = ent.researchableTechs();
+ for (var i in searchable) {
+ if (allResearchable.indexOf(searchable[i]) === -1) {
+ allResearchable.push(searchable[i]);
+ }
+ }
+ });
+
+ var ret = [];
+ for (var i in allResearchable) {
+ var template = this.getTemplate(allResearchable[i]);
+
+ if (template.pairDef())
+ {
+ var techs = template.getPairedTechs();
+ if (this.canResearch(techs[0]._templateName))
+ ret.push([techs[0]._templateName, techs[0]] );
+ if (this.canResearch(techs[1]._templateName))
+ ret.push([techs[1]._templateName, techs[1]] );
+ } else {
+ if (this.canResearch(allResearchable[i]) && template._templateName != "phase_town" && template._templateName != "phase_city_generic")
+ ret.push( [allResearchable[i], template] );
+ }
+ }
+ return ret;
+};
+
+// defcon utilities
+GameState.prototype.timeSinceDefconChange = function() {
+ return this.getTimeElapsed()-this.ai.defconChangeTime;
+};
+GameState.prototype.setDefcon = function(level,force) {
+ if (this.ai.defcon >= level || force) {
+ this.ai.defcon = level;
+ this.ai.defconChangeTime = this.getTimeElapsed();
+ }
+};
+GameState.prototype.defcon = function() {
+ return this.ai.defcon;
+};
+
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/gamestate.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/map-module.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/map-module.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/map-module.js (revision 13225)
@@ -0,0 +1,272 @@
+/* The map module.
+ * Copied with changes from QuantumState's original for qBot, it's a component for storing 8 bit values.
+ */
+
+function Map(sharedScript, originalMap, actualCopy){
+ // get the map to find out the correct dimensions
+ var gameMap = sharedScript.passabilityMap;
+ this.width = gameMap.width;
+ this.height = gameMap.height;
+ this.length = gameMap.data.length;
+
+ if (originalMap && actualCopy){
+ this.map = new Uint8Array(this.length);
+ for (var i = 0; i < originalMap.length; ++i)
+ this.map[i] = originalMap[i];
+ } else if (originalMap) {
+ this.map = originalMap;
+ } else {
+ this.map = new Uint8Array(this.length);
+ }
+ this.cellSize = 4;
+}
+
+Map.prototype.gamePosToMapPos = function(p){
+ return [Math.round(p[0]/this.cellSize), Math.round(p[1]/this.cellSize)];
+};
+
+Map.prototype.point = function(p){
+ var q = this.gamePosToMapPos(p);
+ return this.map[q[0] + this.width * q[1]];
+};
+Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) {
+ strength = strength ? +strength : +maxDist;
+ type = type ? type : 'linear';
+
+ var x0 = Math.max(0, cx - maxDist);
+ var y0 = Math.max(0, cy - maxDist);
+ var x1 = Math.min(this.width, cx + maxDist);
+ var y1 = Math.min(this.height, cy + maxDist);
+ var maxDist2 = maxDist * maxDist;
+
+ var str = 0.0;
+ switch (type){
+ case 'linear':
+ str = +strength / +maxDist;
+ break;
+ case 'quadratic':
+ str = +strength / +maxDist2;
+ break;
+ case 'constant':
+ str = +strength;
+ break;
+ }
+
+ for ( var y = y0; y < y1; ++y) {
+ for ( var x = x0; x < x1; ++x) {
+ var dx = x - cx;
+ var dy = y - cy;
+ var r2 = dx*dx + dy*dy;
+ if (r2 < maxDist2){
+ var quant = 0;
+ switch (type){
+ case 'linear':
+ var r = Math.sqrt(r2);
+ quant = str * (maxDist - r);
+ break;
+ case 'quadratic':
+ quant = str * (maxDist2 - r2);
+ break;
+ case 'constant':
+ quant = str;
+ break;
+ }
+ if (this.map[x + y * this.width] + quant > 255){
+ this.map[x + y * this.width] = 255;
+ } else if (this.map[x + y * this.width] + quant < 0){
+ this.map[x + y * this.width] = 0;
+ } else {
+ this.map[x + y * this.width] += quant;
+ }
+ }
+ }
+ }
+};
+
+Map.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type) {
+ strength = strength ? +strength : +maxDist;
+ type = type ? type : 'constant';
+
+ var x0 = Math.max(0, cx - maxDist);
+ var y0 = Math.max(0, cy - maxDist);
+ var x1 = Math.min(this.width, cx + maxDist);
+ var y1 = Math.min(this.height, cy + maxDist);
+ var maxDist2 = maxDist * maxDist;
+
+ var str = 0.0;
+ switch (type){
+ case 'linear':
+ str = strength / maxDist;
+ break;
+ case 'quadratic':
+ str = strength / maxDist2;
+ break;
+ case 'constant':
+ str = strength;
+ break;
+ }
+
+ for ( var y = y0; y < y1; ++y) {
+ for ( var x = x0; x < x1; ++x) {
+ var dx = x - cx;
+ var dy = y - cy;
+ var r2 = dx*dx + dy*dy;
+ if (r2 < maxDist2){
+ var quant = 0;
+ switch (type){
+ case 'linear':
+ var r = Math.sqrt(r2);
+ quant = str * (maxDist - r);
+ break;
+ case 'quadratic':
+ quant = str * (maxDist2 - r2);
+ break;
+ case 'constant':
+ quant = str;
+ break;
+ }
+ var machin = this.map[x + y * this.width] * quant;
+ if (machin <= 0){
+ this.map[x + y * this.width] = 0; //set anything which would have gone negative to 0
+ }else{
+ this.map[x + y * this.width] = machin;
+ }
+ }
+ }
+ }
+};
+Map.prototype.setInfluence = function(cx, cy, maxDist, value) {
+ value = value ? value : 0;
+
+ var x0 = Math.max(0, cx - maxDist);
+ var y0 = Math.max(0, cy - maxDist);
+ var x1 = Math.min(this.width, cx + maxDist);
+ var y1 = Math.min(this.height, cy + maxDist);
+ var maxDist2 = maxDist * maxDist;
+
+ for ( var y = y0; y < y1; ++y) {
+ for ( var x = x0; x < x1; ++x) {
+ var dx = x - cx;
+ var dy = y - cy;
+ var r2 = dx*dx + dy*dy;
+ if (r2 < maxDist2){
+ this.map[x + y * this.width] = value;
+ }
+ }
+ }
+};
+
+Map.prototype.sumInfluence = function(cx, cy, radius){
+ var x0 = Math.max(0, cx - radius);
+ var y0 = Math.max(0, cy - radius);
+ var x1 = Math.min(this.width, cx + radius);
+ var y1 = Math.min(this.height, cy + radius);
+ var radius2 = radius * radius;
+
+ var sum = 0;
+
+ for ( var y = y0; y < y1; ++y) {
+ for ( var x = x0; x < x1; ++x) {
+ var dx = x - cx;
+ var dy = y - cy;
+ var r2 = dx*dx + dy*dy;
+ if (r2 < radius2){
+ sum += this.map[x + y * this.width];
+ }
+ }
+ }
+ return sum;
+};
+/**
+ * Make each cell's 8-bit value at least one greater than each of its
+ * neighbours' values. Possible assignment of a cap (maximum).
+ */
+Map.prototype.expandInfluences = function(maximum, map) {
+ var grid = this.map;
+ if (map !== undefined)
+ grid = map;
+
+ if (maximum == undefined)
+ maximum = 255;
+ var w = this.width;
+ var h = this.height;
+ for ( var y = 0; y < h; ++y) {
+ var min = maximum;
+ 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;
+ if (min > maximum)
+ min = maximum;
+ }
+
+ 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;
+ if (min > maximum)
+ min = maximum;
+ }
+ }
+
+ for ( var x = 0; x < w; ++x) {
+ var min = maximum;
+ 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;
+ if (min > maximum)
+ min = maximum;
+ }
+
+ 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;
+ if (min > maximum)
+ min = maximum;
+ }
+ }
+};
+
+Map.prototype.findBestTile = function(radius, obstructionTiles){
+ // Find the best non-obstructed tile
+ var bestIdx = 0;
+ var bestVal = -1;
+ for ( var i = 0; i < this.length; ++i) {
+ if (obstructionTiles.map[i] > radius) {
+ var v = this.map[i];
+ if (v > bestVal) {
+ bestVal = v;
+ bestIdx = i;
+ }
+ }
+ }
+
+ return [bestIdx, bestVal];
+};
+// Multiplies current map by the parameter map pixelwise
+Map.prototype.multiply = function(map, onlyBetter, divider, maxMultiplier){
+ for (var i = 0; i < this.length; ++i){
+ if (map.map[i]/divider > 1)
+ this.map[i] = Math.min(maxMultiplier*this.map[i], this.map[i] * (map.map[i]/divider));
+ }
+};
+// add to current map by the parameter map pixelwise
+Map.prototype.add = function(map){
+ for (var i = 0; i < this.length; ++i){
+ this.map[i] += +map.map[i];
+ }
+};
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/map-module.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/resources.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/resources.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/resources.js (revision 13225)
@@ -0,0 +1,74 @@
+function Resources(amounts, population) {
+ if (amounts === undefined) {
+ amounts = {
+ food : 0,
+ wood : 0,
+ stone : 0,
+ metal : 0
+ };
+ }
+ for ( var tKey in this.types) {
+ var t = this.types[tKey];
+ this[t] = amounts[t] || 0;
+ }
+
+ if (population > 0) {
+ this.population = parseInt(population);
+ } else {
+ this.population = 0;
+ }
+}
+
+Resources.prototype.types = [ "food", "wood", "stone", "metal" ];
+
+Resources.prototype.reset = function() {
+ for ( var tKey in this.types) {
+ var t = this.types[tKey];
+ this[t] = 0;
+ }
+ this.population = 0;
+};
+
+Resources.prototype.canAfford = function(that) {
+ for ( var tKey in this.types) {
+ var t = this.types[tKey];
+ if (this[t] < that[t]) {
+ return false;
+ }
+ }
+ return true;
+};
+
+Resources.prototype.add = function(that) {
+ for ( var tKey in this.types) {
+ var t = this.types[tKey];
+ this[t] += that[t];
+ }
+ this.population += that.population;
+};
+
+Resources.prototype.subtract = function(that) {
+ for ( var tKey in this.types) {
+ var t = this.types[tKey];
+ this[t] -= that[t];
+ }
+ this.population += that.population;
+};
+
+Resources.prototype.multiply = function(n) {
+ for ( var tKey in this.types) {
+ var t = this.types[tKey];
+ this[t] *= n;
+ }
+ this.population *= n;
+};
+
+Resources.prototype.toInt = function() {
+ var sum = 0;
+ for ( var tKey in this.types) {
+ var t = this.types[tKey];
+ sum += this[t];
+ }
+ sum += this.population * 50; // based on typical unit costs
+ return sum;
+};
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/resources.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/shared.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/shared.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/shared.js (revision 13225)
@@ -0,0 +1,314 @@
+// Shared script handling templates and basic terrain analysis
+function SharedScript(settings)
+{
+ if (!settings)
+ return;
+
+ // Make some properties non-enumerable, so they won't be serialised
+ Object.defineProperty(this, "_players", {value: settings.players, enumerable: false});
+ Object.defineProperty(this, "_templates", {value: settings.templates, enumerable: false});
+ Object.defineProperty(this, "_derivedTemplates", {value: {}, enumerable: false});
+ Object.defineProperty(this, "_techTemplates", {value: settings.techTemplates, enumerable: false});
+
+ this._entityMetadata = {};
+ for (i in this._players)
+ this._entityMetadata[this._players[i]] = {};
+
+ // always create for 8 + gaia players, since _players isn't aware of the human.
+ this._techModifications = { 0 : {}, 1 : {}, 2 : {}, 3 : {}, 4 : {}, 5 : {}, 6 : {}, 7 : {}, 8 : {} };
+
+ // array of entity collections
+ this._entityCollections = [];
+ // each name is a reference to the actual one.
+ this._entityCollectionsName = {};
+ this._entityCollectionsByDynProp = {};
+ this._entityCollectionsUID = 0;
+
+ this.turn = 0;
+}
+
+//Return a simple object (using no classes etc) that will be serialized
+//into saved games
+// TODO: that
+SharedScript.prototype.Serialize = function()
+{
+};
+
+//Called after the constructor when loading a saved game, with 'data' being
+//whatever Serialize() returned
+// TODO: that
+SharedScript.prototype.Deserialize = function(data)
+{
+};
+
+// Components that will be disabled in foundation entity templates.
+// (This is a bit yucky and fragile since it's the inverse of
+// CCmpTemplateManager::CopyFoundationSubset and only includes components
+// that our EntityTemplate class currently uses.)
+var g_FoundationForbiddenComponents = {
+ "ProductionQueue": 1,
+ "ResourceSupply": 1,
+ "ResourceDropsite": 1,
+ "GarrisonHolder": 1,
+};
+
+// Components that will be disabled in resource entity templates.
+// Roughly the inverse of CCmpTemplateManager::CopyResourceSubset.
+var g_ResourceForbiddenComponents = {
+ "Cost": 1,
+ "Decay": 1,
+ "Health": 1,
+ "UnitAI": 1,
+ "UnitMotion": 1,
+ "Vision": 1
+};
+
+SharedScript.prototype.GetTemplate = function(name)
+{
+ if (this._templates[name])
+ return this._templates[name];
+
+ if (this._derivedTemplates[name])
+ return this._derivedTemplates[name];
+
+ // If this is a foundation template, construct it automatically
+ if (name.indexOf("foundation|") !== -1)
+ {
+ var base = this.GetTemplate(name.substr(11));
+
+ var foundation = {};
+ for (var key in base)
+ if (!g_FoundationForbiddenComponents[key])
+ foundation[key] = base[key];
+
+ this._derivedTemplates[name] = foundation;
+ return foundation;
+ }
+ else if (name.indexOf("resource|") !== -1)
+ {
+ var base = this.GetTemplate(name.substr(9));
+
+ var resource = {};
+ for (var key in base)
+ if (!g_ResourceForbiddenComponents[key])
+ resource[key] = base[key];
+
+ this._derivedTemplates[name] = resource;
+ return resource;
+ }
+
+ error("Tried to retrieve invalid template '"+name+"'");
+ return null;
+};
+
+// initialize the shared component using a given gamestate (the initial gamestate after map creation, usually)
+// this is called right at the end of map generation, before you actually reach the map.
+SharedScript.prototype.initWithState = function(state) {
+ this.passabilityClasses = state.passabilityClasses;
+ this.passabilityMap = state.passabilityMap;
+
+ for (o in state.players)
+ this._techModifications[o] = state.players[o].techModifications;
+
+ this.techModifications = this._techModifications;
+
+ this._entities = {};
+ for (var id in state.entities)
+ {
+ this._entities[id] = new Entity(this, state.entities[id]);
+ }
+ // entity collection updated on create/destroy event.
+ this.entities = new EntityCollection(this, this._entities);
+
+ // create the terrain analyzer
+ this.terrainAnalyzer = new TerrainAnalysis(this, state);
+ this.accessibility = new Accessibility(state, this.terrainAnalyzer);
+
+ this.gameState = {};
+ for (i in this._players)
+ {
+ this.gameState[this._players[i]] = new GameState(this,state,this._players[i]);
+ }
+
+};
+
+// General update of the shared script, before each AI's update
+// applies entity deltas, and each gamestate.
+SharedScript.prototype.onUpdate = function(state)
+{
+ this.ApplyEntitiesDelta(state);
+
+ Engine.ProfileStart("onUpdate");
+
+ this.events = state.events;
+ this.passabilityClasses = state.passabilityClasses;
+ this.passabilityMap = state.passabilityMap;
+ this.players = this._players;
+ this.playersData = state.players;
+ this.territoryMap = state.territoryMap;
+ this.timeElapsed = state.timeElapsed;
+
+ for (o in state.players)
+ this._techModifications[o] = state.players[o].techModifications;
+
+ for (i in this.gameState)
+ this.gameState[i].update(this,state);
+
+ this.terrainAnalyzer.updateMapWithEvents(this);
+
+ //this.OnUpdate();
+
+ this.turn++;
+
+ Engine.ProfileStop();
+};
+
+SharedScript.prototype.ApplyEntitiesDelta = function(state)
+{
+ Engine.ProfileStart("Shared ApplyEntitiesDelta");
+
+ for each (var evt in state.events)
+ {
+ if (evt.type == "Create")
+ {
+ if (!state.entities[evt.msg.entity])
+ {
+ continue; // Sometimes there are things like foundations which get destroyed too fast
+ }
+ this._entities[evt.msg.entity] = new Entity(this, state.entities[evt.msg.entity]);
+ this.entities.addEnt(this._entities[evt.msg.entity]);
+
+ // Update all the entity collections since the create operation affects static properties as well as dynamic
+ for each (var entCollection in this._entityCollections)
+ {
+ entCollection.updateEnt(this._entities[evt.msg.entity]);
+ }
+
+ }
+ else if (evt.type == "Destroy")
+ {
+ // A small warning: javascript "delete" does not actually delete, it only removes the reference in this object/
+ // the "deleted" object remains in memory, and any older reference to will still reference it as if it were not "deleted".
+ // Worse, they might prevent it from being garbage collected, thus making it stay alive and consuming ram needlessly.
+ // So take care, and if you encounter a weird bug with deletion not appearing to work correctly, this is probably why.
+ if (!this._entities[evt.msg.entity])
+ {
+ continue;
+ }
+ // The entity was destroyed but its data may still be useful, so
+ // remember the entity and this AI's metadata concerning it
+ evt.msg.metadata = (evt.msg.metadata || []);
+ evt.msg.entityObj = (evt.msg.entityObj || this._entities[evt.msg.entity]);
+ //evt.msg.metadata[this._player] = this._entityMetadata[evt.msg.entity];
+
+ for each (var entCol in this._entityCollections)
+ {
+ entCol.removeEnt(this._entities[evt.msg.entity]);
+ }
+ this.entities.removeEnt(this._entities[evt.msg.entity]);
+
+ delete this._entities[evt.msg.entity];
+ for (i in this._players)
+ delete this._entityMetadata[this._players[i]][evt.msg.entity];
+ }
+ else if (evt.type == "TrainingFinished")
+ {
+ // Apply metadata stored in training queues
+ for each (var ent in evt.msg.entities)
+ {
+ for (key in evt.msg.metadata)
+ {
+ this.setMetadata(evt.msg.owner, this._entities[ent], key, evt.msg.metadata[key])
+ }
+ }
+ }
+ }
+
+ for (var id in state.entities)
+ {
+ var changes = state.entities[id];
+
+ for (var prop in changes)
+ {
+ this._entities[id]._entity[prop] = changes[prop];
+ this.updateEntityCollections(prop, this._entities[id]);
+ }
+ }
+ Engine.ProfileStop();
+};
+
+SharedScript.prototype.registerUpdatingEntityCollection = function(entCollection, noPush)
+{
+ if (!noPush) {
+ this._entityCollections.push(entCollection);
+ }
+ entCollection.setUID(this._entityCollectionsUID);
+ for each (var prop in entCollection.dynamicProperties())
+ {
+ this._entityCollectionsByDynProp[prop] = this._entityCollectionsByDynProp[prop] || [];
+ this._entityCollectionsByDynProp[prop].push(entCollection);
+ }
+ this._entityCollectionsUID++;
+};
+
+SharedScript.prototype.removeUpdatingEntityCollection = function(entCollection)
+{
+ for (var i in this._entityCollections)
+ {
+ if (this._entityCollections[i].getUID() === entCollection.getUID())
+ {
+ this._entityCollections.splice(i, 1);
+ }
+ }
+
+ for each (var prop in entCollection.dynamicProperties())
+ {
+ for (var i in this._entityCollectionsByDynProp[prop])
+ {
+ if (this._entityCollectionsByDynProp[prop][i].getUID() === entCollection.getUID())
+ {
+ this._entityCollectionsByDynProp[prop].splice(i, 1);
+ }
+ }
+ }
+};
+
+SharedScript.prototype.updateEntityCollections = function(property, ent)
+{
+ if (this._entityCollectionsByDynProp[property] !== undefined)
+ {
+ for each (var entCollection in this._entityCollectionsByDynProp[property])
+ {
+ entCollection.updateEnt(ent);
+ }
+ }
+}
+
+SharedScript.prototype.setMetadata = function(player, ent, key, value)
+{
+ var metadata = this._entityMetadata[player][ent.id()];
+ if (!metadata)
+ metadata = this._entityMetadata[player][ent.id()] = {};
+ metadata[key] = value;
+
+ this.updateEntityCollections('metadata', ent);
+ this.updateEntityCollections('metadata.' + key, ent);
+};
+SharedScript.prototype.getMetadata = function(player, ent, key)
+{
+ var metadata = this._entityMetadata[player][ent.id()];
+
+ if (!metadata || !(key in metadata))
+ return undefined;
+ return metadata[key];
+};
+
+function copyPrototype(descendant, parent) {
+ var sConstructor = parent.toString();
+ var aMatch = sConstructor.match( /\s*function (.*)\(/ );
+ if ( aMatch != null ) { descendant.prototype[aMatch[1]] = parent; }
+ for (var m in parent.prototype) {
+ descendant.prototype[m] = parent.prototype[m];
+ }
+};
+
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/shared.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/technology.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/technology.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/technology.js (revision 13225)
@@ -0,0 +1,138 @@
+// Wrapper around a technology template
+
+function Technology(allTemplates, templateName)
+{
+ this._templateName = templateName;
+ var template = allTemplates[templateName];
+
+ // check if this is one of two paired technologies.
+ this._isPair = template.pair === undefined ? false : true;
+ if (this._isPair)
+ {
+ if (allTemplates[template.pair].top == templateName)
+ this._pairedWith = allTemplates[template.pair].bottom;
+ else
+ this._pairedWith = allTemplates[template.pair].top;
+ }
+ // check if it only defines a pair:
+ this._definesPair = template.top === undefined ? false : true;
+ this._template = template;
+ this._techTemplates = allTemplates;
+}
+// returns generic, or specific if civ provided.
+Technology.prototype.name = function(civ)
+{
+ if (civ === undefined)
+ {
+ return this._template.genericName;
+ }
+ else
+ {
+ if (this._template.specificName === undefined || this._template.specificName[civ] === undefined)
+ return undefined;
+ return this._template.specificName[civ];
+ }
+};
+
+Technology.prototype.pairDef = function()
+{
+ return this._definesPair;
+};
+// in case this defines a pair only, returns the two paired technologies.
+Technology.prototype.getPairedTechs = function()
+{
+ if (!this._definesPair)
+ return undefined;
+
+ var techOne = new Technology(this._techTemplates, this._template.top);
+ var techTwo = new Technology(this._techTemplates, this._template.bottom);
+
+ return [techOne,techTwo];
+};
+
+Technology.prototype.pair = function()
+{
+ if (!this._isPair)
+ return undefined;
+ return this._template.pair;
+};
+
+Technology.prototype.pairedWith = function()
+{
+ if (!this._isPair)
+ return undefined;
+ return this._pairedWith;
+};
+
+Technology.prototype.cost = function()
+{
+ if (!this._template.cost)
+ return undefined;
+ return this._template.cost;
+};
+
+// seconds
+Technology.prototype.researchTime = function()
+{
+ if (!this._template.researchTime)
+ return undefined;
+ return this._template.researchTime;
+};
+
+Technology.prototype.requirements = function()
+{
+ if (!this._template.requirements)
+ return undefined;
+ return this._template.requirements;
+};
+
+Technology.prototype.autoResearch = function()
+{
+ if (!this._template.autoResearch)
+ return undefined;
+ return this._template.autoResearch;
+};
+
+Technology.prototype.supersedes = function()
+{
+ if (!this._template.supersedes)
+ return undefined;
+ return this._template.supersedes;
+};
+
+Technology.prototype.modifications = function()
+{
+ if (!this._template.modifications)
+ return undefined;
+ return this._template.modifications;
+};
+
+Technology.prototype.affects = function()
+{
+ if (!this._template.affects)
+ return undefined;
+ return this._template.affects;
+};
+
+Technology.prototype.isAffected = function(classes)
+{
+ if (!this._template.affects)
+ return false;
+
+ for (index in this._template.affects)
+ {
+ var reqClasses = this._template.affects[index].split(" ");
+ var fitting = true;
+ for (i in reqClasses)
+ {
+ if (classes.indexOf(reqClasses[i]) === -1)
+ {
+ fitting = false;
+ break;
+ }
+ }
+ if (fitting === true)
+ return true;
+ }
+ return false;
+};
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/technology.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis-pathfinder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis-pathfinder.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis-pathfinder.js (revision 13225)
@@ -0,0 +1,309 @@
+// An implementation of A* as a pathfinder.
+// It's oversamplable, and has a specific "distance from block"
+// variable to avoid narrow passages if wanted.
+// It can carry a calculation over multiple turns.
+// It can work over water, or land.
+// Note: while theoretically possible, when this goes from land to water
+// It will return the path to the "boarding" point and
+// a new path will need to be created.
+
+// this should only be called by an AI player after setting gamestate.ai
+
+// The initializer creates an expanded influence map for checking.
+// It's not extraordinarily slow, but it might be.
+function aStarPath(gameState, onWater, disregardEntities) {
+ var self = this;
+
+ // get the terrain analyzer map as a reference.
+ this.Map(gameState.ai, gameState.ai.terrainAnalyzer.map);
+ // get the accessibility as a reference
+ this.accessibility = gameState.ai.accessibility;
+ this.terrainAnalyzer = gameState.ai.terrainAnalyzer;
+
+ if (onWater) {
+ this.waterPathfinder = true;
+ } else
+ this.waterPathfinder = false;
+
+ this.widthMap = new Uint8Array(this.map.length);
+ for (var i = 0; i < this.map.length; ++i) {
+ if (this.map[i] === 0)
+ this.widthMap[i] = 0;
+ else if (!disregardEntities && this.map[i] === 30)
+ this.widthMap[i] = 0;
+ else if (!disregardEntities && this.map[i] === 40)
+ this.widthMap[i] = 0;
+ else if (!disregardEntities && this.map[i] === 41)
+ this.widthMap[i] = 2;
+ else if (!disregardEntities && this.map[i] === 42)
+ this.widthMap[i] = 1;
+ else
+ this.widthMap[i] = 255;
+ }
+ this.expandInfluences(255,this.widthMap);
+}
+copyPrototype(aStarPath, Map);
+
+// marks some points of the map as impassable. This can be used to create different paths, or to avoid going through some areas.
+aStarPath.prototype.markImpassableArea = function(cx, cy, Distance) {
+ [cx,cy] = this.gamePosToMapPos([cx,cy]);
+ var x0 = Math.max(0, cx - Distance);
+ var y0 = Math.max(0, cy - Distance);
+ var x1 = Math.min(this.width, cx + Distance);
+ var y1 = Math.min(this.height, cy + Distance);
+ var maxDist2 = Distance * Distance;
+
+ for ( var y = y0; y < y1; ++y) {
+ for ( var x = x0; x < x1; ++x) {
+ var dx = x - cx;
+ var dy = y - cy;
+ var r2 = dx*dx + dy*dy;
+ if (r2 < maxDist2){
+ this.widthMap[x + y * this.width] = 0;
+ }
+ }
+ }
+};
+
+
+// sending gamestate creates a map
+aStarPath.prototype.getPath = function(start, end, Sampling, minWidth, iterationLimit, gamestate)
+{
+ this.Sampling = Sampling >= 1 ? Sampling : 1;
+ this.minWidth = (minWidth !== undefined && minWidth >= this.Sampling) ? minWidth : this.Sampling;
+
+ if (start[0] < 0 || this.gamePosToMapPos(start)[0] >= this.width || start[1] < 0 || this.gamePosToMapPos(start)[1] >= this.height)
+ return undefined;
+
+ var s = this.terrainAnalyzer.findClosestPassablePoint(this.gamePosToMapPos(start), !this.waterPathfinder,500,true);
+ var e = this.terrainAnalyzer.findClosestPassablePoint(this.gamePosToMapPos(end), !this.waterPathfinder,500,true);
+
+ var w = this.width;
+
+ if (!s || !e) {
+ return undefined;
+ }
+ if (gamestate !== undefined)
+ {
+ this.TotorMap = new Map(gamestate);
+ this.TotorMap.addInfluence(s[0],s[1],1,200,'constant');
+ this.TotorMap.addInfluence(e[0],e[1],1,200,'constant');
+ }
+ this.iterationLimit = 9000000000;
+ if (iterationLimit !== undefined)
+ this.iterationLimit = iterationLimit;
+
+ this.s = s[0] + w*s[1];
+ this.e = e[0] + w*e[1];
+
+ if (this.waterPathfinder && this.map[this.s] !== 200 && this.map[this.s] !== 201)
+ {
+ debug ("Trying a path over water, but we are on land, aborting");
+ return undefined;
+ } else if (!this.waterPathfinder && this.map[this.s] === 200)
+ {
+ debug ("Trying a path over land, but we are over water, aborting");
+ return undefined;
+ }
+
+ this.onWater = this.waterPathfinder;
+ this.pathChangesTransport = false;
+
+ // We are going to create a map, it's going to take memory. To avoid OOM errors, GC before we do so.
+ //Engine.ForceGC();
+
+ this.openList = [];
+ this.parentSquare = new Uint32Array(this.map.length);
+ this.isOpened = new Boolean(this.map.length);
+ this.fCostArray = new Uint32Array(this.map.length);
+ this.gCostArray = new Uint32Array(this.map.length);
+ this.currentSquare = this.s;
+
+ this.totalIteration = 0;
+
+ this.isOpened[this.s] = true;
+ this.openList.push(this.s);
+ this.fCostArray[this.s] = SquareVectorDistance([this.s%w, Math.floor(this.s/w)], [this.e%w, Math.floor(this.e/w)]);
+ this.gCostArray[this.s] = 0;
+ this.parentSquare[this.s] = this.s;
+
+ return this.continuePath(gamestate);
+
+}
+// in case it's not over yet, this can carry on the calculation of a path over multiple turn until it's over
+aStarPath.prototype.continuePath = function(gamestate)
+{
+ var w = this.width;
+ var h = this.height;
+ var positions = [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1]];
+ var cost = [100,100,100,100,150,150,150,150];
+
+ //creation of variables used in the loop
+ var found = false;
+ var shortcut = false;
+
+ var infinity = Math.min();
+ var currentDist = infinity;
+
+ var e = this.e;
+ var s = this.s;
+
+ var iteration = 0;
+
+ var target = [this.e%w, Math.floor(this.e/w)];
+
+ var changes = {};
+
+ var tIndex = 0;
+ // on to A*
+ while (found === false && this.openList.length !== 0 && iteration < this.iterationLimit) {
+ currentDist = infinity;
+
+ if (shortcut === true) {
+ this.currentSquare = this.openList.shift();
+ } else {
+ for (i in this.openList)
+ {
+ var sum = this.fCostArray[this.openList[i]] + this.gCostArray[this.openList[i]];
+ if (sum < currentDist)
+ {
+ this.currentSquare = this.openList[i];
+ tIndex = i;
+ currentDist = sum;
+ }
+ }
+ this.openList.splice(tIndex,1);
+ }
+ if (!this.onWater && this.map[this.currentSquare] === 200) {
+ this.onWater = true;
+ } else if (this.onWater && (this.map[this.currentSquare] !== 200 && this.map[this.currentSquare] !== 201)) {
+ this.onWater = false;
+ }
+
+ shortcut = false;
+ this.isOpened[this.currentSquare] = false;
+
+ if (gamestate !== undefined)
+ this.TotorMap.addInfluence(this.currentSquare % w, Math.floor(this.currentSquare / w),1,40,'constant');
+
+ for (i in positions)
+ {
+ var index = 0 + this.currentSquare +positions[i][0]*this.Sampling +w*this.Sampling*positions[i][1];
+ if (this.widthMap[index] >= this.minWidth || (this.onWater && this.map[index] > 0 && this.map[index] !== 200 && this.map[index] !== 201)
+ || (!this.onWater && this.map[this.index] === 200))
+ {
+ if(this.isOpened[index] === undefined)
+ {
+ this.parentSquare[index] = this.currentSquare;
+
+ this.fCostArray[index] = SquareVectorDistance([index%w, Math.floor(index/w)], target);// * cost[i];
+ this.gCostArray[index] = this.gCostArray[this.currentSquare] + cost[i] * this.Sampling;// - this.map[index];
+
+ if (!this.onWater && this.map[index] === 200) {
+ this.gCostArray[index] += 10000;
+ } else if (this.onWater && this.map[index] !== 200) {
+ this.gCostArray[index] += 10000;
+ }
+
+ if (this.map[index] === 200 || (this.map[index] === 201 && this.onWater))
+ this.gCostArray[index] += 1000;
+
+ if (this.openList[0] !== undefined && this.fCostArray[this.openList[0]] + this.gCostArray[this.openList[0]] > this.fCostArray[index] + this.gCostArray[index])
+ {
+ this.openList.unshift(index);
+ shortcut = true;
+ } else {
+ this.openList.push(index);
+ }
+ this.isOpened[index] = true;
+ if (SquareVectorDistance( [this.currentSquare%w, Math.floor(this.currentSquare/w)] , target) <= this.Sampling*this.Sampling) {
+ if (this.e != this.currentSquare)
+ this.parentSquare[this.e] = this.currentSquare;
+ found = true;
+ break;
+ }
+ } else {
+ var addCost = 0;
+ if (!this.onWater && this.map[index] === 200) {
+ addCost += 10000;
+ } else if (this.onWater && this.map[index] !== 200) {
+ addCost += 10000;
+ }
+ if (this.map[index] === 200 || (this.map[index] === 201 && this.onWater))
+ addCost += 1000;
+
+ // already on the Open or closed list
+ if (this.gCostArray[index] > cost[i] * this.Sampling + addCost + this.gCostArray[this.currentSquare])
+ {
+ this.parentSquare[index] = this.currentSquare;
+ this.gCostArray[index] = cost[i] * this.Sampling + addCost + this.gCostArray[this.currentSquare];
+ }
+ }
+ }
+ }
+ iteration++;
+ }
+ this.totalIteration += iteration;
+ if (iteration === this.iterationLimit && found === false && this.openList.length !== 0)
+ {
+ // we've got to assume that we stopped because we reached the upper limit of iterations
+ return "toBeContinued";
+ }
+ //debug (this.totalIteration);
+ var paths = [];
+ if (found) {
+ this.currentSquare = e;
+ var lastPosx = 0;
+ var lastPosy = 0;
+ while (this.parentSquare[this.currentSquare] !== s)
+ {
+ this.currentSquare = this.parentSquare[this.currentSquare];
+
+ if (!this.onWater && this.map[this.currentSquare] === 200) {
+ //debug ("We must cross water, going " +this.currentSquare + " from parent " + this.parentSquare[this.currentSquare]);
+ this.pathChangesTransport = true;
+ changes[this.currentSquare] = true;
+ this.onWater = true;
+ } else if (this.onWater && (this.map[this.currentSquare] !== 200 && this.map[this.currentSquare] !== 201)) {
+ //debug ("We must cross to the ground, going " +this.currentSquare + " from parent " + this.parentSquare[this.currentSquare]);
+ this.pathChangesTransport = true;
+ changes[this.currentSquare] = true;
+ this.onWater = false;
+ }
+
+ if (gamestate !== undefined && changes[this.currentSquare])
+ this.TotorMap.addInfluence(this.currentSquare % w, Math.floor(this.currentSquare / w),2,200,'constant');
+ if (gamestate !== undefined)
+ this.TotorMap.addInfluence(this.currentSquare % w, Math.floor(this.currentSquare / w),1,50,'constant');
+
+ if (SquareVectorDistance([lastPosx,lastPosy],[this.currentSquare % w, Math.floor(this.currentSquare / w)]) > 300 || changes[this.currentSquare])
+ {
+ lastPosx = (this.currentSquare % w);
+ lastPosy = Math.floor(this.currentSquare / w);
+ paths.push([ [lastPosx*this.cellSize,lastPosy*this.cellSize], changes[this.currentSquare] ]);
+ if (gamestate !== undefined)
+ this.TotorMap.addInfluence(this.currentSquare % w, Math.floor(this.currentSquare / w),1,50 + paths.length,'constant');
+ }
+ }
+ } else {
+ // we have not found a path.
+ // what do we do then?
+ }
+
+ if (gamestate !== undefined)
+ this.TotorMap.dumpIm("Path From " +s +" to " +e +".png",255);
+
+ delete this.parentSquare;
+ delete this.isOpened;
+ delete this.fCostArray;
+ delete this.gCostArray;
+
+
+ // the return, if defined is [ [path, each waypoint being [position, mustchangeTransport] ], is there any transport change, ]
+ if (paths.length > 0) {
+ return [paths, this.pathChangesTransport];
+ } else {
+ return undefined;
+ }
+
+}
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis-pathfinder.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis.js (revision 13225)
@@ -0,0 +1,571 @@
+/*
+ * TerrainAnalysis, inheriting from the Map Component.
+ *
+ * This creates a suitable passability map for pathfinding units and provides the findClosestPassablePoint() function.
+ * This is part of the Shared Script, and thus should only be used for things that are non-player specific.
+ * This.map is a map of the world, where particular stuffs are pointed with a value
+ * For example, impassable land is 0, water is 200, areas near tree (ie forest grounds) are 41ā¦
+ * This is intended for use with 8 bit maps for reduced memory usage.
+ * Upgraded from QuantumState's original TerrainAnalysis for qBot.
+ * You may notice a lot of the A* star codes differ only by a few things.
+ * It's wanted: each does a very slightly different things
+ * But truly separating optimizes.
+ */
+
+function TerrainAnalysis(sharedScript,rawState){
+ var self = this;
+ this.cellSize = 4;
+
+ var passabilityMap = rawState.passabilityMap;
+
+ this.width = passabilityMap.width;
+ this.height = passabilityMap.height;
+
+ // the first two won't change, the third is a reference to a value updated by C++
+ this.obstructionMaskLand = rawState.passabilityClasses["default"];
+ this.obstructionMaskWater = rawState.passabilityClasses["ship"];
+ this.obstructionMask = rawState.passabilityClasses["pathfinderObstruction"];
+
+ var obstructionTiles = new Uint8Array(passabilityMap.data.length);
+
+ /* Generated map legend:
+ 0 is impassable
+ 200 is deep water (ie non-passable by land units)
+ 201 is shallow water (passable by land units and water units)
+ 255 is land (or extremely shallow water where ships can't go).
+ 40 is "tree".
+ The following 41-49 range is "near a tree", with the second number showing how many trees this tile neighbors.
+ 30 is "geological component", such as a mine
+ */
+
+ for (var i = 0; i < passabilityMap.data.length; ++i)
+ {
+ // If impassable for land units, set to 0, else to 255.
+ obstructionTiles[i] = (passabilityMap.data[i] & this.obstructionMaskLand) ? 0 : 255;
+
+ if (!(passabilityMap.data[i] & this.obstructionMaskWater) && obstructionTiles[i] === 0)
+ obstructionTiles[i] = 200; // if navigable and not walkable (ie basic water), set to 200.
+ else if (!(passabilityMap.data[i] & this.obstructionMaskWater) && obstructionTiles[i] === 255)
+ obstructionTiles[i] = 201; // navigable and walkable.
+ }
+
+ var square = [ [-1,-1], [-1,0], [-1, 1], [0,1], [1,1], [1,0], [1,-1], [0,-1], [0,0] ];
+ var xx = 0;
+ var yy = 0;
+ var value = 0;
+ var pos = [];
+ var x = 0;
+ var y = 0;
+ var radius = 0;
+ for (var entI in sharedScript._entities)
+ {
+ var ent = sharedScript._entities[entI];
+ if (ent.hasClass("ForestPlant") === true) {
+ pos = this.gamePosToMapPos(ent.position());
+ x = pos[0];
+ y = pos[1];
+ // unless it's impassable already, mark it as 40.
+ if (obstructionTiles[x + y*this.width] !== 0)
+ obstructionTiles[x + y*this.width] = 40;
+ for (i in square)
+ {
+ xx = square[i][0];
+ yy = square[i][1];
+ if (x+i[0] >= 0 && x+xx < this.width && y+yy >= 0 && y+yy < this.height) {
+ value = obstructionTiles[(x+xx) + (y+yy)*this.width];
+ if (value === 255)
+ obstructionTiles[(x+xx) + (y+yy)*this.width] = 41;
+ else if (value < 49 && value > 40)
+ obstructionTiles[(x+xx) + (y+yy)*this.width] = value + 1;
+ }
+ }
+ } else if (ent.hasClass("Geology") === true) {
+ radius = Math.floor(ent.obstructionRadius() / 4);
+ pos = this.gamePosToMapPos(ent.position());
+ x = pos[0];
+ y = pos[1];
+ // Unless it's impassable, mark as 30. This takes precedence over trees.
+ obstructionTiles[x + y*this.width] = obstructionTiles[x + y*this.width] === 0 ? 0 : 30;
+ for (var xx = -radius; xx <= radius;xx++)
+ for (var yy = -radius; yy <= radius;yy++)
+ if (x+xx >= 0 && x+xx < this.width && y+yy >= 0 && y+yy < this.height)
+ obstructionTiles[(x+xx) + (y+yy)*this.width] = obstructionTiles[(x+xx) + (y+yy)*this.width] === 0 ? 0 : 30;
+ }
+ }
+ // Okay now we have a pretty good knowledge of the map.
+ this.Map(rawState, obstructionTiles);
+
+ this.obstructionMaskLand = null;
+ this.obstructionMaskWater = null;
+ this.obstructionMask = null;
+};
+
+copyPrototype(TerrainAnalysis, Map);
+
+// Returns the (approximately) closest point which is passable by searching in a spiral pattern
+TerrainAnalysis.prototype.findClosestPassablePoint = function(startPoint, onLand, limitDistance, quickscope){
+ var w = this.width;
+ var p = startPoint;
+ var direction = 1;
+
+ if (p[0] + w*p[1] < 0 || p[0] + w*p[1] >= this.length) {
+ return undefined;
+ }
+ // quickscope
+ if (this.map[p[0] + w*p[1]] === 255) {
+ if (this.countConnected(p[0] + w*p[1], onLand) >= 2) {
+ return p;
+ }
+ }
+
+ var count = 0;
+ // search in a spiral pattern. We require a value that is actually accessible in this case, ie 255, 201 or 41 if land, 200/201 if water.
+ for (var i = 1; i < w; i++){
+ for (var j = 0; j < 2; j++){
+ for (var k = 0; k < i; k++){
+ p[j] += direction;
+ // if the value is not markedly inaccessible
+ var index = p[0] + w*p[1];
+ if (this.map[index] !== 0 && this.map[index] !== 90 && this.map[index] !== 120 && this.map[index] !== 30 && this.map[index] !== 40){
+ if (quickscope || this.countConnected(index, onLand) >= 2){
+ return p;
+ }
+ }
+ if (limitDistance !== undefined && count > limitDistance){
+ return undefined;
+ }
+ count++;
+ }
+ }
+ direction *= -1;
+ }
+
+ return undefined;
+};
+
+// Returns an estimate of a tile accessibility. It checks neighboring cells over two levels.
+// returns a count. It's not integer. About 2 should be fairly accessible already.
+TerrainAnalysis.prototype.countConnected = function(startIndex, byLand){
+ var count = 0.0;
+
+ var w = this.width;
+ var positions = [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1],
+ [0,2], [0,-2], [2,0], [-2,0], [2,2], [-2,-2], [2,-2], [-2,2]/*,
+ [1,2], [1,-2], [2,1], [-2,1], [-1,2], [-1,-2], [2,-1], [-2,-1]*/];
+
+ for (i in positions) {
+ var index = startIndex + positions[i][0] + positions[i][1]*w;
+ if (this.map[index] !== 0) {
+ if (byLand) {
+ if (this.map[index] === 201) count++;
+ else if (this.map[index] === 255) count++;
+ else if (this.map[index] === 41) count++;
+ else if (this.map[index] === 42) count += 0.5;
+ else if (this.map[index] === 43) count += 0.3;
+ else if (this.map[index] === 44) count += 0.13;
+ else if (this.map[index] === 45) count += 0.08;
+ else if (this.map[index] === 46) count += 0.05;
+ else if (this.map[index] === 47) count += 0.03;
+ } else {
+ if (this.map[index] === 201) count++;
+ if (this.map[index] === 200) count++;
+ }
+ }
+ }
+ return count;
+};
+
+// TODO: for now this resets to 255.
+TerrainAnalysis.prototype.updateMapWithEvents = function(sharedAI) {
+ var self = this;
+
+ var events = sharedAI.events;
+ var passabilityMap = sharedAI.passabilityMap;
+
+ // looking for creation or destruction of entities, and updates the map accordingly.
+ for (var i in events) {
+ var e = events[i];
+ if (e.type === "Destroy") {
+ if (e.msg.entityObj){
+ var ent = e.msg.entityObj;
+ if (ent.hasClass("Geology")) {
+ var x = self.gamePosToMapPos(ent.position())[0];
+ var y = self.gamePosToMapPos(ent.position())[1];
+ // remove it. Don't really care about surrounding and possible overlappings.
+ var radius = Math.floor(ent.obstructionRadius() / self.cellSize);
+ for (var xx = -radius; xx <= radius;xx++)
+ for (var yy = -radius; yy <= radius;yy++)
+ {
+ if (x+xx >= 0 && x+xx < self.width && y+yy >= 0 && y+yy < self.height && this.map[(x+xx) + (y+yy)*self.width] === 30)
+ {
+ this.map[(x+xx) + (y+yy)*self.width] = 255;
+ }
+ }
+ } else if (ent.hasClass("ForestPlant")){
+ var x = self.gamePosToMapPos(ent.position())[0];
+ var y = self.gamePosToMapPos(ent.position())[1];
+ var nbOfNeigh = 0;
+ for (var xx = -1; xx <= 1;xx++)
+ for (var yy = -1; yy <= 1;yy++)
+ {
+ if (xx == 0 && yy == 0)
+ continue;
+ if (this.map[(x+xx) + (y+yy)*self.width] === 40)
+ nbOfNeigh++;
+ else if (this.map[(x+xx) + (y+yy)*self.width] === 41)
+ {
+ this.map[(x+xx) + (y+yy)*self.width] = 255;
+ }
+ else if (this.map[(x+xx) + (y+yy)*self.width] > 41 && this.map[(x+xx) + (y+yy)*self.width] < 50)
+ this.map[(x+xx) + (y+yy)*self.width] = this.map[(x+xx) + (y+yy)*self.width] - 1;
+ }
+ if (nbOfNeigh > 0)
+ this.map[x + y*self.width] = this.map[x + y*self.width] = 40 + nbOfNeigh;
+ else
+ this.map[x + y*self.width] = this.map[x + y*self.width] = 255;
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Accessibility inherits from TerrainAnalysis
+ *
+ * This can easily and efficiently determine if any two points are connected.
+ * 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.
+ */
+function Accessibility(rawState, terrainAnalyser){
+ var self = this;
+
+ this.Map(rawState, terrainAnalyser.map);
+ this.passMap = new Uint8Array(terrainAnalyser.length);
+
+ this.regionSize = [];
+ this.regionSize.push(0);
+ // initialized to 0, so start to 1 for optimization
+ this.regionID = 1;
+ for (var i = 0; i < this.passMap.length; ++i) {
+ if (this.passMap[i] === 0 && this.map[i] !== 0) { // any non-painted, non-inacessible area.
+ this.regionSize.push(0); // updated
+ this.floodFill(i,this.regionID,false);
+ this.regionID++;
+ } else if (this.passMap[i] === 0) { // any non-painted, inacessible area.
+ this.floodFill(i,1,false);
+ }
+ }
+}
+copyPrototype(Accessibility, TerrainAnalysis);
+
+Accessibility.prototype.getAccessValue = function(position){
+ var gamePos = this.gamePosToMapPos(position);
+ return this.passMap[gamePos[0] + this.width*gamePos[1]];
+};
+
+// Returns true if a point is deemed currently accessible (is not blocked by surrounding trees...)
+// NB: accessible means that you can reach it from one side, not necessariliy that you can go ON it.
+Accessibility.prototype.isAccessible = function(gameState, position, onLand){
+ var gamePos = this.gamePosToMapPos(position);
+
+ // quick check
+ if (this.countConnected(gamePos[0] + this.width*gamePos[1], onLand) >= 2) {
+ return true;
+ }
+ return false;
+};
+
+// Return true if you can go from a point to a point without switching means of transport
+// Hardcore means is also checks for isAccessible at the end (it checks for either water or land though, beware).
+// This is a blind check and not a pathfinder: for all it knows there is a huge block of trees in the middle.
+Accessibility.prototype.pathAvailable = function(gameState, start,end, hardcore){
+ var pstart = this.gamePosToMapPos(start);
+ var istart = pstart[0] + pstart[1]*this.width;
+ var pend = this.gamePosToMapPos(end);
+ var iend = pend[0] + pend[1]*this.width;
+
+ if (this.passMap[istart] === this.passMap[iend]) {
+ if (hardcore && (this.isAccessible(gameState, end,true) || this.isAccessible(gameState, end,false)))
+ return true;
+ else if (hardcore)
+ return false;
+ return true;
+ }
+ return false;
+};
+Accessibility.prototype.getRegionSize = function(position){
+ var pos = this.gamePosToMapPos(position);
+ var index = pos[0] + pos[1]*this.width;
+ if (this.regionSize[this.passMap[index]] === undefined)
+ return 0;
+ return this.regionSize[this.passMap[index]];
+};
+Accessibility.prototype.getRegionSizei = function(index) {
+ if (this.regionSize[this.passMap[index]] === undefined)
+ return 0;
+ return this.regionSize[this.passMap[index]];
+};
+
+// Implementation of a fast flood fill. Reasonably good performances. Runs once at startup.
+// TODO: take big zones of impassable trees into account?
+Accessibility.prototype.floodFill = function(startIndex, value, onWater)
+{
+ this.s = startIndex;
+ if (this.passMap[this.s] !== 0) {
+ return false; // already painted.
+ }
+
+ this.floodFor = "land";
+ if (this.map[this.s] === 200 || (this.map[this.s] === 201 && onWater === true))
+ this.floodFor = "water";
+ else if (this.map[this.s] === 0)
+ this.floodFor = "impassable";
+
+ var w = this.width;
+ var h = this.height;
+
+ var x = 0;
+ var y = 0;
+ // Get x and y from index
+ var IndexArray = [this.s];
+ var newIndex = 0;
+ while(IndexArray.length){
+
+ newIndex = IndexArray.pop();
+
+ y = 0;
+ var loop = false;
+ // vertical iteration
+ do {
+ --y;
+ loop = false;
+ var index = +newIndex + w*y;
+ if (index < 0)
+ break;
+ if (this.floodFor === "impassable" && this.map[index] === 0 && this.passMap[index] === 0) {
+ loop = true;
+ } else if (this.floodFor === "land" && this.passMap[index] === 0 && this.map[index] !== 0 && this.map[index] !== 200) {
+ loop = true;
+ } else if (this.floodFor === "water" && this.passMap[index] === 0 && (this.map[index] === 200 || (this.map[index] === 201 && this.onWater)) ) {
+ loop = true;
+ } else {
+ break;
+ }
+ } while (loop === true) // should actually break
+ ++y;
+ var reachLeft = false;
+ var reachRight = false;
+ loop = true;
+ do {
+ var index = +newIndex + w*y;
+ if (this.floodFor === "impassable" && this.map[index] === 0 && this.passMap[index] === 0) {
+ this.passMap[index] = value;
+ this.regionSize[value]++;
+ } else if (this.floodFor === "land" && this.passMap[index] === 0 && this.map[index] !== 0 && this.map[index] !== 200) {
+ this.passMap[index] = value;
+ this.regionSize[value]++;
+ } else if (this.floodFor === "water" && this.passMap[index] === 0 && (this.map[index] === 200 || (this.map[index] === 201 && this.onWater)) ) {
+ this.passMap[index] = value;
+ this.regionSize[value]++;
+ } else {
+ break;
+ }
+
+ if (index%w > 0)
+ {
+ if (this.floodFor === "impassable" && this.map[index -1] === 0 && this.passMap[index -1] === 0) {
+ if(!reachLeft) {
+ IndexArray.push(index -1);
+ reachLeft = true;
+ }
+ } else if (this.floodFor === "land" && this.passMap[index -1] === 0 && this.map[index -1] !== 0 && this.map[index -1] !== 200) {
+ if(!reachLeft) {
+ IndexArray.push(index -1);
+ reachLeft = true;
+ }
+ } else if (this.floodFor === "water" && this.passMap[index -1] === 0 && (this.map[index -1] === 200 || (this.map[index -1] === 201 && this.onWater)) ) {
+ if(!reachLeft) {
+ IndexArray.push(index -1);
+ reachLeft = true;
+ }
+ } else if(reachLeft) {
+ reachLeft = false;
+ }
+ }
+ if (index%w < w - 1)
+ {
+ if (this.floodFor === "impassable" && this.map[index +1] === 0 && this.passMap[index +1] === 0) {
+ if(!reachRight) {
+ IndexArray.push(index +1);
+ reachRight = true;
+ }
+ } else if (this.floodFor === "land" && this.passMap[index +1] === 0 && this.map[index +1] !== 0 && this.map[index +1] !== 200) {
+ if(!reachRight) {
+ IndexArray.push(index +1);
+ reachRight = true;
+ }
+ } else if (this.floodFor === "water" && this.passMap[index +1] === 0 && (this.map[index +1] === 200 || (this.map[index +1] === 201 && this.onWater)) ) {
+ if(!reachRight) {
+ IndexArray.push(index +1);
+ reachRight = true;
+ }
+ } else if(reachRight) {
+ reachRight = false;
+ }
+ }
+ ++y;
+ } while (index/w < w) // should actually break
+ }
+ return true;
+}
+
+function landSizeCounter(rawState,terrainAnalyzer) {
+ var self = this;
+
+ this.passMap = terrainAnalyzer.map;
+
+ var map = new Uint8Array(this.passMap.length);
+ this.Map(rawState,map);
+
+
+ for (var i = 0; i < this.passMap.length; ++i) {
+ if (this.passMap[i] !== 0)
+ this.map[i] = 255;
+ else
+ this.map[i] = 0;
+ }
+
+ this.expandInfluences();
+}
+copyPrototype(landSizeCounter, TerrainAnalysis);
+
+// Implementation of A* as a flood fill. Possibility of (clever) oversampling
+// for efficiency or for disregarding too small passages.
+// can operate over several turns, though default is only one turn.
+landSizeCounter.prototype.getAccessibleLandSize = function(position, sampling, mode, OnlyBuildable, sizeLimit, iterationLimit)
+{
+ if (sampling === undefined)
+ this.Sampling = 1;
+ else
+ this.Sampling = sampling < 1 ? 1 : sampling;
+
+ // this checks from the actual starting point. If that is inaccessible (0), it returns undefined;
+ if (position.length !== undefined) {
+ // this is an array
+ if (position[0] < 0 || this.gamePosToMapPos(position)[0] >= this.width || position[1] < 0 || this.gamePosToMapPos(position)[1] >= this.height)
+ return undefined;
+
+ var s = this.gamePosToMapPos(position);
+ this.s = s[0] + w*s[1];
+ if (this.map[this.s] === 0 || this.map[this.s] === 200 || (OnlyBuildable === true && this.map[this.s] === 201) ) {
+ return undefined;
+ }
+ } else {
+ this.s = position;
+ if (this.map[this.s] === 0 || this.map[this.s] === 200 || (OnlyBuildable === true && this.map[this.s] === 201) ) {
+ return undefined;
+ }
+ }
+
+ if (mode === undefined)
+ this.mode = "default";
+ else
+ this.mode = mode;
+
+ if (sizeLimit === undefined)
+ this.sizeLimit = 300000;
+ else
+ this.sizeLimit = sizeLimit;
+
+ var w = this.width;
+ var h = this.height;
+
+ // max map size is 512*512, this is higher.
+ this.iterationLimit = 300000;
+ if (iterationLimit !== undefined)
+ this.iterationLimit = iterationLimit;
+
+ this.openList = [];
+ this.isOpened = new Boolean(this.map.length);
+ this.gCostArray = new Uint16Array(this.map.length);
+
+ this.currentSquare = this.s;
+ this.isOpened[this.s] = true;
+ this.openList.push(this.s);
+ this.gCostArray[this.s] = 0;
+
+ this.countedValue = 1;
+ this.countedArray = [this.s];
+
+ if (OnlyBuildable !== undefined)
+ this.onlyBuildable = OnlyBuildable;
+ else
+ this.onlyBuildable = true;
+
+ return this.continueLandSizeCalculation();
+}
+landSizeCounter.prototype.continueLandSizeCalculation = function()
+{
+ var w = this.width;
+ var h = this.height;
+ var positions = [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1]];
+ var cost = [10,10,10,10,15,15,15,15];
+
+ //creation of variables used in the loop
+ var nouveau = false;
+ var shortcut = false;
+ var Sampling = this.Sampling;
+ var infinity = Math.min();
+ var currentDist = infinity;
+
+ var iteration = 0;
+ while (this.openList.length !== 0 && iteration < this.iterationLimit && this.countedValue < this.sizeLimit && this.countedArray.length < this.sizeLimit){
+ currentDist = infinity;
+ for (i in this.openList)
+ {
+ var sum = this.gCostArray[this.openList[i]];
+ if (sum < currentDist)
+ {
+ this.currentSquare = this.openList[i];
+ currentDist = sum;
+ }
+ }
+ this.openList.splice(this.openList.indexOf(this.currentSquare),1);
+
+ shortcut = false;
+ this.isOpened[this.currentSquare] = false;
+ for (i in positions) {
+ var index = 0 + this.currentSquare + positions[i][0]*Sampling + w*Sampling*positions[i][1];
+ if (this.passMap[index] !== 0 && this.passMap[index] !== 200 && this.map[index] >= Sampling && (!this.onlyBuildable || this.passMap[index] !== 201)) {
+ if(this.isOpened[index] === undefined) {
+ if (this.mode === "default")
+ this.countedValue++;
+ else if (this.mode === "array")
+ this.countedArray.push(index);
+ this.gCostArray[index] = this.gCostArray[this.currentSquare] + cost[i] * Sampling;
+ this.openList.push(index);
+ this.isOpened[index] = true;
+ }
+ }
+ }
+ iteration++;
+ }
+
+ if (iteration === this.iterationLimit && this.openList.length !== 0 && this.countedValue !== this.sizeLimit && this.countedArray.length !== this.sizeLimit)
+ {
+ // we've got to assume that we stopped because we reached the upper limit of iterations
+ return "toBeContinued";
+ }
+
+ delete this.parentSquare;
+ delete this.isOpened;
+ delete this.fCostArray;
+ delete this.gCostArray;
+
+ if (this.mode === "default")
+ return this.countedValue;
+ else if (this.mode === "array")
+ return this.countedArray;
+ return undefined;
+}
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/utils.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/utils.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/utils.js (revision 13225)
@@ -0,0 +1,77 @@
+function VectorDistance(a, b)
+{
+ var dx = a[0] - b[0];
+ var dz = a[1] - b[1];
+ return Math.sqrt(dx*dx + dz*dz);
+}
+
+function SquareVectorDistance(a, b)
+{
+ var dx = a[0] - b[0];
+ var dz = a[1] - b[1];
+ return (dx*dx + dz*dz);
+}
+// A is the reference, B must be in "range" of A
+// this supposes the range is already squared
+function inRange(a, b, range)// checks for X distance
+{
+ // will avoid unnecessary checking for position in some rare cases... I'm lazy
+ if (a === undefined || b === undefined || range === undefined)
+ return undefined;
+
+ var dx = a[0] - b[0];
+ var dz = a[1] - b[1];
+ return ((dx*dx + dz*dz ) < range);
+}
+// slower than SquareVectorDistance, faster than VectorDistance but not exactly accurate.
+function ManhattanDistance(a, b)
+{
+ var dx = a[0] - b[0];
+ var dz = a[1] - b[1];
+ return Math.abs(dx) + Math.abs(dz);
+}
+
+function AssocArraytoArray(assocArray) {
+ var endArray = [];
+ for (i in assocArray)
+ endArray.push(assocArray[i]);
+ return endArray;
+};
+
+function MemoizeInit(obj)
+{
+ obj._memoizeCache = {};
+}
+
+function Memoize(funcname, func)
+{
+ return function() {
+ var args = funcname + '|' + Array.prototype.join.call(arguments, '|');
+ if (args in this._memoizeCache)
+ return this._memoizeCache[args];
+
+ var ret = func.apply(this, arguments);
+ this._memoizeCache[args] = ret;
+ return ret;
+ };
+}
+
+function ShallowClone(obj)
+{
+ var ret = {};
+ for (var k in obj)
+ ret[k] = obj[k];
+ return ret;
+}
+
+// Picks a random element from an array
+function PickRandom(list){
+ if (list.length === 0)
+ {
+ return undefined;
+ }
+ else
+ {
+ return list[Math.floor(Math.random()*list.length)];
+ }
+}
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/utils.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot/data.json
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot/data.json (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot/data.json (revision 13225)
@@ -1,5 +1,6 @@
{
"name": "qBot",
"description": "Quantumstate's improved version of the Test Bot",
- "constructor": "QBotAI"
+ "constructor": "QBotAI",
+ "useShared" : false
}
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/_Read Me.txt
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/_Read Me.txt (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/_Read Me.txt (revision 13225)
@@ -0,0 +1,13 @@
+Aegis (working name). Experimental AI for 0 A.D. ( http://play0ad.com/ ). An effort to improve over two bots: qBot (by Quantumstate, based on TestBot) and Marilyn (by Wraitii, itself based on qBot).
+
+Install by placing files into the data/mods/public/simulation/ai/qbot-wc folder.
+
+You may want to set "debug : true" in config.js if you are developping, you will get a better understanding of what the AI does. There are also many commented debug outputs, and many commented map outputs that you may want to uncomment.
+
+This bot is not yet default as it was mostly a work in progress for the past few months. It features early technological support, early naval support, better economic management, better defense and better attack management than qBot. It is generally much stronger than the former, and should hopefully be able to handle more situations properly. It is, however, not faultless.
+
+Please report any error to the wildfire games forum ( http://www.wildfiregames.com/forum/index.php?act=idx ), thanks for playing!
+
+Requires common-api-v3.
+
+(note: no saved game support as of yet. A very basic difficulty setting can be found in config.js).
\ No newline at end of file
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/_Read Me.txt
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/_init.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/_init.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/_init.js (revision 13225)
@@ -1 +1 @@
-Engine.IncludeModule("common-api-v2");
\ No newline at end of file
+Engine.IncludeModule("common-api-v3");
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/attack_plan.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/attack_plan.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/attack_plan.js (revision 13225)
@@ -1,768 +1,950 @@
// basically an attack plan. The name is an artifact.
function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , targetFinder) {
//This is the list of IDs of the units in the plan
this.idList=[];
this.state = "unexecuted";
this.targetPlayer = targetEnemy;
if (this.targetPlayer === -1 || this.targetPlayer === undefined) {
// let's find our prefered target, basically counting our enemies units.
var enemyCount = {};
for (var i = 1; i <=8; i++)
enemyCount[i] = 0;
gameState.getEntities().forEach(function(ent) { if (gameState.isEntityEnemy(ent) && ent.owner() !== 0) { enemyCount[ent.owner()]++; } });
var max = 0;
for (i in enemyCount)
- if (enemyCount[i] >= max)
+ if (enemyCount[i] > max && +i !== PlayerID)
{
this.targetPlayer = +i;
max = enemyCount[i];
}
}
debug ("Target = " +this.targetPlayer);
this.targetFinder = targetFinder || this.defaultTargetFinder;
this.type = type || "normal";
this.name = uniqueID;
this.healthRecord = [];
this.timeOfPlanStart = gameState.getTimeElapsed(); // we get the time at which we decided to start the attack
this.maxPreparationTime = 300*1000;
this.pausingStart = 0;
this.totalPausingTime = 0;
this.paused = false;
this.onArrivalReaction = "proceedOnTargets";
// priority is relative. If all are 0, the only relevant criteria is "currentsize/targetsize".
// if not, this is a "bonus". The higher the priority, the more this unit will get built.
// Should really be clamped to [0.1-1.5] (assuming 1 is default/the norm)
// Eg: if all are priority 1, and the siege is 0.5, the siege units will get built
// only once every other category is at least 50% of its target size.
this.unitStat = {};
- this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 10, "batchSize" : 5, "classes" : ["Infantry","Ranged"], "templates" : [] };
- this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 10, "batchSize" : 5, "classes" : ["Infantry","Melee"], "templates" : [] };
- this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 8 , "batchSize" : 3, "classes" : ["Cavalry","Melee"], "templates" : [] };
- this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 8 , "batchSize" : 3, "classes" : ["Cavalry","Ranged"], "templates" : [] };
- this.unitStat["Siege"] = { "priority" : 0.5, "minSize" : 0, "targetSize" : 3 , "batchSize" : 1, "classes" : ["Siege"], "templates" : [] };
-
-
+ this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 10, "batchSize" : 5, "classes" : ["Infantry","Ranged"],
+ "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
+ this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 10, "batchSize" : 5, "classes" : ["Infantry","Melee"],
+ "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
+ this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 8 , "batchSize" : 3, "classes" : ["Cavalry","Melee"],
+ "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
+ this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 8 , "batchSize" : 3, "classes" : ["Cavalry","Ranged"],
+ "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
+ this.unitStat["Siege"] = { "priority" : 0.5, "minSize" : 0, "targetSize" : 3 , "batchSize" : 1, "classes" : ["Siege"], "interests" : [ ["siegeStrength", 3], ["cost",1] ], "templates" : [] };
+ var priority = 60;
+
if (type === "superSized") {
- this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 18, "batchSize" : 5, "classes" : ["Infantry","Ranged"], "templates" : [] };
- this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 6, "targetSize" : 24, "batchSize" : 5, "classes" : ["Infantry","Melee"], "templates" : [] };
- this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 5, "classes" : ["Cavalry","Melee"], "templates" : [] };
- this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 5, "classes" : ["Cavalry","Ranged"], "templates" : [] };
- this.unitStat["Siege"] = { "priority" : 0.5, "minSize" : 3, "targetSize" : 6 , "batchSize" : 3, "classes" : ["Siege"], "templates" : [] };
+ this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 18, "batchSize" : 5, "classes" : ["Infantry","Ranged"],
+ "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
+ this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 6, "targetSize" : 24, "batchSize" : 5, "classes" : ["Infantry","Melee"],
+ "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
+ this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 5, "classes" : ["Cavalry","Melee"],
+ "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
+ this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 5, "classes" : ["Cavalry","Ranged"],
+ "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
+ this.unitStat["Siege"] = { "priority" : 0.5, "minSize" : 3, "targetSize" : 6 , "batchSize" : 3, "classes" : ["Siege"],"interests" : [ ["siegeStrength",3], ["cost",1] ], "templates" : [] };
this.maxPreparationTime = 450*1000;
+ priority = 70;
}
+ gameState.ai.queueManager.addQueue("plan_" + this.name, priority);
+ this.queue = gameState.ai.queues["plan_" + this.name];
+
/*
this.unitStat["Siege"]["filter"] = function (ent) {
var strength = [ent.attackStrengths("Melee")["crush"],ent.attackStrengths("Ranged")["crush"]];
return (strength[0] > 15 || strength[1] > 15);
};*/
- var filter = Filters.and(Filters.byMetadata("plan",this.name),Filters.byOwner(gameState.player));
+ var filter = Filters.and(Filters.byMetadata(PlayerID, "plan",this.name),Filters.byOwner(PlayerID));
this.unitCollection = gameState.getOwnEntities().filter(filter);
this.unitCollection.registerUpdates();
this.unitCollection.length;
this.unit = {};
// each array is [ratio, [associated classes], associated EntityColl, associated unitStat, name ]
this.buildOrder = [];
// defining the entity collections. Will look for units I own, that are part of this plan.
// Also defining the buildOrders.
for (unitCat in this.unitStat) {
var cat = unitCat;
var Unit = this.unitStat[cat];
- filter = Filters.and(Filters.byClassesAnd(Unit["classes"]),Filters.and(Filters.byMetadata("plan",this.name),Filters.byOwner(gameState.player)));
+ filter = Filters.and(Filters.byClassesAnd(Unit["classes"]),Filters.and(Filters.byMetadata(PlayerID, "plan",this.name),Filters.byOwner(PlayerID)));
this.unit[cat] = gameState.getOwnEntities().filter(filter);
this.unit[cat].registerUpdates();
this.unit[cat].length;
this.buildOrder.push([0, Unit["classes"], this.unit[cat], Unit, cat]);
}
/*if (gameState.getTimeElapsed() > 900000) // 15 minutes
{
this.unitStat.Cavalry.Ranged["minSize"] = 5;
this.unitStat.Cavalry.Melee["minSize"] = 5;
this.unitStat.Infantry.Ranged["minSize"] = 10;
this.unitStat.Infantry.Melee["minSize"] = 10;
this.unitStat.Cavalry.Ranged["targetSize"] = 10;
this.unitStat.Cavalry.Melee["targetSize"] = 10;
this.unitStat.Infantry.Ranged["targetSize"] = 20;
this.unitStat.Infantry.Melee["targetSize"] = 20;
this.unitStat.Siege["targetSize"] = 5;
this.unitStat.Siege["minSize"] = 2;
} else {
this.maxPreparationTime = 180000;
}*/
// todo: REACTIVATE (in all caps)
if (type === "harass_raid" && 0 == 1)
{
this.targetFinder = this.raidingTargetFinder;
this.onArrivalReaction = "huntVillagers";
this.type = "harass_raid";
// This is a Cavalry raid against villagers. A Cavalry Swordsman has a bonus against these. Only build these
this.maxPreparationTime = 180000; // 3 minutes.
if (gameState.playerData.civ === "hele") // hellenes have an ealry Cavalry Swordsman
{
this.unitCount.Cavalry.Melee = { "subCat" : ["Swordsman"] , "usesSubcategories" : true, "Swordsman" : undefined, "priority" : 1, "currentAmount" : 0, "minimalAmount" : 0, "preferedAmount" : 0 };
this.unitCount.Cavalry.Melee.Swordsman = { "priority" : 1, "currentAmount" : 0, "minimalAmount" : 4, "preferedAmount" : 7, "fallback" : "abort" };
} else {
this.unitCount.Cavalry.Melee = { "subCat" : undefined , "usesSubcategories" : false, "priority" : 1, "currentAmount" : 0, "minimalAmount" : 4, "preferedAmount" : 7 };
}
this.unitCount.Cavalry.Ranged["minimalAmount"] = 0;
this.unitCount.Cavalry.Ranged["preferedAmount"] = 0;
this.unitCount.Infantry.Ranged["minimalAmount"] = 0;
this.unitCount.Infantry.Ranged["preferedAmount"] = 0;
this.unitCount.Infantry.Melee["minimalAmount"] = 0;
this.unitCount.Infantry.Melee["preferedAmount"] = 0;
this.unitCount.Siege["preferedAmount"] = 0;
}
this.anyNotMinimal = true; // used for support plans
// taking this so that fortresses won't crash it for now. TODO: change the rally point if it becomes invalid
if(gameState.ai.pathsToMe.length > 1)
var position = [(gameState.ai.pathsToMe[0][0]+gameState.ai.pathsToMe[1][0])/2.0,(gameState.ai.pathsToMe[0][1]+gameState.ai.pathsToMe[1][1])/2.0];
else if (gameState.ai.pathsToMe.length !== 0)
var position = [gameState.ai.pathsToMe[0][0],gameState.ai.pathsToMe[0][1]];
else
var position = [-1,-1];
var CCs = gameState.getOwnEntities().filter(Filters.byClass("CivCentre"));
var nearestCCArray = CCs.filterNearest(position, 1).toEntityArray();
var CCpos = nearestCCArray[0].position();
this.rallyPoint = [0,0];
if (position[0] !== -1) {
this.rallyPoint[0] = (position[0]*3 + CCpos[0]) / 4.0;
this.rallyPoint[1] = (position[1]*3 + CCpos[1]) / 4.0;
} else {
this.rallyPoint[0] = CCpos[0];
this.rallyPoint[1] = CCpos[1];
}
if (type == 'harass_raid')
{
this.rallyPoint[0] = (position[0]*3.9 + 0.1 * CCpos[0]) / 4.0;
this.rallyPoint[1] = (position[1]*3.9 + 0.1 * CCpos[1]) / 4.0;
}
// some variables for during the attack
this.lastPosition = [0,0];
this.position = [0,0];
this.threatList = []; // sounds so FBI
this.tactics = undefined;
- gameState.ai.queueManager.addQueue("plan_" + this.name, 100); // high priority: some may gather anyway
- this.queue = gameState.ai.queues["plan_" + this.name];
-
this.assignUnits(gameState);
+ //debug ("Before");
+ //Engine.DumpHeap();
+
// get a good path to an estimated target.
this.pathFinder = new aStarPath(gameState,false);
+ this.onBoat = false; // tells us if our units are loaded on boats.
+ this.needsShip = false;
+
+ //debug ("after");
+ //Engine.DumpHeap();
};
CityAttack.prototype.getName = function(){
return this.name;
};
CityAttack.prototype.getType = function(){
return this.type;
};
// Returns true if the attack can be executed at the current time
// Basically his checks we have enough units.
// We run a count of our units.
CityAttack.prototype.canStart = function(gameState){
for (unitCat in this.unitStat) {
var Unit = this.unitStat[unitCat];
if (this.unit[unitCat].length < Unit["minSize"])
return false;
}
return true;
// TODO: check if our target is valid and a few other stuffs (good moment to attack?)
};
CityAttack.prototype.isStarted = function(){
if ((this.state !== "unexecuted"))
debug ("Attack plan already started");
return !(this.state == "unexecuted");
};
CityAttack.prototype.isPaused = function(){
return this.paused;
};
CityAttack.prototype.setPaused = function(gameState, boolValue){
if (!this.paused && boolValue === true) {
this.pausingStart = gameState.getTimeElapsed();
this.paused = true;
debug ("Pausing attack plan " +this.name);
} else if (this.paused && boolValue === false) {
this.totalPausingTime += gameState.getTimeElapsed() - this.pausingStart;
this.paused = false;
debug ("Unpausing attack plan " +this.name);
}
};
CityAttack.prototype.mustStart = function(gameState){
if (this.isPaused())
return false;
var MaxReachedEverywhere = true;
for (unitCat in this.unitStat) {
var Unit = this.unitStat[unitCat];
if (this.unit[unitCat].length < Unit["targetSize"]) {
MaxReachedEverywhere = false;
}
}
if (MaxReachedEverywhere)
return true;
return (this.maxPreparationTime + this.timeOfPlanStart + this.totalPausingTime < gameState.getTimeElapsed());
};
// Three returns possible: 1 is "keep going", 0 is "failed plan", 2 is "start"
// 3 is a special case: no valid path returned. Right now I stop attacking alltogether.
CityAttack.prototype.updatePreparation = function(gameState, militaryManager,events) {
var self = this;
if (this.path == undefined || this.target == undefined) {
// find our target
var targets = this.targetFinder(gameState, militaryManager);
if (targets.length === 0){
targets = this.defaultTargetFinder(gameState, militaryManager);
}
if (targets.length) {
+ // picking a target
var rand = Math.floor((Math.random()*targets.length));
this.targetPos = undefined;
var count = 0;
while (!this.targetPos){
this.target = targets.toEntityArray()[rand];
this.targetPos = this.target.position();
count++;
if (count > 1000){
debug("No target with a valid position found");
return false;
}
}
- this.path = this.pathFinder.getPath(this.rallyPoint,this.targetPos, false, 2);
- if (this.path === undefined || this.path[1] === true) {
- return 3;
+ // when we have a target, we path to it.
+ this.path = this.pathFinder.getPath(this.rallyPoint,this.targetPos, 2, 2);//,300000,gameState);
+
+ if (this.path === undefined) {
+ delete this.pathFinder;
+ return 3; // no path.
+ } else if (this.path[1] === true) {
+ // okay so we need a ship.
+ // Basically we'll add it as a new class to train compulsorily, and we'll recompute our path.
+ debug ("We need a ship.");
+ if (!gameState.ai.waterMap)
+ {
+ debug ("This is actually a water map.");
+ gameState.ai.waterMap = true;
+ }
+ this.unitStat["TransportShip"] = { "priority" : 1.1, "minSize" : 2, "targetSize" : 2, "batchSize" : 1, "classes" : ["Warship"], "templates" : [] };
+ if (type === "superSized") {
+ this.unitStat["TransportShip"]["minSize"] = 4;
+ this.unitStat["TransportShip"]["targetSize"] = 4;
+ }
+ var Unit = this.unitStat["TransportShip"];
+ var filter = Filters.and(Filters.byClassesAnd(Unit["classes"]),Filters.and(Filters.byMetadata(PlayerID, "plan",this.name),Filters.byOwner(PlayerID)));
+ this.unit["TransportShip"] = gameState.getOwnEntities().filter(filter);
+ this.unit["TransportShip"].registerUpdates();
+ this.unit["TransportShip"].length;
+ this.buildOrder.push([0, Unit["classes"], this.unit["TransportShip"], Unit, "TransportShip"]);
+ this.needsShip = true;
}
- this.path = this.path[0];
+ this.path = this.path[0].reverse();
+ delete this.pathFinder;
} else if (targets.length == 0 ) {
gameState.ai.gameFinished = true;
debug ("I do not have any target. So I'll just assume I won the game.");
return 0;
}
}
Engine.ProfileStart("Update Preparation");
// keep on while the units finish being trained.
- if (this.mustStart(gameState) && gameState.countOwnQueuedEntitiesWithMetadata("plan", +this.name) ) {
+ if (this.mustStart(gameState) && (gameState.countOwnQueuedEntitiesWithMetadata("plan", +this.name) + this.queue.countTotalQueuedUnits()) > 0 ) {
this.assignUnits(gameState);
if ( (gameState.ai.turn + gameState.ai.player) % 40 == 0) {
this.AllToRallyPoint(gameState, true); // gain some time, start regrouping
- this.unitCollection.forEach(function (entity) { entity.setMetadata("role","attack"); });
}
Engine.ProfileStop();
return 1;
} else if (!this.mustStart(gameState)) {
// We still have time left to recruit units and do stuffs.
// let's sort by training advancement, ie 'current size / target size'
// count the number of queued units too.
// substract priority.
this.buildOrder.sort(function (a,b) { //}) {
var aQueued = gameState.countOwnQueuedEntitiesWithMetadata("special","Plan_"+self.name+"_"+self.buildOrder[0][4]);
aQueued += self.queue.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+self.buildOrder[0][4]);
a[0] = (a[2].length + aQueued)/a[3]["targetSize"];
var bQueued = gameState.countOwnQueuedEntitiesWithMetadata("special","Plan_"+self.name+"_"+self.buildOrder[0][4]);
bQueued += self.queue.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+self.buildOrder[0][4]);
b[0] = (b[2].length + bQueued)/b[3]["targetSize"];
a[0] -= a[3]["priority"];
b[0] -= b[3]["priority"];
return (a[0]) - (b[0]);
});
if (!this.isPaused()) {
this.assignUnits(gameState);
if ( (gameState.ai.turn + gameState.ai.player) % 40 == 0) {
this.AllToRallyPoint(gameState, false);
this.unitCollection.setStance("defensive"); // make sure units won't disperse out of control
}
}
Engine.ProfileStart("Creating units.");
// gets the number in training of the same kind as the first one.
var specialData = "Plan_"+this.name+"_"+this.buildOrder[0][4];
var inTraining = gameState.countOwnQueuedEntitiesWithMetadata("special",specialData);
if (this.queue.countTotalQueuedUnits() + inTraining + this.buildOrder[0][2].length < Math.min(15,this.buildOrder[0][3]["targetSize"]) ) {
if (this.buildOrder[0][0] < 1 && this.queue.length() < 4) {
-
- var template = militaryManager.findBestTrainableUnit(gameState, this.buildOrder[0][1], [ ["strength",1], ["cost",1] ] );
+
+ var template = militaryManager.findBestTrainableUnit(gameState, this.buildOrder[0][1], this.buildOrder[0][3]["interests"] );
//debug ("tried " + uneval(this.buildOrder[0][1]) +", and " + template);
// HACK (TODO replace) : if we have no trainable template... Then we'll simply remove the buildOrder, effectively removing the unit from the plan.
if (template === undefined) {
delete this.unitStat[this.buildOrder[0][4]]; // deleting the associated unitstat.
this.buildOrder.splice(0,1);
-
} else {
+ var max = this.buildOrder[0][3]["batchSize"];
+ if (this.type === "superSized")
+ max *= 2;
if (gameState.getTemplate(template).hasClasses(["CitizenSoldier", "Infantry"]))
- this.queue.addItem( new UnitTrainingPlan(gameState,template, { "role" : "worker", "plan" : this.name, "special" : specialData },this.buildOrder[0][3]["batchSize"] ) );
+ this.queue.addItem( new UnitTrainingPlan(gameState,template, { "role" : "worker", "plan" : this.name, "special" : specialData }, this.buildOrder[0][3]["batchSize"],max ) );
else
- this.queue.addItem( new UnitTrainingPlan(gameState,template, { "role" : "attack", "plan" : this.name, "special" : specialData },this.buildOrder[0][3]["batchSize"] ) );
+ this.queue.addItem( new UnitTrainingPlan(gameState,template, { "role" : "attack", "plan" : this.name, "special" : specialData }, this.buildOrder[0][3]["batchSize"],max ) );
}
}
}
/*
if (!this.startedPathing && this.path === undefined) {
// find our target
var targets = this.targetFinder(gameState, militaryManager);
if (targets.length === 0){
targets = this.defaultTargetFinder(gameState, militaryManager);
}
if (targets.length) {
var rand = Math.floor((Math.random()*targets.length));
this.targetPos = undefined;
var count = 0;
while (!this.targetPos){
var target = targets.toEntityArray()[rand];
this.targetPos = target.position();
count++;
if (count > 1000){
debug("No target with a valid position found");
return false;
}
}
this.startedPathing = true;
// Start pathfinding using the optimized version, with a minimal sampling of 2
this.pathFinder.getPath(this.rallyPoint,this.targetPos, false, 2, gameState);
}
} else if (this.startedPathing) {
var path = this.pathFinder.continuePath(gameState);
if (path !== "toBeContinued") {
this.startedPathing = false;
this.path = path;
debug("Pathing ended");
}
}
*/
// can happen for now
if (this.buildOrder.length === 0) {
debug ("Ending plan: no build orders");
return 0; // will abort the plan, should return something else
}
Engine.ProfileStop();
Engine.ProfileStop();
return 1;
}
+ this.unitCollection.forEach(function (entity) { entity.setMetadata(PlayerID, "role","attack"); });
+
Engine.ProfileStop();
// if we're here, it means we must start (and have no units in training left).
// if we can, do, else, abort.
if (this.canStart(gameState))
return 2;
else
return 0;
return 0;
};
CityAttack.prototype.assignUnits = function(gameState){
var self = this;
// TODO: assign myself units that fit only, right now I'm getting anything.
// Assign all no-roles that fit (after a plan aborts, for example).
var NoRole = gameState.getOwnEntitiesByRole(undefined);
NoRole.forEach(function(ent) {
if (ent.hasClasses(["CitizenSoldier", "Infantry"]))
- ent.setMetadata("role", "worker");
+ ent.setMetadata(PlayerID, "role", "worker");
else
- ent.setMetadata("role", "attack");
- ent.setMetadata("plan", self.name);
+ ent.setMetadata(PlayerID, "role", "attack");
+ ent.setMetadata(PlayerID, "plan", self.name);
});
};
// this sends a unit by ID back to the "rally point"
CityAttack.prototype.ToRallyPoint = function(gameState,id)
{
// Move back to nearest rallypoint
gameState.getEntityById(id).move(this.rallyPoint[0],this.rallyPoint[1]);
}
// this sends all units back to the "rally point" by entity collections.
+// It doesn't disturb ones that could be currently defending, even if the plan is not (yet) paused.
CityAttack.prototype.AllToRallyPoint = function(gameState, evenWorkers) {
var self = this;
if (evenWorkers) {
for (unitCat in this.unit) {
- this.unit[unitCat].move(this.rallyPoint[0],this.rallyPoint[1]);
+ this.unit[unitCat].forEach(function (ent) {
+ if (ent.getMetadata(PlayerID, "role") != "defence" && !ent.hasClass("Warship"))
+ ent.move(self.rallyPoint[0],self.rallyPoint[1]);
+ });
}
} else {
for (unitCat in this.unit) {
this.unit[unitCat].forEach(function (ent) {
- if (ent.getMetadata("role") != "worker")
+ if (ent.getMetadata(PlayerID, "role") != "worker" && ent.getMetadata(PlayerID, "role") != "defence" && !ent.hasClass("Warship"))
ent.move(self.rallyPoint[0],self.rallyPoint[1]);
});
}
}
}
// Default target finder aims for conquest critical targets
CityAttack.prototype.defaultTargetFinder = function(gameState, militaryManager){
var targets = undefined;
- targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings("CivCentre");
+ targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "CivCentre",true);
if (targets.length == 0) {
- targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings("ConquestCritical");
+ targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "ConquestCritical");
}
// If there's nothing, attack anything else that's less critical
if (targets.length == 0) {
- targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings("Town");
+ targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "Town",true);
}
if (targets.length == 0) {
- targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings("Village");
+ targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "Village",true);
}
// no buildings, attack anything conquest critical, even units (it's assuming it won't move).
if (targets.length == 0) {
targets = gameState.getEnemyEntities().filter(Filters.byClass("ConquestCritical"));
}
+ debug ("target is " + targets);
return targets;
};
// tupdate
CityAttack.prototype.raidingTargetFinder = function(gameState, militaryManager, Target){
var targets = undefined;
if (Target == "villager")
{
// let's aim for any resource dropsite. We assume villagers are in the neighborhood (note: the human player could certainly troll us... small (scouting) TODO here.)
targets = gameState.entities.filter(function(ent) {
return (ent.hasClass("Structure") && ent.resourceDropsiteTypes() !== undefined && !ent.hasClass("CivCentre") && ent.owner() === this.targetPlayer && ent.position());
});
if (targets.length == 0) {
targets = gameState.entities.filter(function(ent) {
return (ent.hasClass("CivCentre") && ent.resourceDropsiteTypes() !== undefined && ent.owner() === this.targetPlayer && ent.position());
});
}
if (targets.length == 0) {
// if we're here, it means they also don't have no CC... So I'll just take any building at this point.
targets = gameState.entities.filter(function(ent) {
return (ent.hasClass("Structure") && ent.owner() === this.targetPlayer && ent.position());
});
}
return targets;
} else {
return this.defaultTargetFinder(gameState, militaryManager);
}
};
// Executes the attack plan, after this is executed the update function will be run every turn
// If we're here, it's because we have in our IDlist enough units.
// now the IDlist units are treated turn by turn
CityAttack.prototype.StartAttack = function(gameState, militaryManager){
// check we have a target and a path.
-
if (this.targetPos && this.path !== undefined) {
// erase our queue. This will stop any leftover unit from being trained.
gameState.ai.queueManager.removeQueue("plan_" + this.name);
var curPos = this.unitCollection.getCentrePosition();
- this.unitCollection.forEach(function(ent) { ent.setMetadata("subrole", "attacking"); ent.setMetadata("role", "attack") ;});
+ this.unitCollection.forEach(function(ent) { ent.setMetadata(PlayerID, "subrole", "attacking"); ent.setMetadata(PlayerID, "role", "attack") ;});
// filtering by those that started to attack only
- var filter = Filters.byMetadata("subrole","attacking");
+ var filter = Filters.byMetadata(PlayerID, "subrole","attacking");
this.unitCollection = this.unitCollection.filter(filter);
this.unitCollection.registerUpdates();
//this.unitCollection.length;
for (unitCat in this.unitStat) {
var cat = unitCat;
this.unit[cat] = this.unit[cat].filter(filter);
}
- this.unitCollection.move(this.path[0][0], this.path[0][1]);
+ this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]);
this.unitCollection.setStance("aggressive"); // make sure units won't disperse out of control
- delete this.pathFinder;
-
debug ("Started to attack with the plan " + this.name);
this.state = "walking";
} else {
gameState.ai.gameFinished = true;
debug ("I do not have any target. So I'll just assume I won the game.");
- delete this.pathFinder;
return true;
}
return true;
};
// Runs every turn after the attack is executed
CityAttack.prototype.update = function(gameState, militaryManager, events){
var self = this;
Engine.ProfileStart("Update Attack");
// we're marching towards the target
// Check for attacked units in our band.
var bool_attacked = false;
// raids don't care about attacks much
- // we're over, abort immediately.
- if (this.unitCollection.length === 0)
- return 0;
-
this.position = this.unitCollection.getCentrePosition();
var IDs = this.unitCollection.toIdArray();
// this actually doesn't do anything right now.
if (this.state === "walking") {
var toProcess = {};
var armyToProcess = {};
// Let's check if any of our unit has been attacked. In case yes, we'll determine if we're simply off against an enemy army, a lone unit/builing
// or if we reached the enemy base. Different plans may react differently.
for (var key in events) {
var e = events[key];
if (e.type === "Attacked" && e.msg) {
if (IDs.indexOf(e.msg.target) !== -1) {
var attacker = gameState.getEntityById(e.msg.attacker);
var ourUnit = gameState.getEntityById(e.msg.target);
- if (attacker && attacker.position() && attacker.hasClass("Unit") && attacker.owner() != 0 && attacker.owner() != gameState.player) {
+ if (attacker && attacker.position() && attacker.hasClass("Unit") && attacker.owner() != 0 && attacker.owner() != PlayerID) {
var territoryMap = Map.createTerritoryMap(gameState);
if ( +territoryMap.point(attacker.position()) - 64 === +this.targetPlayer)
{
debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination.");
// we must assume we've arrived at the end of the trail.
this.state = "arrived";
}
//if (militaryManager.enemyWatchers[attacker.owner()]) {
//toProcess[attacker.id()] = attacker;
//var armyID = militaryManager.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id());
//armyToProcess[armyID[0]] = armyID[1];
//}
}
// if we're being attacked by a building, flee.
if (attacker && ourUnit && attacker.hasClass("Structure")) {
ourUnit.flee(attacker);
}
}
}
}
// I don't process attacks if I'm in their base because I'll have already gone to "attacking" mode.
// I'll process by army
var total = 0;
for (armyID in armyToProcess) {
total += armyToProcess[armyID].length;
// TODO: if it's a big army, we may want to refer the scouting/defense manager
}
/*
}&& this.type !== "harass_raid"){ // walking toward the target
var sumAttackerPos = [0,0];
var numAttackers = 0;
// let's check if one of our unit is not under attack, by any chance.
for (var key in events){
var e = events[key];
if (e.type === "Attacked" && e.msg){
if (this.unitCollection.toIdArray().indexOf(e.msg.target) !== -1){
var attacker = HeadQuarters.entity(e.msg.attacker);
if (attacker && attacker.position()){
sumAttackerPos[0] += attacker.position()[0];
sumAttackerPos[1] += attacker.position()[1];
numAttackers += 1;
bool_attacked = true;
// todo: differentiate depending on attacker type... If it's a ship, let's not do anythin, a building, depends on the attack type/
if (this.threatList.indexOf(e.msg.attacker) === -1)
{
var enemySoldiers = HeadQuarters.getEnemySoldiers().toEntityArray();
for (j in enemySoldiers)
{
var enemy = enemySoldiers[j];
if (enemy.position() === undefined) // likely garrisoned
continue;
if (inRange(enemy.position(), attacker.position(), 1000) && this.threatList.indexOf(enemy.id()) === -1)
this.threatList.push(enemy.id());
}
this.threatList.push(e.msg.attacker);
}
}
}
}
}
if (bool_attacked > 0){
var avgAttackerPos = [sumAttackerPos[0]/numAttackers, sumAttackerPos[1]/numAttackers];
units.move(avgAttackerPos[0], avgAttackerPos[1]); // let's run towards it.
this.tactics = new Tactics(gameState,HeadQuarters, this.idList,this.threatList,true);
this.state = "attacking_threat";
}
}else if (this.state === "attacking_threat"){
this.tactics.eventMetadataCleanup(events,HeadQuarters);
var removeList = this.tactics.removeTheirDeads(HeadQuarters);
this.tactics.removeMyDeads(HeadQuarters);
for (var i in removeList){
this.threatList.splice(this.threatList.indexOf(removeList[i]),1);
}
if (this.threatList.length <= 0)
{
this.tactics.disband(HeadQuarters,events);
this.tactics = undefined;
this.state = "walking";
units.move(this.path[0][0], this.path[0][1]);
}else
{
this.tactics.reassignAttacks(HeadQuarters);
}
}*/
}
if (this.state === "walking"){
+
+ this.position = this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition();
+
+ if (SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) {
+ this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).moveIndiv(this.path[0][0][0], this.path[0][0][1]);
+ }
+ if (SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.path[0][0]) < 600) {
+ // okay so here basically two cases. The first one is "we need a boat at this point".
+ // the second one is "we need to unload at this point". The third is "normal".
+ if (this.path[0][1] !== true)
+ {
+ this.path.shift();
+ if (this.path.length > 0){
+ this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).moveIndiv(this.path[0][0][0], this.path[0][0][1]);
+ } else {
+ debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination.");
+ // we must assume we've arrived at the end of the trail.
+ this.state = "arrived";
+ }
+ } else if (this.path[0][1] === true)
+ {
+ // okay we must load our units.
+ // check if we have some kind of ships.
+ var ships = this.unitCollection.filter(Filters.byClass("Warship"));
+ if (ships.length === 0)
+ return 0; // abort
+
+ debug ("switch to boarding");
+ this.state = "boarding";
+ }
+ }
+ } else if (this.state === "shipping") {
+ this.position = this.unitCollection.filter(Filters.byClass("Warship")).getCentrePosition();
+
if (SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) {
- this.unitCollection.move(this.path[0][0], this.path[0][1]);
+ this.unitCollection.filter(Filters.byClass("Warship")).moveIndiv(this.path[0][0][0], this.path[0][0][1]);
}
- if (SquareVectorDistance(this.unitCollection.getCentrePosition(), this.path[0]) < 900){
+ if (SquareVectorDistance(this.position, this.path[0][0]) < 1600) {
+ if (this.path[0][1] !== true)
+ {
+ this.path.shift();
+ if (this.path.length > 0){
+ this.unitCollection.filter(Filters.byClass("Warship")).moveIndiv(this.path[0][0][0], this.path[0][0][1]);
+ } else {
+ debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination, but it's still on the shipā¦");
+ return 0; // abort
+ }
+ } else if (this.path[0][1] === true)
+ {
+ debug ("switch to unboarding");
+ // we unload
+ this.state = "unboarding";
+ }
+ }
+ } else if (this.state === "boarding") {
+ this.position = this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition();
+
+ var ships = this.unitCollection.filter(Filters.byClass("Warship"));
+ if (ships.length === 0)
+ return 0; // abort
+
+ var globalPos = this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition();
+ var shipPos = ships.getCentrePosition();
+
+ if (globalPos !== undefined && SquareVectorDistance(globalPos,shipPos) > 800)
+ { // get them closer
+ ships.moveIndiv(globalPos[0],globalPos[1]);
+ this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).moveIndiv(shipPos[0],shipPos[1]);
+ } else {
+ // okay try to garrison.
+ var shipsArray = ships.toEntityArray();
+ this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).forEach(function (ent) { //}){
+ if (ent.position()) // if we're not garrisoned
+ for (var shipId = 0; shipId < shipsArray.length; shipId++) {
+ if (shipsArray[shipId].garrisoned().length < shipsArray[shipId].garrisonMax())
+ {
+ ent.garrison(shipsArray[shipId]);
+ break;
+ }
+ }
+ });
+ var garrLength = 0;
+ for (var shipId = 0; shipId < shipsArray.length; shipId++)
+ garrLength += shipsArray[shipId].garrisoned().length;
+
+ if (garrLength == this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).length) {
+ // okay.
+ this.path.shift();
+ if (this.path.length > 0){
+ ships.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
+ debug ("switch to shipping");
+ this.state = "shipping";
+ } else {
+ debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination.");
+ // we must assume we've arrived at the end of the trail.
+ this.state = "arrived";
+ }
+ }
+ }
+ } else if (this.state === "unboarding") {
+
+ var ships = this.unitCollection.filter(Filters.byClass("Warship"));
+ if (ships.length === 0)
+ return 0; // abort
+
+ this.position = ships.getCentrePosition();
+
+ // the procedure is pretty simple: we move the ships to the next point and try to unload until all units are over.
+ // TODO: make it better, like avoiding collisions, and so on.
+
+ if (this.path.length > 1)
+ ships.moveIndiv(this.path[1][0][0], this.path[1][0][1]);
+
+ ships.forEach(function (ship) {
+ ship.unloadAll();
+ });
+
+ var shipsArray = ships.toEntityArray();
+ var garrLength = 0;
+ for (var shipId = 0; shipId < shipsArray.length; shipId++)
+ garrLength += shipsArray[shipId].garrisoned().length;
+
+ if (garrLength == 0) {
+ // release the ships
+
+ ships.forEach(function (ent) {
+ ent.setMetadata(PlayerID, "role",undefined);
+ ent.setMetadata(PlayerID, "subrole",undefined);
+ ent.setMetadata(PlayerID, "plan",undefined);
+ });
+ for (var shipId = 0; shipId < shipsArray.length; shipId++)
+ this.unitCollection.removeEnt(shipsArray[shipId]);
+
this.path.shift();
if (this.path.length > 0){
- this.unitCollection.move(this.path[0][0], this.path[0][1]);
+ this.unitCollection.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
+ debug ("switch to walking");
+ this.state = "walking";
} else {
debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination.");
// we must assume we've arrived at the end of the trail.
this.state = "arrived";
}
}
}
+
+
// todo: re-implement raiding
if (this.state === "arrived"){
// let's proceed on with whatever happens now.
// There's a ton of TODOs on this part.
if (this.onArrivalReaction == "proceedOnTargets") {
this.state = "";
this.unitCollection.forEach( function (ent) { //}) {
ent.stopMoving();
});
} else if (this.onArrivalReaction == "huntVillagers") {
// let's get any villager and target them with a tactics manager
var enemyCitizens = gameState.entities.filter(function(ent) {
return (gameState.isEntityEnemy(ent) && ent.hasClass("Support") && ent.owner() !== 0 && ent.position());
});
var targetList = [];
enemyCitizens.forEach( function (enemy) {
if (inRange(enemy.position(), units.getCentrePosition(), 2500) && targetList.indexOf(enemy.id()) === -1)
targetList.push(enemy.id());
});
if (targetList.length > 0)
{
this.tactics = new Tactics(gameState,HeadQuarters, this.idList,targetList);
this.state = "huntVillagers";
} else {
this.state = "";
}
}
}
if (this.state === "" && gameState.ai.playedTurn % 3 === 0) {
// Each unit will randomly pick a target and attack it and then they'll do what they feel like doing for now. TODO
// only the targeted enemy. I've seen the units attack gazelles otherwise.
var enemyUnits = gameState.getEnemyEntities().filter(Filters.and(Filters.byOwner(this.targetPlayer), Filters.byClass("Unit")));
var enemyStructures = gameState.getEnemyEntities().filter(Filters.and(Filters.byOwner(this.targetPlayer), Filters.byClass("Structure")));
this.unitCollection.forEach( function (ent) { //}) {
if (ent.isIdle()) {
var mStruct = enemyStructures.filter(function (enemy) {// }){
if (!enemy.position()) {
return false;
}
- if (SquareVectorDistance(enemy.position(),ent.position()) > ent.visionRange()*ent.visionRange() + 100) {
+ if (SquareVectorDistance(enemy.position(),ent.position()) > ent.visionRange()*ent.visionRange() + 300) {
return false;
}
return true;
});
var mUnit = enemyUnits.filter(function (enemy) {// }){
if (!enemy.position()) {
return false;
}
- if (SquareVectorDistance(enemy.position(),ent.position()) > ent.visionRange()*ent.visionRange() + 100) {
+ if (SquareVectorDistance(enemy.position(),ent.position()) > ent.visionRange()*ent.visionRange() + 300) {
return false;
}
return true;
});
mUnit = mUnit.toEntityArray();
mStruct = mStruct.toEntityArray();
+ mStruct.sort(function (struct) {
+ if (struct.hasClass("ConquestCritical"))
+ return 100 + struct.costSum();
+ else
+ return struct.costSum();
+ })
if (ent.hasClass("Siege")) {
if (mStruct.length !== 0) {
- var rand = Math.floor(Math.random() * mStruct.length*0.99);
+ var rand = Math.floor(Math.random() * mStruct.length*0.2);
ent.attack(mStruct[+rand].id());
//debug ("Siege units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName());
} else if (SquareVectorDistance(self.targetPos, ent.position()) > 900 ){
//debug ("Siege units moving to " + uneval(self.targetPos));
ent.move((self.targetPos[0] + ent.position()[0])/2,(self.targetPos[1] + ent.position()[1])/2);
}
} else {
if (mUnit.length !== 0) {
var rand = Math.floor(Math.random() * mUnit.length*0.99);
ent.attack(mUnit[(+rand)].id());
//debug ("Units attacking a unit from " +mUnit[+rand].owner() + " , " +mUnit[+rand].templateName());
} else if (mStruct.length !== 0) {
var rand = Math.floor(Math.random() * mStruct.length*0.99);
ent.attack(mStruct[+rand].id());
//debug ("Units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName());
} else if (SquareVectorDistance(self.targetPos, ent.position()) > 900 ){
//debug ("Units moving to " + uneval(self.targetPos));
ent.move((self.targetPos[0] + ent.position()[0])/2,(self.targetPos[1] + ent.position()[1])/2);
}
}
}
});
}
/*
if (this.state === "huntVillagers")
{
this.tactics.eventMetadataCleanup(events,HeadQuarters);
this.tactics.removeTheirDeads(HeadQuarters);
this.tactics.removeMyDeads(HeadQuarters);
if (this.tactics.isBattleOver())
{
this.tactics.disband(HeadQuarters,events);
this.tactics = undefined;
this.state = "";
return 0; // assume over
} else
this.tactics.reassignAttacks(HeadQuarters);
}*/
this.lastPosition = this.position;
Engine.ProfileStop();
return this.unitCollection.length;
};
CityAttack.prototype.totalCountUnits = function(gameState){
var totalcount = 0;
for (i in this.idList)
{
totalcount++;
}
return totalcount;
};
// reset any units
CityAttack.prototype.Abort = function(gameState){
this.unitCollection.forEach(function(ent) {
- ent.setMetadata("role",undefined);
- ent.setMetadata("subrole",undefined);
- ent.setMetadata("plan",undefined);
+ ent.setMetadata(PlayerID, "role",undefined);
+ ent.setMetadata(PlayerID, "subrole",undefined);
+ ent.setMetadata(PlayerID, "plan",undefined);
});
for (unitCat in this.unitStat) {
delete this.unitStat[unitCat];
delete this.unit[unitCat];
}
delete this.unitCollection;
gameState.ai.queueManager.removeQueue("plan_" + this.name);
};
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/config.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/config.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/config.js (revision 13225)
@@ -1,64 +1,127 @@
+// Baseconfig is the highest difficulty.
var baseConfig = {
- "attack" : {
- "minAttackSize" : 20, // attackMoveToLocation
- "maxAttackSize" : 60, // attackMoveToLocation
- "enemyRatio" : 1.5, // attackMoveToLocation
- "groupSize" : 10 // military
+ "Military" : {
+ "fortressStartTime" : 840, // Time to wait before building one fortress.
+ "fortressLapseTime" : 300, // Time to wait between building 2 fortresses (minimal)
+ "defenceBuildingTime" : 300, // Time to wait before building towers or fortresses
+ "advancedMilitaryStartTime" : 720, // Time to wait before building advanced military buildings. Also limited by phase 2.
+ "attackPlansStartTime" : 0 // time to wait before attacking. Start as soon as possible (first barracks)
},
+ "Economy" : {
+ "townPhase" : 180, // time to start trying to reach town phase (might be a while after. Still need the requirements + ress )
+ "cityPhase" : 540, // time to start trying to reach city phase
+ "farmsteadStartTime" : 240, // Time to wait before building a farmstead.
+ "marketStartTime" : 620, // Time to wait before building the market.
+ "dockStartTime" : 240, // Time to wait before building the dock
+ "techStartTime" : 600, // time to wait before teching.
+ "targetNumBuilders" : 1.5, // Base number of builders per foundation. Later updated, but this remains a multiplier.
+ "femaleRatio" : 0.4 // percent of females among the workforce.
+ },
+
+ // Note: attack settings are set directly in attack_plan.js
// defence
- "defence" : {
- "acquireDistance" : 220,
- "releaseDistance" : 250,
- "groupRadius" : 20,
- "groupBreakRadius" : 40,
- "groupMergeRadius" : 10,
- "defenderRatio" : 2
+ "Defence" : {
+ "defenceRatio" : 3, // see defence.js for more info.
+ "armyCompactSize" : 700, // squared. Half-diameter of an army.
+ "armyBreakawaySize" : 900 // squared.
},
// military
"buildings" : {
"moderate" : {
"default" : [ "structures/{civ}_barracks" ]
},
"advanced" : {
"hele" : [ "structures/{civ}_gymnasion", "structures/{civ}_fortress" ],
"athen" : [ "structures/{civ}_gymnasion", "structures/{civ}_fortress" ],
"spart" : [ "structures/{civ}_syssiton", "structures/{civ}_fortress" ],
"mace" : [ "structures/{civ}_fortress" ],
"cart" : [ "structures/{civ}_fortress", "structures/{civ}_embassy_celtic",
"structures/{civ}_embassy_iberian", "structures/{civ}_embassy_italiote" ],
"celt" : [ "structures/{civ}_kennel", "structures/{civ}_fortress_b", "structures/{civ}_fortress_g" ],
"iber" : [ "structures/{civ}_fortress" ],
"pers" : [ "structures/{civ}_fortress", "structures/{civ}_stables", "structures/{civ}_apadana" ],
"rome" : [ "structures/{civ}_army_camp", "structures/{civ}_fortress" ],
"maur" : [ "structures/{civ}_elephant_stables", "structures/{civ}_fortress" ]
},
"fort" : {
"default" : [ "structures/{civ}_fortress" ],
"celt" : [ "structures/{civ}_fortress_b", "structures/{civ}_fortress_g" ]
}
},
// qbot
"priorities" : { // Note these are dynamic, you are only setting the initial values
- "house" : 500,
- "citizenSoldier" : 65,
- "villager" : 95,
- "economicBuilding" : 95,
- "field" : 20,
- "advancedSoldier" : 30,
- "siege" : 10,
- "militaryBuilding" : 90,
+ "house" : 250,
+ "citizenSoldier" : 50,
+ "villager" : 60,
+ "economicBuilding" : 80,
+ "dropsites" : 180,
+ "field" : 500,
+ "militaryBuilding" : 120,
"defenceBuilding" : 17,
- "civilCentre" : 1000
+ "majorTech" : 100,
+ "minorTech" : 40,
+ "civilCentre" : 10000 // will hog all resources
},
-
+ "difficulty" : 2, // for now 2 is "hard", ie default. 1 is normal, 0 is easy.
"debug" : false
};
var Config = {
- "debug": false
+ "debug": true,
+ "difficulty" : 2
};
-Config.__proto__ = baseConfig;
\ No newline at end of file
+Config.__proto__ = baseConfig;
+
+// changing settings based on difficulty.
+
+if (Config.difficulty === 1)
+{
+ Config["Military"] = {
+ "fortressStartTime" : 1000,
+ "fortressLapseTime" : 400,
+ "defenceBuildingTime" : 350,
+ "advancedMilitaryStartTime" : 1000,
+ "attackPlansStartTime" : 600
+ };
+ Config["Economy"] = {
+ "townPhase" : 240,
+ "cityPhase" : 660,
+ "farmsteadStartTime" : 600,
+ "marketStartTime" : 800,
+ "techStartTime" : 1320,
+ "targetNumBuilders" : 2,
+ "femaleRatio" : 0.5
+ };
+ Config["Defence"] = {
+ "defenceRatio" : 2.0,
+ "armyCompactSize" : 700,
+ "armyBreakawaySize" : 900
+ };
+} else if (Config.difficulty === 0)
+{
+ Config["Military"] = {
+ "fortressStartTime" : 1500,
+ "fortressLapseTime" : 1000000,
+ "defenceBuildingTime" : 500,
+ "advancedMilitaryStartTime" : 1300,
+ "attackPlansStartTime" : 1200 // 20 minutes ought to give enough times for beginners
+ };
+ Config["Economy"] = {
+ "townPhase" : 360,
+ "cityPhase" : 840,
+ "farmsteadStartTime" : 1200,
+ "marketStartTime" : 1000,
+ "techStartTime" : 600000, // never
+ "targetNumBuilders" : 1,
+ "femaleRatio" : 0.0
+ };
+ Config["Defence"] = {
+ "defenceRatio" : 1.0,
+ "armyCompactSize" : 700,
+ "armyBreakawaySize" : 900
+ };
+}
\ No newline at end of file
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/data.json
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/data.json (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/data.json (revision 13225)
@@ -1,5 +1,6 @@
{
"name": "Aegis Bot",
- "description": "Improvements over qBot by Wraitii. Still experimental(bugs possible).\nThis bot may be harder to defeat than the regular qBot. Please report any problems on the Wildfire forums.",
- "constructor": "QBotAI"
+ "description": "An WIP AI improving upon qBot.\nThis bot is stronger than qBot, use the former if you're a beginner.\nPlease report any problems on the Wildfire forums.",
+ "constructor": "QBotAI",
+ "useShared": true
}
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/defence.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/defence.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/defence.js (revision 13225)
@@ -1,562 +1,703 @@
// directly imported from Marilyn, with slight modifications to work with qBot.
function Defence(){
- this.defenceRatio = 1.8; // How many defenders we want per attacker. Need to balance fewer losses vs. lost economy
- // note: the choice should be a no-brainer most of the time: better deflect the attack.
+ this.defenceRatio = Config.Defence.defenceRatio;// How many defenders we want per attacker. Need to balance fewer losses vs. lost economy
+ // note: the choice should be a no-brainer most of the time: better deflect the attack.
+ // This is also sometimes forcebly overcome by the defense manager.
+ this.armyCompactSize = Config.Defence.armyCompactSize; // a bit more than 40 wide in diameter
+ this.armyBreakawaySize = Config.Defence.armyBreakawaySize; // a bit more than 45 wide in diameter
this.totalAttackNb = 0; // used for attack IDs
this.attacks = [];
this.toKill = [];
+
// keeps a list of targeted enemy at instant T
+ this.enemyArmy = {}; // array of players, storing for each an array of armies.
this.attackerCache = {};
this.listOfEnemies = {};
this.listedEnemyCollection = null; // entity collection of this.listOfEnemies
+ // Some Stats
+ this.nbAttackers = 0;
+ this.nbDefenders = 0;
+
+ // Caching variables
+ this.totalArmyNB = 0;
+ this.enemyUnits = {};
+ this.enemyArmyLoop = {};
// boolean 0/1 that's for optimization
this.attackerCacheLoopIndicator = 0;
// this is a list of units to kill. They should be gaia animals, or lonely units. Works the same as listOfEnemies, ie an entityColelction which I'll have to cleanup
this.listOfWantedUnits = {};
this.WantedUnitsAttacker = {}; // same as attackerCache.
this.defenders = null;
this.idleDefs = null;
}
// DO NOTE: the Defence manager, when it calls for Defence, makes the military manager go into "Defence mode"... This makes it not update any plan that's started or not.
// This allows the Defence manager to take units from the plans for Defence.
// Defcon levels
// 5: no danger whatsoever detected
// 4: a few enemy units are being dealt with, but nothing too dangerous.
// 3: A reasonnably sized enemy army is being dealt with, but it should not be a problem.
// 2: A big enemy army is in the base, but we are not outnumbered
// 1: Huge army in the base, outnumbering us.
Defence.prototype.update = function(gameState, events, militaryManager){
Engine.ProfileStart("Defence Manager");
-
+
// a litlle cache-ing
if (!this.idleDefs) {
- var filter = Filters.and(Filters.byMetadata("role", "defence"), Filters.isIdle());
+ var filter = Filters.and(Filters.byMetadata(PlayerID, "role", "defence"), Filters.isIdle());
this.idleDefs = gameState.getOwnEntities().filter(filter);
this.idleDefs.registerUpdates();
}
if (!this.defenders) {
- var filter = Filters.byMetadata("role", "defence");
+ var filter = Filters.byMetadata(PlayerID, "role", "defence");
this.defenders = gameState.getOwnEntities().filter(filter);
this.defenders.registerUpdates();
}
- if (!this.listedEnemyCollection) {
- var filter = Filters.byMetadata("listed-enemy", true);
+ /*if (!this.listedEnemyCollection) {
+ var filter = Filters.byMetadata(PlayerID, "listed-enemy", true);
this.listedEnemyCollection = gameState.getEnemyEntities().filter(filter);
this.listedEnemyCollection.registerUpdates();
}
this.myBuildings = gameState.getOwnEntities().filter(Filters.byClass("Structure")).toEntityArray();
this.myUnits = gameState.getOwnEntities().filter(Filters.byClass("Unit"));
+ */
+ var filter = Filters.and(Filters.byClassesOr(["CitizenSoldier", "Hero", "Champion", "Siege"]), Filters.byOwner(PlayerID));
+ this.myUnits = gameState.updatingGlobalCollection("player-" +PlayerID + "-soldiers", filter);
+ filter = Filters.and(Filters.byClass("Structure"), Filters.byOwner(PlayerID));
+ this.myBuildings = gameState.updatingGlobalCollection("player-" +PlayerID + "-structures", filter);
+
this.territoryMap = Map.createTerritoryMap(gameState); // used by many func
// First step: we deal with enemy armies, those are the highest priority.
- this.defendFromEnemyArmies(gameState, events, militaryManager);
-
+ this.defendFromEnemies(gameState, events, militaryManager);
+
// second step: we loop through messages, and sort things as needed (dangerous buildings, attack by animals, ships, lone units, whatever).
- // TODO
+ // TODO : a lot.
this.MessageProcess(gameState,events,militaryManager);
this.DealWithWantedUnits(gameState,events,militaryManager);
+ var self = this;
// putting unneeded units at rest
this.idleDefs.forEach(function(ent) {
- if (ent.getMetadata("formerrole"))
- ent.setMetadata("role", ent.getMetadata("formerrole") );
+ if (ent.getMetadata(PlayerID, "formerrole"))
+ ent.setMetadata(PlayerID, "role", ent.getMetadata(PlayerID, "formerrole") );
else
- ent.setMetadata("role", "worker");
- ent.setMetadata("subrole", undefined);
+ ent.setMetadata(PlayerID, "role", "worker");
+ ent.setMetadata(PlayerID, "subrole", undefined);
+ self.nbDefenders--;
});
Engine.ProfileStop();
return;
};
+/*
// returns armies that are still seen as dangerous (in the LOS of any of my buildings for now)
Defence.prototype.reevaluateDangerousArmies = function(gameState, armies) {
var stillDangerousArmies = {};
for (i in armies) {
var pos = armies[i].getCentrePosition();
- if (armies[i].getCentrePosition() && +this.territoryMap.point(armies[i].getCentrePosition()) - 64 === +gameState.player) {
+ if (pos === undefined)
+
+ if (+this.territoryMap.point(pos) - 64 === +PlayerID) {
stillDangerousArmies[i] = armies[i];
continue;
}
for (o in this.myBuildings) {
// if the armies out of my buildings LOS (with a little more, because we're cheating right now and big armies could go undetected)
if (inRange(pos, this.myBuildings[o].position(),this.myBuildings[o].visionRange()*this.myBuildings[o].visionRange() + 2500)) {
stillDangerousArmies[i] = armies[i];
break;
}
}
}
return stillDangerousArmies;
}
// returns armies we now see as dangerous, ie in my territory
Defence.prototype.evaluateArmies = function(gameState, armies) {
var DangerousArmies = {};
for (i in armies) {
- if (armies[i].getCentrePosition() && +this.territoryMap.point(armies[i].getCentrePosition()) - 64 === +gameState.player) {
+ if (armies[i].getCentrePosition() && +this.territoryMap.point(armies[i].getCentrePosition()) - 64 === +PlayerID) {
DangerousArmies[i] = armies[i];
}
}
return DangerousArmies;
+}*/
+// Incorporates an entity in an army. If no army fits, it creates a new one around this one.
+// an army is basically an entity collection.
+Defence.prototype.armify = function(gameState, entity, militaryManager) {
+ if (entity.position() === undefined)
+ return;
+ if (this.enemyArmy[entity.owner()] === undefined)
+ {
+ this.enemyArmy[entity.owner()] = {};
+ } else {
+ for (armyIndex in this.enemyArmy[entity.owner()])
+ {
+ var army = this.enemyArmy[entity.owner()][armyIndex];
+ if (army.getCentrePosition() === undefined)
+ {
+ } else {
+ if (SquareVectorDistance(army.getCentrePosition(), entity.position()) < this.armyCompactSize)
+ {
+ entity.setMetadata(PlayerID, "inArmy", armyIndex);
+ army.addEnt(entity);
+ return;
+ }
+ }
+ }
+ }
+ // if we're here, we need to create an army for it, and freeze it to make sure no unit will be added automatically
+ var newArmy = new EntityCollection(gameState.sharedScript, {}, [Filters.byOwner(entity.owner())]);
+ newArmy.addEnt(entity);
+ newArmy.freeze();
+ newArmy.registerUpdates();
+ entity.setMetadata(PlayerID, "inArmy", this.totalArmyNB);
+ this.enemyArmy[entity.owner()][this.totalArmyNB] = newArmy;
+
+ if (militaryManager)
+ {
+ var self = this;
+ militaryManager.enemyWatchers[entity.owner()].enemySoldiers.forEach(function (ent) { //}){
+ if (ent.position() !== undefined && SquareVectorDistance(entity.position(), ent.position()) < self.armyCompactSize)
+ {
+ ent.setMetadata(PlayerID, "inArmy", self.totalArmyNB);
+ self.enemyArmy[ent.owner()][self.totalArmyNB].addEnt(ent);
+ }
+ });
+ }
+ this.totalArmyNB++;
+
+ return;
+}
+// Returns if a unit should be seen as dangerous or not.
+Defence.prototype.evaluateRawEntity = function(gameState, entity) {
+ if (entity.position && +this.territoryMap.point(entity.position) - 64 === +PlayerID && entity._template.Attack !== undefined)
+ return true;
+ return false;
+}
+Defence.prototype.evaluateEntity = function(gameState, entity) {
+ if (entity.position() && +this.territoryMap.point(entity.position()) - 64 === +PlayerID && entity.attackTypes() !== undefined)
+ return true;
+ return false;
}
// This deals with incoming enemy armies, setting the defcon if needed. It will take new soldiers, and assign them to attack
-// it's still a fair share of dumb, so TODO improve
-Defence.prototype.defendFromEnemyArmies = function(gameState, events, militaryManager) {
+// TODO: still is still pretty dumb, it could use improvements.
+Defence.prototype.defendFromEnemies = function(gameState, events, militaryManager) {
+ var self = this;
- // The enemy Watchers keep a list of armies. This class here tells them if an army is dangerous, and they manage the merging/splitting/disbanding.
- // With this system, we can get any dangerous armies. Thus, we can know where the danger is, and react.
- // So Defence deals with attacks from animals too (which aren't watched).
- // The attackrs here are dealt with on a per unit basis.
+ // New, faster system will loop for enemy soldiers, and also females on occasions ( TODO )
+ // if a dangerous unit is found, it will check for neighbors and make them into an "army", an entityCollection
+ // > updated against owner, for the day when I throw healers in the deal.
+ // armies are checked against each other now and then to see if they should be merged, and units in armies are checked to see if they should be taken away from the army.
// We keep a list of idle defenders. For any new attacker, we'll check if we have any idle defender available, and if not, we assign available units.
// At the end of each turn, if we still have idle defenders, we either assign them to neighboring units, or we release them.
- var dangerArmies = {};
- this.enemyUnits = {};
-
- // for now armies are never seen as "no longer dangerous"... TODO
- for (enemyID in militaryManager.enemyWatchers) {
- this.enemyUnits[enemyID] = militaryManager.enemyWatchers[enemyID].getAllEnemySoldiers();
-
- var dangerousArmies = militaryManager.enemyWatchers[enemyID].getDangerousArmies();
- // we check if all the dangerous armies are still dangerous.
- var newDangerArmies = this.reevaluateDangerousArmies(gameState,dangerousArmies);
-
- var safeArmies = militaryManager.enemyWatchers[enemyID].getSafeArmies();
- // we check not dangerous armies, to see if they suddenly became dangerous
- var unsafeArmies = this.evaluateArmies(gameState,safeArmies);
- for (i in unsafeArmies)
- newDangerArmies[i] = unsafeArmies[i];
-
- // and any dangerous armies we push in "dangerArmies"
- militaryManager.enemyWatchers[enemyID].resetDangerousArmies();
- for (o in newDangerArmies)
- militaryManager.enemyWatchers[enemyID].setAsDangerous(o);
-
- for (i in newDangerArmies)
- dangerArmies[i] = newDangerArmies[i];
- }
-
- var self = this;
-
- var nbOfAttackers = 0;
-
+ var nbOfAttackers = 0; // actually new attackers.
var newEnemies = [];
- // clean up before adding new units (slight speeding up, since new units can't already be dead)
- for (i in this.listOfEnemies) {
- if (this.listOfEnemies[i].length === 0) {
- // if we had defined the attackerCache, ie if we had tried to attack this unit.
- if (this.attackerCache[i] !== undefined) {
- this.attackerCache[i].forEach(function(ent) { ent.stopMoving(); });
- delete this.attackerCache[i];
+
+ // clean up using events.
+ for each(evt in events)
+ {
+ if (evt.type == "Destroy")
+ {
+ if (this.listOfEnemies[evt.msg.entity] !== undefined)
+ {
+ if (this.attackerCache[evt.msg.entity] !== undefined) {
+ this.attackerCache[evt.msg.entity].forEach(function(ent) { ent.stopMoving(); });
+ delete self.attackerCache[evt.msg.entity];
+ }
+ delete this.listOfEnemies[evt.msg.entity];
+ this.nbAttackers--;
}
- delete this.listOfEnemies[i];
- } else {
- var unit = this.listOfEnemies[i].toEntityArray()[0];
- var enemyWatcher = militaryManager.enemyWatchers[unit.owner()];
- if (enemyWatcher.isPartOfDangerousArmy(unit.id())) {
- nbOfAttackers++;
- if (this.attackerCache[unit.id()].length == 0) {
- newEnemies.push(unit);
+ }
+ }
+
+ // Optimizations: this will slowly iterate over all units (saved at an instant T) and all armies.
+ // It'll add new units if they are now dangerous and were not before
+ // It'll also deal with cleanup of armies.
+ // When it's finished it'll start over.
+ for (enemyID in this.enemyArmy)
+ {
+ //this.enemyUnits[enemyID] = militaryManager.enemyWatchers[enemyID].getAllEnemySoldiers();
+ if (this.enemyUnits[enemyID] === undefined || this.enemyUnits[enemyID].length === 0)
+ {
+ this.enemyUnits[enemyID] = militaryManager.enemyWatchers[enemyID].enemySoldiers.toEntityArray();
+ continue;
+ }
+ // we have some units still to check in this array. Check 15 (TODO: DIFFLEVEL)
+ // Note: given the way memory works, if the entity has been recently deleted, its reference may still exist.
+ // and this.enemyUnits[enemyID][0] may still point to that reference, "reviving" the unit.
+ // So we've got to make sure it's not supposed to be dead.
+ for (var check = 0; check < 15; check++)
+ {
+ if (this.enemyUnits[enemyID].length > 0 && gameState.getEntityById(this.enemyUnits[enemyID][0].id()) !== undefined)
+ {
+ if (this.enemyUnits[enemyID][0].getMetadata(PlayerID, "inArmy") !== undefined)
+ {
+ this.enemyUnits[enemyID].splice(0,1);
+ continue;
}
- } else {
- // if we had defined the attackerCache, ie if we had tried to attack this unit.
- if (this.attackerCache[unit.id()] != undefined) {
- this.attackerCache[unit.id()].forEach(function(ent) { ent.stopMoving(); });
- delete this.attackerCache[unit.id()];
+ var dangerous = this.evaluateEntity(gameState, this.enemyUnits[enemyID][0]);
+ if (dangerous)
+ {
+ this.armify(gameState, this.enemyUnits[enemyID][0], militaryManager);
}
- this.listOfEnemies[unit.id()].toEntityArray()[0].setMetadata("listed-enemy",undefined);
- delete this.listOfEnemies[unit.id()];
+ this.enemyUnits[enemyID].splice(0,1);
}
}
- }
- // okay so now, for every dangerous armies, we loop.
- for (armyID in dangerArmies) {
- // looping through army units
- dangerArmies[armyID].forEach(function(ent) {
- // do we have already registered an entityCollection for it?
- if (self.listOfEnemies[ent.id()] === undefined) {
- // no, we register a new entity collection in listOfEnemies, listing exactly one unit as long as it remains alive and owned by my enemy.
- // can't be bothered to recode everything
- var owner = ent.owner();
- var filter = Filters.and(Filters.byOwner(owner),Filters.byID(ent.id()));
- self.listOfEnemies[ent.id()] = self.enemyUnits[owner].filter(filter);
- self.listOfEnemies[ent.id()].registerUpdates();
- self.listOfEnemies[ent.id()].length;
- self.listOfEnemies[ent.id()].toEntityArray()[0].setMetadata("listed-enemy",true);
-
- // let's also register an entity collection for units attacking this unit (so we can new if it's attacked)
- filter = Filters.and(Filters.byOwner(gameState.player),Filters.byTargetedEntity(ent.id()));
- self.attackerCache[ent.id()] = self.myUnits.filter(filter);
- self.attackerCache[ent.id()].registerUpdates();
- nbOfAttackers++;
- newEnemies.push(ent);
+ // okay then we'll check one of the armies
+ // creating the array to iterate over.
+ if (this.enemyArmyLoop[enemyID] === undefined || this.enemyArmyLoop[enemyID].length === 0)
+ {
+ this.enemyArmyLoop[enemyID] = [];
+ for (i in this.enemyArmy[enemyID])
+ this.enemyArmyLoop[enemyID].push(this.enemyArmy[enemyID][i]);
+ }
+ // and now we check the last known army.
+ if (this.enemyArmyLoop[enemyID].length !== 0) {
+ var army = this.enemyArmyLoop[enemyID][0];
+ var position = army.getCentrePosition();
+ if (!position)
+ {
+ // todo: scrap that army, it means all units are likely garrisoned.
+ this.enemyArmyLoop[enemyID].splice(0,1);
}
- });
+ army.forEach(function (ent) { //}){
+ // check if the unit is a breakaway
+ if (ent.position() && SquareVectorDistance(position, ent.position()) > self.armyBreakawaySize)
+ {
+ ent.setMetadata(PlayerID, "inArmy", undefined);
+ army.removeEnt(ent);
+ if (self.evaluateEntity(gameState,ent))
+ self.armify(gameState,ent);
+ } else {
+ // check if we have registered that unit already.
+ if (self.listOfEnemies[ent.id()] === undefined) {
+ self.listOfEnemies[ent.id()] = new EntityCollection(gameState.sharedScript, {}, [Filters.byOwner(ent.owner())]);
+ self.listOfEnemies[ent.id()].freeze();
+ self.listOfEnemies[ent.id()].addEnt(ent);
+ self.listOfEnemies[ent.id()].registerUpdates();
+
+ self.attackerCache[ent.id()] = self.myUnits.filter(Filters.byTargetedEntity(ent.id()));
+ self.attackerCache[ent.id()].registerUpdates();
+ nbOfAttackers++;
+ self.nbAttackers++;
+ newEnemies.push(ent);
+ } else if (self.attackerCache[ent.id()] === undefined || self.attackerCache[ent.id()].length == 0) {
+ nbOfAttackers++;
+ newEnemies.push(ent);
+ }
+ }
+ });
+
+ // TODO: check if the army itself is not dangerous anymore.
+
+ this.enemyArmyLoop[enemyID].splice(0,1);
+ }
+
+ // okay so now the army update is done.
}
+
// Reordering attack because the pathfinder is for now not dynamically updated
for (o in this.attackerCache) {
if ((this.attackerCacheLoopIndicator + o) % 2 === 0) {
this.attackerCache[o].forEach(function (ent) {
- ent.attack(+o);
- });
+ ent.attack(+o);
+ });
}
}
this.attackerCacheLoopIndicator++;
this.attackerCacheLoopIndicator = this.attackerCacheLoopIndicator % 2;
+
+ //debug ("total number "+ this.nbAttackers);
+ //debug ("total number "+ this.nbDefenders);
- if (nbOfAttackers === 0) {
+ if (this.nbAttackers === 0) {
militaryManager.unpauseAllPlans(gameState);
return;
}
// If I'm here, I have a list of enemy units, and a list of my units attacking it (in absolute terms, I could use a list of any unit attacking it).
// now I'll list my idle defenders, then my idle soldiers that could defend.
// and then I'll assign my units.
// and then rock on.
- if (nbOfAttackers < 10){
+ if (this.nbAttackers < 10){
gameState.setDefcon(4); // few local units
- } else if (nbOfAttackers >= 10){
+ } else if (this.nbAttackers >= 10){
gameState.setDefcon(3);
}
// we're having too many.
- if (this.myUnits.filter(Filters.byMetadata("role","defence")).length > nbOfAttackers*this.defenceRatio*1.3) {
- this.myUnits.filter(Filters.byMetadata("role","defence")).forEach(function (defender) { //}){
+ if (this.myUnits.filter(Filters.byMetadata(PlayerID, "role","defence")).length > nbOfAttackers*this.defenceRatio*1.3) {
+ this.myUnits.filter(Filters.byMetadata(PlayerID, "role","defence")).forEach(function (defender) { //}){
if (defender.unitAIOrderData() && defender.unitAIOrderData()["target"]) {
if (self.attackerCache[defender.unitAIOrderData()["target"]].length > 3) {
// okay release me.
defender.stopMoving();
- if (defender.getMetadata("formerrole"))
- defender.setMetadata("role", defender.getMetadata("formerrole") );
+ if (defender.getMetadata(PlayerID, "formerrole"))
+ defender.setMetadata(PlayerID, "role", defender.getMetadata(PlayerID, "formerrole") );
else
- defender.setMetadata("role", "worker");
- defender.setMetadata("subrole", undefined);
-
+ defender.setMetadata(PlayerID, "role", "worker");
+ defender.setMetadata(PlayerID, "subrole", undefined);
+ self.nbDefenders--;
}
}
});
}
- var nonDefenders = this.myUnits.filter(Filters.or(Filters.not(Filters.byMetadata("role","defence")),Filters.isIdle()));
+ var nonDefenders = this.myUnits.filter(Filters.or(Filters.not(Filters.byMetadata(PlayerID, "role","defence")),Filters.isIdle()));
nonDefenders = nonDefenders.filter(Filters.not(Filters.byClass("Female")));
- nonDefenders = nonDefenders.filter(Filters.not(Filters.byMetadata("subrole","attacking")));
+ nonDefenders = nonDefenders.filter(Filters.not(Filters.byMetadata(PlayerID, "subrole","attacking")));
var defenceRatio = this.defenceRatio;
- if (newEnemies.length * defenceRatio > nonDefenders.length) {
- defenceRatio = 1;
- }
+ //debug ("newEnemies.length "+ newEnemies.length);
+ //debug ("nonDefenders.length "+ nonDefenders.length);
+
+ if (newEnemies.length * defenceRatio > nonDefenders.length)
+ defenceRatio--;
+ if (newEnemies.length * defenceRatio > nonDefenders.length)
+ defenceRatio--;
+
+ if (defenceRatio < 1)
+ defenceRatio = 1;
+
+ if (gameState.defcon() < 3)
+ militaryManager.pauseAllPlans(gameState);
+
// For each enemy, we'll pick two units.
for each (enemy in newEnemies) {
if (nonDefenders.length === 0)
break;
// garrisoned.
if (enemy.position() === undefined)
continue;
var assigned = self.attackerCache[enemy.id()].length;
if (assigned >= defenceRatio)
return;
- // let's check for a counter.
- //debug ("Enemy is a " + uneval(enemy._template.Identity.Classes._string) );
- var potCounters = gameState.ai.templateManager.getCountersToClasses(gameState,enemy.classes(),enemy.templateName());
- //debug ("Counters are" +uneval(potCounters));
- var counters = [];
- for (o in potCounters) {
- var counter = nonDefenders.filter(Filters.and(Filters.byType(potCounters[o][0]), Filters.byStaticDistance(enemy.position(), 150) )).toEntityArray();
- if (counter.length !== 0)
- for (unit in counter)
- counters.push(counter[unit]);
- }
- //debug ("I have " +counters.length +"countering units");
- for (var i = 0; i < defenceRatio && i < counters.length; i++) {
- if (counters[i].getMetadata("plan") !== undefined)
- militaryManager.pausePlan(gameState,counters[i].getMetadata("plan"));
- if (counters[i].getMetadata("role") == "worker" || counters[i].getMetadata("role") == "attack")
- counters[i].setMetadata("formerrole", counters[i].getMetadata("role"));
- counters[i].setMetadata("role","defence");
- counters[i].setMetadata("subrole","defending");
- counters[i].attack(+enemy.id());
- nonDefenders.updateEnt(counters[i]);
- assigned++;
- //debug ("Sending a " +counters[i].templateName() +" to counter a " + enemy.templateName());
- }
- if (assigned !== defenceRatio) {
- // take closest units
- nonDefenders.filter(Filters.byClass("CitizenSoldier")).filterNearest(enemy.position(),defenceRatio-assigned).forEach(function (defender) { //}){
- if (defender.getMetadata("plan") !== undefined)
- militaryManager.pausePlan(gameState,defender.getMetadata("plan"));
- if (defender.getMetadata("role") == "worker" || defender.getMetadata("role") == "attack")
- defender.setMetadata("formerrole", defender.getMetadata("role"));
- defender.setMetadata("role","defence");
- defender.setMetadata("subrole","defending");
- defender.attack(+enemy.id());
- nonDefenders.updateEnt(defender);
- assigned++;
- });
+ // We'll sort through our units that can legitimately attack.
+ // Sorting is done by distance, and if the unit counters the attacker it's "assumed" it's a little closer.
+ var data = [];
+ for (var id in nonDefenders._entities)
+ {
+ var ent = nonDefenders._entities[id];
+ if (ent.position())
+ data.push([id, ent, SquareVectorDistance(enemy.position(), ent.position())]);
}
+ data.sort(function (a, b) {
+ var vala = a[2];
+ var valb = b[2];
+ if (a[1].countersClasses(b[1].classes()))
+ vala *= 0.8;
+ if (b[1].countersClasses(a[1].classes()))
+ valb *= 0.8;
+ return (vala - valb); });
+
+ var ret = {};
+ for each (var val in data.slice(0, Math.min(nonDefenders._length, defenceRatio - assigned)))
+ ret[val[0]] = val[1];
+
+ var defs = new EntityCollection(nonDefenders._ai, ret);
+
+ // successfully sorted
+ defs.forEach(function (defender) { //}){
+ if (defender.getMetadata(PlayerID, "role") == "worker" || defender.getMetadata(PlayerID, "role") == "attack")
+ defender.setMetadata(PlayerID, "formerrole", defender.getMetadata(PlayerID, "role"));
+ defender.setMetadata(PlayerID, "role","defence");
+ defender.setMetadata(PlayerID, "subrole","defending");
+ defender.attack(+enemy.id());
+ nonDefenders.updateEnt(defender);
+ assigned++;
+ self.nbDefenders++;
+ });
}
+
/*
// yes. We'll pick new units (pretty randomly for now, todo)
// first from attack plans, then from workers.
var newSoldiers = gameState.getOwnEntities().filter(function (ent) {
- if (ent.getMetadata("plan") != undefined && ent.getMetadata("role") != "defence")
+ if (ent.getMetadata(PlayerID, "plan") != undefined && ent.getMetadata(PlayerID, "role") != "defence")
return true;
return false;
});
newSoldiers.forEach(function(ent) {
- if (ent.getMetadata("subrole","attacking")) // gone with the wind to avenge their brothers.
+ if (ent.getMetadata(PlayerID, "subrole","attacking")) // gone with the wind to avenge their brothers.
return;
if (nbOfAttackers <= 0)
return;
- militaryManager.pausePlan(gameState,ent.getMetadata("plan"));
- ent.setMetadata("formerrole", ent.getMetadata("role"));
- ent.setMetadata("role","defence");
- ent.setMetadata("subrole","newdefender");
+ militaryManager.pausePlan(gameState,ent.getMetadata(PlayerID, "plan"));
+ ent.setMetadata(PlayerID, "formerrole", ent.getMetadata(PlayerID, "role"));
+ ent.setMetadata(PlayerID, "role","defence");
+ ent.setMetadata(PlayerID, "subrole","newdefender");
nbOfAttackers--;
});
if (nbOfAttackers > 0) {
newSoldiers = gameState.getOwnEntitiesByRole("worker");
newSoldiers.forEach(function(ent) {
if (nbOfAttackers <= 0)
return;
// If we're not female, we attack
// and if we're not already assigned from above (might happen, not sure, rather be cautious)
- if (ent.hasClass("CitizenSoldier") && ent.getMetadata("subrole") != "newdefender") {
- ent.setMetadata("formerrole", "worker");
- ent.setMetadata("role","defence");
- ent.setMetadata("subrole","newdefender");
+ if (ent.hasClass("CitizenSoldier") && ent.getMetadata(PlayerID, "subrole") != "newdefender") {
+ ent.setMetadata(PlayerID, "formerrole", "worker");
+ ent.setMetadata(PlayerID, "role","defence");
+ ent.setMetadata(PlayerID, "subrole","newdefender");
nbOfAttackers--;
}
});
}
// okay
newSoldiers = gameState.getOwnEntitiesByMetadata("subrole","newdefender");
// we're okay, but there's a big amount of units
// todo: check against total number of soldiers
if (nbOfAttackers <= 0 && newSoldiers.length > 35)
gameState.setDefcon(2);
else if (nbOfAttackers > 0) {
// we are actually lacking units
gameState.setDefcon(1);
}
// TODO. For now, each unit will pick the closest unit that is attacked by only one/zero guy, or any if there is none.
// ought to regroup them first for optimization.
newSoldiers.forEach(function(ent) { //}) {
var enemies = self.listedEnemyCollection.filterNearest(ent.position()).toEntityArray();
var target = -1;
var secondaryTarget = enemies[0]; // second best pick
for (o in enemies) {
var enemy = enemies[o];
if (self.attackerCache[enemy.id()].length < 2) {
target = +enemy.id();
break;
}
}
- ent.setMetadata("subrole","defending");
+ ent.setMetadata(PlayerID, "subrole","defending");
ent.attack(+target);
});
*/
return;
}
// this processes the attackmessages
// So that a unit that gets attacked will not be completely dumb.
// warning: huge levels of indentation coming.
Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
+ var self = this;
+
for (var key in events){
var e = events[key];
if (e.type === "Attacked" && e.msg){
if (gameState.isEntityOwn(gameState.getEntityById(e.msg.target))) {
var attacker = gameState.getEntityById(e.msg.attacker);
var ourUnit = gameState.getEntityById(e.msg.target);
// the attacker must not be already dead, and it must not be me (think catapults that miss).
- if (attacker !== undefined && attacker.owner() !== gameState.player && attacker.position() !== undefined) {
+ if (attacker !== undefined && attacker.owner() !== PlayerID && attacker.position() !== undefined) {
// note: our unit can already by dead by now... We'll then have to rely on the enemy to react.
// if we're not on enemy territory
var territory = +this.territoryMap.point(attacker.position()) - 64;
// we do not consider units that are defenders, and we do not consider units that are part of an attacking attack plan
// (attacking attacking plans are dealing with threats on their own).
- if (ourUnit !== undefined && (ourUnit.getMetadata("role") == "defence" || ourUnit.getMetadata("subrole") == "attacking"))
+ if (ourUnit !== undefined && (ourUnit.getMetadata(PlayerID, "role") == "defence" || ourUnit.getMetadata(PlayerID, "subrole") == "attacking"))
continue;
// let's check for animals
if (attacker.owner() == 0) {
// if our unit is still alive, we make it react
// in this case we attack.
if (ourUnit !== undefined) {
if (ourUnit.hasClass("Unit") && !ourUnit.hasClass("Support"))
ourUnit.attack(e.msg.attacker);
else {
ourUnit.flee(attacker);
}
}
// anyway we'll register the animal as dangerous, and attack it.
- var filter = Filters.byID(attacker.id());
- this.listOfWantedUnits[attacker.id()] = gameState.getEntities().filter(filter);
+ this.listOfWantedUnits[attacker.id()] = new EntityCollection(gameState.sharedScript);
+ this.listOfWantedUnits[attacker.id()].addEnt(attacker);
+ this.listOfWantedUnits[attacker.id()].freeze();
this.listOfWantedUnits[attacker.id()].registerUpdates();
- this.listOfWantedUnits[attacker.id()].length;
- filter = Filters.and(Filters.byOwner(gameState.player),Filters.byTargetedEntity(attacker.id()));
+
+ var filter = Filters.byTargetedEntity(attacker.id());
this.WantedUnitsAttacker[attacker.id()] = this.myUnits.filter(filter);
this.WantedUnitsAttacker[attacker.id()].registerUpdates();
- this.WantedUnitsAttacker[attacker.id()].length;
- } else if (territory != attacker.owner()) { // preliminary check: attacks in enemy territory are not counted as attacks
+
+
+ } // preliminary check: we do not count attacked military units (for sanity for now, TODO).
+ else if ( (territory != attacker.owner() && ourUnit.hasClass("Support")) || (!ourUnit.hasClass("Support") && territory == PlayerID))
+ {
// Also TODO: this does not differentiate with buildings...
// These ought to be treated differently.
// units in attack plans will react independently, but we still list the attacks here.
if (attacker.hasClass("Structure")) {
// todo: we ultimately have to check wether it's a danger point or an isolated area, and if it's a danger point, mark it as so.
} else {
// TODO: right now a soldier always retaliate... Perhaps it should be set in "Defence" mode.
if (!attacker.hasClass("Female") && !attacker.hasClass("Ship")) {
- // This unit is dangerous. We'll ask the enemy manager if it's part of a big army, in which case we'll list it as dangerous (so it'll be treated next turn by the other manager)
- // If it's not part of a big army, depending on our priority we may want to kill it (using the same things as animals for that)
- // TODO (perhaps not any more, but let's mark it anyway)
- var army = militaryManager.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id());
- if (army !== undefined && army[1].length > 5) {
- militaryManager.enemyWatchers[attacker.owner()].setAsDangerous(army[0]);
- } else if (army !== undefined && !militaryManager.enemyWatchers[attacker.owner()].isDangerous(army[0])) {
+ // This unit is dangerous. if it's in an army, it's being dealt with.
+ // if it's not in an army, it means it's either a lone raider, or it has got friends.
+ // In which case we must check for other dangerous units around, and perhaps armify them.
+ // TODO: perhaps someday army detection will have improved and this will require change.
+ var armyID = attacker.getMetadata(PlayerID, "inArmy");
+ if (armyID == undefined || !this.enemyArmy[attacker.owner()] || !this.enemyArmy[attacker.owner()][armyID]) {
+ // armify it, then armify units close to him.
+ this.armify(gameState,attacker);
+ armyID = attacker.getMetadata(PlayerID, "inArmy");
+
+ var position = attacker.position();
+ var close = militaryManager.enemyWatchers[attacker.owner()].enemySoldiers.filter(Filters.byDistance(position, self.armyCompactSize));
+ if (close.length > 15)
+ {
+ close.forEach(function (ent) { //}){
+ if (SquareVectorDistance(position, ent.position()) < self.armyCompactSize)
+ {
+ ent.setMetadata(PlayerID, "inArmy", armyID);
+ self.enemyArmy[ent.owner()][armyID].addEnt(ent)
+ }
+ });
+ }
+ // Defencemanager will deal with them in the next turn.
+ /*
// we register this unit as wanted, TODO register the whole army
// another function will deal with it.
- var filter = Filters.and(Filters.byOwner(attacker.owner()),Filters.byID(attacker.id()));
- this.listOfWantedUnits[attacker.id()] = this.enemyUnits[attacker.owner()].filter(filter);
+ this.listOfWantedUnits[attacker.id()] = new EntityCollection(gameState.sharedScript, {}, [Filters.byOwner(attacker.owner())]);
+ this.listOfWantedUnits[attacker.id()].addEnt(attacker);
this.listOfWantedUnits[attacker.id()].registerUpdates();
- this.listOfWantedUnits[attacker.id()].length;
- filter = Filters.and(Filters.byOwner(gameState.player),Filters.byTargetedEntity(attacker.id()));
+
+ filter = Filters.and(Filters.byOwner(PlayerID),Filters.byTargetedEntity(attacker.id()));
this.WantedUnitsAttacker[attacker.id()] = this.myUnits.filter(filter);
this.WantedUnitsAttacker[attacker.id()].registerUpdates();
- this.WantedUnitsAttacker[attacker.id()].length;
+ */
}
if (ourUnit && ourUnit.hasClass("Unit")) {
if (ourUnit.hasClass("Support")) {
// TODO: it's a villager. Garrison it.
// TODO: make other neighboring villagers garrison
// Right now we'll flee from the attacker.
ourUnit.flee(attacker);
} else {
// It's a soldier. Right now we'll retaliate
// TODO: check for stronger units against this type, check for fleeing options, etc.
ourUnit.attack(e.msg.attacker);
}
}
-
}
}
}
}
}
}
}
-};
+}; // nice sets of closing brackets, isn't it?
+
// At most, this will put defcon to 4
Defence.prototype.DealWithWantedUnits = function(gameState, events, militaryManager) {
//if (gameState.defcon() < 3)
// return;
var self = this;
var nbOfAttackers = 0;
var nbOfDealtWith = 0;
// clean up before adding new units (slight speeding up, since new units can't already be dead)
for (i in this.listOfWantedUnits) {
if (this.listOfWantedUnits[i].length === 0 || this.listOfEnemies[i] !== undefined) { // unit died/was converted/is already dealt with as part of an army
delete this.WantedUnitsAttacker[i];
delete this.listOfWantedUnits[i];
} else {
nbOfAttackers++;
if (this.WantedUnitsAttacker[i].length > 0)
nbOfDealtWith++;
}
}
// note: we can deal with units the way we want because anyway, the Army Defender has already done its task.
// If there are still idle defenders here, it's because they aren't needed.
// I can also call other units: they're not needed.
// Note however that if the defcon level is too high, this won't do anything because it's low priority.
// this also won't take units from attack managers
if (nbOfAttackers === 0)
return;
// at most, we'll deal with 3 enemies at once.
if (nbOfDealtWith >= 3)
return;
// dynamic properties are not updated nearly fast enough here so a little caching
var addedto = {};
// we send 3 units to each target just to be sure. TODO refine.
// we do not use plan units
this.idleDefs.forEach(function(ent) {
- if (nbOfDealtWith < 3 && nbOfAttackers > 0 && ent.getMetadata("plan") == undefined)
+ if (nbOfDealtWith < 3 && nbOfAttackers > 0 && ent.getMetadata(PlayerID, "plan") == undefined)
for (o in self.listOfWantedUnits) {
if ( (addedto[o] == undefined && self.WantedUnitsAttacker[o].length < 3) || (addedto[o] && self.WantedUnitsAttacker[o].length + addedto[o] < 3)) {
if (self.WantedUnitsAttacker[o].length === 0)
nbOfDealtWith++;
- ent.setMetadata("formerrole", ent.getMetadata("role"));
- ent.setMetadata("role","defence");
- ent.setMetadata("subrole", "defending");
+ ent.setMetadata(PlayerID, "formerrole", ent.getMetadata(PlayerID, "role"));
+ ent.setMetadata(PlayerID, "role","defence");
+ ent.setMetadata(PlayerID, "subrole", "defending");
ent.attack(+o);
if (addedto[o])
addedto[o]++;
else
addedto[o] = 1;
break;
}
if (self.WantedUnitsAttacker[o].length == 3)
nbOfAttackers--; // we hav eenough units, mark this one as being OKAY
}
});
// still some undealt with attackers, recruit citizen soldiers
if (nbOfAttackers > 0 && nbOfDealtWith < 2) {
gameState.setDefcon(4);
var newSoldiers = gameState.getOwnEntitiesByRole("worker");
newSoldiers.forEach(function(ent) {
// If we're not female, we attack
if (ent.hasClass("CitizenSoldier"))
if (nbOfDealtWith < 3 && nbOfAttackers > 0)
for (o in self.listOfWantedUnits) {
if ( (addedto[o] == undefined && self.WantedUnitsAttacker[o].length < 3) || (addedto[o] && self.WantedUnitsAttacker[o].length + addedto[o] < 3)) {
if (self.WantedUnitsAttacker[o].length === 0)
nbOfDealtWith++;
- ent.setMetadata("formerrole", ent.getMetadata("role"));
- ent.setMetadata("role","defence");
- ent.setMetadata("subrole", "defending");
+ ent.setMetadata(PlayerID, "formerrole", ent.getMetadata(PlayerID, "role"));
+ ent.setMetadata(PlayerID, "role","defence");
+ ent.setMetadata(PlayerID, "subrole", "defending");
ent.attack(+o);
if (addedto[o])
addedto[o]++;
else
addedto[o] = 1;
break;
}
if (self.WantedUnitsAttacker[o].length == 3)
nbOfAttackers--; // we hav eenough units, mark this one as being OKAY
}
});
}
return;
}
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js (revision 13225)
@@ -1,819 +1,1165 @@
+/* Economy Manager
+ * Deals with anything economic. Worker logic is in worker.js.
+ */
+
var EconomyManager = function() {
- this.targetNumBuilders = 3; // number of workers we want building stuff
- this.targetNumFields = 3;
+ this.targetNumBuilders = Config.Economy.targetNumBuilders; // number of workers we want building stuff
+ this.targetNumFields = 3; // initial setting only.
+
+ // Used by the QueueManager to determine future needs.
+ this.baseNeed = {};
+ this.baseNeed["food"] = 150;
+ this.baseNeed["wood"] = 150;
+ this.baseNeed["stone"] = 0;
+
+ this.baseNeed["metal"] = 0;
+
+ // see rePrioritize() for more info
+ this.lastStatG = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0}; // resource collecting stats: gathered
+ this.lastStatU = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0}; // resource collecting stats: used
+
+ this.marketStartTime = Config.Economy.marketStartTime * 1000;
+ this.dockStartTime = Config.Economy.dockStartTime * 1000;
+ this.farmsteadStartTime = Config.Economy.farmsteadStartTime * 1000;
+ this.techStartTime = Config.Economy.techStartTime * 1000;
this.resourceMaps = {}; // Contains maps showing the density of wood, stone and metal
+ this.CCResourceMaps = {}; // Contains maps showing the density of wood, stone and metal, optimized for CC placement.
this.setCount = 0; //stops villagers being reassigned to other resources too frequently, count a set number of
//turns before trying to reassign them.
// this means we'll have about a big third of women, and thus we can maximize resource gathering rates.
- this.femaleRatio = 0.4;
+ this.femaleRatio = Config.Economy.femaleRatio;
this.farmingFields = false;
this.dropsiteNumbers = {"wood": 1, "stone": 0.5, "metal": 0.5};
};
// More initialisation for stuff that needs the gameState
-EconomyManager.prototype.init = function(gameState){
+EconomyManager.prototype.init = function(gameState, events){
this.targetNumWorkers = Math.max(Math.floor(gameState.getPopulationMax()*0.55), 1);
+ // Athen's fastest Citizen soldier requires stone.
+ if (gameState.civ() == "athen")
+ {
+ this.baseNeed["food"] = 140;
+ this.baseNeed["wood"] = 100;
+ this.baseNeed["stone"] = 50;
+ }
+
+
// initialize once all the resource maps.
- this.updateResourceMaps(gameState, ["food","wood","stone","metal"]);
+ this.updateResourceMaps(gameState, events);
this.updateResourceConcentrations(gameState,"food");
this.updateResourceConcentrations(gameState,"wood");
this.updateResourceConcentrations(gameState,"stone");
this.updateResourceConcentrations(gameState,"metal");
- this.updateNearbyResources(gameState, "food");
+ /*this.updateNearbyResources(gameState, "food");
this.updateNearbyResources(gameState, "wood");
this.updateNearbyResources(gameState, "stone");
this.updateNearbyResources(gameState, "metal");
+ */
};
// okay, so here we'll create both females and male workers.
// We'll try to keep close to the "ratio" defined atop.
// qBot picks the best citizen soldier available: the cheapest and the fastest walker
// some civs such as Macedonia have 2 kinds of citizen soldiers: phalanx that are slow
// (speed:6) and peltasts that are very fast (speed: 11). Here, qBot will choose the peltast
// resulting in faster resource gathering.
// I'll also avoid creating citizen soldiers in the beginning because it's slower.
EconomyManager.prototype.trainMoreWorkers = function(gameState, queues) {
// Count the workers in the world and in progress
var numFemales = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("units/{civ}_support_female_citizen"));
numFemales += queues.villager.countTotalQueuedUnits();
// counting the workers that aren't part of a plan
var numWorkers = 0;
gameState.getOwnEntities().forEach (function (ent) {
- if (ent.getMetadata("role") == "worker" && ent.getMetadata("plan") == undefined)
+ if (ent.getMetadata(PlayerID, "role") == "worker" && ent.getMetadata(PlayerID, "plan") == undefined)
numWorkers++;
});
+ var numInTraining = 0;
gameState.getOwnTrainingFacilities().forEach(function(ent) {
ent.trainingQueue().forEach(function(item) {
if (item.metadata && item.metadata.role == "worker" && item.metadata.plan == undefined)
numWorkers += item.count;
+ numInTraining += item.count;
});
});
var numQueued = queues.villager.countTotalQueuedUnits() + queues.citizenSoldier.countTotalQueuedUnits();
var numTotal = numWorkers + numQueued;
- this.targetNumFields = numFemales/15;
+ this.targetNumFields = numFemales/23;
+ // ought to refine this.
if ((gameState.ai.playedTurn+2) % 3 === 0) {
- this.dropsiteNumbers = {"wood": Math.ceil((numWorkers)/25)/2, "stone": Math.ceil((numWorkers)/40)/2, "metal": Math.ceil((numWorkers)/30)/2};
+ this.dropsiteNumbers = {"wood": Math.ceil((numWorkers)/18)/2, "stone": Math.ceil((numWorkers)/30)/2, "metal": Math.ceil((numWorkers)/20)/2};
}
//debug (numTotal + "/" +this.targetNumWorkers + ", " +numFemales +"/" +numTotal);
// If we have too few, train more
// should plan enough to always have femalesā¦
- if (numTotal < this.targetNumWorkers && numQueued < 20) {
+ if (numTotal < this.targetNumWorkers && numQueued < 2 && (numQueued+numInTraining) < Math.min(Math.ceil(gameState.getTimeElapsed() / 60000)+ 4,15000)) {
var template = gameState.applyCiv("units/{civ}_support_female_citizen");
- var size = Math.min(Math.ceil(gameState.getTimeElapsed() / 240000),5);
+ var size = Math.min(Math.ceil(gameState.getTimeElapsed() / 30000),5);
if (numFemales/numTotal > this.femaleRatio && gameState.getTimeElapsed() > 60*1000) {
- var size = Math.min(Math.ceil(gameState.getTimeElapsed() / 120000),5);
template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], [ ["cost",1], ["speed",0.5]]);
if (!template) {
template = gameState.applyCiv("units/{civ}_support_female_citizen");
}
}
if (template === gameState.applyCiv("units/{civ}_support_female_citizen"))
queues.villager.addItem(new UnitTrainingPlan(gameState, template, { "role" : "worker" },size ));
else
queues.citizenSoldier.addItem(new UnitTrainingPlan(gameState, template, { "role" : "worker" },size ));
}
};
+
+// Tries to research any available tech
+// Only one at once. Also does military tech (selection is completely random atm)
+// TODO: Lots, lots, lots here.
+EconomyManager.prototype.tryResearchTechs = function(gameState, queues) {
+ if (queues.minorTech.totalLength() === 0)
+ {
+ var possibilities = gameState.findAvailableTech();
+ if (possibilities.length === 0)
+ return;
+ // randomly pick one. No worries about pairs in that case.
+ var p = Math.floor((Math.random()*possibilities.length));
+ queues.minorTech.addItem(new ResearchPlan(gameState, possibilities[p][0]));
+ }
+}
+
// picks the best template based on parameters and classes
EconomyManager.prototype.findBestTrainableUnit = function(gameState, classes, parameters) {
var units = gameState.findTrainableUnits(classes);
if (units.length === 0)
return undefined;
units.sort(function(a, b) { //}) {
var aDivParam = 0, bDivParam = 0;
var aTopParam = 0, bTopParam = 0;
for (i in parameters) {
var param = parameters[i];
if (param[0] == "base") {
aTopParam = param[1];
bTopParam = param[1];
}
if (param[0] == "strength") {
- aTopParam += a[1].getMaxStrength() * param[1];
- bTopParam += b[1].getMaxStrength() * param[1];
+ aTopParam += getMaxStrength(a[1]) * param[1];
+ bTopParam += getMaxStrength(b[1]) * param[1];
}
if (param[0] == "speed") {
aTopParam += a[1].walkSpeed() * param[1];
bTopParam += b[1].walkSpeed() * param[1];
}
if (param[0] == "cost") {
aDivParam += a[1].costSum() * param[1];
bDivParam += b[1].costSum() * param[1];
}
}
return -(aTopParam/(aDivParam+1)) + (bTopParam/(bDivParam+1));
});
return units[0][0];
};
// Pick the resource which most needs another worker
EconomyManager.prototype.pickMostNeededResources = function(gameState) {
-
var self = this;
// Find what resource type we're most in need of
if (!gameState.turnCache["gather-weights-calculated"]){
- this.gatherWeights = gameState.ai.queueManager.futureNeeds(gameState);
+ this.gatherWeights = gameState.ai.queueManager.futureNeeds(gameState,this);
gameState.turnCache["gather-weights-calculated"] = true;
}
-
+
var numGatherers = {};
for (type in this.gatherWeights){
numGatherers[type] = gameState.updatingCollection("workers-gathering-" + type,
- Filters.byMetadata("gather-type", type), gameState.getOwnEntitiesByRole("worker")).length;
+ Filters.byMetadata(PlayerID, "gather-type", type)).length;//, gameState.getOwnEntitiesByRole("worker")).length;
}
-
+ //var totalWeight = numGatherers[a].length/gameState.getOwnEntitiesByRole("worker")).length;
var types = Object.keys(this.gatherWeights);
+
types.sort(function(a, b) {
// Prefer fewer gatherers (divided by weight)
var va = numGatherers[a] / (self.gatherWeights[a]+1);
var vb = numGatherers[b] / (self.gatherWeights[b]+1);
return va-vb;
});
-
return types;
};
EconomyManager.prototype.reassignRolelessUnits = function(gameState) {
//TODO: Move this out of the economic section
var roleless = gameState.getOwnEntitiesByRole(undefined);
roleless.forEach(function(ent) {
if (ent.hasClass("Worker")){
- ent.setMetadata("role", "worker");
+ ent.setMetadata(PlayerID, "role", "worker");
}else if(ent.hasClass("CitizenSoldier") || ent.hasClass("Champion")){
- ent.setMetadata("role", "soldier");
+ ent.setMetadata(PlayerID, "role", "soldier");
}else{
- ent.setMetadata("role", "unknown");
+ ent.setMetadata(PlayerID, "role", "unknown");
}
});
};
// If the numbers of workers on the resources is unbalanced then set some of workers to idle so
// they can be reassigned by reassignIdleWorkers.
EconomyManager.prototype.setWorkersIdleByPriority = function(gameState){
- this.gatherWeights = gameState.ai.queueManager.futureNeeds(gameState);
+ this.gatherWeights = gameState.ai.queueManager.futureNeeds(gameState,this);
var numGatherers = {};
var totalGatherers = 0;
var totalWeight = 0;
for ( var type in this.gatherWeights){
numGatherers[type] = 0;
totalWeight += this.gatherWeights[type];
}
gameState.getOwnEntitiesByRole("worker").forEach(function(ent) {
- if (ent.getMetadata("subrole") === "gatherer"){
- numGatherers[ent.getMetadata("gather-type")] += 1;
+ if (ent.getMetadata(PlayerID, "subrole") === "gatherer"){
+ numGatherers[ent.getMetadata(PlayerID, "gather-type")] += 1;
totalGatherers += 1;
}
});
for ( var type in this.gatherWeights){
var allocation = Math.floor(totalGatherers * (this.gatherWeights[type]/totalWeight));
if (allocation < numGatherers[type]){
var numToTake = numGatherers[type] - allocation;
gameState.getOwnEntitiesByRole("worker").forEach(function(ent) {
- if (ent.getMetadata("subrole") === "gatherer" && ent.getMetadata("gather-type") === type && numToTake > 0){
- ent.setMetadata("subrole", "idle");
+ if (ent.getMetadata(PlayerID, "subrole") === "gatherer" && ent.getMetadata(PlayerID, "gather-type") === type && numToTake > 0){
+ ent.setMetadata(PlayerID, "subrole", "idle");
+ ent.stopMoving();
numToTake -= 1;
}
});
}
}
};
EconomyManager.prototype.reassignIdleWorkers = function(gameState) {
var self = this;
// Search for idle workers, and tell them to gather resources based on demand
- var filter = Filters.or(Filters.isIdle(), Filters.byMetadata("subrole", "idle"));
+ var filter = Filters.or(Filters.isIdle(), Filters.byMetadata(PlayerID, "subrole", "idle"));
var idleWorkers = gameState.updatingCollection("idle-workers", filter, gameState.getOwnEntitiesByRole("worker"));
if (idleWorkers.length) {
var resourceSupplies;
idleWorkers.forEach(function(ent) {
// Check that the worker isn't garrisoned
if (ent.position() === undefined){
return;
}
var types = self.pickMostNeededResources(gameState);
-
- ent.setMetadata("subrole", "gatherer");
- ent.setMetadata("gather-type", types[0]);
+ ent.setMetadata(PlayerID, "subrole", "gatherer");
+ ent.setMetadata(PlayerID, "gather-type", types[0]);
});
}
};
EconomyManager.prototype.workersBySubrole = function(gameState, subrole) {
var workers = gameState.getOwnEntitiesByRole("worker");
- return gameState.updatingCollection("subrole-" + subrole, Filters.byMetadata("subrole", subrole), workers);
+ return gameState.updatingCollection("subrole-" + subrole, Filters.byMetadata(PlayerID, "subrole", subrole), workers);
};
-EconomyManager.prototype.assignToFoundations = function(gameState) {
+EconomyManager.prototype.assignToFoundations = function(gameState, noRepair) {
// If we have some foundations, and we don't have enough
// builder-workers,
// try reassigning some other workers who are nearby
// up to 2.5 buildings at once (that is 3, but one won't be complete).
var foundations = gameState.getOwnFoundations().toEntityArray();
- var damagedBuildings = gameState.getOwnEntities().filter(function (ent) { if (ent.needsRepair() && ent.getMetadata("plan") == undefined) { return true; } return false; }).toEntityArray();
+ var damagedBuildings = gameState.getOwnEntities().filter(function (ent) { if (ent.needsRepair() && ent.getMetadata(PlayerID, "plan") == undefined) { return true; } return false; }).toEntityArray();
// Check if nothing to build
if (!foundations.length && !damagedBuildings.length){
return;
}
var workers = gameState.getOwnEntitiesByRole("worker");
var builderWorkers = this.workersBySubrole(gameState, "builder");
var addedWorkers = 0;
for (i in foundations) {
var target = foundations[i];
if (target._template.BuildRestrictions.Category === "Field")
continue; // we do not build fields
var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target).length;
if (assigned < this.targetNumBuilders) {
- if (builderWorkers.length + addedWorkers < this.targetNumBuilders*Math.min(2.5,gameState.getTimeElapsed()/60000)) {
- var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata("subrole") !== "builder" && ent.getMetadata("gather-type") !== "food" && ent.position() !== undefined); });
- var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders - assigned);
+ if (builderWorkers.length + addedWorkers < this.targetNumBuilders*Math.min(3.0,gameState.getTimeElapsed()/60000)) {
+ var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.getMetadata(PlayerID, "gather-type") !== "food" && ent.position() !== undefined); });
+ var nearestNonBuilders = null;
+ if (target.hasClass("CivCentre"))
+ nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders*2.0 - assigned);
+ else
+ nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders*2.0 - assigned);
nearestNonBuilders.forEach(function(ent) {
addedWorkers++;
- ent.setMetadata("subrole", "builder");
- ent.setMetadata("target-foundation", target);
+ ent.setMetadata(PlayerID, "subrole", "builder");
+ ent.setMetadata(PlayerID, "target-foundation", target);
});
if (this.targetNumBuilders - assigned - nearestNonBuilders.length > 0) {
- var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata("subrole") !== "builder" && ent.position() !== undefined); });
+ var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.position() !== undefined); });
var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders - assigned);
nearestNonBuilders.forEach(function(ent) {
addedWorkers++;
- ent.setMetadata("subrole", "builder");
- ent.setMetadata("target-foundation", target);
+ ent.setMetadata(PlayerID, "subrole", "builder");
+ ent.setMetadata(PlayerID, "target-foundation", target);
});
}
}
}
}
// don't repair if we're still under attack, unless it's like a vital (civcentre or wall) building that's getting destroyed.
for (i in damagedBuildings) {
var target = damagedBuildings[i];
if (gameState.defcon() < 5) {
if (target.healthLevel() > 0.5 || !target.hasClass("CivCentre") || !target.hasClass("StoneWall")) {
continue;
}
- }
+ } else if (noRepair && !target.hasClass("CivCentre"))
+ continue;
var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target).length;
if (assigned < this.targetNumBuilders) {
if (builderWorkers.length + addedWorkers < this.targetNumBuilders*2.5) {
- var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata("subrole") !== "builder" && ent.position() !== undefined); });
+ var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.position() !== undefined); });
if (gameState.defcon() < 5)
- nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata("subrole") !== "builder" && ent.hasClass("Female") && ent.position() !== undefined); });
+ nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.hasClass("Female") && ent.position() !== undefined); });
var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders - assigned);
nearestNonBuilders.forEach(function(ent) {
addedWorkers++;
- ent.setMetadata("subrole", "builder");
- ent.setMetadata("target-foundation", target);
+ ent.setMetadata(PlayerID, "subrole", "builder");
+ ent.setMetadata(PlayerID, "target-foundation", target);
});
}
}
}
};
EconomyManager.prototype.buildMoreFields = function(gameState, queues) {
if (this.farmingFields === true) {
var numFarms = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_field"));
numFarms += queues.field.countTotalQueuedUnits();
- if (numFarms < this.targetNumFields + Math.floor(gameState.getTimeElapsed() / 900000))
+ if (numFarms < this.targetNumFields + Math.floor(gameState.getTimeElapsed() / 750000))
queues.field.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_field"));
} else {
var foodAmount = 0;
gameState.getOwnDropsites("food").forEach( function (ent) { //}){
- if (ent.getMetadata("resource-quantity-food") != undefined) {
- foodAmount += ent.getMetadata("resource-quantity-food");
+ if (ent.getMetadata(PlayerID, "resource-quantity-food") != undefined) {
+ foodAmount += ent.getMetadata(PlayerID, "resource-quantity-food");
} else {
- foodAmount = 300; // wait till we initialize
+ foodAmount = 450; // wait till we initialize
}
});
- if (foodAmount < 300)
+ if (foodAmount < 450)
this.farmingFields = true;
}
};
// If all the CC's are destroyed then build a new one
EconomyManager.prototype.buildNewCC= function(gameState, queues) {
var numCCs = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_civil_centre"));
numCCs += queues.civilCentre.totalLength();
- for ( var i = numCCs; i < 1; i++) {
- queues.civilCentre.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre"));
- }
+ // no use trying to lay foundations that will be destroyed
+ if (gameState.defcon() > 2)
+ for ( var i = numCCs; i < 1; i++) {
+ gameState.ai.queueManager.clear();
+ this.baseNeed["food"] = 0;
+ this.baseNeed["wood"] = 50;
+ this.baseNeed["stone"] = 50;
+ this.baseNeed["metal"] = 50;
+ queues.civilCentre.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre"));
+ }
+ return (gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_civil_centre")) == 0 && gameState.currentPhase > 1);
};
+// TODO: make it regularly update stone+metal mines
// creates and maintains a map of unused resource density
// this also takes dropsites into account.
// resources that are "part" of a dropsite are not counted.
EconomyManager.prototype.updateResourceMaps = function(gameState, events) {
-
- // TODO: centralize with that other function that uses the same variables
- // The weight of the influence function is amountOfResource/decreaseFactor
- var decreaseFactor = {'wood': 12.0, 'stone': 10.0, 'metal': 10.0, 'food': 20.0};
+
+ // By how much to divide the resource amount for plotting.
+ var decreaseFactor = {'wood': 25.0, 'stone': 40.0, 'metal': 40.0, 'food': 20.0};
// This is the maximum radius of the influence
- var radius = {'wood':25.0, 'stone': 24.0, 'metal': 24.0, 'food': 24.0};
+ var dpRadius = 10;
+ var radius = {'wood':10.0, 'stone': 24.0, 'metal': 24.0, 'food': 24.0};
+
// smallRadius is the distance necessary to mark a resource as linked to a dropsite.
- var smallRadius = { 'food':70*70,'wood':120*120,'stone':60*60,'metal':60*60 };
+ var smallRadius = { 'food':80*80,'wood':60*60,'stone':70*70,'metal':70*70 };
// bigRadius is the distance for a weak link (resources are considered when building other dropsites)
// and their resource amount is divided by 3 when checking for dropsite resource level.
- var bigRadius = { 'food':100*100,'wood':180*180,'stone':120*120,'metal':120*120 };
+ var bigRadius = { 'food':140*140,'wood':140*140,'stone':140*140,'metal':140*140 };
var self = this;
for (var resource in radius){
// if there is no resourceMap create one with an influence for everything with that resource
if (! this.resourceMaps[resource]){
this.resourceMaps[resource] = new Map(gameState);
-
- var supplies = gameState.getResourceSupplies(resource);
+ this.CCResourceMaps[resource] = new Map(gameState);
+ // disabled as the game sends "create" events for all entities created at game start.
+ /*var supplies = gameState.getResourceSupplies(resource);
supplies.forEach(function(ent){
if (!ent.position()){
return;
}
var x = Math.round(ent.position()[0] / gameState.cellSize);
var z = Math.round(ent.position()[1] / gameState.cellSize);
var strength = Math.round(ent.resourceSupplyMax()/decreaseFactor[resource]);
self.resourceMaps[resource].addInfluence(x, z, radius[resource], strength);
- });
+ });*/
}
}
+
+ var needUpdate = {};
+
// Look for destroy events and subtract the entities original influence from the resourceMap
// also look for dropsite destruction and add the associated entities (along with unmarking them)
for (var i in events) {
var e = events[i];
if (e.type === "Destroy") {
if (e.msg.entityObj){
var ent = e.msg.entityObj;
if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic !== "treasure") {
- if (e.msg.metadata[gameState.getPlayerID()] && !e.msg.metadata[gameState.getPlayerID()]["linked-dropsite"]) {
+ if (e.msg.metadata[PlayerID] && !e.msg.metadata[PlayerID]["linked-dropsite"]) {
var resource = ent.resourceSupplyType().generic;
var x = Math.round(ent.position()[0] / gameState.cellSize);
var z = Math.round(ent.position()[1] / gameState.cellSize);
var strength = Math.round(ent.resourceSupplyMax()/decreaseFactor[resource]);
- this.resourceMaps[resource].addInfluence(x, z, radius[resource], -strength);
+
+ if (resource === "wood" || resource === "food")
+ {
+ this.resourceMaps[resource].addInfluence(x, z, 2, 5,'constant');
+ this.resourceMaps[resource].addInfluence(x, z, 10.0, -strength,'constant');
+ this.CCResourceMaps[resource].addInfluence(x, z, 15, -strength/2.0,'constant');
+ } else if (resource === "stone" || resource === "metal")
+ {
+ this.resourceMaps[resource].addInfluence(x, z, 3, 50);
+ this.resourceMaps[resource].addInfluence(x, z, 12.0, -strength/1.5);
+ this.resourceMaps[resource].addInfluence(x, z, 12.0, -strength/2.0,'constant');
+ this.CCResourceMaps[resource].addInfluence(x, z, 30, -strength,'constant');
+ }
}
}
- if (ent && ent.owner() == gameState.player && ent.resourceDropsiteTypes() !== undefined) {
+ if (ent && ent.owner() == PlayerID && ent.resourceDropsiteTypes() !== undefined) {
var resources = ent.resourceDropsiteTypes();
for (i in resources) {
var resource = resources[i];
// loop through all dropsites to see if the resources of his entity collection could
// be taken over by another dropsite
var dropsites = gameState.getOwnDropsites(resource);
- var metadata = e.msg.metadata[gameState.getPlayerID()];
+ var metadata = e.msg.metadata[PlayerID];
+
+ // can happen if it's destroyed before we've initialised it.
+ if (!metadata)
+ break;
metadata["linked-resources-" + resource].filter( function (supply) { //}){
var takenOver = false;
dropsites.forEach( function (otherDropsite) { //}) {
var distance = SquareVectorDistance(supply.position(), otherDropsite.position());
- if (supply.getMetadata("linked-dropsite") == undefined || supply.getMetadata("linked-dropsite-dist") > distance) {
+ if (supply.getMetadata(PlayerID, "linked-dropsite") == undefined || supply.getMetadata(PlayerID, "linked-dropsite-dist") > distance) {
if (distance < bigRadius[resource]) {
- supply.setMetadata("linked-dropsite", otherDropsite.id() );
- supply.setMetadata("linked-dropsite-dist", +distance);
+ supply.setMetadata(PlayerID, "linked-dropsite", otherDropsite.id() );
+ supply.setMetadata(PlayerID, "linked-dropsite-dist", +distance);
if (distance < smallRadius[resource]) {
takenOver = true;
- supply.setMetadata("linked-dropsite-nearby", true );
+ supply.setMetadata(PlayerID, "linked-dropsite-nearby", true );
} else {
- supply.setMetadata("linked-dropsite-nearby", false );
+ supply.setMetadata(PlayerID, "linked-dropsite-nearby", false );
}
}
}
});
if (!takenOver) {
var x = Math.round(supply.position()[0] / gameState.cellSize);
var z = Math.round(supply.position()[1] / gameState.cellSize);
var strength = Math.round(supply.resourceSupplyMax()/decreaseFactor[resource]);
- self.resourceMaps[resource].addInfluence(x, z, radius[resource], strength);
+ if (resource === "wood" || resource === "food")
+ {
+ self.CCResourceMaps[resource].addInfluence(x, z, 15, strength/2.0,'constant');
+ self.resourceMaps[resource].addInfluence(x, z, 10.0, strength,'constant');
+ self.resourceMaps[resource].addInfluence(x, z, 2, -5,'constant');
+ } else if (resource === "stone" || resource === "metal")
+ {
+ self.CCResourceMaps[resource].addInfluence(x, z, 30, strength,'constant');
+ self.resourceMaps[resource].addInfluence(x, z, 12.0, strength/1.5);
+ self.resourceMaps[resource].addInfluence(x, z, 12.0, strength/2.0,'constant');
+ self.resourceMaps[resource].addInfluence(x, z, 3, -50);
+ }
}
});
+ needUpdate[resource] = true;
}
}
}
} else if (e.type === "Create") {
- if (e.msg.entityObj){
- var ent = e.msg.entityObj;
+ if (e.msg.entity){
+ var ent = gameState.getEntityById(e.msg.entity);
if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic !== "treasure"){
var resource = ent.resourceSupplyType().generic;
-
var addToMap = true;
var dropsites = gameState.getOwnDropsites(resource);
dropsites.forEach( function (otherDropsite) { //}) {
var distance = SquareVectorDistance(ent.position(), otherDropsite.position());
- if (ent.getMetadata("linked-dropsite") == undefined || ent.getMetadata("linked-dropsite-dist") > distance) {
+ if (ent.getMetadata(PlayerID, "linked-dropsite") == undefined || ent.getMetadata(PlayerID, "linked-dropsite-dist") > distance) {
if (distance < bigRadius[resource]) {
if (distance < smallRadius[resource]) {
- if (ent.getMetadata("linked-dropsite") == undefined)
+ if (ent.getMetadata(PlayerID, "linked-dropsite") == undefined)
addToMap = false;
- ent.setMetadata("linked-dropsite-nearby", true );
+ ent.setMetadata(PlayerID, "linked-dropsite-nearby", true );
} else {
- ent.setMetadata("linked-dropsite-nearby", false );
+ ent.setMetadata(PlayerID, "linked-dropsite-nearby", false );
}
- ent.setMetadata("linked-dropsite", otherDropsite.id() );
- ent.setMetadata("linked-dropsite-dist", +distance);
+ ent.setMetadata(PlayerID, "linked-dropsite", otherDropsite.id() );
+ ent.setMetadata(PlayerID, "linked-dropsite-dist", +distance);
}
}
});
if (addToMap) {
var x = Math.round(ent.position()[0] / gameState.cellSize);
var z = Math.round(ent.position()[1] / gameState.cellSize);
var strength = Math.round(ent.resourceSupplyMax()/decreaseFactor[resource]);
- this.resourceMaps[resource].addInfluence(x, z, radius[resource], strength);
+ if (resource === "wood" || resource === "food")
+ {
+ this.CCResourceMaps[resource].addInfluence(x, z, 15, strength/2.0,'constant');
+ this.resourceMaps[resource].addInfluence(x, z, 10.0, strength,'constant');
+ this.resourceMaps[resource].addInfluence(x, z, 2, -5,'constant');
+ } else if (resource === "stone" || resource === "metal")
+ {
+ this.CCResourceMaps[resource].addInfluence(x, z, 30, strength,'constant');
+ this.resourceMaps[resource].addInfluence(x, z, 12.0, strength/1.5);
+ this.resourceMaps[resource].addInfluence(x, z, 12.0, strength/2.0,'constant');
+ this.resourceMaps[resource].addInfluence(x, z, 3, -50);
+ }
+ }
+ } else if (ent && ent.position() && ent.resourceDropsiteTypes) {
+ var resources = ent.resourceDropsiteTypes();
+ for (i in resources) {
+ var resource = resources[i];
+ needUpdate[resource] = true;
}
}
}
}
}
- //this.resourceMaps['wood'].dumpIm("tree_density.png");
+
+ for (i in needUpdate)
+ {
+ this.updateNearbyResources(gameState,i);
+ this.updateResourceConcentrations(gameState,i);
+ }
+
+ /*if (gameState.ai.playedTurn % 20 === 1)
+ {
+ this.resourceMaps['wood'].dumpIm("s_tree_density_ " + gameState.getTimeElapsed() +".png", 1001);
+ this.resourceMaps['stone'].dumpIm("stone_density_ " + gameState.getTimeElapsed() +".png", 1001);
+ this.resourceMaps['metal'].dumpIm("s_metal_density_ " + gameState.getTimeElapsed() +".png", 1001);
+ this.CCResourceMaps['wood'].dumpIm("CC_TREE " + gameState.getTimeElapsed() +".png", 1001);
+ this.CCResourceMaps['stone'].dumpIm("CC_STONE " + gameState.getTimeElapsed() +".png", 1001);
+ this.CCResourceMaps['metal'].dumpIm("CC_METAL " + gameState.getTimeElapsed() +".png", 1001);
+ }*/
};
// Returns the position of the best place to build a new dropsite for the specified resource
EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource){
var friendlyTiles = new Map(gameState);
- friendlyTiles.add(this.resourceMaps[resource]);
+ friendlyTiles.madd(this.resourceMaps[resource],1.5);
for (i in this.resourceMaps)
if (i !== "food")
- friendlyTiles.multiply(this.resourceMaps[i],true,100,1.5);
+ friendlyTiles.addIfNotNull(this.resourceMaps[i]);
- //friendlyTiles.dumpIm(gameState.getTimeElapsed() + "_" + resource + "_density_fade_base.png", 65000);
+ //friendlyTiles.dumpIm(gameState.getTimeElapsed() + "_" + resource + "_density_fade_base.png", 1000);
var territory = Map.createTerritoryMap(gameState);
- friendlyTiles.multiplyTerritory(gameState,territory);
- friendlyTiles.annulateTerritory(gameState,territory);
+ friendlyTiles.multiplyTerritory(gameState,territory,true);
+
+ //friendlyTiles.dumpIm(gameState.getTimeElapsed() + "_" + resource + "_density_fade_caracas.png", 3000);
+
+ //var resources = ["wood","stone","metal"];
+ //for (i in resources)
+ // friendlyTiles.subtract(this.dropsiteMaps[resources[i]]);
- var resources = ["wood","stone","metal"];
- for (i in resources) {
- gameState.getOwnDropsites(resources[i]).forEach(function(ent) { //)){
- // We don't want multiple dropsites at one spot so set to zero if too close.
- var pos = ent.position();
- var x = Math.round(pos[0] / gameState.cellSize);
- var z = Math.round(pos[1] / gameState.cellSize);
- friendlyTiles.setInfluence(x, z, 17, 0);
- });
- }
- //friendlyTiles.dumpIm(gameState.getTimeElapsed() + "_" + resource + "_density_fade_final.png", 10000);
- friendlyTiles.multiply(gameState.ai.distanceFromMeMap,true,gameState.ai.distanceFromMeMap.width/3,2);
- //friendlyTiles.dumpIm(gameState.getTimeElapsed() + "_" + resource + "_density_fade_final2.png", 10000);
+ if (gameState.ai.distanceFromMeMap !== undefined)
+ friendlyTiles.add(gameState.ai.distanceFromMeMap);
+
+ //friendlyTiles.dumpIm(gameState.getTimeElapsed() + "_" + resource + "_density_fade_final.png", 1000);
var obstructions = Map.createObstructionMap(gameState);
obstructions.expandInfluences();
var isCivilCenter = false;
- var bestIdx = friendlyTiles.findBestTile(2, obstructions)[0];
+ var best = friendlyTiles.findBestTile(2, obstructions);
+ var bestIdx = best[0];
+
+ if (best[1] <= 750 && gameState.currentPhase() >= 2)
+ {
+ // restart the search this time for a CC
+ friendlyTiles = new Map(gameState);
+
+ friendlyTiles.madd(this.CCResourceMaps[resource],1.8);
+ for (i in this.CCResourceMaps)
+ if (i !== "food")
+ friendlyTiles.addIfNotNull(this.CCResourceMaps[i]);
+
+ friendlyTiles.annulateTerritory(gameState,territory);
+
+ if (gameState.ai.distanceFromMeMap !== undefined)
+ friendlyTiles.add(gameState.ai.distanceFromMeMap);
+
+ //friendlyTiles.dumpIm(gameState.getTimeElapsed() + "_" + resource + "_density_fade_final2_CC.png", 5000);
+
+ best = friendlyTiles.findBestTile(4, obstructions);
+ bestIdx = best[0];
+ } else {
+ //friendlyTiles.dumpIm(gameState.getTimeElapsed() + "_" + resource + "_density_fade_final2.png", 5000);
+ }
+
+ if (best[1] < 700)
+ {
+ // tell the dropsite builder we haven't found anything satisfactory.
+ return [false, [-1,0]];
+ }
+
+ //warn ("Getting " + best[1] + " for " +resource);
+
var x = ((bestIdx % friendlyTiles.width) + 0.5) * gameState.cellSize;
var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * gameState.cellSize;
if (territory.getOwner([x,z]) === 0) {
isCivilCenter = true;
bestIdx = friendlyTiles.findBestTile(4, obstructions)[0];
x = ((bestIdx % friendlyTiles.width) + 0.5) * gameState.cellSize;
z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * gameState.cellSize;
}
return [isCivilCenter, [x,z]];
};
EconomyManager.prototype.updateResourceConcentrations = function(gameState, resource){
var self = this;
gameState.getOwnDropsites(resource).forEach(function(dropsite) { //}){
var amount = 0;
var amountFar = 0;
- if (dropsite.getMetadata("linked-resources-" + resource) == undefined)
+ // loop through the entity collections of linked-resources, if there is one.
+ if (dropsite.getMetadata(PlayerID, "linked-resources-" + resource) == undefined)
return;
- dropsite.getMetadata("linked-resources-" + resource).forEach(function(supply){ //}){
- if (supply.getMetadata("full") == true)
+ dropsite.getMetadata(PlayerID, "linked-resources-" + resource).forEach(function(supply){ //}){
+ if (supply.getMetadata(PlayerID, "full") == true || supply.getMetadata(PlayerID, "inaccessible") == true)
return;
- if (supply.getMetadata("linked-dropsite-nearby") == true)
+ if (supply.getMetadata(PlayerID, "linked-dropsite-nearby") == true)
amount += supply.resourceSupplyAmount();
else
amountFar += supply.resourceSupplyAmount();
- supply.setMetadata("dp-update-value",supply.resourceSupplyAmount());
+ supply.setMetadata(PlayerID, "dp-update-value",supply.resourceSupplyAmount());
});
- dropsite.setMetadata("resource-quantity-" + resource, amount);
- dropsite.setMetadata("resource-quantity-far-" + resource, amountFar);
+ dropsite.setMetadata(PlayerID, "resource-quantity-" + resource, amount);
+ dropsite.setMetadata(PlayerID, "resource-quantity-far-" + resource, amountFar);
+ //debug (dropsite + " has " + amount + ", " + amountFar +" of " +resource);
});
};
// Stores lists of nearby resources
+// This is done only once per dropsite.
EconomyManager.prototype.updateNearbyResources = function(gameState,resource){
var self = this;
var resources = ["food", "wood", "stone", "metal"];
var resourceSupplies;
- // TODO: centralize with that other function that uses the same variables
- // The weight of the influence function is amountOfResource/decreaseFactor
- var decreaseFactor = {'wood': 12.0, 'stone': 10.0, 'metal': 10.0, 'food': 20.0};
+ // By how much to divide the resource amount for plotting.
+ var decreaseFactor = {'wood': 25.0, 'stone': 40.0, 'metal': 40.0, 'food': 20.0};
// This is the maximum radius of the influence
- var radius = {'wood':25.0, 'stone': 24.0, 'metal': 24.0, 'food': 24.0};
+ var radius = {'wood':10.0, 'stone': 24.0, 'metal': 24.0, 'food': 24.0};
+
// smallRadius is the distance necessary to mark a resource as linked to a dropsite.
var smallRadius = { 'food':80*80,'wood':60*60,'stone':70*70,'metal':70*70 };
// bigRadius is the distance for a weak link (resources are considered when building other dropsites)
// and their resource amount is divided by 3 when checking for dropsite resource level.
var bigRadius = { 'food':140*140,'wood':140*140,'stone':140*140,'metal':140*140 };
gameState.getOwnDropsites(resource).forEach(function(ent) { //}){
- if (ent.getMetadata("nearby-resources-" + resource) === undefined){
+ if (ent.getMetadata(PlayerID, "nearby-resources-" + resource) === undefined){
// let's defined the entity collections (by metadata)
gameState.getResourceSupplies(resource).filter( function (supply) { //}){
var distance = SquareVectorDistance(supply.position(), ent.position());
// if we're close than the current linked-dropsite, or if it's not linked
// TODO: change when actualy resource counting is implemented.
- if (supply.getMetadata("linked-dropsite") == undefined || supply.getMetadata("linked-dropsite-dist") > distance) {
+ if (supply.getMetadata(PlayerID, "linked-dropsite") == undefined || supply.getMetadata(PlayerID, "linked-dropsite-dist") > distance) {
if (distance < bigRadius[resource]) {
if (distance < smallRadius[resource]) {
// it's new to the game, remove it from the resource maps
- if (supply.getMetadata("linked-dropsite") == undefined || supply.getMetadata("linked-dropsite-nearby") == false) {
+ if (supply.getMetadata(PlayerID, "linked-dropsite") == undefined || supply.getMetadata(PlayerID, "linked-dropsite-nearby") == false) {
var x = Math.round(supply.position()[0] / gameState.cellSize);
var z = Math.round(supply.position()[1] / gameState.cellSize);
var strength = Math.round(supply.resourceSupplyMax()/decreaseFactor[resource]);
- self.resourceMaps[resource].addInfluence(x, z, radius[resource], -strength);
+ if (resource === "wood" || resource === "food")
+ {
+ self.CCResourceMaps[resource].addInfluence(x, z, 15, -strength/2.0,'constant');
+ self.resourceMaps[resource].addInfluence(x, z, 2, 5,'constant');
+ self.resourceMaps[resource].addInfluence(x, z, 10.0, -strength,'constant');
+ } else if (resource === "stone" || resource === "metal")
+ {
+ self.CCResourceMaps[resource].addInfluence(x, z, 30, -strength,'constant');
+ self.resourceMaps[resource].addInfluence(x, z, 3, 50);
+ self.resourceMaps[resource].addInfluence(x, z, 12.0, -strength/1.5);
+ self.resourceMaps[resource].addInfluence(x, z, 12.0, -strength/2.0,'constant');
+ }
}
- supply.setMetadata("linked-dropsite-nearby", true );
+ supply.setMetadata(PlayerID, "linked-dropsite-nearby", true );
} else {
- supply.setMetadata("linked-dropsite-nearby", false );
+ supply.setMetadata(PlayerID, "linked-dropsite-nearby", false );
}
- supply.setMetadata("linked-dropsite", ent.id() );
- supply.setMetadata("linked-dropsite-dist", +distance);
+ supply.setMetadata(PlayerID, "linked-dropsite", ent.id() );
+ supply.setMetadata(PlayerID, "linked-dropsite-dist", +distance);
}
}
});
// This one is both for the nearby and the linked
- var filter = Filters.byMetadata("linked-dropsite", ent.id());
+ var filter = Filters.byMetadata(PlayerID, "linked-dropsite", ent.id());
var collection = gameState.getResourceSupplies(resource).filter(filter);
collection.registerUpdates();
- ent.setMetadata("linked-resources-" + resource, collection);
+ ent.setMetadata(PlayerID, "linked-resources-" + resource, collection);
- filter = Filters.byMetadata("linked-dropsite-nearby",true);
+ filter = Filters.byMetadata(PlayerID, "linked-dropsite-nearby",true);
var collection2 = collection.filter(filter);
collection2.registerUpdates();
- ent.setMetadata("nearby-resources-" + resource, collection2);
+ ent.setMetadata(PlayerID, "nearby-resources-" + resource, collection2);
}
- /*
- // Make resources glow wildly
+
+
+ /*// Make resources glow wildly
if (resource == "food"){
- ent.getMetadata("linked-resources-" + resource).forEach(function(ent){
+ ent.getMetadata(PlayerID, "linked-resources-" + resource).forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [1,0,0]});
});
- ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){
+ ent.getMetadata(PlayerID, "nearby-resources-" + resource).forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [10,0,0]});
});
}
if (resource == "wood"){
- ent.getMetadata("linked-resources-" + resource).forEach(function(ent){
+ ent.getMetadata(PlayerID, "linked-resources-" + resource).forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,1,0]});
});
- ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){
+ ent.getMetadata(PlayerID, "nearby-resources-" + resource).forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,10,0]});
});
}
if (resource == "metal"){
- ent.getMetadata("linked-resources-" + resource).forEach(function(ent){
+ ent.getMetadata(PlayerID, "linked-resources-" + resource).forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,0,1]});
});
- ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){
+ ent.getMetadata(PlayerID, "nearby-resources-" + resource).forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,0,10]});
});
}
if (resource == "stone"){
- ent.getMetadata("linked-resources-" + resource).forEach(function(ent){
+ ent.getMetadata(PlayerID, "linked-resources-" + resource).forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,0.5,1]});
});
- ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){
+ ent.getMetadata(PlayerID, "nearby-resources-" + resource).forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,5,10]});
});
}*/
});
};
//return the number of resource dropsites with an acceptable amount of the resource nearby
EconomyManager.prototype.checkResourceConcentrations = function(gameState, resource){
//TODO: make these values adaptive
var requiredInfluence = {"wood": 2500, "stone": 600, "metal": 600};
var count = 0;
gameState.getOwnDropsites(resource).forEach(function(ent) { //}){
- if (ent.getMetadata("resource-quantity-" + resource) == undefined || typeof(ent.getMetadata("resource-quantity-" + resource)) !== "number") {
+ if (ent.getMetadata(PlayerID, "resource-quantity-" + resource) == undefined || typeof(ent.getMetadata(PlayerID, "resource-quantity-" + resource)) !== "number") {
count++; // assume it's OK if we don't know.
return;
}
- var quantity = +ent.getMetadata("resource-quantity-" + resource);
- var quantityFar = +ent.getMetadata("resource-quantity-far-" + resource);
+ var quantity = +ent.getMetadata(PlayerID, "resource-quantity-" + resource);
+ var quantityFar = +ent.getMetadata(PlayerID, "resource-quantity-far-" + resource);
if (quantity >= requiredInfluence[resource]) {
count++;
} else if (quantity + quantityFar >= requiredInfluence[resource]) {
count += 0.5 + (quantity/requiredInfluence[resource])/2;
} else {
count += ((quantity + quantityFar)/requiredInfluence[resource])/2;
}
});
return count;
};
+EconomyManager.prototype.buildTemple = function(gameState, queues){
+ if (gameState.currentPhase() >= 2 ) {
+ if (queues.economicBuilding.countTotalQueuedUnits() === 0 &&
+ gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_temple")) === 0){
+ queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_temple"));
+ }
+ }
+};
+
EconomyManager.prototype.buildMarket = function(gameState, queues){
- if (gameState.getTimeElapsed() > 620 * 1000){
+ if (gameState.getTimeElapsed() > this.marketStartTime && gameState.currentPhase() >= 2 ) {
if (queues.economicBuilding.countTotalQueuedUnitsWithClass("BarterMarket") === 0 &&
- gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_market")) === 0){
+ gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_market")) === 0){
//only ever build one mill/CC/market at a time
queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_market"));
}
}
};
+
+// Build a farmstead to go to town phase faster and prepare for research. Only really active on higher diff mode.
+EconomyManager.prototype.buildFarmstead = function(gameState, queues){
+ if (gameState.getTimeElapsed() > this.farmsteadStartTime) {
+ if (queues.economicBuilding.countTotalQueuedUnitsWithClass("DropsiteFood") === 0 &&
+ gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_farmstead")) === 0){
+ //only ever build one mill/CC/market at a time
+ queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_farmstead"));
+ }
+ }
+};
+
+EconomyManager.prototype.buildDock = function(gameState, queues){
+ if (!gameState.ai.waterMap)
+ return;
+ if (gameState.getTimeElapsed() > this.dockStartTime) {
+ if (queues.economicBuilding.countTotalQueuedUnitsWithClass("NavalMarket") === 0 &&
+ gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_dock")) === 0){
+ if (gameState.civ() == "cart" && gameState.currentPhase() > 1)
+ queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_super_dock"));
+ else if (gameState.civ() !== "cart")
+ queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_dock"));
+ }
+ }
+};
+
// if qBot has resources it doesn't need, it'll try to barter it for resources it needs
// once per turn because the info doesn't update between a turn and I don't want to fix it.
// pretty efficient.
EconomyManager.prototype.tryBartering = function(gameState){
var done = false;
if (gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_market")) >= 1) {
- var needs = gameState.ai.queueManager.futureNeeds(gameState,true);
+ var needs = gameState.ai.queueManager.futureNeeds(gameState,false);
var ress = gameState.ai.queueManager.getAvailableResources(gameState);
for (sell in needs) {
for (buy in needs) {
if (!done && buy != sell && needs[sell] <= 0 && ress[sell] > 400) { // if we don't need it and have a buffer
if ( (ress[buy] < 400) || needs[buy] > 0) { // if we need that other resource/ have too little of it
var markets = gameState.getOwnEntitiesByType(gameState.applyCiv("structures/{civ}_market")).toEntityArray();
markets[0].barter(buy,sell,100);
//debug ("bartered " +sell +" for " + buy + ", value 100");
done = true;
}
}
}
}
}
};
// so this always try to build dropsites.
EconomyManager.prototype.buildDropsites = function(gameState, queues){
- if ( (queues.economicBuilding.totalLength() - queues.economicBuilding.countTotalQueuedUnitsWithClass("BarterMarket")) === 0 &&
- gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_mill")) === 0 &&
+ if ( queues.dropsites.totalLength() === 0 && gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_mill")) === 0 &&
gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_civil_centre")) === 0){
//only ever build one mill/CC/market at a time
if (gameState.getTimeElapsed() > 30 * 1000){
for (var resource in this.dropsiteNumbers){
+
if (this.checkResourceConcentrations(gameState, resource) < this.dropsiteNumbers[resource]){
-
var spot = this.getBestResourceBuildSpot(gameState, resource);
-
+ if (spot[1][0] === -1)
+ break;
if (spot[0] === true){
- queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre", spot[1]));
+ queues.dropsites.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre", spot[1]));
} else {
- queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_mill", spot[1]));
+ queues.dropsites.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_mill", spot[1]));
}
break;
}
}
}
}
};
+// build more houses if needed.
+EconomyManager.prototype.buildMoreHouses = function(gameState, queues) {
+ if (gameState.getTimeElapsed() < 25000)
+ return;
+
+ // temporary 'remaining population space' based check, need to do
+ // predictive in future
+ if (gameState.getPopulationLimit() - gameState.getPopulation() < 20
+ && gameState.getPopulationLimit() < gameState.getPopulationMax()) {
+
+ var numConstructing = gameState.countEntitiesByType(gameState.applyCiv("foundation|structures/{civ}_house"));
+ var numPlanned = queues.house.totalLength();
+
+ var additional = Math.ceil((20 - (gameState.getPopulationLimit() - gameState.getPopulation())) / 10)
+ - numConstructing - numPlanned;
+
+ for ( var i = 0; i < additional; i++) {
+ queues.house.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_house"));
+ }
+ }
+};
+
+// Change our priorities based on our gathering statistics.
+EconomyManager.prototype.rePrioritize = function(gameState) {
+ var statG = gameState.playerData.statistics.resourcesGathered;
+ var statU = gameState.playerData.statistics.resourcesUsed;
+ var resources = ["food", "wood", "stone", "metal"];
+ for each (ress in resources)
+ {
+ var eff = (statG[ress]-this.lastStatG[ress]) / (statU[ress]-this.lastStatU[ress]);
+
+ if ((statU[ress]-this.lastStatU[ress]) === 0)
+ continue;
+
+ if (eff < 0.6)
+ this.baseNeed[ress] = Math.max(10, this.baseNeed[ress] + 10);
+ else if (eff < 0.7)
+ this.baseNeed[ress] = Math.max(10, this.baseNeed[ress] + 6);
+ else if (eff < 0.8)
+ this.baseNeed[ress] = Math.max(10, this.baseNeed[ress] + 4);
+ else if (eff > 1.2)
+ this.baseNeed[ress] = Math.max(10, this.baseNeed[ress] - 10 );
+ else if (eff > 1.1)
+ this.baseNeed[ress] = Math.max(10, this.baseNeed[ress] - 8 );
+ else if (eff > 1.0)
+ this.baseNeed[ress] = Math.max(10, this.baseNeed[ress] - 5 );
+ else if (eff > 0.9)
+ this.baseNeed[ress] = Math.max(10, this.baseNeed[ress] - 3 );
+ //debug (ress + " Eff: " + eff);
+ }
+
+ //debug ("Stats ");
+ //debug ("Food: " + this.baseNeed["food"]);
+ //debug ("Wood: " + this.baseNeed["wood"]);
+ //debug ("Stone: " + this.baseNeed["stone"]);
+ //debug ("Metal: " + this.baseNeed["metal"]);
+
+ this.lastStatG = statG;
+ this.lastStatU = statU;
+};
-
EconomyManager.prototype.update = function(gameState, queues, events) {
Engine.ProfileStart("economy update");
this.reassignRolelessUnits(gameState);
- this.buildNewCC(gameState,queues);
+ // run a particular BO if we have no CC as this is highest priority
+ if (this.buildNewCC(gameState,queues))
+ {
+ Engine.ProfileStart("Update Resource Maps and Concentrations");
+ this.updateResourceMaps(gameState, events);
+
+ if (gameState.ai.playedTurn % 2 === 0) {
+ var resources = ["food", "wood", "stone", "metal"];
+ this.updateNearbyResources(gameState, resources[(gameState.ai.playedTurn % 8)/2]);
+ } else if (gameState.ai.playedTurn % 2 === 1) {
+ var resources = ["food", "wood", "stone", "metal"];
+ this.updateResourceConcentrations(gameState, resources[((gameState.ai.playedTurn+1) % 8)/2]);
+ }
+ Engine.ProfileStop();
+
+ if (Config.difficulty !== 0)
+ this.tryBartering(gameState);
+
+ if (gameState.ai.playedTurn % 20 === 0){
+ this.setWorkersIdleByPriority(gameState);
+ } else {
+ Engine.ProfileStart("Assign builders");
+ this.assignToFoundations(gameState, false);
+ Engine.ProfileStop();
+
+ Engine.ProfileStart("Reassign Idle Workers");
+ this.reassignIdleWorkers(gameState);
+ Engine.ProfileStop();
+ }
+
+ // TODO: do this incrementally a la defence.js
+ Engine.ProfileStart("Run Workers");
+ gameState.getOwnEntitiesByRole("worker").forEach(function(ent){
+ if (!ent.getMetadata(PlayerID, "worker-object")){
+ ent.setMetadata(PlayerID, "worker-object", new Worker(ent));
+ }
+ ent.getMetadata(PlayerID, "worker-object").update(gameState);
+ });
+ // Gatherer count updates for non-workers
+ var filter = Filters.and(Filters.not(Filters.byMetadata(PlayerID, "worker-object", undefined)),
+ Filters.not(Filters.byMetadata(PlayerID, "role", "worker")));
+ gameState.updatingCollection("reassigned-workers", filter, gameState.getOwnEntities()).forEach(function(ent){
+ ent.getMetadata(PlayerID, "worker-object").updateGathererCounts(gameState);
+ });
+
+ // Gatherer count updates for destroyed units
+ for (var i in events) {
+ var e = events[i];
+
+ if (e.type === "Destroy") {
+ if (e.msg.metadata && e.msg.metadata[PlayerID] && e.msg.metadata[PlayerID]["worker-object"]){
+ e.msg.metadata[PlayerID]["worker-object"].updateGathererCounts(gameState, true);
+ delete e.msg.metadata[PlayerID]["worker-object"];
+ }
+ }
+ }
+ Engine.ProfileStop();
+ return;
+ }
// this function also deals with a few things that are number-of-workers related
- Engine.ProfileStart("Train workers and build farms");
+ Engine.ProfileStart("Train workers and build farms, houses. Research techs.");
this.trainMoreWorkers(gameState, queues);
-
+
+ if ((gameState.ai.playedTurn+2) % 20 === 0 && gameState.getTimeElapsed() > this.techStartTime)
+ this.tryResearchTechs(gameState,queues);
+
if ((gameState.ai.playedTurn+1) % 3 === 0)
this.buildMoreFields(gameState,queues);
- Engine.ProfileStop();
+ this.buildMoreHouses(gameState,queues);
+ Engine.ProfileStop();
+
//Later in the game we want to build stuff faster.
if (gameState.getTimeElapsed() > 15*60*1000) {
- this.targetNumBuilders = 6;
- }else{
- this.targetNumBuilders = 3;
+ this.targetNumBuilders = Config.Economy.targetNumBuilders*4;
+ }else if (gameState.getTimeElapsed() > 5*60*1000) {
+ this.targetNumBuilders = Config.Economy.targetNumBuilders*2;
+ } else {
+ this.targetNumBuilders = Config.Economy.targetNumBuilders;
+ }
+ if (gameState.currentPhase() == 1)
+ this.femaleRatio = Config.Economy.femaleRatio * 2.0;
+ else
+ this.femaleRatio = Config.Economy.femaleRatio;
+
+ if (this.baseNeed["metal"] === 0 && gameState.getTimeElapsed() > 540*1000) {
+ this.baseNeed["food"] = 140;
+ this.baseNeed["wood"] = 100;
+ this.baseNeed["stone"] = 50;
+ this.baseNeed["metal"] = 80;
}
+ if (Config.difficulty === 2 && gameState.getTimeElapsed() > 540*1000 && gameState.ai.playedTurn % 60 === 10)
+ this.rePrioritize(gameState);
Engine.ProfileStart("Update Resource Maps and Concentrations");
this.updateResourceMaps(gameState, events);
if (gameState.ai.playedTurn % 2 === 0) {
var resources = ["food", "wood", "stone", "metal"];
this.updateNearbyResources(gameState, resources[(gameState.ai.playedTurn % 8)/2]);
} else if (gameState.ai.playedTurn % 2 === 1) {
var resources = ["food", "wood", "stone", "metal"];
this.updateResourceConcentrations(gameState, resources[((gameState.ai.playedTurn+1) % 8)/2]);
}
Engine.ProfileStop();
- if (gameState.ai.playedTurn % 8 === 0) {
+ if (gameState.ai.playedTurn % 4 === 0) {
Engine.ProfileStart("Build new Dropsites");
this.buildDropsites(gameState, queues);
Engine.ProfileStop();
}
- this.tryBartering(gameState);
+ if (Config.difficulty !== 0)
+ this.tryBartering(gameState);
+
+ this.buildFarmstead(gameState, queues);
this.buildMarket(gameState, queues);
-
- // TODO: implement a timer based system for this
- this.setCount += 1;
- if (this.setCount >= 20){
+ if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_market")) === 1)
+ this.buildTemple(gameState, queues);
+ this.buildDock(gameState, queues); // not if not a water map.
+
+ if (gameState.ai.playedTurn % 20 === 0){
this.setWorkersIdleByPriority(gameState);
- this.setCount = 0;
}
-
Engine.ProfileStart("Assign builders");
this.assignToFoundations(gameState);
Engine.ProfileStop();
Engine.ProfileStart("Reassign Idle Workers");
this.reassignIdleWorkers(gameState);
Engine.ProfileStop();
// this is pretty slow, run it once in a while
- if (gameState.ai.playedTurn % 4 === 0) {
+ if (gameState.ai.playedTurn % 4 === 1) {
Engine.ProfileStart("Swap Workers");
var gathererGroups = {};
gameState.getOwnEntitiesByRole("worker").forEach(function(ent){
var key = uneval(ent.resourceGatherRates());
if (!gathererGroups[key]){
gathererGroups[key] = {"food": [], "wood": [], "metal": [], "stone": []};
}
- if (ent.getMetadata("gather-type") in gathererGroups[key]){
- gathererGroups[key][ent.getMetadata("gather-type")].push(ent);
+ if (ent.getMetadata(PlayerID, "gather-type") in gathererGroups[key]){
+ gathererGroups[key][ent.getMetadata(PlayerID, "gather-type")].push(ent);
}
});
for (var i in gathererGroups){
for (var j in gathererGroups){
var a = eval(i);
var b = eval(j);
if (a !== undefined && b !== undefined)
if (a["food.grain"]/b["food.grain"] > a["wood.tree"]/b["wood.tree"] && gathererGroups[i]["wood"].length > 0
&& gathererGroups[j]["food"].length > 0){
for (var k = 0; k < Math.min(gathererGroups[i]["wood"].length, gathererGroups[j]["food"].length); k++){
- gathererGroups[i]["wood"][k].setMetadata("gather-type", "food");
- gathererGroups[j]["food"][k].setMetadata("gather-type", "wood");
+ gathererGroups[i]["wood"][k].setMetadata(PlayerID, "gather-type", "food");
+ gathererGroups[j]["food"][k].setMetadata(PlayerID, "gather-type", "wood");
}
}
}
}
Engine.ProfileStop();
}
+
+ // TODO: do this incrementally a la defence.js
Engine.ProfileStart("Run Workers");
gameState.getOwnEntitiesByRole("worker").forEach(function(ent){
- if (!ent.getMetadata("worker-object")){
- ent.setMetadata("worker-object", new Worker(ent));
+ if (!ent.getMetadata(PlayerID, "worker-object")){
+ ent.setMetadata(PlayerID, "worker-object", new Worker(ent));
}
- ent.getMetadata("worker-object").update(gameState);
+ ent.getMetadata(PlayerID, "worker-object").update(gameState);
});
-
// Gatherer count updates for non-workers
- var filter = Filters.and(Filters.not(Filters.byMetadata("worker-object", undefined)),
- Filters.not(Filters.byMetadata("role", "worker")));
+ var filter = Filters.and(Filters.not(Filters.byMetadata(PlayerID, "worker-object", undefined)),
+ Filters.not(Filters.byMetadata(PlayerID, "role", "worker")));
gameState.updatingCollection("reassigned-workers", filter, gameState.getOwnEntities()).forEach(function(ent){
- ent.getMetadata("worker-object").updateGathererCounts(gameState);
+ ent.getMetadata(PlayerID, "worker-object").updateGathererCounts(gameState);
});
// Gatherer count updates for destroyed units
for (var i in events) {
var e = events[i];
if (e.type === "Destroy") {
- if (e.msg.metadata && e.msg.metadata[gameState.getPlayerID()] && e.msg.metadata[gameState.getPlayerID()]["worker-object"]){
- e.msg.metadata[gameState.getPlayerID()]["worker-object"].updateGathererCounts(gameState, true);
- delete e.msg.metadata[gameState.getPlayerID()]["worker-object"];
+ if (e.msg.metadata && e.msg.metadata[PlayerID] && e.msg.metadata[PlayerID]["worker-object"]){
+ e.msg.metadata[PlayerID]["worker-object"].updateGathererCounts(gameState, true);
+ delete e.msg.metadata[PlayerID]["worker-object"];
}
}
}
Engine.ProfileStop();
Engine.ProfileStop();
};
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/enemy-watcher.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/enemy-watcher.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/enemy-watcher.js (revision 13225)
@@ -1,215 +1,212 @@
/*
- * A class that keeps track of enem buildings, units, and pretty much anything I can think of (still a LOT TODO here)
+ * A class that keeps track of enemy buildings, units, and pretty much anything I can think of (still a LOT TODO here)
* Only watches one enemy, you'll need one per enemy.
+ * Note: pretty much unused in the current version.
*/
var enemyWatcher = function(gameState, playerToWatch) {
this.watched = playerToWatch;
- // creating fitting entity collections
+ // using global entity collections, shared by any AI that knows the name of this.
+
var filter = Filters.and(Filters.byClass("Structure"), Filters.byOwner(this.watched));
- this.enemyBuildings = gameState.getEnemyEntities().filter(filter);
- this.enemyBuildings.registerUpdates();
-
+ this.enemyBuildings = gameState.updatingGlobalCollection("player-" +this.watched + "-structures", filter);
+
+ filter = Filters.and(Filters.byClass("Worker"), Filters.byOwner(this.watched));
+ this.enemyCivilians = gameState.updatingGlobalCollection("player-" +this.watched + "-civilians", filter);
+
+ filter = Filters.and(Filters.byClassesOr(["CitizenSoldier", "Hero", "Champion", "Siege"]), Filters.byOwner(this.watched));
+ this.enemySoldiers = gameState.updatingGlobalCollection("player-" +this.watched + "-soldiers", filter);
+
filter = Filters.and(Filters.byClass("Worker"), Filters.byOwner(this.watched));
this.enemyCivilians = gameState.getEnemyEntities().filter(filter);
this.enemyCivilians.registerUpdates();
filter = Filters.and(Filters.byClassesOr(["CitizenSoldier", "Hero", "Champion", "Siege"]), Filters.byOwner(this.watched));
this.enemySoldiers = gameState.getEnemyEntities().filter(filter);
this.enemySoldiers.registerUpdates();
-
- // okay now we register here only enemy soldiers that we are monitoring (ie we see as part of an armyā¦)
- filter = Filters.and(Filters.byClassesOr(["CitizenSoldier", "Hero", "Champion", "Siege"]), Filters.and(Filters.byMetadata("monitored","true"),Filters.byOwner(this.watched)));
- this.monitoredEnemySoldiers = gameState.getEnemyEntities().filter(filter);
- this.monitoredEnemySoldiers.registerUpdates();
-
- // and here those that we do not monitor
- filter = Filters.and(Filters.byClassesOr(["CitizenSoldier","Hero","Champion","Siege"]), Filters.and(Filters.not(Filters.byMetadata("monitored","true")),Filters.byOwner(this.watched)));
- this.unmonitoredEnemySoldiers = gameState.getEnemyEntities().filter(filter);
- this.unmonitoredEnemySoldiers.registerUpdates();
// entity collections too.
this.armies = {};
this.enemyBuildingClass = {};
this.totalNBofArmies = 0;
// this is an array of integers, refering to "this.armies[ XX ]"
this.dangerousArmies = [];
};
enemyWatcher.prototype.getAllEnemySoldiers = function() {
return this.enemySoldiers;
};
enemyWatcher.prototype.getAllEnemyBuildings = function() {
return this.enemyBuildings;
};
-enemyWatcher.prototype.getEnemyBuildings = function(specialClass, OneTime) {
- var filter = Filters.byClass(specialClass);
- var returnable = this.enemyBuildings.filter(filter);
- if (!this.enemyBuildingClass[specialClass] && !OneTime) {
- this.enemyBuildingClass[specialClass] = returnable;
- this.enemyBuildingClass[specialClass].registerUpdates();
- return this.enemyBuildingClass[specialClass];
- }
- return returnable;
+enemyWatcher.prototype.getEnemyBuildings = function(gameState, specialClass, OneTime) {
+ var filter = Filters.byClass(specialClass);
+
+ if (OneTime && gameState.getGEC("player-" +this.watched + "-structures-" +specialClass))
+ return gameState.getGEC("player-" +this.watched + "-structures-" +specialClass);
+ else if (OneTime)
+ return this.enemyBuildings.filter(filter);
+
+ return gameState.updatingGlobalCollection("player-" +this.watched + "-structures-" +specialClass, filter, gameState.getGEC("player-" +this.watched + "-structure"));
};
enemyWatcher.prototype.getDangerousArmies = function() {
var toreturn = {};
for (i in this.dangerousArmies)
toreturn[this.dangerousArmies[i]] = this.armies[this.dangerousArmies[i]];
return toreturn;
};
enemyWatcher.prototype.getSafeArmies = function() {
var toreturn = {};
for (i in this.armies)
if (this.dangerousArmies.indexOf(i) == -1)
toreturn[i] = this.armies[i];
return toreturn;
};
enemyWatcher.prototype.resetDangerousArmies = function() {
this.dangerousArmies = [];
};
enemyWatcher.prototype.setAsDangerous = function(armyID) {
if (this.dangerousArmies.indexOf(armyID) === -1)
this.dangerousArmies.push(armyID);
};
enemyWatcher.prototype.isDangerous = function(armyID) {
if (this.dangerousArmies.indexOf(armyID) === -1)
return false;
return true;
};
// returns [id, army]
enemyWatcher.prototype.getArmyFromMember = function(memberID) {
for (i in this.armies) {
if (this.armies[i].toIdArray().indexOf(memberID) !== -1)
return [i,this.armies[i]];
}
return undefined;
};
enemyWatcher.prototype.isPartOfDangerousArmy = function(memberID) {
var armyID = this.getArmyFromMember(memberID)[0];
if (this.isDangerous(armyID))
return true;
return false;
};
enemyWatcher.prototype.cleanDebug = function() {
for (armyID in this.armies) {
var army = this.armies[armyID];
debug ("Army " +armyID);
debug (army.length +" members, centered around " +army.getCentrePosition());
}
}
// this will monitor any unmonitored soldier.
enemyWatcher.prototype.detectArmies = function(gameState){
//this.cleanDebug();
var self = this;
if (gameState.ai.playedTurn % 4 === 0) {
Engine.ProfileStart("Looking for new soldiers");
// let's loop through unmonitored enemy soldiers
this.unmonitoredEnemySoldiers.forEach( function (enemy) { //}){
if (enemy.position() === undefined)
return;
// this was an unmonitored unit, we do not know any army associated with it. We assign it a new army (we'll merge later if needed)
- enemy.setMetadata("monitored","true");
+ enemy.setMetadata(PlayerID, "monitored","true");
var armyID = gameState.player + "" + self.totalNBofArmies;
self.totalNBofArmies++,
- enemy.setMetadata("EnemyWatcherArmy",armyID);
- var filter = Filters.byMetadata("EnemyWatcherArmy",armyID);
+ enemy.setMetadata(PlayerID, "EnemyWatcherArmy",armyID);
+ var filter = Filters.byMetadata(PlayerID, "EnemyWatcherArmy",armyID);
var army = self.enemySoldiers.filter(filter);
self.armies[armyID] = army;
self.armies[armyID].registerUpdates();
self.armies[armyID].length;
});
Engine.ProfileStop();
} else if (gameState.ai.playedTurn % 16 === 3) {
Engine.ProfileStart("Merging");
this.mergeArmies(); // calls "scrap empty armies"
Engine.ProfileStop();
} else if (gameState.ai.playedTurn % 16 === 7) {
Engine.ProfileStart("Splitting");
this.splitArmies(gameState);
Engine.ProfileStop();
}
};
// this will merge any two army who are too close together. The distance for "army" is fairly big.
// note: this doesn't actually merge two entity collections... It simply changes the unit metadatas, and will clear the empty entity collection
enemyWatcher.prototype.mergeArmies = function(){
for (army in this.armies) {
var firstArmy = this.armies[army];
if (firstArmy.length !== 0) {
var firstAPos = firstArmy.getApproximatePosition(4);
for (otherArmy in this.armies) {
if (otherArmy !== army && this.armies[otherArmy].length !== 0) {
var secondArmy = this.armies[otherArmy];
// we're not self merging, so we check if the two armies are close together
if (inRange(firstAPos,secondArmy.getApproximatePosition(4), 4000 ) ) {
// okay so we merge the two together
// if the other one was dangerous and we weren't, we're now.
if (this.dangerousArmies.indexOf(otherArmy) !== -1 && this.dangerousArmies.indexOf(army) === -1)
this.dangerousArmies.push(army);
secondArmy.forEach( function(ent) {
- ent.setMetadata("EnemyWatcherArmy",army);
+ ent.setMetadata(PlayerID, "EnemyWatcherArmy",army);
});
}
}
}
}
}
this.ScrapEmptyArmies();
};
enemyWatcher.prototype.ScrapEmptyArmies = function(){
var removelist = [];
for (army in this.armies) {
if (this.armies[army].length === 0) {
removelist.push(army);
// if the army was dangerous, we remove it from the list
if (this.dangerousArmies.indexOf(army) !== -1)
this.dangerousArmies.splice(this.dangerousArmies.indexOf(army),1);
}
}
for each (toRemove in removelist) {
delete this.armies[toRemove];
}
};
// splits any unit too far from the centerposition
enemyWatcher.prototype.splitArmies = function(gameState){
var self = this;
- var map = gameState.getTerritoryMap();
+ var map = Map.createTerritoryMap(gameState);
for (armyID in this.armies) {
var army = this.armies[armyID];
var centre = army.getApproximatePosition(4);
if (map.getOwner(centre) === gameState.player)
continue;
army.forEach( function (enemy) {
if (enemy.position() === undefined)
return;
if (!inRange(enemy.position(),centre, 3500) ) {
var newArmyID = gameState.player + "" + self.totalNBofArmies;
if (self.dangerousArmies.indexOf(armyID) !== -1)
self.dangerousArmies.push(newArmyID);
self.totalNBofArmies++,
- enemy.setMetadata("EnemyWatcherArmy",newArmyID);
- var filter = Filters.byMetadata("EnemyWatcherArmy",newArmyID);
+ enemy.setMetadata(PlayerID, "EnemyWatcherArmy",newArmyID);
+ var filter = Filters.byMetadata(PlayerID, "EnemyWatcherArmy",newArmyID);
var newArmy = self.enemySoldiers.filter(filter);
self.armies[newArmyID] = newArmy;
self.armies[newArmyID].registerUpdates();
self.armies[newArmyID].length;
}
});
}
};
\ No newline at end of file
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/entity-extend.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/entity-extend.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/entity-extend.js (revision 13225)
@@ -1,191 +1,60 @@
-EntityTemplate.prototype.genericName = function() {
- if (!this._template.Identity || !this._template.Identity.GenericName)
- return undefined;
- return this._template.Identity.GenericName;
-};
-EntityTemplate.prototype.walkSpeed = function() {
- if (!this._template.UnitMotion || !this._template.UnitMotion.WalkSpeed)
- return undefined;
- return this._template.UnitMotion.WalkSpeed;
-};
-EntityTemplate.prototype.buildTime = function() {
- if (!this._template.Cost || !this._template.Cost.buildTime)
- return undefined;
- return this._template.Cost.buildTime;
-};
-EntityTemplate.prototype.getPopulationBonus = function() {
- if (!this._template.Cost || !this._template.Cost.PopulationBonus)
- return undefined;
-
- return this._template.Cost.PopulationBonus;
-};
-// will return either "food", "wood", "stone", "metal" and not treasure.
-EntityTemplate.prototype.getResourceType = function() {
- if (!this._template.ResourceSupply)
- return undefined;
- var [type, subtype] = this._template.ResourceSupply.Type.split('.');
- if (type == "treasure")
- return subtype;
- return type;
-};
-EntityTemplate.prototype.garrisonMax = function() {
- if (!this._template.GarrisonHolder)
- return undefined;
- return this._template.GarrisonHolder.Max;
-};
-EntityTemplate.prototype.hasClasses = function(array) {
- var classes = this.classes();
- if (!classes)
- return false;
-
- for (i in array)
- if (classes.indexOf(array[i]) === -1)
- return false;
- return true;
-};
-
-// returns the classes this counters:
-// each countered class is an array specifying what is required (even if only one) and the Multiplier [ ["whatever","other whatever"] , 0 ].
-EntityTemplate.prototype.getCounteredClasses = function() {
- if (!this._template.Attack)
- return undefined;
-
- var Classes = [];
- for (i in this._template.Attack) {
- if (!this._template.Attack[i].Bonuses)
- continue;
- for (o in this._template.Attack[i].Bonuses) {
- if (this._template.Attack[i].Bonuses[o].Classes == undefined)
- continue;
- Classes.push([this._template.Attack[i].Bonuses[o].Classes.split(" "), +this._template.Attack[i].Bonuses[o].Multiplier]);
- }
- }
- return Classes;
-};
-
-EntityTemplate.prototype.getMaxStrength = function()
+// returns some sort of DPS * health factor. If you specify a class, it'll use the modifiers against that class too.
+function getMaxStrength(ent, againstClass)
{
+
var strength = 0.0;
- var attackTypes = this.attackTypes();
- var armourStrength = this.armourStrengths();
- var hp = this.maxHitpoints() / 100.0; // some normalization
+ var attackTypes = ent.attackTypes();
+ var armourStrength = ent.armourStrengths();
+ var hp = ent.maxHitpoints() / 100.0; // some normalization
for (var typeKey in attackTypes) {
var type = attackTypes[typeKey];
- var attackStrength = this.attackStrengths(type);
- var attackRange = this.attackRange(type);
- var attackTimes = this.attackTimes(type);
+ var attackStrength = ent.attackStrengths(type);
+ var attackRange = ent.attackRange(type);
+ var attackTimes = ent.attackTimes(type);
for (var str in attackStrength) {
var val = parseFloat(attackStrength[str]);
+ if (againstClass)
+ val *= ent.getMultiplierAgainst(type, againstClass);
switch (str) {
case "crush":
strength += (val * 0.085) / 3;
break;
case "hack":
strength += (val * 0.075) / 3;
break;
case "pierce":
strength += (val * 0.065) / 3;
break;
}
}
if (attackRange){
strength += (attackRange.max * 0.0125) ;
}
for (var str in attackTimes) {
var val = parseFloat(attackTimes[str]);
switch (str){
case "repeat":
strength += (val / 100000);
break;
case "prepare":
strength -= (val / 100000);
break;
}
}
}
for (var str in armourStrength) {
var val = parseFloat(armourStrength[str]);
switch (str) {
case "crush":
strength += (val * 0.085) / 3;
break;
case "hack":
strength += (val * 0.075) / 3;
break;
case "pierce":
strength += (val * 0.065) / 3;
break;
}
}
return strength * hp;
};
-
-EntityTemplate.prototype.costSum = function() {
- if (!this._template.Cost)
- return undefined;
-
- var ret = 0;
- for (var type in this._template.Cost.Resources)
- ret += +this._template.Cost.Resources[type];
- return ret;
-};
-
-
-
-Entity.prototype.deleteMetadata = function(id) {
- delete this._ai._entityMetadata[this.id()];
-};
-
-Entity.prototype.healthLevel = function() {
- return (this.hitpoints() / this.maxHitpoints());
-};
-
-Entity.prototype.visibility = function(player) {
- return this._entity.visibility[player-1];
-};
-
-Entity.prototype.unload = function(id) {
- if (!this._template.GarrisonHolder)
- return undefined;
- Engine.PostCommand({"type": "unload", "garrisonHolder": this.id(), "entity": id});
- return this;
-};
-
-Entity.prototype.unloadAll = function() {
- if (!this._template.GarrisonHolder)
- return undefined;
- Engine.PostCommand({"type": "unload-all", "garrisonHolder": this.id()});
- return this;
-};
-
-Entity.prototype.garrison = function(target) {
- Engine.PostCommand({"type": "garrison", "entities": [this.id()], "target": target.id(),"queued": false});
- return this;
-};
-
-Entity.prototype.stopMoving = function() {
- if (this.position() !== undefined)
- Engine.PostCommand({"type": "walk", "entities": [this.id()], "x": this.position()[0], "z": this.position()[1], "queued": false});
-};
-// from from a unit in the opposite direction.
-Entity.prototype.flee = function(unitToFleeFrom) {
- if (this.position() !== undefined && unitToFleeFrom.position() !== undefined) {
- var FleeDirection = [unitToFleeFrom.position()[0] - this.position()[0],unitToFleeFrom.position()[1] - this.position()[1]];
- var dist = VectorDistance(unitToFleeFrom.position(), this.position() );
- FleeDirection[0] = (FleeDirection[0]/dist) * 5;
- FleeDirection[1] = (FleeDirection[1]/dist) * 5;
-
- Engine.PostCommand({"type": "walk", "entities": [this.id()], "x": this.position()[0] + FleeDirection[0]*5, "z": this.position()[1] + FleeDirection[1]*5, "queued": false});
- }
- return this;
-};
-Entity.prototype.barter = function(buyType, sellType, amount) {
-
- Engine.PostCommand({"type": "barter", "sell" : sellType, "buy" : buyType, "amount" : amount });
- return this;
-};
-Entity.prototype.disband = function() {
-
- Engine.PostCommand({"type": "delete-entities", "entities" : [this.id()] });
- return this;
-};
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/entitycollection-extend.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/entitycollection-extend.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/entitycollection-extend.js (revision 13225)
@@ -1,89 +1,10 @@
-EntityCollection.prototype.attack = function(unit)
-{
- var unitId;
- if (typeof(unit) === "Entity"){
- unitId = unit.id();
- }else{
- unitId = unit;
- }
-
- Engine.PostCommand({"type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false});
- return this;
-};
-// violent, aggressive, defensive, passive, standground
-EntityCollection.prototype.setStance = function(stance){
- Engine.PostCommand({"type": "stance", "entities": this.toIdArray(), "name" : stance, "queued": false});
- return this;
-};
-
function EntityCollectionFromIds(gameState, idList){
var ents = {};
for (var i in idList){
var id = idList[i];
if (gameState.entities._entities[id]) {
ents[id] = gameState.entities._entities[id];
}
}
return new EntityCollection(gameState.ai, ents);
}
-
-EntityCollection.prototype.getCentrePosition = function(){
- var sumPos = [0, 0];
- var count = 0;
- this.forEach(function(ent){
- if (ent.position()){
- sumPos[0] += ent.position()[0];
- sumPos[1] += ent.position()[1];
- count ++;
- }
- });
- if (count === 0){
- return undefined;
- }else{
- return [sumPos[0]/count, sumPos[1]/count];
- }
-};
-
-EntityCollection.prototype.getApproximatePosition = function(sample){
- var sumPos = [0, 0];
- var i = 0;
- for (var id in this._entities)
- {
- var ent = this._entities[id];
- if (ent.position()) {
- sumPos[0] += ent.position()[0];
- sumPos[1] += ent.position()[1];
- i++;
- }
- if (i === sample)
- break;
- }
- if (sample === 0){
- return undefined;
- }else{
- return [sumPos[0]/i, sumPos[1]/i];
- }
-};
-
-EntityCollection.prototype.filterNearest = function(targetPos, n)
-{
- // Compute the distance of each entity
- var data = []; // [ [id, ent, distance], ... ]
- for (var id in this._entities)
- {
- var ent = this._entities[id];
- if (ent.position())
- data.push([id, ent, SquareVectorDistance(targetPos, ent.position())]);
- }
-
- // Sort by increasing distance
- data.sort(function (a, b) { return (a[2] - b[2]); });
- if (n === undefined)
- n = this._length;
- // Extract the first n
- var ret = {};
- for each (var val in data.slice(0, n))
- ret[val[0]] = val[1];
-
- return new EntityCollection(this._ai, ret);
-};
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/map-module.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/map-module.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/map-module.js (revision 13225)
@@ -1,386 +1,451 @@
const TERRITORY_PLAYER_MASK = 0x3F;
//TODO: Make this cope with negative cell values
function Map(gameState, originalMap, actualCopy){
// get the map to find out the correct dimensions
var gameMap = gameState.getMap();
this.width = gameMap.width;
this.height = gameMap.height;
this.length = gameMap.data.length;
if (originalMap && actualCopy){
this.map = new Uint16Array(this.length);
for (var i = 0; i < originalMap.length; ++i)
this.map[i] = originalMap[i];
} else if (originalMap) {
this.map = originalMap;
} else {
this.map = new Uint16Array(this.length);
}
this.cellSize = gameState.cellSize;
}
Map.prototype.gamePosToMapPos = function(p){
- return [Math.round(p[0]/this.cellSize), Math.round(p[1]/this.cellSize)];
+ return [Math.floor(p[0]/this.cellSize), Math.floor(p[1]/this.cellSize)];
};
Map.prototype.point = function(p){
var q = this.gamePosToMapPos(p);
return this.map[q[0] + this.width * q[1]];
};
Map.createObstructionMap = function(gameState, template){
var passabilityMap = gameState.getMap();
var territoryMap = gameState.ai.territoryMap;
// default values
var placementType = "land";
var buildOwn = true;
var buildAlly = true;
var buildNeutral = true;
var buildEnemy = false;
// If there is a template then replace the defaults
if (template){
placementType = template.buildPlacementType();
buildOwn = template.hasBuildTerritory("own");
buildAlly = template.hasBuildTerritory("ally");
buildNeutral = template.hasBuildTerritory("neutral");
buildEnemy = template.hasBuildTerritory("enemy");
}
- var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction");
- // Only accept valid land tiles (we don't handle docks yet)
- switch(placementType){
- case "shore":
- obstructionMask |= gameState.getPassabilityClassMask("building-shore");
- break;
- case "land":
- default:
- obstructionMask |= gameState.getPassabilityClassMask("building-land");
- break;
- }
+ var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction") | gameState.getPassabilityClassMask("building-land");
- var playerID = gameState.getPlayerID();
-
- var obstructionTiles = new Uint16Array(passabilityMap.data.length);
- for (var i = 0; i < passabilityMap.data.length; ++i)
+ if (placementType == "shore")
{
- 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)
- );
- var tileAccessible = (gameState.ai.accessibility.map[i] == 1);
- obstructionTiles[i] = (!tileAccessible || invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 65535;
+ // assume Dock, TODO.
+ var obstructionTiles = new Uint16Array(passabilityMap.data.length);
+ var okay = false;
+ for (var x = 0; x < passabilityMap.width; ++x)
+ {
+ for (var y = 0; y < passabilityMap.height; ++y)
+ {
+ okay = false;
+ var i = x + y*passabilityMap.width;
+ var tilePlayer = (territoryMap.data[i] & TERRITORY_PLAYER_MASK);
+
+ var positions = [[0,1], [1,1], [1,0], [1,-1], [0,-1], [-1,-1], [-1,0], [-1,1]];
+ var available = 0;
+ for each (stuff in positions)
+ {
+ var index = x + stuff[0] + (y+stuff[1])*passabilityMap.width;
+ var index2 = x + stuff[0]*2 + (y+stuff[1]*2)*passabilityMap.width;
+ var index3 = x + stuff[0]*3 + (y+stuff[1]*3)*passabilityMap.width;
+ var index4 = x + stuff[0]*4 + (y+stuff[1]*4)*passabilityMap.width;
+ if ((passabilityMap.data[index] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index) > 500)
+ if ((passabilityMap.data[index2] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index2) > 500)
+ if ((passabilityMap.data[index3] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index3) > 500)
+ if ((passabilityMap.data[index4] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index4) > 500) {
+ if (available < 2)
+ available++;
+ else
+ okay = true;
+ }
+ }
+ // checking for accessibility: if a neighbor is inaccessible, this is too. If it's not on the same "accessible map" as us, we crash-i~u.
+ var radius = 3;
+ for (var xx = -radius;xx <= radius; xx++)
+ for (var yy = -radius;yy <= radius; yy++)
+ {
+ var id = x + xx + (y+yy)*passabilityMap.width;
+ if (id > 0 && id < passabilityMap.data.length)
+ if (gameState.ai.terrainAnalyzer.map[id] === 0 || gameState.ai.terrainAnalyzer.map[id] == 30 || gameState.ai.terrainAnalyzer.map[id] == 40)
+ okay = false;
+ }
+ if (gameState.ai.myIndex !== gameState.ai.accessibility.passMap[i])
+ okay = false;
+ if (gameState.isPlayerEnemy(tilePlayer) && tilePlayer !== 0)
+ okay = false;
+ if ((passabilityMap.data[i] & (gameState.getPassabilityClassMask("building-shore") | gameState.getPassabilityClassMask("default"))))
+ okay = false;
+ obstructionTiles[i] = okay ? 65535 : 0;
+ }
+ }
+ } else {
+ var playerID = PlayerID;
+
+ 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)
+ );
+ var tileAccessible = (gameState.ai.myIndex === gameState.ai.accessibility.passMap[i]);
+ if (placementType === "shore")
+ tileAccessible = true;
+ obstructionTiles[i] = (!tileAccessible || invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 65535;
+ }
}
-
+
var map = new Map(gameState, obstructionTiles);
if (template && template.buildDistance()){
var minDist = template.buildDistance().MinDistance;
var category = template.buildDistance().FromCategory;
if (minDist !== undefined && category !== undefined){
gameState.getOwnEntities().forEach(function(ent) {
if (ent.buildCategory() === category && ent.position()){
var pos = ent.position();
var x = Math.round(pos[0] / gameState.cellSize);
var z = Math.round(pos[1] / gameState.cellSize);
map.addInfluence(x, z, minDist/gameState.cellSize, -65535, 'constant');
}
});
}
}
return map;
};
Map.createTerritoryMap = function(gameState) {
var map = gameState.ai.territoryMap;
var ret = new Map(gameState, map.data);
ret.getOwner = function(p) {
return this.point(p) & TERRITORY_PLAYER_MASK;
}
ret.getOwnerIndex = function(p) {
return this.map[p] & TERRITORY_PLAYER_MASK;
}
return ret;
};
+
Map.prototype.drawDistance = function(gameState, elements) {
for ( var y = 0; y < this.height; ++y) {
for ( var x = 0; x < this.width; ++x) {
var minDist = 500000;
for (i in elements) {
var px = elements[i].position()[0]/gameState.cellSize;
var py = elements[i].position()[1]/gameState.cellSize;
var dist = VectorDistance([px,py], [x,y]);
if (dist < minDist)
minDist = dist;
}
- this.map[x + y*this.width] = Math.max(1,this.width - minDist);
+ this.map[x + y*this.width] = Math.max(1,(this.width - minDist)*2.5);
}
}
};
Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) {
strength = strength ? +strength : +maxDist;
type = type ? type : 'linear';
var x0 = Math.max(0, cx - maxDist);
var y0 = Math.max(0, cy - maxDist);
var x1 = Math.min(this.width, cx + maxDist);
var y1 = Math.min(this.height, cy + maxDist);
var maxDist2 = maxDist * maxDist;
var str = 0.0;
switch (type){
case 'linear':
str = +strength / +maxDist;
break;
case 'quadratic':
str = +strength / +maxDist2;
break;
case 'constant':
str = +strength;
break;
}
+
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
var quant = 0;
switch (type){
case 'linear':
var r = Math.sqrt(r2);
quant = str * (maxDist - r);
break;
case 'quadratic':
quant = str * (maxDist2 - r2);
break;
case 'constant':
quant = str;
break;
}
if (-1 * quant > this.map[x + y * this.width]){
this.map[x + y * this.width] = 0; //set anything which would have gone negative to 0
}else{
this.map[x + y * this.width] += quant;
}
}
}
}
};
Map.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type) {
strength = strength ? +strength : +maxDist;
type = type ? type : 'constant';
var x0 = Math.max(0, cx - maxDist);
var y0 = Math.max(0, cy - maxDist);
var x1 = Math.min(this.width, cx + maxDist);
var y1 = Math.min(this.height, cy + maxDist);
var maxDist2 = maxDist * maxDist;
var str = 0.0;
switch (type){
case 'linear':
str = strength / maxDist;
break;
case 'quadratic':
str = strength / maxDist2;
break;
case 'constant':
str = strength;
break;
}
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
var quant = 0;
switch (type){
case 'linear':
var r = Math.sqrt(r2);
quant = str * (maxDist - r);
break;
case 'quadratic':
quant = str * (maxDist2 - r2);
break;
case 'constant':
quant = str;
break;
}
var machin = this.map[x + y * this.width] * quant;
if (machin <= 0){
this.map[x + y * this.width] = 0; //set anything which would have gone negative to 0
}else{
this.map[x + y * this.width] = machin;
}
}
}
}
};
Map.prototype.setInfluence = function(cx, cy, maxDist, value) {
value = value ? value : 0;
var x0 = Math.max(0, cx - maxDist);
var y0 = Math.max(0, cy - maxDist);
var x1 = Math.min(this.width, cx + maxDist);
var y1 = Math.min(this.height, cy + maxDist);
var maxDist2 = maxDist * maxDist;
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
this.map[x + y * this.width] = value;
}
}
}
};
Map.prototype.sumInfluence = function(cx, cy, radius){
var x0 = Math.max(0, cx - radius);
var y0 = Math.max(0, cy - radius);
var x1 = Math.min(this.width, cx + radius);
var y1 = Math.min(this.height, cy + radius);
var radius2 = radius * radius;
var sum = 0;
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < radius2){
sum += this.map[x + y * this.width];
}
}
}
return sum;
};
/**
* 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)?
*/
Map.prototype.expandInfluences = function() {
var w = this.width;
var h = this.height;
var grid = this.map;
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;
}
}
};
Map.prototype.findBestTile = function(radius, obstructionTiles){
// Find the best non-obstructed tile
var bestIdx = 0;
var bestVal = -1;
for ( var i = 0; i < this.length; ++i) {
if (obstructionTiles.map[i] > radius) {
var v = this.map[i];
if (v > bestVal) {
bestVal = v;
bestIdx = i;
}
}
}
return [bestIdx, bestVal];
};
// Multiplies current map by 3 if in my territory
-Map.prototype.multiplyTerritory = function(gameState,map){
+Map.prototype.multiplyTerritory = function(gameState,map,evenNeutral){
for (var i = 0; i < this.length; ++i){
- if (map.getOwnerIndex(i) === gameState.player)
+ if (map.getOwnerIndex(i) === PlayerID)
this.map[i] *= 2.5;
+ else if (map.getOwnerIndex(i) !== PlayerID && (map.getOwnerIndex(i) !== 0 || evenNeutral))
+ this.map[i] = 0;
}
};
// sets to 0 any enemy territory
-Map.prototype.annulateTerritory = function(gameState,map){
+Map.prototype.annulateTerritory = function(gameState,map,evenNeutral){
for (var i = 0; i < this.length; ++i){
- if (map.getOwnerIndex(i) !== gameState.player && map.getOwnerIndex(i) !== 0)
+ if (map.getOwnerIndex(i) !== PlayerID && (map.getOwnerIndex(i) !== 0 || evenNeutral))
this.map[i] = 0;
}
};
// Multiplies current map by the parameter map pixelwise
Map.prototype.multiply = function(map, onlyBetter,divider,maxMultiplier){
for (var i = 0; i < this.length; ++i){
if (map.map[i]/divider > 1)
this.map[i] = Math.min(maxMultiplier*this.map[i], this.map[i] * (map.map[i]/divider));
}
};
// add to current map by the parameter map pixelwise
Map.prototype.add = function(map){
for (var i = 0; i < this.length; ++i){
this.map[i] += +map.map[i];
}
};
+// add to current map by the parameter map pixelwise with a multiplier
+Map.prototype.madd = function(map, multiplier){
+ for (var i = 0; i < this.length; ++i){
+ this.map[i] += +map.map[i]*multiplier;
+ }
+};
+// add to current map if the map is not null in that point
+Map.prototype.addIfNotNull = function(map){
+ for (var i = 0; i < this.length; ++i){
+ if (this.map[i] !== 0)
+ this.map[i] += +map.map[i];
+ }
+};
// add to current map by the parameter map pixelwise
Map.prototype.subtract = function(map){
for (var i = 0; i < this.length; ++i){
- this.map[i] += map.map[i];
- if (this.map[i] <= 0)
- this.map[i] = 0;
+ this.map[i] = this.map[i] - map.map[i] < 0 ? 0 : this.map[i] - map.map[i];
+ }
+};
+// add to current map by the parameter map pixelwise with a multiplier
+Map.prototype.subtractMultiplied = function(map,multiple){
+ for (var i = 0; i < this.length; ++i){
+ this.map[i] = this.map[i] - map.map[i]*multiple < 0 ? 0 : this.map[i] - map.map[i]*multiple;
}
};
Map.prototype.dumpIm = function(name, threshold){
name = name ? name : "default.png";
threshold = threshold ? threshold : 65500;
Engine.DumpImage(name, this.map, this.width, this.height, threshold);
};
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/plan-building.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/plan-building.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/plan-building.js (revision 13225)
@@ -1,170 +1,183 @@
var BuildingConstructionPlan = function(gameState, type, position) {
this.type = gameState.applyCiv(type);
this.position = position;
this.template = gameState.getTemplate(this.type);
if (!this.template) {
this.invalidTemplate = true;
this.template = undefined;
debug("Cannot build " + this.type);
return;
}
this.category = "building";
this.cost = new Resources(this.template.cost());
this.number = 1; // The number of buildings to build
};
BuildingConstructionPlan.prototype.canExecute = function(gameState) {
if (this.invalidTemplate){
return false;
}
// TODO: verify numeric limits etc
-
+ if (this.template.requiredTech() && !gameState.isResearched(this.template.requiredTech()))
+ return false;
+
var builders = gameState.findBuilders(this.type);
return (builders.length != 0);
};
BuildingConstructionPlan.prototype.execute = function(gameState) {
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);
if (!pos){
debug("No room to place " + this.type);
return;
}
- builders[0].construct(this.type, pos.x, pos.z, pos.angle);
+ if (gameState.getTemplate(this.type).buildCategory() === "Dock")
+ {
+ for (var angle = 0; angle < Math.PI * 2; angle += Math.PI/4)
+ {
+ builders[0].construct(this.type, pos.x, pos.z, angle);
+ }
+ } else
+ builders[0].construct(this.type, pos.x, pos.z, pos.angle);
};
BuildingConstructionPlan.prototype.getCost = function() {
return this.cost;
};
BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
var template = gameState.getTemplate(this.type);
var cellSize = gameState.cellSize; // size of each tile
// First, find all tiles that are far enough away from obstructions:
var obstructionMap = Map.createObstructionMap(gameState,template);
- ///obstructionMap.dumpIm("obstructions.png");
+ //obstructionMap.dumpIm(template.buildCategory() + "_obstructions.png");
+
+ if (template.buildCategory() !== "Dock")
+ obstructionMap.expandInfluences();
- obstructionMap.expandInfluences();
-
// Compute each tile's closeness to friendly structures:
var friendlyTiles = new Map(gameState);
var alreadyHasHouses = false;
// If a position was specified then place the building as close to it as possible
if (this.position){
var x = Math.round(this.position[0] / cellSize);
var z = Math.round(this.position[1] / cellSize);
friendlyTiles.addInfluence(x, z, 200);
- }else{
+ } else {
// No position was specified so try and find a sensible place to build
gameState.getOwnEntities().forEach(function(ent) {
if (ent.hasClass("Structure")) {
var infl = 32;
if (ent.hasClass("CivCentre"))
infl *= 4;
var pos = ent.position();
var x = Math.round(pos[0] / cellSize);
var z = Math.round(pos[1] / cellSize);
if (ent.buildCategory() == "Wall") { // no real blockers, but can't build where they are
friendlyTiles.addInfluence(x, z, 2,-1000);
return;
}
if (template._template.BuildRestrictions.Category === "Field"){
if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf("food") !== -1){
if (ent.hasClass("CivCentre"))
friendlyTiles.addInfluence(x, z, infl/4, infl);
else
friendlyTiles.addInfluence(x, z, infl, infl);
}
}else{
if (template.genericName() == "House" && ent.genericName() == "House") {
friendlyTiles.addInfluence(x, z, 15.0,20,'linear'); // houses are close to other houses
alreadyHasHouses = true;
} else if (template.genericName() == "House") {
friendlyTiles.addInfluence(x, z, Math.ceil(infl/2.0),infl); // houses are farther away from other buildings but houses
friendlyTiles.addInfluence(x, z, Math.ceil(infl/4.0),-infl/2.0); // houses are farther away from other buildings but houses
} else if (ent.genericName() != "House") // houses have no influence on other buildings
friendlyTiles.addInfluence(x, z, infl);
// If this is not a field add a negative influence near the CivCentre because we want to leave this
// area for fields.
if (ent.hasClass("CivCentre") && template.genericName() != "House"){
friendlyTiles.addInfluence(x, z, Math.floor(infl/8), Math.floor(-infl/2));
} else if (ent.hasClass("CivCentre")) {
friendlyTiles.addInfluence(x, z, infl/3.0, infl + 1);
friendlyTiles.addInfluence(x, z, Math.ceil(infl/5.0), -(infl/2.0), 'linear');
}
}
}
});
}
- //friendlyTiles.dumpIm("Building " +gameState.getTimeElapsed() + ".png", 200);
-
+ //friendlyTiles.dumpIm(template.buildCategory() + "_" +gameState.getTimeElapsed() + ".png", 200);
// Find target building's approximate obstruction radius, and expand by a bit to make sure we're not too close, this
// allows room for units to walk between buildings.
// note: not for houses and dropsites who ought to be closer to either each other or a resource.
// also not for fields who can be stacked quite a bit
+ var radius = 0;
if (template.genericName() == "Field")
- var radius = Math.ceil(template.obstructionRadius() / cellSize) - 0.7;
+ radius = Math.ceil(template.obstructionRadius() / cellSize) - 0.7;
else if (template.buildCategory() === "Dock")
- var radius = 0;
+ radius = 1;//Math.floor(template.obstructionRadius() / cellSize);
else if (template.genericName() != "House" && !template.hasClass("DropsiteWood") && !template.hasClass("DropsiteStone") && !template.hasClass("DropsiteMetal"))
- var radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
+ radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
else
- var radius = Math.ceil(template.obstructionRadius() / cellSize);
+ radius = Math.ceil(template.obstructionRadius() / cellSize);
// further contract cause walls
- if (gameState.playerData.civ == "iber")
- radius *= 0.95;
+ // Note: I'm currently destroying them so that doesn't matter.
+ //if (gameState.playerData.civ == "iber")
+ // radius *= 0.95;
// Find the best non-obstructed
if (template.genericName() == "House" && !alreadyHasHouses) {
// try to get some space first
var bestTile = friendlyTiles.findBestTile(10, obstructionMap);
var bestIdx = bestTile[0];
var bestVal = bestTile[1];
- } else if (template.genericName() == "House") {
- radius *= 0.9;
}
+
if (bestVal === undefined || bestVal === -1) {
var bestTile = friendlyTiles.findBestTile(radius, obstructionMap);
var bestIdx = bestTile[0];
var bestVal = bestTile[1];
}
- if (bestVal === -1){
+ if (bestVal === -1) {
return false;
}
+ //friendlyTiles.setInfluence((bestIdx % friendlyTiles.width), Math.floor(bestIdx / friendlyTiles.width), 1, 200);
+ //friendlyTiles.dumpIm(template.buildCategory() + "_" +gameState.getTimeElapsed() + ".png", 200);
+
var x = ((bestIdx % friendlyTiles.width) + 0.5) * cellSize;
var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * cellSize;
// default angle
var angle = 3*Math.PI/4;
-
+
return {
"x" : x,
"z" : z,
"angle" : angle
};
};
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/plan-training.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/plan-training.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/plan-training.js (revision 13225)
@@ -1,67 +1,73 @@
-var UnitTrainingPlan = function(gameState, type, metadata, number) {
+var UnitTrainingPlan = function(gameState, type, metadata, number, maxMerge) {
this.type = gameState.applyCiv(type);
this.metadata = metadata;
this.template = gameState.getTemplate(this.type);
if (!this.template) {
this.invalidTemplate = true;
this.template = undefined;
return;
}
this.category= "unit";
this.cost = new Resources(this.template.cost(), this.template._template.Cost.Population);
if (!number){
this.number = 1;
}else{
this.number = number;
}
+ if (!maxMerge)
+ this.maxMerge = 5;
+ else
+ this.maxMerge = maxMerge;
};
UnitTrainingPlan.prototype.canExecute = function(gameState) {
if (this.invalidTemplate)
return false;
// TODO: we should probably check pop caps
var trainers = gameState.findTrainers(this.type);
return (trainers.length != 0);
};
UnitTrainingPlan.prototype.execute = function(gameState) {
//warn("Executing UnitTrainingPlan " + uneval(this));
var self = this;
var trainers = gameState.findTrainers(this.type).toEntityArray();
// Prefer training buildings with short queues
// (TODO: this should also account for units added to the queue by
// plans that have already been executed this turn)
if (trainers.length > 0){
trainers.sort(function(a, b) {
if (self.metadata["plan"] !== undefined) {
var aa = a.trainingQueueTime();
var bb = b.trainingQueueTime();
if (a.hasClass("Civic"))
aa += 20;
if (b.hasClass("Civic"))
bb += 20;
return (a.trainingQueueTime() - b.trainingQueueTime());
}
return a.trainingQueueTime() - b.trainingQueueTime();
});
trainers[0].train(this.type, this.number, this.metadata);
}
};
UnitTrainingPlan.prototype.getCost = function(){
var multCost = new Resources();
multCost.add(this.cost);
multCost.multiply(this.number);
return multCost;
};
-UnitTrainingPlan.prototype.addItem = function(){
- this.number += 1;
+UnitTrainingPlan.prototype.addItem = function(amount){
+ if (amount === undefined)
+ amount = 1;
+ this.number += amount;
};
\ No newline at end of file
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/queue-manager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/queue-manager.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/queue-manager.js (revision 13225)
@@ -1,339 +1,304 @@
//This takes the input queues and picks which items to fund with resources until no more resources are left to distribute.
//
//In this manager all resources are 'flattened' into a single type=(food+wood+metal+stone+pop*50 (see resources.js))
//the following refers to this simple as resource
//
// Each queue has an account which records the amount of resource it can spend. If no queue has an affordable item
// then the amount of resource is increased to all accounts in direct proportion to the priority until an item on one
// of the queues becomes affordable.
//
// A consequence of the system is that a rarely used queue will end up with a very large account. I am unsure if this
// is good or bad or neither.
//
// Each queue object has two queues in it, one with items waiting for resources and the other with items which have been
// allocated resources and are due to be executed. The secondary queues are helpful because then units can be trained
// in groups of 5 and buildings are built once per turn to avoid placement clashes.
var QueueManager = function(queues, priorities) {
this.queues = queues;
this.priorities = priorities;
this.account = {};
+ this.accounts = {};
+
+ // the sorting would need to be updated on priority change but there is currently none.
+ var self = this;
+ this.queueArrays = [];
for (var p in this.queues) {
this.account[p] = 0;
+ this.accounts[p] = new Resources();
+ this.queueArrays.push([p,this.queues[p]]);
}
+ this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) });
+
this.curItemQueue = [];
+
};
-QueueManager.prototype.getAvailableResources = function(gameState) {
+QueueManager.prototype.getAvailableResources = function(gameState, noAccounts) {
var resources = gameState.getResources();
+ if (Config.difficulty == 1)
+ resources.multiply(0.75);
+ else if (Config.difficulty == 1)
+ resources.multiply(0.5);
+ if (noAccounts)
+ return resources;
for (var key in this.queues) {
- resources.subtract(this.queues[key].outQueueCost());
+ resources.subtract(this.accounts[key]);
}
return resources;
};
-QueueManager.prototype.futureNeeds = function(gameState, onlyNeeds) {
+QueueManager.prototype.futureNeeds = function(gameState, EcoManager) {
// Work out which plans will be executed next using priority and return the total cost of these plans
var recurse = function(queues, qm, number, depth){
var needs = new Resources();
var totalPriority = 0;
for (var i = 0; i < queues.length; i++){
totalPriority += qm.priorities[queues[i]];
}
for (var i = 0; i < queues.length; i++){
var num = Math.round(((qm.priorities[queues[i]]/totalPriority) * number));
if (num < qm.queues[queues[i]].countQueuedUnits()){
var cnt = 0;
for ( var j = 0; cnt < num; j++) {
cnt += qm.queues[queues[i]].queue[j].number;
needs.add(qm.queues[queues[i]].queue[j].getCost());
number -= qm.queues[queues[i]].queue[j].number;
}
}else{
for ( var j = 0; j < qm.queues[queues[i]].length(); j++) {
needs.add(qm.queues[queues[i]].queue[j].getCost());
number -= qm.queues[queues[i]].queue[j].number;
}
queues.splice(i, 1);
i--;
}
}
// Check that more items were selected this call and that there are plans left to be allocated
// Also there is a fail-safe max depth
if (queues.length > 0 && number > 0 && depth < 20){
needs.add(recurse(queues, qm, number, depth + 1));
}
return needs;
};
//number of plans to look at
- var current = this.getAvailableResources(gameState);
-
+ var current = this.getAvailableResources(gameState, true);
+
var futureNum = 20;
var queues = [];
for (var q in this.queues){
queues.push(q);
}
var needs = recurse(queues, this, futureNum, 0);
- if (onlyNeeds) {
+ if (EcoManager === false) {
return {
"food" : Math.max(needs.food - current.food, 0),
"wood" : Math.max(needs.wood + 15*needs.population - current.wood, 0),
"stone" : Math.max(needs.stone - current.stone, 0),
"metal" : Math.max(needs.metal - current.metal, 0)
};
- } else if (gameState.getTimeElapsed() > 300*1000) {
- // Return predicted values minus the current stockpiles along with a base rater for all resources
- return {
- "food" : Math.max(needs.food - current.food, 0) + 150,
- "wood" : Math.max(needs.wood + 15*needs.population - current.wood, 0) + 150, //TODO: read the house cost in case it changes in the future
- "stone" : Math.max(needs.stone - current.stone, 0) + 50,
- "metal" : Math.max(needs.metal - current.metal, 0) + 100
- };
} else {
+ // Return predicted values minus the current stockpiles along with a base rater for all resources
return {
- "food" : Math.max(needs.food - current.food, 0) + 150,
- "wood" : Math.max(needs.wood + 15*needs.population - current.wood, 0) + 150, //TODO: read the house cost in case it changes in the future
- "stone" : Math.max(needs.stone - current.stone, 0),
- "metal" : Math.max(needs.metal - current.metal, 0)
+ "food" : Math.max(needs.food - current.food, 0) + EcoManager.baseNeed["food"],
+ "wood" : Math.max(needs.wood + 15*needs.population - current.wood, 0) + EcoManager.baseNeed["wood"], //TODO: read the house cost in case it changes in the future
+ "stone" : Math.max(needs.stone - current.stone, 0) + EcoManager.baseNeed["stone"],
+ "metal" : Math.max(needs.metal - current.metal, 0) + EcoManager.baseNeed["metal"]
};
}
};
-// runs through the curItemQueue and allocates resources be sending the
-// affordable plans to the Out Queues. Returns a list of the unneeded resources
-// so they can be used by lower priority plans.
-QueueManager.prototype.affordableToOutQueue = function(gameState) {
- var availableRes = this.getAvailableResources(gameState);
- if (this.curItemQueue.length === 0) {
- return availableRes;
- }
-
- var resources = this.getAvailableResources(gameState);
-
- // Check everything in the curItemQueue, if it is affordable then mark it
- // for execution
- for ( var i = 0; i < this.curItemQueue.length; i++) {
- availableRes.subtract(this.queues[this.curItemQueue[i]].getNext().getCost());
- if (resources.canAfford(this.queues[this.curItemQueue[i]].getNext().getCost())) {
- this.account[this.curItemQueue[i]] -= this.queues[this.curItemQueue[i]].getNext().getCost().toInt();
- this.queues[this.curItemQueue[i]].nextToOutQueue();
- resources = this.getAvailableResources(gameState);
- this.curItemQueue[i] = null;
- }
- }
-
- // Clear the spent items
- var tmpQueue = [];
- for ( var i = 0; i < this.curItemQueue.length; i++) {
- if (this.curItemQueue[i] !== null) {
- tmpQueue.push(this.curItemQueue[i]);
- }
- }
- this.curItemQueue = tmpQueue;
-
- return availableRes;
-};
-
-QueueManager.prototype.onlyUsesSpareAndUpdateSpare = function(unitCost, spare){
- // This allows plans to be given resources if there are >500 spare after all the
- // higher priority plan queues have been looked at and there are still enough resources
- // We make it >0 so that even if we have no stone available we can still have non stone
- // plans being given resources.
- var spareNonNegRes = {
- food: Math.max(0, spare.food - 500),
- wood: Math.max(0, spare.wood - 500),
- stone: Math.max(0, spare.stone - 500),
- metal: Math.max(0, spare.metal - 500)
- };
- var spareNonNeg = new Resources(spareNonNegRes);
- var ret = false;
- if (spareNonNeg.canAfford(unitCost)){
- ret = true;
- }
-
- // If there are no negative resources then there weren't any higher priority items so we
- // definitely want to say that this can be added to the list.
- var tmp = true;
- for (key in spare.types){
- var type = spare.types[key];
- if (spare[type] < 0){
- tmp = false;
- }
- }
- // If either to the above sections returns true then
- ret = ret || tmp;
-
- spare.subtract(unitCost); // take the resources of the current unit from spare since this
- // must be higher priority than any which are looked at
- // afterwards.
-
- return ret;
-};
-
-String.prototype.rpad = function(padString, length) {
- var str = this;
- while (str.length < length)
- str = str + padString;
- return str;
-};
-
QueueManager.prototype.printQueues = function(gameState){
debug("OUTQUEUES");
for (var i in this.queues){
var qStr = "";
var q = this.queues[i];
+ if (q.outQueue.length > 0)
+ debug((i + ":"));
for (var j in q.outQueue){
- qStr += q.outQueue[j].type + " ";
+ qStr = " " + q.outQueue[j].type + " ";
if (q.outQueue[j].number)
qStr += "x" + q.outQueue[j].number;
- }
- if (qStr != ""){
- debug((i + ":").rpad(" ", 20) + qStr);
+ debug (qStr);
}
}
debug("INQUEUES");
for (var i in this.queues){
var qStr = "";
var q = this.queues[i];
+ if (q.queue.length > 0)
+ debug((i + ":"));
for (var j in q.queue){
- qStr += q.queue[j].type + " ";
+ qStr = " " + q.queue[j].type + " ";
if (q.queue[j].number)
qStr += "x" + q.queue[j].number;
- qStr += " ";
- }
- if (qStr != ""){
- debug((i + ":").rpad(" ", 20) + qStr);
+ debug (qStr);
}
}
- debug("Accounts: " + uneval(this.account));
- debug("Needed Resources:" + uneval(this.futureNeeds(gameState)));
+ debug ("Accounts");
+ for (p in this.accounts)
+ {
+ debug(p + ": " + uneval(this.accounts[p]));
+ }
+ debug("Needed Resources:" + uneval(this.futureNeeds(gameState,false)));
+ debug ("Current Resources:" + uneval(gameState.getResources()));
+ debug ("Available Resources:" + uneval(this.getAvailableResources(gameState)));
+};
+
+QueueManager.prototype.clear = function(){
+ this.curItemQueue = [];
+ for (i in this.queues)
+ this.queues[i].empty();
};
QueueManager.prototype.update = function(gameState) {
+ var self = this;
for (var i in this.priorities){
if (!(this.priorities[i] > 0)){
this.priorities[i] = 1; // TODO: make the Queue Manager not die when priorities are zero.
warn("QueueManager received bad priorities, please report this error: " + uneval(this.priorities));
}
}
Engine.ProfileStart("Queue Manager");
- //this.printQueues(gameState);
+
+ //if (gameState.ai.playedTurn % 10 === 0)
+ // this.printQueues(gameState);
Engine.ProfileStart("Pick items from queues");
- // See if there is a high priority item from last time.
- this.affordableToOutQueue(gameState);
- do {
- // pick out all affordable items, and list the ratios of (needed
- // cost)/priority for unaffordable items.
- var ratio = {};
- var ratioMin = 1000000;
- var ratioMinQueue = undefined;
- for (var p in this.queues) {
- if (this.queues[p].length() > 0 && this.curItemQueue.indexOf(p) === -1) {
- var cost = this.queues[p].getNext().getCost().toInt();
- if (cost < this.account[p]) {
- this.curItemQueue.push(p);
- // break;
- } else {
- ratio[p] = (cost - this.account[p]) / this.priorities[p];
- if (ratio[p] < ratioMin) {
- ratioMin = ratio[p];
- ratioMinQueue = p;
- }
- }
+
+ // TODO: this only pushes the first object. SHould probably try to push any possible object to maximize productivity. Perhaps a settinh?
+ // looking at queues in decreasing priorities and pushing to the current item queues.
+ for (i in this.queueArrays)
+ {
+ var name = this.queueArrays[i][0];
+ var queue = this.queueArrays[i][1];
+ if (queue.length() > 0)
+ {
+ var item = queue.getNext();
+ var total = new Resources();
+ total.add(this.accounts[name]);
+ total.subtract(queue.outQueueCost());
+ if (total.canAfford(item.getCost()))
+ {
+ queue.nextToOutQueue();
}
+ } else if (queue.totalLength() === 0) {
+ this.accounts[name].reset();
}
-
- // Checks to see that there is an item in at least one queue, otherwise
- // breaks the loop.
- if (this.curItemQueue.length === 0 && ratioMinQueue === undefined) {
- break;
- }
-
- var availableRes = this.affordableToOutQueue(gameState);
-
- var allSpare = availableRes["food"] > 0 && availableRes["wood"] > 0 && availableRes["stone"] > 0 && availableRes["metal"] > 0;
- // if there are no affordable items use any resources which aren't
- // wanted by a higher priority item
- if ((availableRes["food"] > 0 || availableRes["wood"] > 0 || availableRes["stone"] > 0 || availableRes["metal"] > 0)
- && ratioMinQueue !== undefined) {
- while (Object.keys(ratio).length > 0 && (availableRes["food"] > 0 || availableRes["wood"] > 0 || availableRes["stone"] > 0 || availableRes["metal"] > 0)){
- ratioMin = Math.min(); //biggest value
- for (var key in ratio){
- if (ratio[key] < ratioMin){
- ratioMin = ratio[key];
- ratioMinQueue = key;
- }
- }
- if (this.onlyUsesSpareAndUpdateSpare(this.queues[ratioMinQueue].getNext().getCost(), availableRes)){
- if (allSpare){
- for (var p in this.queues) {
- this.account[p] += ratioMin * this.priorities[p];
- }
+ }
+
+ var availableRes = this.getAvailableResources(gameState);
+ // assign some accounts to queues. This is done by priority, and by need. Note that this currently only looks at the next element.
+ for (ress in availableRes)
+ {
+ if (availableRes[ress] > 0 && ress != "population")
+ {
+ var totalPriority = 0;
+ // Okay so this is where it gets complicated.
+ // If a queue requires "ress" for the next element (in the queue or the outqueue)
+ // And the account is not high enough for it (multiplied by queue length... Might be bad, might not be).
+ // Then we add it to the total priority.
+ // (sorry about readability... Those big 'ifs' basically check if there is a need in the inqueue/outqueue
+ for (j in this.queues) {
+ if ((this.queues[j].length() > 0 && this.queues[j].getNext().getCost()[ress] > 0)
+ || (this.queues[j].outQueueLength() > 0 && this.queues[j].outQueueNext().getCost()[ress] > 0))
+ if ( (this.queues[j].length() && this.accounts[j][ress] < this.queues[j].length() * (this.queues[j].getNext().getCost()[ress]))
+ || (this.queues[j].outQueueLength() && this.accounts[j][ress] < this.queues[j].outQueueLength() * (this.queues[j].outQueueNext().getCost()[ress])))
+ totalPriority += this.priorities[j];
+ }
+ // Now we allow resources to the accounts. We can at most allow "priority/totalpriority*available"
+ // But we'll sometimes allow less if that would overflow.
+ for (j in this.queues) {
+ if ((this.queues[j].length() > 0 && this.queues[j].getNext().getCost()[ress] > 0)
+ || (this.queues[j].outQueueLength() > 0 && this.queues[j].outQueueNext().getCost()[ress] > 0))
+ if ( (this.queues[j].length() && this.accounts[j][ress] < this.queues[j].length() * (this.queues[j].getNext().getCost()[ress]))
+ || (this.queues[j].outQueueLength() && this.accounts[j][ress] < this.queues[j].outQueueLength() * (this.queues[j].outQueueNext().getCost()[ress])))
+ {
+ // we'll add at much what can be allowed to this queue.
+ var toAdd = Math.floor(this.priorities[j]/totalPriority * availableRes[ress]);
+ var maxNeed = 0;
+ if (this.queues[j].length())
+ maxNeed = this.queues[j].length() * (this.queues[j].getNext().getCost()[ress]);
+ if (this.queues[j].outQueueLength() && this.queues[j].outQueueLength() * (this.queues[j].outQueueNext().getCost()[ress]) > maxNeed)
+ maxNeed = this.queues[j].outQueueLength() * (this.queues[j].outQueueNext().getCost()[ress]);
+ if (toAdd + this.accounts[j][ress] > maxNeed)
+ toAdd = maxNeed - this.accounts[j][ress]; // always inferior to the original level.
+ //debug ("Adding " + toAdd + " of " + ress + " to the account of " + j);
+ this.accounts[j][ress] += toAdd;
}
- //this.account[ratioMinQueue] -= this.queues[ratioMinQueue].getNext().getCost().toInt();
- this.curItemQueue.push(ratioMinQueue);
- allSpare = availableRes["food"] > 0 && availableRes["wood"] > 0 && availableRes["stone"] > 0 && availableRes["metal"] > 0;
- }
- delete ratio[ratioMinQueue];
}
-
}
- this.affordableToOutQueue(gameState);
- } while (this.curItemQueue.length === 0);
+ }
Engine.ProfileStop();
Engine.ProfileStart("Execute items");
// Handle output queues by executing items where possible
for (var p in this.queues) {
while (this.queues[p].outQueueLength() > 0) {
var next = this.queues[p].outQueueNext();
if (next.category === "building") {
if (gameState.buildingsBuilt == 0) {
- if (this.queues[p].outQueueNext().canExecute(gameState)) {
+ if (next.canExecute(gameState)) {
+ this.accounts[p].subtract(next.getCost())
+ //debug ("Starting " + next.type + " substracted " + uneval(next.getCost()))
this.queues[p].executeNext(gameState);
gameState.buildingsBuilt += 1;
-
} else {
break;
}
} else {
break;
}
} else {
if (this.queues[p].outQueueNext().canExecute(gameState)){
+ //debug ("Starting " + next.type + " substracted " + uneval(next.getCost()))
+ this.accounts[p].subtract(next.getCost())
this.queues[p].executeNext(gameState);
- }else{
+ } else {
break;
}
}
}
}
Engine.ProfileStop();
Engine.ProfileStop();
};
QueueManager.prototype.addQueue = function(queueName, priority) {
if (this.queues[queueName] == undefined) {
this.queues[queueName] = new Queue();
this.priorities[queueName] = priority;
this.account[queueName] = 0;
+ this.accounts[queueName] = new Resources();
+
+ var self = this;
+ this.queueArrays = [];
+ for (var p in this.queues)
+ this.queueArrays.push([p,this.queues[p]]);
+ this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) });
}
}
QueueManager.prototype.removeQueue = function(queueName) {
if (this.queues[queueName] !== undefined) {
if ( this.curItemQueue.indexOf(queueName) !== -1) {
this.curItemQueue.splice(this.curItemQueue.indexOf(queueName),1);
}
delete this.queues[queueName];
delete this.priorities[queueName];
delete this.account[queueName];
+ delete this.accounts[queueName];
+
+ var self = this;
+ this.queueArrays = [];
+ for (var p in this.queues)
+ this.queueArrays.push([p,this.queues[p]]);
+ this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) });
}
}
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/timer.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/timer.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/timer.js (revision 13225)
@@ -1,104 +1,106 @@
//The Timer class // The instance of this class is created in the qBot object under the name 'timer'
//The methods that are available to call from this instance are:
//timer.setTimer : Creates a new timer with the given interval (miliseconds).
// Optionally set dalay or a limited repeat value.
//timer.checkTimer : Gives true if called at the time of the interval.
//timer.clearTimer : Deletes the timer permanently. No way to get the same timer back.
//timer.activateTimer : Sets the status of a deactivated timer to active.
//timer.deactivateTimer : Deactivates a timer. Deactivated timers will never give true.
+// Currently totally unused, iirc.
+
//-EmjeR-// Timer class //
var Timer = function() {
///Private array.
var alarmList = [];
///Private methods
function num_alarms() {
return alarmList.length;
};
function get_alarm(id) {
return alarmList[id];
};
function add_alarm(index, alarm) {
alarmList[index] = alarm;
};
function delete_alarm(id) {
// Set the array element to undefined
delete alarmList[id];
};
///Privileged methods
// Add an new alarm to the list
this.setTimer = function(gameState, interval, delay, repeat) {
delay = delay || 0;
repeat = repeat || -1;
var index = num_alarms();
//Add a new alarm to the list
add_alarm(index, new alarm(gameState, index, interval, delay, repeat));
return index;
};
// Check if a alarm has reached its interval.
this.checkTimer = function(gameState,id) {
var alarm = get_alarm(id);
if (alarm === undefined)
return false;
if (!alarm.active)
return false;
var time = gameState.getTimeElapsed();
var alarmState = false;
// If repeat forever (repeat is -1). Or if the alarm has rung less times than repeat.
if (alarm.repeat < 0 || alarm.counter < alarm.repeat) {
var time_diffrence = time - alarm.start_time - alarm.delay - alarm.interval * alarm.counter;
if (time_diffrence > alarm.interval) {
alarmState = true;
alarm.counter++;
}
}
// Check if the alarm has rung 'alarm.repeat' times if so, delete the alarm.
if (alarm.counter >= alarm.repeat && alarm.repeat != -1) {
this.clearTimer(id);
}
return alarmState;
};
// Remove an alarm from the list.
this.clearTimer = function(id) {
delete_alarm(id);
};
// Activate a deactivated alarm.
this.activateTimer = function(id) {
var alarm = get_alarm(id);
alarm.active = true;
};
// Deactivate an active alarm but don't delete it.
this.deactivateTimer = function(id) {
var alarm = get_alarm(id);
alarm.active = false;
};
};
//-EmjeR-// Alarm class //
function alarm(gameState, id, interval, delay, repeat) {
this.id = id;
this.interval = interval;
this.delay = delay;
this.repeat = repeat;
this.start_time = gameState.getTimeElapsed();
this.active = true;
this.counter = 0;
};
Index: ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js (revision 13225)
@@ -1,1751 +1,1757 @@
function GuiInterface() {}
GuiInterface.prototype.Schema =
"";
GuiInterface.prototype.Serialize = function()
{
// This component isn't network-synchronised so we mustn't serialise
// its non-deterministic data. Instead just return an empty object.
return {};
};
GuiInterface.prototype.Deserialize = function(obj)
{
this.Init();
};
GuiInterface.prototype.Init = function()
{
this.placementEntity = undefined; // = undefined or [templateName, entityID]
this.placementWallEntities = undefined;
this.placementWallLastAngle = 0;
this.rallyPoints = undefined;
this.notifications = [];
this.renamedEntities = [];
};
/*
* All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg)
* from GUI scripts, and executed here with arguments (player, arg).
*
* CAUTION: The input to the functions in this module is not network-synchronised, so it
* mustn't affect the simulation state (i.e. the data that is serialised and can affect
* the behaviour of the rest of the simulation) else it'll cause out-of-sync errors.
*/
/**
* Returns global information about the current game state.
* This is used by the GUI and also by AI scripts.
*/
GuiInterface.prototype.GetSimulationState = function(player)
{
var ret = {
"players": []
};
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var n = cmpPlayerMan.GetNumPlayers();
for (var i = 0; i < n; ++i)
{
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
var cmpPlayerEntityLimits = Engine.QueryInterface(playerEnt, IID_EntityLimits);
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
// Work out what phase we are in
var cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager);
var phase = "";
if (cmpTechnologyManager.IsTechnologyResearched("phase_city"))
phase = "city";
else if (cmpTechnologyManager.IsTechnologyResearched("phase_town"))
phase = "town";
else if (cmpTechnologyManager.IsTechnologyResearched("phase_village"))
phase = "village";
// store player ally/neutral/enemy data as arrays
var allies = [];
var neutrals = [];
var enemies = [];
for (var j = 0; j < n; ++j)
{
allies[j] = cmpPlayer.IsAlly(j);
neutrals[j] = cmpPlayer.IsNeutral(j);
enemies[j] = cmpPlayer.IsEnemy(j);
}
var playerData = {
"name": cmpPlayer.GetName(),
"civ": cmpPlayer.GetCiv(),
"colour": cmpPlayer.GetColour(),
"popCount": cmpPlayer.GetPopulationCount(),
"popLimit": cmpPlayer.GetPopulationLimit(),
"popMax": cmpPlayer.GetMaxPopulation(),
"resourceCounts": cmpPlayer.GetResourceCounts(),
"trainingBlocked": cmpPlayer.IsTrainingBlocked(),
"state": cmpPlayer.GetState(),
"team": cmpPlayer.GetTeam(),
"teamsLocked": cmpPlayer.GetLockTeams(),
"phase": phase,
"isAlly": allies,
"isNeutral": neutrals,
"isEnemy": enemies,
"entityLimits": cmpPlayerEntityLimits.GetLimits(),
"entityCounts": cmpPlayerEntityLimits.GetCounts(),
- "techModifications": cmpTechnologyManager.GetTechModifications()
+ "techModifications": cmpTechnologyManager.GetTechModifications(),
+ "researchQueued": cmpTechnologyManager.GetQueuedResearch(),
+ "researchStarted": cmpTechnologyManager.GetStartedResearch(),
+ "researchedTechs": cmpTechnologyManager.GetResearchedTechs(),
+ "classCounts": cmpTechnologyManager.GetClassCounts(),
+ "typeCountsByClass": cmpTechnologyManager.GetTypeCountsByClass()
};
ret.players.push(playerData);
}
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
{
ret.circularMap = cmpRangeManager.GetLosCircular();
}
// Add timeElapsed
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
ret.timeElapsed = cmpTimer.GetTime();
return ret;
};
GuiInterface.prototype.GetExtendedSimulationState = function(player)
{
// Get basic simulation info
var ret = this.GetSimulationState();
// Add statistics to each player
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var n = cmpPlayerMan.GetNumPlayers();
for (var i = 0; i < n; ++i)
{
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
var cmpPlayerStatisticsTracker = Engine.QueryInterface(playerEnt, IID_StatisticsTracker);
ret.players[i].statistics = cmpPlayerStatisticsTracker.GetStatistics();
}
return ret;
};
GuiInterface.prototype.GetRenamedEntities = function(player)
{
return this.renamedEntities;
};
GuiInterface.prototype.ClearRenamedEntities = function(player)
{
this.renamedEntities = [];
};
GuiInterface.prototype.GetEntityState = function(player, ent)
{
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
// All units must have a template; if not then it's a nonexistent entity id
var template = cmpTemplateManager.GetCurrentTemplateName(ent);
if (!template)
return null;
var ret = {
"id": ent,
"template": template
};
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (cmpIdentity)
{
ret.identity = {
"rank": cmpIdentity.GetRank(),
"classes": cmpIdentity.GetClassesList(),
"selectionGroupName": cmpIdentity.GetSelectionGroupName()
};
}
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (cmpPosition && cmpPosition.IsInWorld())
{
ret.position = cmpPosition.GetPosition();
}
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
if (cmpHealth)
{
ret.hitpoints = Math.ceil(cmpHealth.GetHitpoints());
ret.maxHitpoints = cmpHealth.GetMaxHitpoints();
ret.needsRepair = cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints());
ret.needsHeal = !cmpHealth.IsUnhealable();
}
var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
if (cmpAttack)
{
var type = cmpAttack.GetBestAttack(); // TODO: how should we decide which attack to show? show all?
ret.attack = cmpAttack.GetAttackStrengths(type);
var range = cmpAttack.GetRange(type);
ret.attack.type = type;
ret.attack.minRange = range.min;
ret.attack.maxRange = range.max;
}
var cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver);
if (cmpArmour)
{
ret.armour = cmpArmour.GetArmourStrengths();
}
var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
if (cmpBuilder)
{
ret.buildEntities = cmpBuilder.GetEntitiesList();
}
var cmpPack = Engine.QueryInterface(ent, IID_Pack);
if (cmpPack)
{
ret.pack = {
"packed": cmpPack.IsPacked(),
"progress": cmpPack.GetProgress(),
};
}
var cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue);
if (cmpProductionQueue)
{
ret.production = {
"entities": cmpProductionQueue.GetEntitiesList(),
"technologies": cmpProductionQueue.GetTechnologiesList(),
"queue": cmpProductionQueue.GetQueue(),
};
}
var cmpTrader = Engine.QueryInterface(ent, IID_Trader);
if (cmpTrader)
{
ret.trader = {
"goods": cmpTrader.GetGoods(),
"preferredGoods": cmpTrader.GetPreferredGoods()
};
}
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
if (cmpFoundation)
{
ret.foundation = {
"progress": cmpFoundation.GetBuildPercentage()
};
}
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
if (cmpObstruction)
{
ret.obstruction = {
"controlGroup": cmpObstruction.GetControlGroup(),
"controlGroup2": cmpObstruction.GetControlGroup2(),
};
}
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
{
ret.player = cmpOwnership.GetOwner();
}
var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
if (cmpResourceSupply)
{
ret.resourceSupply = {
"max": cmpResourceSupply.GetMaxAmount(),
"amount": cmpResourceSupply.GetCurrentAmount(),
"type": cmpResourceSupply.GetType(),
"killBeforeGather": cmpResourceSupply.GetKillBeforeGather()
};
}
var cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer);
if (cmpResourceGatherer)
{
ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates();
ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus();
}
var cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite);
if (cmpResourceDropsite)
{
ret.resourceDropsite = {
"types": cmpResourceDropsite.GetTypes()
};
}
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
{
ret.rallyPoint = {'position': cmpRallyPoint.GetPositions()[0]}; // undefined or {x,z} object
}
var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
if (cmpGarrisonHolder)
{
ret.garrisonHolder = {
"entities": cmpGarrisonHolder.GetEntities(),
"allowedClasses": cmpGarrisonHolder.GetAllowedClassesList()
};
}
var cmpPromotion = Engine.QueryInterface(ent, IID_Promotion);
if (cmpPromotion)
{
ret.promotion = {
"curr": cmpPromotion.GetCurrentXp(),
"req": cmpPromotion.GetRequiredXp()
};
}
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
ret.unitAI = {
"state": cmpUnitAI.GetCurrentState(),
"orders": cmpUnitAI.GetOrders(),
};
}
var cmpGate = Engine.QueryInterface(ent, IID_Gate);
if (cmpGate)
{
ret.gate = {
"locked": cmpGate.IsLocked(),
};
}
if (!cmpFoundation && cmpIdentity && cmpIdentity.HasClass("BarterMarket"))
{
var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
ret.barterMarket = { "prices": cmpBarter.GetPrices() };
}
var cmpHeal = Engine.QueryInterface(ent, IID_Heal);
if (cmpHeal)
{
ret.Healer = {
"unhealableClasses": cmpHeal.GetUnhealableClasses(),
"healableClasses": cmpHeal.GetHealableClasses(),
};
}
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false);
return ret;
};
GuiInterface.prototype.GetTemplateData = function(player, name)
{
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateManager.GetTemplate(name);
if (!template)
return null;
var ret = {};
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
var techMods = cmpTechnologyManager.GetTechModifications();
if (template.Armour)
{
ret.armour = {
"hack": GetTechModifiedProperty(techMods, template, "Armour/Hack", +template.Armour.Hack),
"pierce": GetTechModifiedProperty(techMods, template, "Armour/Pierce", +template.Armour.Pierce),
"crush": GetTechModifiedProperty(techMods, template, "Armour/Crush", +template.Armour.Crush),
};
}
if (template.Attack)
{
ret.attack = {};
for (var type in template.Attack)
{
ret.attack[type] = {
"hack": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/Hack", +(template.Attack[type].Hack || 0)),
"pierce": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/Pierce", +(template.Attack[type].Pierce || 0)),
"crush": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/Crush", +(template.Attack[type].Crush || 0)),
"minRange": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/MinRange", +(template.Attack[type].MinRange || 0)),
"maxRange": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/MaxRange", +template.Attack[type].MaxRange),
};
}
}
if (template.BuildRestrictions)
{
// required properties
ret.buildRestrictions = {
"placementType": template.BuildRestrictions.PlacementType,
"territory": template.BuildRestrictions.Territory,
"category": template.BuildRestrictions.Category,
};
// optional properties
if (template.BuildRestrictions.Distance)
{
ret.buildRestrictions.distance = {
"fromCategory": template.BuildRestrictions.Distance.FromCategory,
};
if (template.BuildRestrictions.Distance.MinDistance) ret.buildRestrictions.distance.min = +template.BuildRestrictions.Distance.MinDistance;
if (template.BuildRestrictions.Distance.MaxDistance) ret.buildRestrictions.distance.max = +template.BuildRestrictions.Distance.MaxDistance;
}
}
if (template.TrainingRestrictions)
{
ret.trainingRestrictions = {
"category": template.TrainingRestrictions.Category,
};
}
if (template.Cost)
{
ret.cost = {};
if (template.Cost.Resources.food) ret.cost.food = GetTechModifiedProperty(techMods, template, "Cost/Resources/food", +template.Cost.Resources.food);
if (template.Cost.Resources.wood) ret.cost.wood = GetTechModifiedProperty(techMods, template, "Cost/Resources/wood", +template.Cost.Resources.wood);
if (template.Cost.Resources.stone) ret.cost.stone = GetTechModifiedProperty(techMods, template, "Cost/Resources/stone", +template.Cost.Resources.stone);
if (template.Cost.Resources.metal) ret.cost.metal = GetTechModifiedProperty(techMods, template, "Cost/Resources/metal", +template.Cost.Resources.metal);
if (template.Cost.Population) ret.cost.population = GetTechModifiedProperty(techMods, template, "Cost/Population", +template.Cost.Population);
if (template.Cost.PopulationBonus) ret.cost.populationBonus = GetTechModifiedProperty(techMods, template, "Cost/PopulationBonus", +template.Cost.PopulationBonus);
if (template.Cost.BuildTime) ret.cost.time = GetTechModifiedProperty(techMods, template, "Cost/BuildTime", +template.Cost.BuildTime);
}
if (template.Footprint)
{
ret.footprint = {"height": template.Footprint.Height};
if (template.Footprint.Square)
ret.footprint.square = {"width": +template.Footprint.Square["@width"], "depth": +template.Footprint.Square["@depth"]};
else if (template.Footprint.Circle)
ret.footprint.circle = {"radius": +template.Footprint.Circle["@radius"]};
else
warn("[GetTemplateData] Unrecognized Footprint type");
}
if (template.Obstruction)
{
ret.obstruction = {
"active": ("" + template.Obstruction.Active == "true"),
"blockMovement": ("" + template.Obstruction.BlockMovement == "true"),
"blockPathfinding": ("" + template.Obstruction.BlockPathfinding == "true"),
"blockFoundation": ("" + template.Obstruction.BlockFoundation == "true"),
"blockConstruction": ("" + template.Obstruction.BlockConstruction == "true"),
"disableBlockMovement": ("" + template.Obstruction.DisableBlockMovement == "true"),
"disableBlockPathfinding": ("" + template.Obstruction.DisableBlockPathfinding == "true"),
"shape": {}
};
if (template.Obstruction.Static)
{
ret.obstruction.shape.type = "static";
ret.obstruction.shape.width = +template.Obstruction.Static["@width"];
ret.obstruction.shape.depth = +template.Obstruction.Static["@depth"];
}
else if (template.Obstruction.Unit)
{
ret.obstruction.shape.type = "unit";
ret.obstruction.shape.radius = +template.Obstruction.Unit["@radius"];
}
else
{
ret.obstruction.shape.type = "cluster";
}
}
if (template.Pack)
{
ret.pack = {
"state": template.Pack.State,
"time": GetTechModifiedProperty(techMods, template, "Pack/Time", +template.Pack.Time),
};
}
if (template.Health)
{
ret.health = Math.round(GetTechModifiedProperty(techMods, template, "Health/Max", +template.Health.Max));
}
if (template.Identity)
{
ret.selectionGroupName = template.Identity.SelectionGroupName;
ret.name = {
"specific": (template.Identity.SpecificName || template.Identity.GenericName),
"generic": template.Identity.GenericName
};
ret.icon = template.Identity.Icon;
ret.tooltip = template.Identity.Tooltip;
ret.requiredTechnology = template.Identity.RequiredTechnology;
ret.identityClassesString = GetTemplateIdentityClassesString(template);
}
if (template.UnitMotion)
{
ret.speed = {
"walk": +template.UnitMotion.WalkSpeed,
};
if (template.UnitMotion.Run) ret.speed.run = +template.UnitMotion.Run.Speed;
}
if (template.Trader)
ret.trader = template.Trader;
if (template.WallSet)
{
ret.wallSet = {
"templates": {
"tower": template.WallSet.Templates.Tower,
"gate": template.WallSet.Templates.Gate,
"long": template.WallSet.Templates.WallLong,
"medium": template.WallSet.Templates.WallMedium,
"short": template.WallSet.Templates.WallShort,
},
"maxTowerOverlap": +template.WallSet.MaxTowerOverlap,
"minTowerOverlap": +template.WallSet.MinTowerOverlap,
};
}
if (template.WallPiece)
{
ret.wallPiece = {"length": +template.WallPiece.Length};
}
return ret;
};
GuiInterface.prototype.GetTechnologyData = function(player, name)
{
var cmpTechTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TechnologyTemplateManager);
var template = cmpTechTempMan.GetTemplate(name);
if (!template)
{
warn("Tried to get data for invalid technology: " + name);
return null;
}
var ret = {};
// Get specific name for this civ or else the generic specific name
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
var specific = undefined;
if (template.specificName)
{
if (template.specificName[cmpPlayer.GetCiv()])
specific = template.specificName[cmpPlayer.GetCiv()];
else
specific = template.specificName['generic'];
}
ret.name = {
"specific": specific,
"generic": template.genericName,
};
ret.icon = "technologies/" + template.icon;
ret.cost = {
"food": template.cost ? (+template.cost.food) : 0,
"wood": template.cost ? (+template.cost.wood) : 0,
"metal": template.cost ? (+template.cost.metal) : 0,
"stone": template.cost ? (+template.cost.stone) : 0,
"time": template.researchTime ? (+template.researchTime) : 0,
}
ret.tooltip = template.tooltip;
if (template.requirementsTooltip)
ret.requirementsTooltip = template.requirementsTooltip;
else
ret.requirementsTooltip = "";
ret.description = template.description;
return ret;
};
GuiInterface.prototype.IsTechnologyResearched = function(player, tech)
{
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
if (!cmpTechnologyManager)
return false;
return cmpTechnologyManager.IsTechnologyResearched(tech);
};
// Checks whether the requirements for this technology have been met
GuiInterface.prototype.CheckTechnologyRequirements = function(player, tech)
{
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
if (!cmpTechnologyManager)
return false;
return cmpTechnologyManager.CanResearch(tech);
};
// Returns technologies that are being actively researched, along with
// which entity is researching them and how far along the research is.
GuiInterface.prototype.GetStartedResearch = function(player)
{
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
if (!cmpTechnologyManager)
return false;
var ret = {};
for (var tech in cmpTechnologyManager.GetTechsStarted())
{
ret[tech] = { "researcher": cmpTechnologyManager.GetResearcher(tech) };
var cmpProductionQueue = Engine.QueryInterface(ret[tech].researcher, IID_ProductionQueue);
if (cmpProductionQueue)
ret[tech].progress = cmpProductionQueue.GetQueue()[0].progress;
else
ret[tech].progress = 0;
}
return ret;
}
// Returns the battle state of the player.
GuiInterface.prototype.GetBattleState = function(player)
{
var cmpBattleDetection = QueryPlayerIDInterface(player, IID_BattleDetection);
return cmpBattleDetection.GetState();
};
// Used to show a red square over GUI elements you can't yet afford.
GuiInterface.prototype.GetNeededResources = function(player, amounts)
{
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
return cmpPlayer.GetNeededResources(amounts);
};
GuiInterface.prototype.PushNotification = function(notification)
{
this.notifications.push(notification);
};
GuiInterface.prototype.GetNextNotification = function()
{
if (this.notifications.length)
return this.notifications.pop();
else
return "";
};
GuiInterface.prototype.GetAvailableFormations = function(player, data)
{
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(player), IID_Player);
return cmpPlayer.GetFormations();
};
GuiInterface.prototype.GetFormationRequirements = function(player, data)
{
return GetFormationRequirements(data.formationName);
};
GuiInterface.prototype.CanMoveEntsIntoFormation = function(player, data)
{
return CanMoveEntsIntoFormation(data.ents, data.formationName);
};
GuiInterface.prototype.IsFormationSelected = function(player, data)
{
for each (var ent in data.ents)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
// GetLastFormationName is named in a strange way as it (also) is
// the value of the current formation (see Formation.js LoadFormation)
if (cmpUnitAI.GetLastFormationName() == data.formationName)
return true;
}
}
return false;
};
GuiInterface.prototype.IsStanceSelected = function(player, data)
{
for each (var ent in data.ents)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
if (cmpUnitAI.GetStanceName() == data.stance)
return true;
}
}
return false;
};
GuiInterface.prototype.SetSelectionHighlight = function(player, cmd, selected)
{
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var playerColours = {}; // cache of owner -> colour map
for each (var ent in cmd.entities)
{
var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable);
if (!cmpSelectable)
continue;
// Find the entity's owner's colour:
var owner = -1;
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
owner = cmpOwnership.GetOwner();
var colour = playerColours[owner];
if (!colour)
{
colour = {"r":1, "g":1, "b":1};
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(owner), IID_Player);
if (cmpPlayer)
colour = cmpPlayer.GetColour();
playerColours[owner] = colour;
}
cmpSelectable.SetSelectionHighlight({"r":colour.r, "g":colour.g, "b":colour.b, "a":cmd.alpha}, cmd.selected);
}
};
GuiInterface.prototype.SetStatusBars = function(player, cmd)
{
for each (var ent in cmd.entities)
{
var cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
if (cmpStatusBars)
cmpStatusBars.SetEnabled(cmd.enabled);
}
};
GuiInterface.prototype.GetPlayerEntities = function(player)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
return cmpRangeManager.GetEntitiesByPlayer(player);
};
/**
* Displays the rally points of a given list of entities (carried in cmd.entities).
*
* The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should
* be rendered, in order to support instantaneously rendering a rally point marker at a specified location
* instead of incurring a delay while PostNetworkCommand processes the set-rallypoint command (see input.js).
* If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
* RallyPoint component.
*/
GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
{
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(player), IID_Player);
// If there are some rally points already displayed, first hide them
for each (var ent in this.entsRallyPointsDisplayed)
{
var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
if (cmpRallyPointRenderer)
cmpRallyPointRenderer.SetDisplayed(false);
}
this.entsRallyPointsDisplayed = [];
// Show the rally points for the passed entities
for each (var ent in cmd.entities)
{
var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
if (!cmpRallyPointRenderer)
continue;
// entity must have a rally point component to display a rally point marker
// (regardless of whether cmd specifies a custom location)
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (!cmpRallyPoint)
continue;
// Verify the owner
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (!(cmpPlayer && cmpPlayer.CanControlAllUnits()))
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
continue;
// If the command was passed an explicit position, use that and
// override the real rally point position; otherwise use the real position
var pos;
if (cmd.x && cmd.z)
pos = cmd;
else
pos = cmpRallyPoint.GetPositions()[0]; // may return undefined if no rally point is set
if (pos)
{
// Only update the position if we changed it (cmd.queued is set)
if (cmd.queued == true)
cmpRallyPointRenderer.AddPosition({'x': pos.x, 'y': pos.z}); // AddPosition takes a CFixedVector2D which has X/Y components, not X/Z
else if (cmd.queued == false)
cmpRallyPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
cmpRallyPointRenderer.SetDisplayed(true);
// remember which entities have their rally points displayed so we can hide them again
this.entsRallyPointsDisplayed.push(ent);
}
}
};
/**
* Display the building placement preview.
* cmd.template is the name of the entity template, or "" to disable the preview.
* cmd.x, cmd.z, cmd.angle give the location.
*
* Returns result object from CheckPlacement:
* {
* "success": true iff the placement is valid, else false
* "message": message to display in UI for invalid placement, else empty string
* }
*/
GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
{
var result = {
"success": false,
"message": "",
}
// See if we're changing template
if (!this.placementEntity || this.placementEntity[0] != cmd.template)
{
// Destroy the old preview if there was one
if (this.placementEntity)
Engine.DestroyEntity(this.placementEntity[1]);
// Load the new template
if (cmd.template == "")
{
this.placementEntity = undefined;
}
else
{
this.placementEntity = [cmd.template, Engine.AddLocalEntity("preview|" + cmd.template)];
}
}
if (this.placementEntity)
{
var ent = this.placementEntity[1];
// Move the preview into the right location
var pos = Engine.QueryInterface(ent, IID_Position);
if (pos)
{
pos.JumpTo(cmd.x, cmd.z);
pos.SetYRotation(cmd.angle);
}
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpOwnership.SetOwner(player);
// Check whether building placement is valid
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
if (!cmpBuildRestrictions)
error("cmpBuildRestrictions not defined");
else
result = cmpBuildRestrictions.CheckPlacement();
// Set it to a red shade if this is an invalid location
var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
if (cmpVisual)
{
if (cmd.actorSeed !== undefined)
cmpVisual.SetActorSeed(cmd.actorSeed);
if (!result.success)
cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1);
else
cmpVisual.SetShadingColour(1, 1, 1, 1);
}
}
return result;
};
/**
* Previews the placement of a wall between cmd.start and cmd.end, or just the starting piece of a wall if cmd.end is not
* specified. Returns an object with information about the list of entities that need to be newly constructed to complete
* at least a part of the wall, or false if there are entities required to build at least part of the wall but none of
* them can be validly constructed.
*
* It's important to distinguish between three lists of entities that are at play here, because they may be subsets of one
* another depending on things like snapping and whether some of the entities inside them can be validly positioned.
* We have:
* - The list of entities that previews the wall. This list is usually equal to the entities required to construct the
* entire wall. However, if there is snapping to an incomplete tower (i.e. a foundation), it includes extra entities
* to preview the completed tower on top of its foundation.
*
* - The list of entities that need to be newly constructed to build the entire wall. This list is regardless of whether
* any of them can be validly positioned. The emphasishere here is on 'newly'; this list does not include any existing
* towers at either side of the wall that we snapped to. Or, more generally; it does not include any _entities_ that we
* snapped to; we might still snap to e.g. terrain, in which case the towers on either end will still need to be newly
* constructed.
*
* - The list of entities that need to be newly constructed to build at least a part of the wall. This list is the same
* as the one above, except that it is truncated at the first entity that cannot be validly positioned. This happens
* e.g. if the player tries to build a wall straight through an obstruction. Note that any entities that can be validly
* constructed but come after said first invalid entity are also truncated away.
*
* With this in mind, this method will return false if the second list is not empty, but the third one is. That is, if there
* were entities that are needed to build the wall, but none of them can be validly constructed. False is also returned in
* case of unexpected errors (typically missing components), and when clearing the preview by passing an empty wallset
* argument (see below). Otherwise, it will return an object with the following information:
*
* result: {
* 'startSnappedEnt': ID of the entity that we snapped to at the starting side of the wall. Currently only supports towers.
* 'endSnappedEnt': ID of the entity that we snapped to at the (possibly truncated) ending side of the wall. Note that this
* can only be set if no truncation of the second list occurs; if we snapped to an entity at the ending side
* but the wall construction was truncated before we could reach it, it won't be set here. Currently only
* supports towers.
* 'pieces': Array with the following data for each of the entities in the third list:
* [{
* 'template': Template name of the entity.
* 'x': X coordinate of the entity's position.
* 'z': Z coordinate of the entity's position.
* 'angle': Rotation around the Y axis of the entity (in radians).
* },
* ...]
* 'cost': { The total cost required for constructing all the pieces as listed above.
* 'food': ...,
* 'wood': ...,
* 'stone': ...,
* 'metal': ...,
* 'population': ...,
* 'populationBonus': ...,
* }
* }
*
* @param cmd.wallSet Object holding the set of wall piece template names. Set to an empty value to clear the preview.
* @param cmd.start Starting point of the wall segment being created.
* @param cmd.end (Optional) Ending point of the wall segment being created. If not defined, it is understood that only
* the starting point of the wall is available at this time (e.g. while the player is still in the process
* of picking a starting point), and that therefore only the first entity in the wall (a tower) should be
* previewed.
* @param cmd.snapEntities List of candidate entities to snap the start and ending positions to.
*/
GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
{
var wallSet = cmd.wallSet;
var start = {
"pos": cmd.start,
"angle": 0,
"snapped": false, // did the start position snap to anything?
"snappedEnt": INVALID_ENTITY, // if we snapped, was it to an entity? if yes, holds that entity's ID
};
var end = {
"pos": cmd.end,
"angle": 0,
"snapped": false, // did the start position snap to anything?
"snappedEnt": INVALID_ENTITY, // if we snapped, was it to an entity? if yes, holds that entity's ID
};
// --------------------------------------------------------------------------------
// do some entity cache management and check for snapping
if (!this.placementWallEntities)
this.placementWallEntities = {};
if (!wallSet)
{
// we're clearing the preview, clear the entity cache and bail
var numCleared = 0;
for (var tpl in this.placementWallEntities)
{
for each (var ent in this.placementWallEntities[tpl].entities)
Engine.DestroyEntity(ent);
this.placementWallEntities[tpl].numUsed = 0;
this.placementWallEntities[tpl].entities = [];
// keep template data around
}
return false;
}
else
{
// Move all existing cached entities outside of the world and reset their use count
for (var tpl in this.placementWallEntities)
{
for each (var ent in this.placementWallEntities[tpl].entities)
{
var pos = Engine.QueryInterface(ent, IID_Position);
if (pos)
pos.MoveOutOfWorld();
}
this.placementWallEntities[tpl].numUsed = 0;
}
// Create cache entries for templates we haven't seen before
for each (var tpl in wallSet.templates)
{
if (!(tpl in this.placementWallEntities))
{
this.placementWallEntities[tpl] = {
"numUsed": 0,
"entities": [],
"templateData": this.GetTemplateData(player, tpl),
};
// ensure that the loaded template data contains a wallPiece component
if (!this.placementWallEntities[tpl].templateData.wallPiece)
{
error("[SetWallPlacementPreview] No WallPiece component found for wall set template '" + tpl + "'");
return false;
}
}
}
}
// prevent division by zero errors further on if the start and end positions are the same
if (end.pos && (start.pos.x === end.pos.x && start.pos.z === end.pos.z))
end.pos = undefined;
// See if we need to snap the start and/or end coordinates to any of our list of snap entities. Note that, despite the list
// of snapping candidate entities, it might still snap to e.g. terrain features. Use the "ent" key in the returned snapping
// data to determine whether it snapped to an entity (if any), and to which one (see GetFoundationSnapData).
if (cmd.snapEntities)
{
var snapRadius = this.placementWallEntities[wallSet.templates.tower].templateData.wallPiece.length * 0.5; // determined through trial and error
var startSnapData = this.GetFoundationSnapData(player, {
"x": start.pos.x,
"z": start.pos.z,
"template": wallSet.templates.tower,
"snapEntities": cmd.snapEntities,
"snapRadius": snapRadius,
});
if (startSnapData)
{
start.pos.x = startSnapData.x;
start.pos.z = startSnapData.z;
start.angle = startSnapData.angle;
start.snapped = true;
if (startSnapData.ent)
start.snappedEnt = startSnapData.ent;
}
if (end.pos)
{
var endSnapData = this.GetFoundationSnapData(player, {
"x": end.pos.x,
"z": end.pos.z,
"template": wallSet.templates.tower,
"snapEntities": cmd.snapEntities,
"snapRadius": snapRadius,
});
if (endSnapData)
{
end.pos.x = endSnapData.x;
end.pos.z = endSnapData.z;
end.angle = endSnapData.angle;
end.snapped = true;
if (endSnapData.ent)
end.snappedEnt = endSnapData.ent;
}
}
}
// clear the single-building preview entity (we'll be rolling our own)
this.SetBuildingPlacementPreview(player, {"template": ""});
// --------------------------------------------------------------------------------
// calculate wall placement and position preview entities
var result = {
"pieces": [],
"cost": {"food": 0, "wood": 0, "stone": 0, "metal": 0, "population": 0, "populationBonus": 0, "time": 0},
};
var previewEntities = [];
if (end.pos)
previewEntities = GetWallPlacement(this.placementWallEntities, wallSet, start, end); // see helpers/Walls.js
// For wall placement, we may (and usually do) need to have wall pieces overlap each other more than would
// otherwise be allowed by their obstruction shapes. However, during this preview phase, this is not so much of
// an issue, because all preview entities have their obstruction components deactivated, meaning that their
// obstruction shapes do not register in the simulation and hence cannot affect it. This implies that the preview
// entities cannot be found to obstruct each other, which largely solves the issue of overlap between wall pieces.
// Note that they will still be obstructed by existing shapes in the simulation (that have the BLOCK_FOUNDATION
// flag set), which is what we want. The only exception to this is when snapping to existing towers (or
// foundations thereof); the wall segments that connect up to these will be found to be obstructed by the
// existing tower/foundation, and be shaded red to indicate that they cannot be placed there. To prevent this,
// we manually set the control group of the outermost wall pieces equal to those of the snapped-to towers, so
// that they are free from mutual obstruction (per definition of obstruction control groups). This is done by
// assigning them an extra "controlGroup" field, which we'll then set during the placement loop below.
// Additionally, in the situation that we're snapping to merely a foundation of a tower instead of a fully
// constructed one, we'll need an extra preview entity for the starting tower, which also must not be obstructed
// by the foundation it snaps to.
if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
{
var startEntObstruction = Engine.QueryInterface(start.snappedEnt, IID_Obstruction);
if (previewEntities.length > 0 && startEntObstruction)
previewEntities[0].controlGroups = [startEntObstruction.GetControlGroup()];
// if we're snapping to merely a foundation, add an extra preview tower and also set it to the same control group
var startEntState = this.GetEntityState(player, start.snappedEnt);
if (startEntState.foundation)
{
var cmpPosition = Engine.QueryInterface(start.snappedEnt, IID_Position);
if (cmpPosition)
{
previewEntities.unshift({
"template": wallSet.templates.tower,
"pos": start.pos,
"angle": cmpPosition.GetRotation().y,
"controlGroups": [(startEntObstruction ? startEntObstruction.GetControlGroup() : undefined)],
"excludeFromResult": true, // preview only, must not appear in the result
});
}
}
}
else
{
// Didn't snap to an existing entity, add the starting tower manually. To prevent odd-looking rotation jumps
// when shift-clicking to build a wall, reuse the placement angle that was last seen on a validly positioned
// wall piece.
// To illustrate the last point, consider what happens if we used some constant instead, say, 0. Issuing the
// build command for a wall is asynchronous, so when the preview updates after shift-clicking, the wall piece
// foundations are not registered yet in the simulation. This means they cannot possibly be picked in the list
// of candidate entities for snapping. In the next preview update, we therefore hit this case, and would rotate
// the preview to 0 radians. Then, after one or two simulation updates or so, the foundations register and
// onSimulationUpdate in session.js updates the preview again. It first grabs a new list of snapping candidates,
// which this time does include the new foundations; so we snap to the entity, and rotate the preview back to
// the foundation's angle.
// The result is a noticeable rotation to 0 and back, which is undesirable. So, for a split second there until
// the simulation updates, we fake it by reusing the last angle and hope the player doesn't notice.
previewEntities.unshift({
"template": wallSet.templates.tower,
"pos": start.pos,
"angle": (previewEntities.length > 0 ? previewEntities[0].angle : this.placementWallLastAngle)
});
}
if (end.pos)
{
// Analogous to the starting side case above
if (end.snappedEnt && end.snappedEnt != INVALID_ENTITY)
{
var endEntObstruction = Engine.QueryInterface(end.snappedEnt, IID_Obstruction);
// Note that it's possible for the last entity in previewEntities to be the same as the first, i.e. the
// same wall piece snapping to both a starting and an ending tower. And it might be more common than you would
// expect; the allowed overlap between wall segments and towers facilitates this to some degree. To deal with
// the possibility of dual initial control groups, we use a '.controlGroups' array rather than a single
// '.controlGroup' property. Note that this array can only ever have 0, 1 or 2 elements (checked at a later time).
if (previewEntities.length > 0 && endEntObstruction)
{
previewEntities[previewEntities.length-1].controlGroups = (previewEntities[previewEntities.length-1].controlGroups || []);
previewEntities[previewEntities.length-1].controlGroups.push(endEntObstruction.GetControlGroup());
}
// if we're snapping to a foundation, add an extra preview tower and also set it to the same control group
var endEntState = this.GetEntityState(player, end.snappedEnt);
if (endEntState.foundation)
{
var cmpPosition = Engine.QueryInterface(end.snappedEnt, IID_Position);
if (cmpPosition)
{
previewEntities.push({
"template": wallSet.templates.tower,
"pos": end.pos,
"angle": cmpPosition.GetRotation().y,
"controlGroups": [(endEntObstruction ? endEntObstruction.GetControlGroup() : undefined)],
"excludeFromResult": true
});
}
}
}
else
{
previewEntities.push({
"template": wallSet.templates.tower,
"pos": end.pos,
"angle": (previewEntities.length > 0 ? previewEntities[previewEntities.length-1].angle : this.placementWallLastAngle)
});
}
}
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
if (!cmpTerrain)
{
error("[SetWallPlacementPreview] System Terrain component not found");
return false;
}
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (!cmpRangeManager)
{
error("[SetWallPlacementPreview] System RangeManager component not found");
return false;
}
// Loop through the preview entities, and construct the subset of them that need to be, and can be, validly constructed
// to build at least a part of the wall (meaning that the subset is truncated after the first entity that needs to be,
// but cannot validly be, constructed). See method-level documentation for more details.
var allPiecesValid = true;
var numRequiredPieces = 0; // number of entities that are required to build the entire wall, regardless of validity
for (var i = 0; i < previewEntities.length; ++i)
{
var entInfo = previewEntities[i];
var ent = null;
var tpl = entInfo.template;
var tplData = this.placementWallEntities[tpl].templateData;
var entPool = this.placementWallEntities[tpl];
if (entPool.numUsed >= entPool.entities.length)
{
// allocate new entity
ent = Engine.AddLocalEntity("preview|" + tpl);
entPool.entities.push(ent);
}
else
{
// reuse an existing one
ent = entPool.entities[entPool.numUsed];
}
if (!ent)
{
error("[SetWallPlacementPreview] Failed to allocate or reuse preview entity of template '" + tpl + "'");
continue;
}
// move piece to right location
// TODO: consider reusing SetBuildingPlacementReview for this, enhanced to be able to deal with multiple entities
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (cmpPosition)
{
cmpPosition.JumpTo(entInfo.pos.x, entInfo.pos.z);
cmpPosition.SetYRotation(entInfo.angle);
// if this piece is a tower, then it should have a Y position that is at least as high as its surrounding pieces
if (tpl === wallSet.templates.tower)
{
var terrainGroundPrev = null;
var terrainGroundNext = null;
if (i > 0)
terrainGroundPrev = cmpTerrain.GetGroundLevel(previewEntities[i-1].pos.x, previewEntities[i-1].pos.z);
if (i < previewEntities.length - 1)
terrainGroundNext = cmpTerrain.GetGroundLevel(previewEntities[i+1].pos.x, previewEntities[i+1].pos.z);
if (terrainGroundPrev != null || terrainGroundNext != null)
{
var targetY = Math.max(terrainGroundPrev, terrainGroundNext);
cmpPosition.SetHeightFixed(targetY);
}
}
}
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
if (!cmpObstruction)
{
error("[SetWallPlacementPreview] Preview entity of template '" + tpl + "' does not have an Obstruction component");
continue;
}
// Assign any predefined control groups. Note that there can only be 0, 1 or 2 predefined control groups; if there are
// more, we've made a programming error. The control groups are assigned from the entInfo.controlGroups array on a
// first-come first-served basis; the first value in the array is always assigned as the primary control group, and
// any second value as the secondary control group.
// By default, we reset the control groups to their standard values. Remember that we're reusing entities; if we don't
// reset them, then an ending wall segment that was e.g. at one point snapped to an existing tower, and is subsequently
// reused as a non-snapped ending wall segment, would no longer be capable of being obstructed by the same tower it was
// once snapped to.
var primaryControlGroup = ent;
var secondaryControlGroup = INVALID_ENTITY;
if (entInfo.controlGroups && entInfo.controlGroups.length > 0)
{
if (entInfo.controlGroups.length > 2)
{
error("[SetWallPlacementPreview] Encountered preview entity of template '" + tpl + "' with more than 2 initial control groups");
break;
}
primaryControlGroup = entInfo.controlGroups[0];
if (entInfo.controlGroups.length > 1)
secondaryControlGroup = entInfo.controlGroups[1];
}
cmpObstruction.SetControlGroup(primaryControlGroup);
cmpObstruction.SetControlGroup2(secondaryControlGroup);
// check whether this wall piece can be validly positioned here
var validPlacement = false;
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpOwnership.SetOwner(player);
// Check whether it's in a visible or fogged region
// tell GetLosVisibility to force RetainInFog because preview entities set this to false,
// which would show them as hidden instead of fogged
// TODO: should definitely reuse SetBuildingPlacementPreview, this is just straight up copy/pasta
var visible = (cmpRangeManager.GetLosVisibility(ent, player, true) != "hidden");
if (visible)
{
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
if (!cmpBuildRestrictions)
{
error("[SetWallPlacementPreview] cmpBuildRestrictions not defined for preview entity of template '" + tpl + "'");
continue;
}
// TODO: Handle results of CheckPlacement
validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement().success);
// If a wall piece has two control groups, it's likely a segment that spans
// between two existing towers. To avoid placing a duplicate wall segment,
// check for collisions with entities that share both control groups.
if (validPlacement && entInfo.controlGroups && entInfo.controlGroups.length > 1)
validPlacement = cmpObstruction.CheckDuplicateFoundation();
}
allPiecesValid = allPiecesValid && validPlacement;
// The requirement below that all pieces so far have to have valid positions, rather than only this single one,
// ensures that no more foundations will be placed after a first invalidly-positioned piece. (It is possible
// for pieces past some invalidly-positioned ones to still have valid positions, e.g. if you drag a wall
// through and past an existing building).
// Additionally, the excludeFromResult flag is set for preview entities that were manually added to be placed
// on top of foundations of incompleted towers that we snapped to; they must not be part of the result.
if (!entInfo.excludeFromResult)
numRequiredPieces++;
if (allPiecesValid && !entInfo.excludeFromResult)
{
result.pieces.push({
"template": tpl,
"x": entInfo.pos.x,
"z": entInfo.pos.z,
"angle": entInfo.angle,
});
this.placementWallLastAngle = entInfo.angle;
// grab the cost of this wall piece and add it up (note; preview entities don't have their Cost components
// copied over, so we need to fetch it from the template instead).
// TODO: we should really use a Cost object or at least some utility functions for this, this is mindless
// boilerplate that's probably duplicated in tons of places.
result.cost.food += tplData.cost.food;
result.cost.wood += tplData.cost.wood;
result.cost.stone += tplData.cost.stone;
result.cost.metal += tplData.cost.metal;
result.cost.population += tplData.cost.population;
result.cost.populationBonus += tplData.cost.populationBonus;
result.cost.time += tplData.cost.time;
}
var canAfford = true;
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
if (cmpPlayer && cmpPlayer.GetNeededResources(result.cost))
var canAfford = false;
var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
if (cmpVisual)
{
if (!allPiecesValid || !canAfford)
cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1);
else
cmpVisual.SetShadingColour(1, 1, 1, 1);
}
entPool.numUsed++;
}
// If any were entities required to build the wall, but none of them could be validly positioned, return failure
// (see method-level documentation).
if (numRequiredPieces > 0 && result.pieces.length == 0)
return false;
if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
result.startSnappedEnt = start.snappedEnt;
// We should only return that we snapped to an entity if all pieces up until that entity can be validly constructed,
// i.e. are included in result.pieces (see docs for the result object).
if (end.pos && end.snappedEnt && end.snappedEnt != INVALID_ENTITY && allPiecesValid)
result.endSnappedEnt = end.snappedEnt;
return result;
};
/**
* Given the current position {data.x, data.z} of an foundation of template data.template, returns the position and angle to snap
* it to (if necessary/useful).
*
* @param data.x The X position of the foundation to snap.
* @param data.z The Z position of the foundation to snap.
* @param data.template The template to get the foundation snapping data for.
* @param data.snapEntities Optional; list of entity IDs to snap to if {data.x, data.z} is within a circle of radius data.snapRadius
* around the entity. Only takes effect when used in conjunction with data.snapRadius.
* When this option is used and the foundation is found to snap to one of the entities passed in this list
* (as opposed to e.g. snapping to terrain features), then the result will contain an additional key "ent",
* holding the ID of the entity that was snapped to.
* @param data.snapRadius Optional; when used in conjunction with data.snapEntities, indicates the circle radius around an entity that
* {data.x, data.z} must be located within to have it snap to that entity.
*/
GuiInterface.prototype.GetFoundationSnapData = function(player, data)
{
var cmpTemplateMgr = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateMgr.GetTemplate(data.template);
if (!template)
{
warn("[GetFoundationSnapData] Failed to load template '" + data.template + "'");
return false;
}
if (data.snapEntities && data.snapRadius && data.snapRadius > 0)
{
// see if {data.x, data.z} is inside the snap radius of any of the snap entities; and if so, to which it is closest
// (TODO: break unlikely ties by choosing the lowest entity ID)
var minDist2 = -1;
var minDistEntitySnapData = null;
var radius2 = data.snapRadius * data.snapRadius;
for each (ent in data.snapEntities)
{
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
continue;
var pos = cmpPosition.GetPosition();
var dist2 = (data.x - pos.x) * (data.x - pos.x) + (data.z - pos.z) * (data.z - pos.z);
if (dist2 > radius2)
continue;
if (minDist2 < 0 || dist2 < minDist2)
{
minDist2 = dist2;
minDistEntitySnapData = {"x": pos.x, "z": pos.z, "angle": cmpPosition.GetRotation().y, "ent": ent};
}
}
if (minDistEntitySnapData != null)
return minDistEntitySnapData;
}
if (template.BuildRestrictions.Category == "Dock")
{
+ // warning: copied almost identically in helpers/command.js , "GetDockAngle".
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
if (!cmpTerrain || !cmpWaterManager)
{
return false;
}
// Get footprint size
var halfSize = 0;
if (template.Footprint.Square)
{
halfSize = Math.max(template.Footprint.Square["@depth"], template.Footprint.Square["@width"])/2;
}
else if (template.Footprint.Circle)
{
halfSize = template.Footprint.Circle["@radius"];
}
/* Find direction of most open water, algorithm:
* 1. Pick points in a circle around dock
* 2. If point is in water, add to array
* 3. Scan array looking for consecutive points
* 4. Find longest sequence of consecutive points
* 5. If sequence equals all points, no direction can be determined,
* expand search outward and try (1) again
* 6. Calculate angle using average of sequence
*/
const numPoints = 16;
for (var dist = 0; dist < 4; ++dist)
{
var waterPoints = [];
for (var i = 0; i < numPoints; ++i)
{
var angle = (i/numPoints)*2*Math.PI;
var d = halfSize*(dist+1);
var nx = data.x - d*Math.sin(angle);
var nz = data.z + d*Math.cos(angle);
if (cmpTerrain.GetGroundLevel(nx, nz) < cmpWaterManager.GetWaterLevel(nx, nz))
{
waterPoints.push(i);
}
}
var consec = [];
var length = waterPoints.length;
for (var i = 0; i < length; ++i)
{
var count = 0;
for (var j = 0; j < (length-1); ++j)
{
if (((waterPoints[(i + j) % length]+1) % numPoints) == waterPoints[(i + j + 1) % length])
{
++count;
}
else
{
break;
}
}
consec[i] = count;
}
var start = 0;
var count = 0;
for (var c in consec)
{
if (consec[c] > count)
{
start = c;
count = consec[c];
}
}
// If we've found a shoreline, stop searching
if (count != numPoints-1)
{
return {"x": data.x, "z": data.z, "angle": -(((waterPoints[start] + consec[start]/2) % numPoints)/numPoints*2*Math.PI)};
}
}
}
return false;
};
GuiInterface.prototype.PlaySound = function(player, data)
{
// Ignore if no entity was passed
if (!data.entity)
return;
PlaySound(data.name, data.entity);
};
function isIdleUnit(ent, idleClass)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
// TODO: Do something with garrisoned idle units
return (cmpUnitAI && cmpIdentity && cmpUnitAI.IsIdle() && !cmpUnitAI.IsGarrisoned() && idleClass && cmpIdentity.HasClass(idleClass));
}
GuiInterface.prototype.FindIdleUnits = function(player, data)
{
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var playerEntities = rangeMan.GetEntitiesByPlayer(player).filter( function(e) {
var cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI);
if (cmpUnitAI)
return true;
return false;
});
var idleUnits = [];
var noFilter = (data.prevUnit == undefined && data.excludeUnits == undefined);
for (var j = 0; j < playerEntities.length; ++j)
{
var ent = playerEntities[j];
if (!isIdleUnit(ent, data.idleClass))
continue;
if (noFilter || ((data.prevUnit == undefined || ent > data.prevUnit) &&
(data.excludeUnits == undefined || data.excludeUnits.indexOf(ent) == -1)))
{
idleUnits.push(ent);
playerEntities.splice(j--, 1);
}
if (data.limit && idleUnits.length >= data.limit)
break;
}
return idleUnits;
}
GuiInterface.prototype.GetTradingRouteGain = function(player, data)
{
if (!data.firstMarket || !data.secondMarket)
return null;
return CalculateTraderGain(data.firstMarket, data.secondMarket, data.template);
}
GuiInterface.prototype.GetTradingDetails = function(player, data)
{
var cmpEntityTrader = Engine.QueryInterface(data.trader, IID_Trader);
if (!cmpEntityTrader || !cmpEntityTrader.CanTrade(data.target))
return null;
var firstMarket = cmpEntityTrader.GetFirstMarket();
var secondMarket = cmpEntityTrader.GetSecondMarket();
var result = null;
if (data.target === firstMarket)
{
result = {
"type": "is first",
"goods": cmpEntityTrader.GetPreferredGoods(),
"hasBothMarkets": cmpEntityTrader.HasBothMarkets()
};
if (cmpEntityTrader.HasBothMarkets())
result.gain = cmpEntityTrader.GetGain();
}
else if (data.target === secondMarket)
{
result = {
"type": "is second",
"gain": cmpEntityTrader.GetGain(),
"goods": cmpEntityTrader.GetPreferredGoods()
};
}
else if (!firstMarket)
{
result = {"type": "set first"};
}
else if (!secondMarket)
{
result = {
"type": "set second",
"gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
"goods": cmpEntityTrader.GetPreferredGoods()
};
}
else
{
// Else both markets are not null and target is different from them
result = {"type": "set first"};
}
return result;
};
GuiInterface.prototype.CanAttack = function(player, data)
{
var cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
if (!cmpAttack)
return false;
return cmpAttack.CanAttack(data.target);
};
/*
* Returns batch build time.
*/
GuiInterface.prototype.GetBatchTime = function(player, data)
{
var cmpProductionQueue = Engine.QueryInterface(data.entity, IID_ProductionQueue);
if (!cmpProductionQueue)
return 0;
return cmpProductionQueue.GetBatchTime(data.batchSize);
};
GuiInterface.prototype.SetPathfinderDebugOverlay = function(player, enabled)
{
var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
cmpPathfinder.SetDebugOverlay(enabled);
};
GuiInterface.prototype.SetObstructionDebugOverlay = function(player, enabled)
{
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
cmpObstructionManager.SetDebugOverlay(enabled);
};
GuiInterface.prototype.SetMotionDebugOverlay = function(player, data)
{
for each (var ent in data.entities)
{
var cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
if (cmpUnitMotion)
cmpUnitMotion.SetDebugOverlay(data.enabled);
}
};
GuiInterface.prototype.SetRangeDebugOverlay = function(player, enabled)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
cmpRangeManager.SetDebugOverlay(enabled);
};
GuiInterface.prototype.OnGlobalEntityRenamed = function(msg)
{
this.renamedEntities.push(msg);
}
// List the GuiInterface functions that can be safely called by GUI scripts.
// (GUI scripts are non-deterministic and untrusted, so these functions must be
// appropriately careful. They are called with a first argument "player", which is
// trusted and indicates the player associated with the current client; no data should
// be returned unless this player is meant to be able to see it.)
var exposedFunctions = {
"GetSimulationState": 1,
"GetExtendedSimulationState": 1,
"GetRenamedEntities": 1,
"ClearRenamedEntities": 1,
"GetEntityState": 1,
"GetTemplateData": 1,
"GetTechnologyData": 1,
"IsTechnologyResearched": 1,
"CheckTechnologyRequirements": 1,
"GetStartedResearch": 1,
"GetBattleState": 1,
"GetNeededResources": 1,
"GetNextNotification": 1,
"GetAvailableFormations": 1,
"GetFormationRequirements": 1,
"CanMoveEntsIntoFormation": 1,
"IsFormationSelected": 1,
"IsStanceSelected": 1,
"SetSelectionHighlight": 1,
"SetStatusBars": 1,
"GetPlayerEntities": 1,
"DisplayRallyPoint": 1,
"SetBuildingPlacementPreview": 1,
"SetWallPlacementPreview": 1,
"GetFoundationSnapData": 1,
"PlaySound": 1,
"FindIdleUnits": 1,
"GetTradingRouteGain": 1,
"GetTradingDetails": 1,
"CanAttack": 1,
"GetBatchTime": 1,
"SetPathfinderDebugOverlay": 1,
"SetObstructionDebugOverlay": 1,
"SetMotionDebugOverlay": 1,
"SetRangeDebugOverlay": 1,
};
GuiInterface.prototype.ScriptCall = function(player, name, args)
{
if (exposedFunctions[name])
return this[name](player, args);
else
throw new Error("Invalid GuiInterface Call name \""+name+"\"");
};
Engine.RegisterComponentType(IID_GuiInterface, "GuiInterface", GuiInterface);
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/InitGame.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/InitGame.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/InitGame.js (revision 13225)
@@ -1,37 +1,39 @@
function InitGame(settings)
{
// This will be called after the map settings have been loaded,
// before the simulation has started.
// This is only called at the start of a new game, not when loading
// a saved game.
// No settings when loading a map in Atlas, so do nothing
if (!settings)
return;
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var cmpAIManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIManager);
for (var i = 0; i < settings.PlayerData.length; ++i)
{
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(i+1), IID_Player);
if (!settings.CheatsEnabled)
cmpPlayer.SetCheatEnabled(false);
if (settings.PlayerData[i] && settings.PlayerData[i].AI && settings.PlayerData[i].AI != "")
{
cmpAIManager.AddPlayer(settings.PlayerData[i].AI, i+1);
cmpPlayer.SetAI(true);
cmpPlayer.SetCheatEnabled(true);
}
if (settings.PopulationCap)
cmpPlayer.SetMaxPopulation(settings.PopulationCap);
if (settings.mapType !== "scenario" && settings.StartingResources)
var resourceCounts = cmpPlayer.GetResourceCounts();
var newResourceCounts = {};
for (var resouces in resourceCounts)
newResourceCounts[resouces] = settings.StartingResources;
cmpPlayer.SetResourceCounts(newResourceCounts);
}
+ cmpAIManager.TryLoadSharedComponent();
+ cmpAIManager.RunGamestateInit();
}
Engine.RegisterGlobal("InitGame", InitGame);
Index: ps/trunk/source/simulation2/components/CCmpAIManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpAIManager.cpp (revision 13224)
+++ ps/trunk/source/simulation2/components/CCmpAIManager.cpp (revision 13225)
@@ -1,734 +1,1015 @@
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpAIManager.h"
#include "simulation2/MessageTypes.h"
#include "graphics/Terrain.h"
#include "lib/timer.h"
#include "lib/tex/tex.h"
#include "lib/allocators/shared_ptr.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Util.h"
#include "simulation2/components/ICmpAIInterface.h"
#include "simulation2/components/ICmpCommandQueue.h"
#include "simulation2/components/ICmpObstructionManager.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTemplateManager.h"
+#include "simulation2/components/ICmpTechnologyTemplateManager.h"
#include "simulation2/components/ICmpTerritoryManager.h"
#include "simulation2/helpers/Grid.h"
#include "simulation2/serialization/DebugSerializer.h"
#include "simulation2/serialization/StdDeserializer.h"
#include "simulation2/serialization/StdSerializer.h"
#include "simulation2/serialization/SerializeTemplates.h"
/**
* @file
* Player AI interface.
* AI is primarily scripted, and the CCmpAIManager component defined here
* takes care of managing all the scripts.
*
* To avoid slow AI scripts causing jerky rendering, they are run in a background
* thread (maintained by CAIWorker) so that it's okay if they take a whole simulation
* turn before returning their results (though preferably they shouldn't use nearly
* that much CPU).
*
* CCmpAIManager grabs the world state after each turn (making use of AIInterface.js
* and AIProxy.js to decide what data to include) then passes it to CAIWorker.
* The AI scripts will then run asynchronously and return a list of commands to execute.
* Any attempts to read the command list (including indirectly via serialization)
* will block until it's actually completed, so the rest of the engine should avoid
* reading it for as long as possible.
*
* JS values are passed between the game and AI threads using ScriptInterface::StructuredClone.
*
* TODO: actually the thread isn't implemented yet, because performance hasn't been
* sufficiently problematic to justify the complexity yet, but the CAIWorker interface
* is designed to hopefully support threading when we want it.
*/
/**
* Implements worker thread for CCmpAIManager.
*/
class CAIWorker
{
private:
class CAIPlayer
{
NONCOPYABLE(CAIPlayer);
public:
CAIPlayer(CAIWorker& worker, const std::wstring& aiName, player_id_t player,
const shared_ptr& runtime, boost::rand48& rng) :
m_Worker(worker), m_AIName(aiName), m_Player(player), m_ScriptInterface("Engine", "AI", runtime)
{
m_ScriptInterface.SetCallbackData(static_cast (this));
m_ScriptInterface.ReplaceNondeterministicRNG(rng);
m_ScriptInterface.LoadGlobalScripts();
m_ScriptInterface.RegisterFunction("IncludeModule");
+ m_ScriptInterface.RegisterFunction("DumpHeap");
+ m_ScriptInterface.RegisterFunction("ForceGC");
m_ScriptInterface.RegisterFunction("PostCommand");
m_ScriptInterface.RegisterFunction, u32, u32, u32, CAIPlayer::DumpImage>("DumpImage");
}
~CAIPlayer()
{
// Clean up rooted objects before destroying their script context
m_Obj = CScriptValRooted();
m_Commands.clear();
}
static void IncludeModule(void* cbdata, std::wstring name)
{
CAIPlayer* self = static_cast (cbdata);
self->LoadScripts(name);
}
-
+ static void DumpHeap(void* cbdata)
+ {
+ CAIPlayer* self = static_cast (cbdata);
+
+ //std::cout << JS_GetGCParameter(self->m_ScriptInterface.GetRuntime(), JSGC_BYTES) << std::endl;
+ self->m_ScriptInterface.DumpHeap();
+ }
+ static void ForceGC(void* cbdata)
+ {
+ CAIPlayer* self = static_cast (cbdata);
+
+ JS_GC(self->m_ScriptInterface.GetContext());
+ }
static void PostCommand(void* cbdata, CScriptValRooted cmd)
{
CAIPlayer* self = static_cast (cbdata);
self->m_Commands.push_back(self->m_ScriptInterface.WriteStructuredClone(cmd.get()));
}
/**
* Debug function for AI scripts to dump 2D array data (e.g. terrain tile weights).
*/
static void DumpImage(void* UNUSED(cbdata), std::wstring name, std::vector data, u32 w, u32 h, u32 max)
{
// TODO: this is totally not threadsafe.
VfsPath filename = L"screenshots/aidump/" + name;
if (data.size() != w*h)
{
debug_warn(L"DumpImage: data size doesn't match w*h");
return;
}
if (max == 0)
{
debug_warn(L"DumpImage: max must not be 0");
return;
}
const size_t bpp = 8;
int flags = TEX_BOTTOM_UP|TEX_GREY;
const size_t img_size = w * h * bpp/8;
const size_t hdr_size = tex_hdr_size(filename);
shared_ptr buf;
AllocateAligned(buf, hdr_size+img_size, maxSectorSize);
Tex t;
if (tex_wrap(w, h, bpp, flags, buf, hdr_size, &t) < 0)
return;
u8* img = buf.get() + hdr_size;
for (size_t i = 0; i < data.size(); ++i)
img[i] = (u8)((data[i] * 255) / max);
tex_write(&t, filename);
tex_free(&t);
}
bool LoadScripts(const std::wstring& moduleName)
{
// Ignore modules that are already loaded
if (m_LoadedModules.find(moduleName) != m_LoadedModules.end())
return true;
// Mark this as loaded, to prevent it recursively loading itself
m_LoadedModules.insert(moduleName);
// Load and execute *.js
VfsPaths pathnames;
vfs::GetPathnames(g_VFS, L"simulation/ai/" + moduleName + L"/", L"*.js", pathnames);
for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
{
if (!m_ScriptInterface.LoadGlobalScriptFile(*it))
{
LOGERROR(L"Failed to load script %ls", it->string().c_str());
return false;
}
}
return true;
}
bool Initialise(bool callConstructor)
{
if (!LoadScripts(m_AIName))
return false;
OsPath path = L"simulation/ai/" + m_AIName + L"/data.json";
CScriptValRooted metadata = m_Worker.LoadMetadata(path);
if (metadata.uninitialised())
{
LOGERROR(L"Failed to create AI player: can't find %ls", path.string().c_str());
return false;
}
// Get the constructor name from the metadata
std::string constructor;
if (!m_ScriptInterface.GetProperty(metadata.get(), "constructor", constructor))
{
LOGERROR(L"Failed to create AI player: %ls: missing 'constructor'", path.string().c_str());
return false;
}
// Get the constructor function from the loaded scripts
CScriptVal ctor;
if (!m_ScriptInterface.GetProperty(m_ScriptInterface.GetGlobalObject(), constructor.c_str(), ctor)
|| ctor.undefined())
{
LOGERROR(L"Failed to create AI player: %ls: can't find constructor '%hs'", path.string().c_str(), constructor.c_str());
return false;
}
+ m_ScriptInterface.GetProperty(metadata.get(), "useShared", m_UseSharedComponent);
+
CScriptVal obj;
if (callConstructor)
{
// Set up the data to pass as the constructor argument
CScriptVal settings;
m_ScriptInterface.Eval(L"({})", settings);
m_ScriptInterface.SetProperty(settings.get(), "player", m_Player, false);
ENSURE(m_Worker.m_HasLoadedEntityTemplates);
m_ScriptInterface.SetProperty(settings.get(), "templates", m_Worker.m_EntityTemplates, false);
obj = m_ScriptInterface.CallConstructor(ctor.get(), settings.get());
}
else
{
// For deserialization, we want to create the object with the correct prototype
// but don't want to actually run the constructor again
// XXX: actually we don't currently use this path for deserialization - maybe delete it?
obj = m_ScriptInterface.NewObjectFromConstructor(ctor.get());
}
if (obj.undefined())
{
LOGERROR(L"Failed to create AI player: %ls: error calling constructor '%hs'", path.string().c_str(), constructor.c_str());
return false;
}
m_Obj = CScriptValRooted(m_ScriptInterface.GetContext(), obj);
return true;
}
void Run(CScriptVal state)
{
m_Commands.clear();
m_ScriptInterface.CallFunctionVoid(m_Obj.get(), "HandleMessage", state);
}
+ // overloaded with a sharedAI part.
+ // javascript can handle both natively on the same function.
+ void Run(CScriptVal state, CScriptValRooted SharedAI)
+ {
+ m_Commands.clear();
+ m_ScriptInterface.CallFunctionVoid(m_Obj.get(), "HandleMessage", state, SharedAI);
+ }
+ void InitWithSharedScript(CScriptVal state, CScriptValRooted SharedAI)
+ {
+ m_Commands.clear();
+ m_ScriptInterface.CallFunctionVoid(m_Obj.get(), "InitWithSharedScript", state, SharedAI);
+ }
CAIWorker& m_Worker;
std::wstring m_AIName;
player_id_t m_Player;
-
+ bool m_UseSharedComponent;
+
ScriptInterface m_ScriptInterface;
CScriptValRooted m_Obj;
std::vector > m_Commands;
std::set m_LoadedModules;
};
public:
struct SCommandSets
{
player_id_t player;
std::vector > commands;
};
CAIWorker() :
- // TODO: Passing a 32 MB argument to CreateRuntime() is a temporary fix
+ // TODO: Passing a 24 MB argument to CreateRuntime() is a temporary fix
// to prevent frequent AI out-of-memory crashes. The argument should be
- // removed as soon as AI data-sharing has been implemented. See #1650.
- m_ScriptRuntime(ScriptInterface::CreateRuntime(33554432)),
+ // removed as soon whenever the new pathfinder is committed
+ // And the AIs can stop relying on their own little hands.
+ m_ScriptRuntime(ScriptInterface::CreateRuntime(25165824)),
m_ScriptInterface("Engine", "AI", m_ScriptRuntime),
m_TurnNum(0),
m_CommandsComputed(true),
- m_HasLoadedEntityTemplates(false)
+ m_HasLoadedEntityTemplates(false),
+ m_HasSharedComponent(false)
{
- m_ScriptInterface.SetCallbackData(static_cast (this));
// TODO: ought to seed the RNG (in a network-synchronised way) before we use it
m_ScriptInterface.ReplaceNondeterministicRNG(m_RNG);
m_ScriptInterface.LoadGlobalScripts();
+
+ m_ScriptInterface.SetCallbackData(NULL);
+
+ m_ScriptInterface.RegisterFunction("PostCommand");
+ m_ScriptInterface.RegisterFunction("DumpHeap");
+ m_ScriptInterface.RegisterFunction("ForceGC");
+
}
~CAIWorker()
{
// Clear rooted script values before destructing the script interface
m_EntityTemplates = CScriptValRooted();
m_PlayerMetadata.clear();
m_Players.clear();
m_GameState.reset();
m_PassabilityMapVal = CScriptValRooted();
m_TerritoryMapVal = CScriptValRooted();
}
+ // This is called by AIs if they use the v3 API.
+ // If the AIs originate the call, cbdata is not NULL.
+ // If the shared component does, it is, so it must not be taken into account.
+ static void PostCommand(void* cbdata, CScriptValRooted cmd)
+ {
+ if (cbdata == NULL) {
+ debug_warn(L"Warning: the shared component has tried to push an engine command. Ignoring.");
+ return;
+ }
+ CAIPlayer* self = static_cast (cbdata);
+ self->m_Commands.push_back(self->m_ScriptInterface.WriteStructuredClone(cmd.get()));
+ }
+ // The next two ought to be implmeneted someday but for now as it returns "null" it can't
+ static void DumpHeap(void* cbdata)
+ {
+ if (cbdata == NULL) {
+ debug_warn(L"Warning: the shared component has asked for DumpHeap. Ignoring.");
+ return;
+ }
+ CAIWorker* self = static_cast (cbdata);
+ self->m_ScriptInterface.DumpHeap();
+ }
+ static void ForceGC(void* cbdata)
+ {
+ if (cbdata == NULL) {
+ debug_warn(L"Warning: the shared component has asked for ForceGC. Ignoring.");
+ return;
+ }
+ CAIWorker* self = static_cast (cbdata);
+ PROFILE3("AI compute GC");
+ JS_GC(self->m_ScriptInterface.GetContext());
+ }
+
+ bool TryLoadSharedComponent(bool callConstructor)
+ {
+ // only load if there are AI players.
+ if (m_Players.size() == 0)
+ return false;
+ // we don't need to load it.
+ if (!m_HasSharedComponent)
+ return false;
+
+ // reset the value so it can be used to determine if we actually initialized it.
+ m_HasSharedComponent = false;
+
+ VfsPaths sharedPathnames;
+ // Check for "shared" module.
+ vfs::GetPathnames(g_VFS, L"simulation/ai/common-api-v3/", L"*.js", sharedPathnames);
+ for (VfsPaths::iterator it = sharedPathnames.begin(); it != sharedPathnames.end(); ++it)
+ {
+ if (!m_ScriptInterface.LoadGlobalScriptFile(*it))
+ {
+ LOGERROR(L"Failed to load shared script %ls", it->string().c_str());
+ return false;
+ }
+ m_HasSharedComponent = true;
+ }
+ if (!m_HasSharedComponent)
+ return false;
+
+ // mainly here for the error messages
+ OsPath path = L"simulation/ai/common-api-v2/";
+
+ // Constructor name is SharedScript
+ CScriptVal ctor;
+ if (!m_ScriptInterface.GetProperty(m_ScriptInterface.GetGlobalObject(), "SharedScript", ctor)
+ || ctor.undefined())
+ {
+ LOGERROR(L"Failed to create shared AI component: %ls: can't find constructor '%hs'", path.string().c_str(), "SharedScript");
+ return false;
+ }
+
+ if (callConstructor)
+ {
+ // Set up the data to pass as the constructor argument
+ CScriptVal settings;
+ m_ScriptInterface.Eval(L"({})", settings);
+ CScriptVal playersID;
+ m_ScriptInterface.Eval(L"({})", playersID);
+
+ for (size_t i = 0; i < m_Players.size(); ++i)
+ {
+ jsval val = m_ScriptInterface.ToJSVal(m_ScriptInterface.GetContext(), m_Players[i]->m_Player);
+ m_ScriptInterface.SetPropertyInt(playersID.get(), i, CScriptVal(val), true);
+ }
+
+ m_ScriptInterface.SetProperty(settings.get(), "players", playersID);
+
+ ENSURE(m_HasLoadedEntityTemplates);
+ m_ScriptInterface.SetProperty(settings.get(), "templates", m_EntityTemplates, false);
+
+ m_ScriptInterface.SetProperty(settings.get(), "techTemplates", m_TechTemplates, false);
+
+ m_SharedAIObj = CScriptValRooted(m_ScriptInterface.GetContext(),m_ScriptInterface.CallConstructor(ctor.get(), settings.get()));
+ }
+ else
+ {
+ // For deserialization, we want to create the object with the correct prototype
+ // but don't want to actually run the constructor again
+ // XXX: actually we don't currently use this path for deserialization - maybe delete it?
+ m_SharedAIObj = CScriptValRooted(m_ScriptInterface.GetContext(),m_ScriptInterface.NewObjectFromConstructor(ctor.get()));
+ }
+
+ if (m_SharedAIObj.undefined())
+ {
+ LOGERROR(L"Failed to create shared AI component: %ls: error calling constructor '%hs'", path.string().c_str(), "SharedScript");
+ return false;
+ }
+
+ return true;
+ }
+
bool AddPlayer(const std::wstring& aiName, player_id_t player, bool callConstructor)
{
shared_ptr ai(new CAIPlayer(*this, aiName, player, m_ScriptRuntime, m_RNG));
if (!ai->Initialise(callConstructor))
return false;
+
+ // this will be set to true if we need to load the shared Component.
+ if (!m_HasSharedComponent)
+ m_HasSharedComponent = ai->m_UseSharedComponent;
+
+ m_ScriptInterface.MaybeGC();
m_Players.push_back(ai);
return true;
}
+ bool RunGamestateInit(const shared_ptr& gameState, const Grid& passabilityMap, const Grid& territoryMap)
+ {
+ // this will be run last by InitGame.Js, passing the full game representation.
+ // For now it will run for the shared Component.
+ CScriptVal state = m_ScriptInterface.ReadStructuredClone(gameState);
+ JSContext* cx = m_ScriptInterface.GetContext();
+
+ m_PassabilityMapVal = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, passabilityMap));
+ m_TerritoryMapVal = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, territoryMap));
+ if (m_HasSharedComponent)
+ {
+ m_ScriptInterface.SetProperty(state.get(), "passabilityMap", m_PassabilityMapVal, true);
+ m_ScriptInterface.SetProperty(state.get(), "territoryMap", m_TerritoryMapVal, true);
+
+ m_ScriptInterface.CallFunctionVoid(m_SharedAIObj.get(), "initWithState", state);
+ m_ScriptInterface.MaybeGC();
+
+ for (size_t i = 0; i < m_Players.size(); ++i)
+ {
+ if (m_HasSharedComponent && m_Players[i]->m_UseSharedComponent)
+ m_Players[i]->InitWithSharedScript(state,m_SharedAIObj);
+ }
+ }
+
+ return true;
+ }
void StartComputation(const shared_ptr& gameState, const Grid& passabilityMap, const Grid& territoryMap, bool territoryMapDirty)
{
ENSURE(m_CommandsComputed);
m_GameState = gameState;
if (passabilityMap.m_DirtyID != m_PassabilityMap.m_DirtyID)
{
m_PassabilityMap = passabilityMap;
JSContext* cx = m_ScriptInterface.GetContext();
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;
}
void WaitToFinishComputation()
{
if (!m_CommandsComputed)
{
PerformComputation();
m_CommandsComputed = true;
}
}
void GetCommands(std::vector& commands)
{
- WaitToFinishComputation();
-
+ WaitToFinishComputation();
+
commands.clear();
commands.resize(m_Players.size());
for (size_t i = 0; i < m_Players.size(); ++i)
{
commands[i].player = m_Players[i]->m_Player;
commands[i].commands = m_Players[i]->m_Commands;
}
}
+ void RegisterTechTemplates(const shared_ptr& techTemplates) {
+ JSContext* cx = m_ScriptInterface.GetContext();
+ m_TechTemplates = CScriptValRooted(cx, m_ScriptInterface.ReadStructuredClone(techTemplates));
+ }
+
void LoadEntityTemplates(const std::vector >& templates)
{
m_HasLoadedEntityTemplates = true;
m_ScriptInterface.Eval("({})", m_EntityTemplates);
for (size_t i = 0; i < templates.size(); ++i)
{
jsval val = templates[i].second->ToJSVal(m_ScriptInterface.GetContext(), false);
m_ScriptInterface.SetProperty(m_EntityTemplates.get(), templates[i].first.c_str(), CScriptVal(val), true);
}
// Since the template data is shared between AI players, freeze it
// to stop any of them changing it and confusing the other players
m_ScriptInterface.FreezeObject(m_EntityTemplates.get(), true);
}
void Serialize(std::ostream& stream, bool isDebug)
{
WaitToFinishComputation();
if (isDebug)
{
CDebugSerializer serializer(m_ScriptInterface, stream);
serializer.Indent(4);
SerializeState(serializer);
}
else
{
CStdSerializer serializer(m_ScriptInterface, stream);
SerializeState(serializer);
}
}
void SerializeState(ISerializer& serializer)
{
std::stringstream rngStream;
rngStream << m_RNG;
serializer.StringASCII("rng", rngStream.str(), 0, 32);
serializer.NumberU32_Unbounded("turn", m_TurnNum);
serializer.NumberU32_Unbounded("num ais", (u32)m_Players.size());
for (size_t i = 0; i < m_Players.size(); ++i)
{
serializer.String("name", m_Players[i]->m_AIName, 1, 256);
serializer.NumberI32_Unbounded("player", m_Players[i]->m_Player);
serializer.NumberU32_Unbounded("num commands", (u32)m_Players[i]->m_Commands.size());
for (size_t j = 0; j < m_Players[i]->m_Commands.size(); ++j)
{
CScriptVal val = m_ScriptInterface.ReadStructuredClone(m_Players[i]->m_Commands[j]);
serializer.ScriptVal("command", val);
}
CScriptVal scriptData;
if (!m_ScriptInterface.CallFunction(m_Players[i]->m_Obj.get(), "Serialize", scriptData))
LOGERROR(L"AI script Serialize call failed");
serializer.ScriptVal("data", scriptData);
}
}
void Deserialize(std::istream& stream)
{
ENSURE(m_CommandsComputed); // deserializing while we're still actively computing would be bad
CStdDeserializer deserializer(m_ScriptInterface, stream);
m_PlayerMetadata.clear();
m_Players.clear();
std::string rngString;
std::stringstream rngStream;
deserializer.StringASCII("rng", rngString, 0, 32);
rngStream << rngString;
rngStream >> m_RNG;
deserializer.NumberU32_Unbounded("turn", m_TurnNum);
uint32_t numAis;
deserializer.NumberU32_Unbounded("num ais", numAis);
for (size_t i = 0; i < numAis; ++i)
{
std::wstring name;
player_id_t player;
deserializer.String("name", name, 1, 256);
deserializer.NumberI32_Unbounded("player", player);
if (!AddPlayer(name, player, true))
throw PSERROR_Deserialize_ScriptError();
uint32_t numCommands;
deserializer.NumberU32_Unbounded("num commands", numCommands);
m_Players.back()->m_Commands.reserve(numCommands);
for (size_t j = 0; j < numCommands; ++j)
{
CScriptVal val;
deserializer.ScriptVal("command", val);
m_Players.back()->m_Commands.push_back(m_ScriptInterface.WriteStructuredClone(val.get()));
}
CScriptVal scriptData;
deserializer.ScriptVal("data", scriptData);
if (!m_ScriptInterface.CallFunctionVoid(m_Players.back()->m_Obj.get(), "Deserialize", scriptData))
LOGERROR(L"AI script Deserialize call failed");
}
}
+
+ int getPlayerSize()
+ {
+ return m_Players.size();
+ }
private:
CScriptValRooted LoadMetadata(const VfsPath& path)
{
if (m_PlayerMetadata.find(path) == m_PlayerMetadata.end())
{
// Load and cache the AI player metadata
m_PlayerMetadata[path] = m_ScriptInterface.ReadJSONFile(path);
}
return m_PlayerMetadata[path];
}
void PerformComputation()
{
+ if (m_Players.size() == 0)
+ {
+ // Run the GC every so often.
+ // (This isn't particularly necessary, but it makes profiling clearer
+ // since it avoids random GC delays while running other scripts)
+ if (m_TurnNum++ % 50 == 0)
+ {
+ PROFILE3("AI compute GC");
+ m_ScriptInterface.MaybeGC();
+ }
+ return;
+ }
+
// Deserialize the game state, to pass to the AI's HandleMessage
CScriptVal state;
{
PROFILE3("AI compute read state");
state = m_ScriptInterface.ReadStructuredClone(m_GameState);
m_ScriptInterface.SetProperty(state.get(), "passabilityMap", m_PassabilityMapVal, true);
m_ScriptInterface.SetProperty(state.get(), "territoryMap", m_TerritoryMapVal, true);
}
// It would be nice to do
// m_ScriptInterface.FreezeObject(state.get(), true);
// to prevent AI scripts accidentally modifying the state and
// affecting other AI scripts they share it with. But the performance
// cost is far too high, so we won't do that.
+ // If there is a shared component, run it
+ if (m_HasSharedComponent)
+ {
+ PROFILE3("AI run shared component");
+ m_ScriptInterface.CallFunctionVoid(m_SharedAIObj.get(), "onUpdate", state);
+ }
+
for (size_t i = 0; i < m_Players.size(); ++i)
{
PROFILE3("AI script");
PROFILE2_ATTR("player: %d", m_Players[i]->m_Player);
PROFILE2_ATTR("script: %ls", m_Players[i]->m_AIName.c_str());
- m_Players[i]->Run(state);
+ if (m_HasSharedComponent && m_Players[i]->m_UseSharedComponent)
+ m_Players[i]->Run(state,m_SharedAIObj);
+ else
+ m_Players[i]->Run(state);
}
+ // Run GC if we are about to overflow
+ if (JS_GetGCParameter(m_ScriptInterface.GetRuntime(), JSGC_BYTES) > 24000000)
+ {
+ PROFILE3("AI compute GC");
+
+ JS_GC(m_ScriptInterface.GetContext());
+ }
+
// Run the GC every so often.
// (This isn't particularly necessary, but it makes profiling clearer
// since it avoids random GC delays while running other scripts)
- if (m_TurnNum++ % 25 == 0)
+ /*if (m_TurnNum++ % 20 == 0)
{
PROFILE3("AI compute GC");
m_ScriptInterface.MaybeGC();
- }
+ }*/
}
shared_ptr m_ScriptRuntime;
ScriptInterface m_ScriptInterface;
boost::rand48 m_RNG;
u32 m_TurnNum;
CScriptValRooted m_EntityTemplates;
bool m_HasLoadedEntityTemplates;
+ CScriptValRooted m_TechTemplates;
std::map m_PlayerMetadata;
std::vector > m_Players; // use shared_ptr just to avoid copying
+ bool m_HasSharedComponent;
+ CScriptValRooted m_SharedAIObj;
+ std::vector m_Commands;
+
shared_ptr m_GameState;
Grid m_PassabilityMap;
CScriptValRooted m_PassabilityMapVal;
Grid m_TerritoryMap;
CScriptValRooted m_TerritoryMapVal;
bool m_CommandsComputed;
};
/**
* Implementation of ICmpAIManager.
*/
class CCmpAIManager : public ICmpAIManager
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_ProgressiveLoad);
}
DEFAULT_COMPONENT_ALLOCATOR(AIManager)
static std::string GetSchema()
{
return "";
}
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_TerritoriesDirtyID = 0;
StartLoadEntityTemplates();
+
+ // loading the technology template
+ ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
+
+ CmpPtr cmpTechTemplateManager(GetSimContext(), SYSTEM_ENTITY);
+ ENSURE(cmpTechTemplateManager);
+
+ // Get the game state from AIInterface
+ CScriptVal techTemplates = cmpTechTemplateManager->GetAllTechs();
+
+ m_Worker.RegisterTechTemplates(scriptInterface.WriteStructuredClone(techTemplates.get()));
}
virtual void Deinit()
{
}
virtual void Serialize(ISerializer& serialize)
{
// Because the AI worker uses its own ScriptInterface, we can't use the
// ISerializer (which was initialised with the simulation ScriptInterface)
// directly. So we'll just grab the ISerializer's stream and write to it
// with an independent serializer.
m_Worker.Serialize(serialize.GetStream(), serialize.IsDebug());
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
{
Init(paramNode);
ForceLoadEntityTemplates();
m_Worker.Deserialize(deserialize.GetStream());
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_ProgressiveLoad:
{
const CMessageProgressiveLoad& msgData = static_cast (msg);
*msgData.total += (int)m_TemplateNames.size();
if (*msgData.progressed)
break;
if (ContinueLoadEntityTemplates())
*msgData.progressed = true;
*msgData.progress += (int)m_TemplateLoadedIdx;
break;
}
}
}
virtual void AddPlayer(std::wstring id, player_id_t player)
{
m_Worker.AddPlayer(id, player, true);
// AI players can cheat and see through FoW/SoD, since that greatly simplifies
// their implementation.
// (TODO: maybe cleverer AIs should be able to optionally retain FoW/SoD)
CmpPtr cmpRangeManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpRangeManager)
cmpRangeManager->SetLosRevealAll(player, true);
}
+
+ virtual void TryLoadSharedComponent()
+ {
+ m_Worker.TryLoadSharedComponent(true);
+ }
+
+ virtual void RunGamestateInit()
+ {
+ ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
+
+ CmpPtr cmpAIInterface(GetSimContext(), SYSTEM_ENTITY);
+ ENSURE(cmpAIInterface);
+
+ // Get the game state from AIInterface
+ CScriptVal state = cmpAIInterface->GetFullRepresentation();
+
+ // Get the passability data
+ Grid dummyGrid;
+ const Grid* passabilityMap = &dummyGrid;
+ CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
+ if (cmpPathfinder)
+ 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 && cmpTerritoryManager->NeedUpdate(&m_TerritoriesDirtyID))
+ {
+ territoryMap = &cmpTerritoryManager->GetTerritoryGrid();
+ territoryMapDirty = true;
+ }
+
+ LoadPathfinderClasses(state);
+
+ m_Worker.RunGamestateInit(scriptInterface.WriteStructuredClone(state.get()), *passabilityMap, *territoryMap);
+ }
virtual void StartComputation()
{
PROFILE("AI setup");
ForceLoadEntityTemplates();
ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
+ if (m_Worker.getPlayerSize() == 0)
+ return;
+
CmpPtr cmpAIInterface(GetSimContext(), SYSTEM_ENTITY);
ENSURE(cmpAIInterface);
// Get the game state from AIInterface
CScriptVal state = cmpAIInterface->GetRepresentation();
// Get the passability data
Grid dummyGrid;
const Grid* passabilityMap = &dummyGrid;
CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
if (cmpPathfinder)
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 && cmpTerritoryManager->NeedUpdate(&m_TerritoriesDirtyID))
{
territoryMap = &cmpTerritoryManager->GetTerritoryGrid();
territoryMapDirty = true;
}
LoadPathfinderClasses(state);
m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state.get()), *passabilityMap, *territoryMap, territoryMapDirty);
}
virtual void PushCommands()
{
ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
std::vector commands;
m_Worker.GetCommands(commands);
CmpPtr cmpCommandQueue(GetSimContext(), SYSTEM_ENTITY);
if (!cmpCommandQueue)
return;
for (size_t i = 0; i < commands.size(); ++i)
{
for (size_t j = 0; j < commands[i].commands.size(); ++j)
{
cmpCommandQueue->PushLocalCommand(commands[i].player,
scriptInterface.ReadStructuredClone(commands[i].commands[j]));
}
}
}
private:
std::vector m_TemplateNames;
size_t m_TemplateLoadedIdx;
std::vector > m_Templates;
size_t m_TerritoriesDirtyID;
void StartLoadEntityTemplates()
{
CmpPtr cmpTemplateManager(GetSimContext(), SYSTEM_ENTITY);
ENSURE(cmpTemplateManager);
m_TemplateNames = cmpTemplateManager->FindAllTemplates(false);
m_TemplateLoadedIdx = 0;
m_Templates.reserve(m_TemplateNames.size());
}
// Tries to load the next entity template. Returns true if we did some work.
bool ContinueLoadEntityTemplates()
{
if (m_TemplateLoadedIdx >= m_TemplateNames.size())
return false;
CmpPtr cmpTemplateManager(GetSimContext(), SYSTEM_ENTITY);
ENSURE(cmpTemplateManager);
const CParamNode* node = cmpTemplateManager->GetTemplateWithoutValidation(m_TemplateNames[m_TemplateLoadedIdx]);
if (node)
m_Templates.push_back(std::make_pair(m_TemplateNames[m_TemplateLoadedIdx], node));
m_TemplateLoadedIdx++;
// If this was the last template, send the data to the worker
if (m_TemplateLoadedIdx == m_TemplateNames.size())
m_Worker.LoadEntityTemplates(m_Templates);
return true;
}
void ForceLoadEntityTemplates()
{
while (ContinueLoadEntityTemplates())
{
}
}
void LoadPathfinderClasses(CScriptVal state)
{
CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
if (!cmpPathfinder)
return;
ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
CScriptVal classesVal;
scriptInterface.Eval("({ pathfinderObstruction: 1, foundationObstruction: 2 })", classesVal);
std::map classes = cmpPathfinder->GetPassabilityClasses();
for (std::map::iterator it = classes.begin(); it != classes.end(); ++it)
scriptInterface.SetProperty(classesVal.get(), it->first.c_str(), it->second, true);
scriptInterface.SetProperty(state.get(), "passabilityClasses", classesVal, true);
}
CAIWorker m_Worker;
};
REGISTER_COMPONENT_TYPE(AIManager)
Index: ps/trunk/source/simulation2/components/ICmpAIManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpAIManager.cpp (revision 13224)
+++ ps/trunk/source/simulation2/components/ICmpAIManager.cpp (revision 13225)
@@ -1,76 +1,78 @@
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "ICmpAIManager.h"
#include "simulation2/system/InterfaceScripted.h"
#include "lib/file/vfs/vfs_util.h"
#include "ps/Filesystem.h"
BEGIN_INTERFACE_WRAPPER(AIManager)
DEFINE_INTERFACE_METHOD_2("AddPlayer", void, ICmpAIManager, AddPlayer, std::wstring, player_id_t)
+DEFINE_INTERFACE_METHOD_0("TryLoadSharedComponent", void, ICmpAIManager, TryLoadSharedComponent)
+DEFINE_INTERFACE_METHOD_0("RunGamestateInit", void, ICmpAIManager, RunGamestateInit)
END_INTERFACE_WRAPPER(AIManager)
// Implement the static method that finds all AI scripts
// that can be loaded via AddPlayer:
struct GetAIsHelper
{
NONCOPYABLE(GetAIsHelper);
public:
GetAIsHelper(ScriptInterface& scriptInterface) :
m_ScriptInterface(scriptInterface)
{
}
void Run()
{
vfs::ForEachFile(g_VFS, L"simulation/ai/", Callback, (uintptr_t)this, L"*.json", vfs::DIR_RECURSIVE);
}
static Status Callback(const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), const uintptr_t cbData)
{
GetAIsHelper* self = (GetAIsHelper*)cbData;
// Extract the 3rd component of the path (i.e. the directory after simulation/ai/)
fs::wpath components = pathname.string();
fs::wpath::iterator it = components.begin();
std::advance(it, 2);
std::wstring dirname = GetWstringFromWpath(*it);
CScriptValRooted ai;
self->m_ScriptInterface.Eval("({})", ai);
self->m_ScriptInterface.SetProperty(ai.get(), "id", dirname, true);
self->m_ScriptInterface.SetProperty(ai.get(), "data", self->m_ScriptInterface.ReadJSONFile(pathname), true);
self->m_AIs.push_back(ai);
return INFO::OK;
}
std::vector m_AIs;
ScriptInterface& m_ScriptInterface;
};
std::vector ICmpAIManager::GetAIs(ScriptInterface& scriptInterface)
{
GetAIsHelper helper(scriptInterface);
helper.Run();
return helper.m_AIs;
}
Index: ps/trunk/source/simulation2/components/ICmpTechnologyTemplateManager.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpTechnologyTemplateManager.h (nonexistent)
+++ ps/trunk/source/simulation2/components/ICmpTechnologyTemplateManager.h (revision 13225)
@@ -0,0 +1,38 @@
+/* Copyright (C) 2012 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_ICMPTECHNOLOGYTEMPLATEMANAGER
+#define INCLUDED_ICMPTECHNOLOGYTEMPLATEMANAGER
+
+#include "simulation2/system/Interface.h"
+
+#include "maths/Fixed.h"
+
+/**
+ * Technology template manager interface.
+ * (This interface only includes the functions needed by native code for accessing
+ * technology template data, the associated logic is handled in scripts)
+ */
+class ICmpTechnologyTemplateManager : public IComponent
+{
+public:
+ virtual CScriptVal GetAllTechs() = 0;
+
+ DECLARE_INTERFACE_TYPE(TechnologyTemplateManager)
+};
+
+#endif // INCLUDED_ICMPTECHNOLOGYTEMPLATEMANAGER
Property changes on: ps/trunk/source/simulation2/components/ICmpTechnologyTemplateManager.h
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/components/AIInterface.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/AIInterface.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/components/AIInterface.js (revision 13225)
@@ -1,59 +1,84 @@
function AIInterface() {}
AIInterface.prototype.Schema =
"";
AIInterface.prototype.Init = function()
{
this.events = [];
this.changedEntities = {};
};
AIInterface.prototype.GetRepresentation = function()
{
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
// Return the same game state as the GUI uses
- var state = cmpGuiInterface.GetSimulationState(-1);
+ var state = cmpGuiInterface.GetExtendedSimulationState(-1);
// Add some extra AI-specific data
state.events = this.events;
// Reset the event list for the next turn
this.events = [];
// Add entity representations
Engine.ProfileStart("proxy representations");
state.entities = {};
for (var id in this.changedEntities)
{
var aiProxy = Engine.QueryInterface(+id, IID_AIProxy);
if (aiProxy)
state.entities[id] = aiProxy.GetRepresentation();
}
this.changedEntities = {};
Engine.ProfileStop();
return state;
};
+// Intended to be called first, during the map initialization: no caching
+AIInterface.prototype.GetFullRepresentation = function()
+{
+ var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+
+ // Return the same game state as the GUI uses
+ var state = cmpGuiInterface.GetSimulationState(-1);
+
+ // Add some extra AI-specific data
+ state.events = this.events;
+
+ // Add entity representations
+ Engine.ProfileStart("proxy representations");
+ state.entities = {};
+ // all entities are changed in the initial state.
+ for (var id in this.changedEntities)
+ {
+ var aiProxy = Engine.QueryInterface(+id, IID_AIProxy);
+ if (aiProxy)
+ state.entities[id] = aiProxy.GetFullRepresentation();
+ }
+ Engine.ProfileStop();
+
+ return state;
+};
AIInterface.prototype.ChangedEntity = function(ent)
{
this.changedEntities[ent] = 1;
};
// AIProxy sets up a load of event handlers to capture interesting things going on
// in the world, which we will report to AI. Handle those, and add a few more handlers
// for events that AIProxy won't capture.
AIInterface.prototype.PushEvent = function(type, msg)
{
this.events.push({"type": type, "msg": msg});
};
AIInterface.prototype.OnGlobalPlayerDefeated = function(msg)
{
this.events.push({"type": "PlayerDefeated", "msg": msg});
};
Engine.RegisterComponentType(IID_AIInterface, "AIInterface", AIInterface);
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js (revision 13225)
@@ -1,1269 +1,1367 @@
// Setting this to true will display some warnings when commands
// are likely to fail, which may be useful for debugging AIs
var g_DebugCommands = false;
function ProcessCommand(player, cmd)
{
// Do some basic checks here that commanding player is valid
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (!cmpPlayerMan || player < 0)
return;
var playerEnt = cmpPlayerMan.GetPlayerByID(player);
if (playerEnt == INVALID_ENTITY)
return;
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
if (!cmpPlayer)
return;
var controlAllUnits = cmpPlayer.CanControlAllUnits();
// Note: checks of UnitAI targets are not robust enough here, as ownership
// can change after the order is issued, they should be checked by UnitAI
// when the specific behavior (e.g. attack, garrison) is performed.
// (Also it's not ideal if a command silently fails, it's nicer if UnitAI
// moves the entities closer to the target before giving up.)
// Now handle various commands
switch (cmd.type)
{
case "debug-print":
print(cmd.message);
break;
case "chat":
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({"type": "chat", "player": player, "message": cmd.message});
break;
case "cheat":
Cheat(cmd);
break;
case "quit":
// Let the AI exit the game for testing purposes
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({"type": "quit"});
break;
case "diplomacy":
switch(cmd.to)
{
case "ally":
cmpPlayer.SetAlly(cmd.player);
break;
case "neutral":
cmpPlayer.SetNeutral(cmd.player);
break;
case "enemy":
cmpPlayer.SetEnemy(cmd.player);
break;
default:
warn("Invalid command: Could not set "+player+" diplomacy status of player "+cmd.player+" to "+cmd.to);
}
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({"type": "diplomacy", "player": player, "player1": cmd.player, "status": cmd.to});
break;
case "tribute":
cmpPlayer.TributeResource(cmd.player, cmd.amounts);
break;
case "control-all":
cmpPlayer.SetControlAllUnits(cmd.flag);
break;
case "reveal-map":
// Reveal the map for all players, not just the current player,
// primarily to make it obvious to everyone that the player is cheating
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
cmpRangeManager.SetLosRevealAll(-1, cmd.enable);
break;
case "walk":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Walk(cmd.x, cmd.z, cmd.queued);
});
break;
case "attack-walk":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.queued);
});
break;
case "attack":
if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target)))
{
// This check is for debugging only!
warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd));
}
// See UnitAI.CanAttack for target checks
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Attack(cmd.target, cmd.queued);
});
break;
case "heal":
if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target)))
{
// This check is for debugging only!
warn("Invalid command: heal target is not owned by player "+player+" or their ally: "+uneval(cmd));
}
// See UnitAI.CanHeal for target checks
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Heal(cmd.target, cmd.queued);
});
break;
case "repair":
// This covers both repairing damaged buildings, and constructing unfinished foundations
if (g_DebugCommands && !IsOwnedByAllyOfPlayer(player, cmd.target))
{
// This check is for debugging only!
warn("Invalid command: repair target is not owned by ally of player "+player+": "+uneval(cmd));
}
// See UnitAI.CanRepair for target checks
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Repair(cmd.target, cmd.autocontinue, cmd.queued);
});
break;
case "gather":
if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByGaia(cmd.target)))
{
// This check is for debugging only!
warn("Invalid command: resource is not owned by gaia or player "+player+": "+uneval(cmd));
}
// See UnitAI.CanGather for target checks
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Gather(cmd.target, cmd.queued);
});
break;
case "gather-near-position":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.GatherNearPosition(cmd.x, cmd.z, cmd.resourceType, cmd.resourceTemplate, cmd.queued);
});
break;
case "returnresource":
// Check dropsite is owned by player
if (g_DebugCommands && !IsOwnedByPlayer(player, cmd.target))
{
// This check is for debugging only!
warn("Invalid command: dropsite is not owned by player "+player+": "+uneval(cmd));
}
// See UnitAI.CanReturnResource for target checks
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.ReturnResource(cmd.target, cmd.queued);
});
break;
case "train":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
// Check entity limits
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTempMan.GetTemplate(cmd.template);
var unitCategory = null;
if (template.TrainingRestrictions)
unitCategory = template.TrainingRestrictions.Category;
// Verify that the building(s) can be controlled by the player
if (entities.length > 0)
{
for each (var ent in entities)
{
if (unitCategory)
{
var cmpPlayerEntityLimits = QueryOwnerInterface(ent, IID_EntityLimits);
if (!cmpPlayerEntityLimits.AllowedToTrain(unitCategory, cmd.count))
{
if (g_DebugCommands)
warn(unitCategory + " train limit is reached: " + uneval(cmd));
continue;
}
}
var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager);
// TODO: Enable this check once the AI gets technology support
if (cmpTechnologyManager.CanProduce(cmd.template) || true)
{
var queue = Engine.QueryInterface(ent, IID_ProductionQueue);
// Check if the building can train the unit
if (queue && queue.GetEntitiesList().indexOf(cmd.template) != -1)
queue.AddBatch(cmd.template, "unit", +cmd.count, cmd.metadata);
}
else
{
warn("Invalid command: training requires unresearched technology: " + uneval(cmd));
}
}
}
else if (g_DebugCommands)
{
warn("Invalid command: training building(s) cannot be controlled by player "+player+": "+uneval(cmd));
}
break;
case "research":
// Verify that the building can be controlled by the player
if (CanControlUnit(cmd.entity, player, controlAllUnits))
{
var cmpTechnologyManager = QueryOwnerInterface(cmd.entity, IID_TechnologyManager);
// TODO: Enable this check once the AI gets technology support
if (cmpTechnologyManager.CanResearch(cmd.template) || cmpPlayer.IsAI())
{
var queue = Engine.QueryInterface(cmd.entity, IID_ProductionQueue);
if (queue)
queue.AddBatch(cmd.template, "technology");
}
else if (g_DebugCommands)
{
warn("Invalid command: Requirements to research technology are not met: " + uneval(cmd));
}
}
else if (g_DebugCommands)
{
warn("Invalid command: research building cannot be controlled by player "+player+": "+uneval(cmd));
}
break;
case "stop-production":
// Verify that the building can be controlled by the player
if (CanControlUnit(cmd.entity, player, controlAllUnits))
{
var queue = Engine.QueryInterface(cmd.entity, IID_ProductionQueue);
if (queue)
queue.RemoveBatch(cmd.id);
}
else if (g_DebugCommands)
{
warn("Invalid command: production building cannot be controlled by player "+player+": "+uneval(cmd));
}
break;
case "construct":
TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd);
break;
case "construct-wall":
TryConstructWall(player, cmpPlayer, controlAllUnits, cmd);
break;
case "delete-entities":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
for each (var ent in entities)
{
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
if (cmpHealth)
{
var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
if (!cmpResourceSupply || !cmpResourceSupply.GetKillBeforeGather())
cmpHealth.Kill();
}
else
Engine.DestroyEntity(ent);
}
break;
case "set-rallypoint":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
for each (var ent in entities)
{
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
{
if (!cmd.queued)
cmpRallyPoint.Unset();
cmpRallyPoint.AddPosition(cmd.x, cmd.z);
cmpRallyPoint.AddData(cmd.data);
}
}
break;
case "unset-rallypoint":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
for each (var ent in entities)
{
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
cmpRallyPoint.Unset();
}
break;
case "defeat-player":
// Send "OnPlayerDefeated" message to player
Engine.PostMessage(playerEnt, MT_PlayerDefeated, { "playerId": player } );
break;
case "garrison":
// Verify that the building can be controlled by the player
if (CanControlUnit(cmd.target, player, controlAllUnits))
{
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Garrison(cmd.target, cmd.queued);
});
}
else if (g_DebugCommands)
{
warn("Invalid command: garrison target cannot be controlled by player "+player+": "+uneval(cmd));
}
break;
case "stop":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
cmpUnitAI.Stop(cmd.queued);
});
break;
case "unload":
// Verify that the building can be controlled by the player
if (CanControlUnit(cmd.garrisonHolder, player, controlAllUnits))
{
var cmpGarrisonHolder = Engine.QueryInterface(cmd.garrisonHolder, IID_GarrisonHolder);
var notUngarrisoned = 0;
for each (ent in cmd.entities)
if (!cmpGarrisonHolder || !cmpGarrisonHolder.Unload(ent))
notUngarrisoned++;
if (notUngarrisoned != 0)
notifyUnloadFailure(player, cmd.garrisonHolder)
}
else if (g_DebugCommands)
{
warn("Invalid command: unload target cannot be controlled by player "+player+": "+uneval(cmd));
}
break;
case "unload-template":
var selected = FilterEntityList(cmd.garrisonHolders, player, controlAllUnits);
for each (var garrisonHolder in selected)
{
var cmpGarrisonHolder = Engine.QueryInterface(garrisonHolder, IID_GarrisonHolder);
if (!cmpGarrisonHolder || !cmpGarrisonHolder.UnloadTemplate(cmd.template, cmd.all))
notifyUnloadFailure(player, garrisonHolder)
}
break;
case "unload-all":
var selected = FilterEntityList(cmd.garrisonHolders, player, controlAllUnits);
for each (var garrisonHolder in selected)
{
var cmpGarrisonHolder = Engine.QueryInterface(garrisonHolder, IID_GarrisonHolder);
if (!cmpGarrisonHolder || !cmpGarrisonHolder.UnloadAll())
notifyUnloadFailure(player, garrisonHolder)
}
break;
case "formation":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities, player, cmd.name).forEach(function(cmpUnitAI) {
cmpUnitAI.MoveIntoFormation(cmd);
});
break;
case "promote":
// No need to do checks here since this is a cheat anyway
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({"type": "chat", "player": player, "message": "(Cheat - promoted units)"});
for each (var ent in cmd.entities)
{
var cmpPromotion = Engine.QueryInterface(ent, IID_Promotion);
if (cmpPromotion)
cmpPromotion.IncreaseXp(cmpPromotion.GetRequiredXp() - cmpPromotion.GetCurrentXp());
}
break;
case "stance":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
for each (var ent in entities)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.SwitchToStance(cmd.name);
}
break;
case "wall-to-gate":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
for each (var ent in entities)
{
TryTransformWallToGate(ent, cmpPlayer, cmd.template);
}
break;
case "lock-gate":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
for each (var ent in entities)
{
var cmpGate = Engine.QueryInterface(ent, IID_Gate);
if (cmpGate)
{
if (cmd.lock)
cmpGate.LockGate();
else
cmpGate.UnlockGate();
}
}
break;
case "setup-trade-route":
for each (var ent in cmd.entities)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.SetupTradeRoute(cmd.target, cmd.source);
}
break;
case "select-trading-goods":
for each (var ent in cmd.entities)
{
var cmpTrader = Engine.QueryInterface(ent, IID_Trader);
if (cmpTrader)
cmpTrader.SetPreferredGoods(cmd.preferredGoods);
}
break;
case "barter":
var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
cmpBarter.ExchangeResources(playerEnt, cmd.sell, cmd.buy, cmd.amount);
break;
case "set-shading-color":
// Debug command to make an entity brightly colored
for each (var ent in cmd.entities)
{
var cmpVisual = Engine.QueryInterface(ent, IID_Visual)
if (cmpVisual)
cmpVisual.SetShadingColour(cmd.rgb[0], cmd.rgb[1], cmd.rgb[2], 0) // alpha isn't used so just send 0
}
break;
case "pack":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
for each (var ent in entities)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
if (cmd.pack)
cmpUnitAI.Pack(cmd.queued);
else
cmpUnitAI.Unpack(cmd.queued);
}
}
break;
case "cancel-pack":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
for each (var ent in entities)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
if (cmd.pack)
cmpUnitAI.CancelPack(cmd.queued);
else
cmpUnitAI.CancelUnpack(cmd.queued);
}
}
break;
default:
error("Invalid command: unknown command type: "+uneval(cmd));
}
}
/**
* Sends a GUI notification about unit(s) that failed to ungarrison.
*/
function notifyUnloadFailure(player, garrisonHolder)
{
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
var notification = {"player": cmpPlayer.GetPlayerID(), "message": "Unable to ungarrison unit(s)" };
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
}
/**
* Get some information about the formations used by entities.
* The entities must have a UnitAI component.
*/
function ExtractFormations(ents)
{
var entities = []; // subset of ents that have UnitAI
var members = {}; // { formationentity: [ent, ent, ...], ... }
for each (var ent in ents)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
var fid = cmpUnitAI.GetFormationController();
if (fid != INVALID_ENTITY)
{
if (!members[fid])
members[fid] = [];
members[fid].push(ent);
}
entities.push(ent);
}
var ids = [ id for (id in members) ];
return { "entities": entities, "members": members, "ids": ids };
}
/**
+ * Tries to find the best angle to put a dock at a given position
+ * Taken from GuiInterface.js
+ */
+function GetDockAngle(templateName,x,y)
+{
+ var cmpTemplateMgr = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+ var template = cmpTemplateMgr.GetTemplate(templateName);
+
+ if (template.BuildRestrictions.Category !== "Dock")
+ return undefined;
+
+ var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
+ var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
+ if (!cmpTerrain || !cmpWaterManager)
+ {
+ return undefined;
+ }
+
+ // Get footprint size
+ var halfSize = 0;
+ if (template.Footprint.Square)
+ {
+ halfSize = Math.max(template.Footprint.Square["@depth"], template.Footprint.Square["@width"])/2;
+ }
+ else if (template.Footprint.Circle)
+ {
+ halfSize = template.Footprint.Circle["@radius"];
+ }
+
+ /* Find direction of most open water, algorithm:
+ * 1. Pick points in a circle around dock
+ * 2. If point is in water, add to array
+ * 3. Scan array looking for consecutive points
+ * 4. Find longest sequence of consecutive points
+ * 5. If sequence equals all points, no direction can be determined,
+ * expand search outward and try (1) again
+ * 6. Calculate angle using average of sequence
+ */
+ const numPoints = 16;
+ for (var dist = 0; dist < 4; ++dist)
+ {
+ var waterPoints = [];
+ for (var i = 0; i < numPoints; ++i)
+ {
+ var angle = (i/numPoints)*2*Math.PI;
+ var d = halfSize*(dist+1);
+ var nx = x - d*Math.sin(angle);
+ var nz = y + d*Math.cos(angle);
+
+ if (cmpTerrain.GetGroundLevel(nx, nz) < cmpWaterManager.GetWaterLevel(nx, nz))
+ {
+ waterPoints.push(i);
+ }
+ }
+ var consec = [];
+ var length = waterPoints.length;
+ for (var i = 0; i < length; ++i)
+ {
+ var count = 0;
+ for (var j = 0; j < (length-1); ++j)
+ {
+ if (((waterPoints[(i + j) % length]+1) % numPoints) == waterPoints[(i + j + 1) % length])
+ {
+ ++count;
+ }
+ else
+ {
+ break;
+ }
+ }
+ consec[i] = count;
+ }
+ var start = 0;
+ var count = 0;
+ for (var c in consec)
+ {
+ if (consec[c] > count)
+ {
+ start = c;
+ count = consec[c];
+ }
+ }
+
+ // If we've found a shoreline, stop searching
+ if (count != numPoints-1)
+ {
+ return -(((waterPoints[start] + consec[start]/2) % numPoints)/numPoints*2*Math.PI);
+ }
+ }
+ return undefined;
+}
+
+/**
* Attempts to construct a building using the specified parameters.
* Returns true on success, false on failure.
*/
function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd)
{
// Message structure:
// {
// "type": "construct",
// "entities": [...], // entities that will be ordered to construct the building (if applicable)
// "template": "...", // template name of the entity being constructed
// "x": ...,
// "z": ...,
// "angle": ...,
// "actorSeed": ...,
// "autorepair": true, // whether to automatically start constructing/repairing the new foundation
// "autocontinue": true, // whether to automatically gather/build/etc after finishing this
// "queued": true, // whether to add the construction/repairing of this foundation to entities' queue (if applicable)
// "obstructionControlGroup": ..., // Optional; the obstruction control group ID that should be set for this building prior to obstruction
// // testing to determine placement validity. If specified, must be a valid control group ID (> 0).
// "obstructionControlGroup2": ..., // Optional; secondary obstruction control group ID that should be set for this building prior to obstruction
// // testing to determine placement validity. May be INVALID_ENTITY.
// }
/*
* Construction process:
* . Take resources away immediately.
* . Create a foundation entity with 1hp, 0% build progress.
* . Increase hp and build progress up to 100% when people work on it.
* . If it's destroyed, an appropriate fraction of the resource cost is refunded.
* . If it's completed, it gets replaced with the real building.
*/
// Check whether we can control these units
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
if (!entities.length)
return false;
var foundationTemplate = "foundation|" + cmd.template;
// Tentatively create the foundation (we might find later that it's a invalid build command)
var ent = Engine.AddEntity(foundationTemplate);
if (ent == INVALID_ENTITY)
{
// Error (e.g. invalid template names)
error("Error creating foundation entity for '" + cmd.template + "'");
return false;
}
+ // If it's a dock, get the right angle.
+ var angle = GetDockAngle(cmd.template,cmd.x,cmd.z);
+ if (angle !== undefined)
+ cmd.angle = angle;
+
// Move the foundation to the right place
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
cmpPosition.JumpTo(cmd.x, cmd.z);
cmpPosition.SetYRotation(cmd.angle);
// Set the obstruction control group if needed
if (cmd.obstructionControlGroup || cmd.obstructionControlGroup2)
{
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
// primary control group must always be valid
if (cmd.obstructionControlGroup)
{
if (cmd.obstructionControlGroup <= 0)
warn("[TryConstructBuilding] Invalid primary obstruction control group " + cmd.obstructionControlGroup + " received; must be > 0");
cmpObstruction.SetControlGroup(cmd.obstructionControlGroup);
}
if (cmd.obstructionControlGroup2)
cmpObstruction.SetControlGroup2(cmd.obstructionControlGroup2);
}
// Make it owned by the current player
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpOwnership.SetOwner(player);
// Check whether building placement is valid
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
if (cmpBuildRestrictions)
{
var ret = cmpBuildRestrictions.CheckPlacement();
if (!ret.success)
{
if (g_DebugCommands)
{
warn("Invalid command: build restrictions check failed for player "+player+": "+uneval(cmd));
}
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({ "player": player, "message": ret.message });
// Remove the foundation because the construction was aborted
Engine.DestroyEntity(ent);
return false;
}
}
else
error("cmpBuildRestrictions not defined");
// Check entity limits
var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits);
if (!cmpEntityLimits || !cmpEntityLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory()))
{
if (g_DebugCommands)
{
warn("Invalid command: build limits check failed for player "+player+": "+uneval(cmd));
}
// Remove the foundation because the construction was aborted
Engine.DestroyEntity(ent);
return false;
}
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
// TODO: Enable this check once the AI gets technology support
if (!cmpTechnologyManager.CanProduce(cmd.template) && !cmpPlayer.IsAI())
{
if (g_DebugCommands)
{
warn("Invalid command: required technology check failed for player "+player+": "+uneval(cmd));
}
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({ "player": player, "message": "Building's technology requirements are not met." });
// Remove the foundation because the construction was aborted
Engine.DestroyEntity(ent);
}
// We need the cost after tech modifications
// To calculate this with an entity requires ownership, so use the template instead
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateManager.GetTemplate(foundationTemplate);
var costs = {};
for (var r in template.Cost.Resources)
{
costs[r] = +template.Cost.Resources[r];
if (cmpTechnologyManager)
costs[r] = cmpTechnologyManager.ApplyModificationsTemplate("Cost/Resources/"+r, costs[r], template);
}
if (!cmpPlayer.TrySubtractResources(costs))
{
if (g_DebugCommands)
warn("Invalid command: building cost check failed for player "+player+": "+uneval(cmd));
Engine.DestroyEntity(ent);
return false;
}
var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
if (cmpVisual && cmd.actorSeed !== undefined)
cmpVisual.SetActorSeed(cmd.actorSeed);
// Initialise the foundation
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
cmpFoundation.InitialiseConstruction(player, cmd.template);
// Tell the units to start building this new entity
if (cmd.autorepair)
{
ProcessCommand(player, {
"type": "repair",
"entities": entities,
"target": ent,
"autocontinue": cmd.autocontinue,
"queued": cmd.queued
});
}
return ent;
}
function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd)
{
// 'cmd' message structure:
// {
// "type": "construct-wall",
// "entities": [...], // entities that will be ordered to construct the wall (if applicable)
// "pieces": [ // ordered list of information about the pieces making up the wall (towers, wall segments, ...)
// {
// "template": "...", // one of the templates from the wallset
// "x": ...,
// "z": ...,
// "angle": ...,
// },
// ...
// ],
// "wallSet": {
// "templates": {
// "tower": // tower template name
// "long": // long wall segment template name
// ... // etc.
// },
// "maxTowerOverlap": ...,
// "minTowerOverlap": ...,
// },
// "startSnappedEntity": // optional; entity ID of tower being snapped to at the starting side of the wall
// "endSnappedEntity": // optional; entity ID of tower being snapped to at the ending side of the wall
// "autorepair": true, // whether to automatically start constructing/repairing the new foundation
// "autocontinue": true, // whether to automatically gather/build/etc after finishing this
// "queued": true, // whether to add the construction/repairing of this wall's pieces to entities' queue (if applicable)
// }
if (cmd.pieces.length <= 0)
return;
if (cmd.startSnappedEntity && cmd.pieces[0].template == cmd.wallSet.templates.tower)
{
error("[TryConstructWall] Starting wall piece cannot be a tower (" + cmd.wallSet.templates.tower + ") when snapping at the starting side");
return;
}
if (cmd.endSnappedEntity && cmd.pieces[cmd.pieces.length - 1].template == cmd.wallSet.templates.tower)
{
error("[TryConstructWall] Ending wall piece cannot be a tower (" + cmd.wallSet.templates.tower + ") when snapping at the ending side");
return;
}
// Assign obstruction control groups to allow the wall pieces to mutually overlap during foundation placement
// and during construction. The scheme here is that whatever wall pieces are inbetween two towers inherit the control
// groups of both of the towers they are connected to (either newly constructed ones as part of the wall, or existing
// towers in the case of snapping). The towers themselves all keep their default unique control groups.
// To support this, every non-tower piece registers the entity ID of the towers (or foundations thereof) that neighbour
// it on either side. Specifically, each non-tower wall piece has its primary control group set equal to that of the
// first tower encountered towards the starting side of the wall, and its secondary control group set equal to that of
// the first tower encountered towards the ending side of the wall (if any).
// We can't build the whole wall at once by linearly stepping through the wall pieces and build them, because the
// wall segments may/will need the entity IDs of towers that come afterwards. So, build it in two passes:
//
// FIRST PASS:
// - Go from start to end and construct wall piece foundations as far as we can without running into a piece that
// cannot be built (e.g. because it is obstructed). At each non-tower, set the most recently built tower's ID
// as the primary control group, thus allowing it to be built overlapping the previous piece.
// - If we encounter a new tower along the way (which will gain its own control group), do the following:
// o First build it using temporarily the same control group of the previous (non-tower) piece
// o Set the previous piece's secondary control group to the tower's entity ID
// o Restore the primary control group of the constructed tower back its original (unique) value.
// The temporary control group is necessary to allow the newer tower with its unique control group ID to be able
// to be placed while overlapping the previous piece.
//
// SECOND PASS:
// - Go end to start from the last successfully placed wall piece (which might be a tower we backtracked to), this
// time registering the right neighbouring tower in each non-tower piece.
// first pass; L -> R
var lastTowerIndex = -1; // index of the last tower we've encountered in cmd.pieces
var lastTowerControlGroup = null; // control group of the last tower we've encountered, to assign to non-tower pieces
// If we're snapping to an existing entity at the starting end, set lastTowerControlGroup to its control group ID so that
// the first wall piece can be built while overlapping it.
if (cmd.startSnappedEntity)
{
var cmpSnappedStartObstruction = Engine.QueryInterface(cmd.startSnappedEntity, IID_Obstruction);
if (!cmpSnappedStartObstruction)
{
error("[TryConstructWall] Snapped entity on starting side does not have an obstruction component");
return;
}
lastTowerControlGroup = cmpSnappedStartObstruction.GetControlGroup();
//warn("setting lastTowerControlGroup to control group of start snapped entity " + cmd.startSnappedEntity + ": " + lastTowerControlGroup);
}
var i = 0;
for (; i < cmd.pieces.length; ++i)
{
var piece = cmd.pieces[i];
// All wall pieces after the first must be queued.
if (i > 0 && !cmd.queued)
cmd.queued = true;
// 'lastTowerControlGroup' must always be defined and valid here, except if we're at the first piece and we didn't do
// start position snapping (implying that the first entity we build must be a tower)
if (lastTowerControlGroup === null || lastTowerControlGroup == INVALID_ENTITY)
{
if (!(i == 0 && piece.template == cmd.wallSet.templates.tower && !cmd.startSnappedEntity))
{
error("[TryConstructWall] Expected last tower control group to be available, none found (1st pass, iteration " + i + ")");
break;
}
}
var constructPieceCmd = {
"type": "construct",
"entities": cmd.entities,
"template": piece.template,
"x": piece.x,
"z": piece.z,
"angle": piece.angle,
"autorepair": cmd.autorepair,
"autocontinue": cmd.autocontinue,
"queued": cmd.queued,
// Regardless of whether we're building a tower or an intermediate wall piece, it is always (first) constructed
// using the control group of the last tower (see comments above).
"obstructionControlGroup": lastTowerControlGroup,
};
// If we're building the last piece and we're attaching to a snapped entity, we need to add in the snapped entity's
// control group directly at construction time (instead of setting it in the second pass) to allow it to be built
// while overlapping the snapped entity.
if (i == cmd.pieces.length - 1 && cmd.endSnappedEntity)
{
var cmpEndSnappedObstruction = Engine.QueryInterface(cmd.endSnappedEntity, IID_Obstruction);
if (cmpEndSnappedObstruction)
constructPieceCmd.obstructionControlGroup2 = cmpEndSnappedObstruction.GetControlGroup();
}
var pieceEntityId = TryConstructBuilding(player, cmpPlayer, controlAllUnits, constructPieceCmd);
if (pieceEntityId)
{
// wall piece foundation successfully built, save the entity ID in the piece info object so we can reference it later
piece.ent = pieceEntityId;
// if we built a tower, do the control group dance (see outline above) and update lastTowerControlGroup and lastTowerIndex
if (piece.template == cmd.wallSet.templates.tower)
{
var cmpTowerObstruction = Engine.QueryInterface(pieceEntityId, IID_Obstruction);
var newTowerControlGroup = pieceEntityId;
if (i > 0)
{
//warn(" updating previous wall piece's secondary control group to " + newTowerControlGroup);
var cmpPreviousObstruction = Engine.QueryInterface(cmd.pieces[i-1].ent, IID_Obstruction);
// TODO: ensure that cmpPreviousObstruction exists
// TODO: ensure that the previous obstruction does not yet have a secondary control group set
cmpPreviousObstruction.SetControlGroup2(newTowerControlGroup);
}
// TODO: ensure that cmpTowerObstruction exists
cmpTowerObstruction.SetControlGroup(newTowerControlGroup); // give the tower its own unique control group
lastTowerIndex = i;
lastTowerControlGroup = newTowerControlGroup;
}
}
else
{
// failed to build wall piece, abort
i = j + 1; // compensate for the -1 subtracted by lastBuiltPieceIndex below
break;
}
}
var lastBuiltPieceIndex = i - 1;
var wallComplete = (lastBuiltPieceIndex == cmd.pieces.length - 1);
// At this point, 'i' is the index of the last wall piece that was successfully constructed (which may or may not be a tower).
// Now do the second pass going right-to-left, registering the control groups of the towers to the right of each piece (if any)
// as their secondary control groups.
lastTowerControlGroup = null; // control group of the last tower we've encountered, to assign to non-tower pieces
// only start off with the ending side's snapped tower's control group if we were able to build the entire wall
if (cmd.endSnappedEntity && wallComplete)
{
var cmpSnappedEndObstruction = Engine.QueryInterface(cmd.endSnappedEntity, IID_Obstruction);
if (!cmpSnappedEndObstruction)
{
error("[TryConstructWall] Snapped entity on ending side does not have an obstruction component");
return;
}
lastTowerControlGroup = cmpSnappedEndObstruction.GetControlGroup();
}
for (var j = lastBuiltPieceIndex; j >= 0; --j)
{
var piece = cmd.pieces[j];
if (!piece.ent)
{
error("[TryConstructWall] No entity ID set for constructed entity of template '" + piece.template + "'");
continue;
}
var cmpPieceObstruction = Engine.QueryInterface(piece.ent, IID_Obstruction);
if (!cmpPieceObstruction)
{
error("[TryConstructWall] Wall piece of template '" + piece.template + "' has no Obstruction component");
continue;
}
if (piece.template == cmd.wallSet.templates.tower)
{
// encountered a tower entity, update the last tower control group
lastTowerControlGroup = cmpPieceObstruction.GetControlGroup();
}
else
{
// Encountered a non-tower entity, update its secondary control group to 'lastTowerControlGroup'.
// Note that the wall piece may already have its secondary control group set to the tower's entity ID from a control group
// dance during the first pass, in which case we should validate it against 'lastTowerControlGroup'.
var existingSecondaryControlGroup = cmpPieceObstruction.GetControlGroup2();
if (existingSecondaryControlGroup == INVALID_ENTITY)
{
if (lastTowerControlGroup != null && lastTowerControlGroup != INVALID_ENTITY)
{
cmpPieceObstruction.SetControlGroup2(lastTowerControlGroup);
}
}
else if (existingSecondaryControlGroup != lastTowerControlGroup)
{
error("[TryConstructWall] Existing secondary control group of non-tower entity does not match expected value (2nd pass, iteration " + j + ")");
break;
}
}
}
}
/**
* Remove the given list of entities from their current formations.
*/
function RemoveFromFormation(ents)
{
var formation = ExtractFormations(ents);
for (var fid in formation.members)
{
var cmpFormation = Engine.QueryInterface(+fid, IID_Formation);
if (cmpFormation)
cmpFormation.RemoveMembers(formation.members[fid]);
}
}
/**
* Returns a list of UnitAI components, each belonging either to a
* selected unit or to a formation entity for groups of the selected units.
*/
function GetFormationUnitAIs(ents, player, formName)
{
// If an individual was selected, remove it from any formation
// and command it individually
if (ents.length == 1)
{
// Skip unit if it has no UnitAI
var cmpUnitAI = Engine.QueryInterface(ents[0], IID_UnitAI);
if (!cmpUnitAI)
return [];
RemoveFromFormation(ents);
return [ cmpUnitAI ];
}
// Separate out the units that don't support the chosen formation
var formedEnts = [];
var nonformedUnitAIs = [];
for each (var ent in ents)
{
// Skip units with no UnitAI
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (!cmpUnitAI)
continue;
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
// TODO: We only check if the formation is usable by some units
// if we move them to it. We should check if we can use formations
// for the other cases.
// We only use "LineClosed" instead of "Line Closed" to access the templates.
if (cmpIdentity && cmpIdentity.CanUseFormation(formName === undefined ? "LineClosed" : formName.replace(/\s+/,'')))
formedEnts.push(ent);
else
nonformedUnitAIs.push(cmpUnitAI);
}
if (formedEnts.length == 0)
{
// No units support the foundation - return all the others
return nonformedUnitAIs;
}
// Find what formations the formationable selected entities are currently in
var formation = ExtractFormations(formedEnts);
var formationEnt = undefined;
if (formation.ids.length == 1)
{
// Selected units either belong to this formation or have no formation
// Check that all its members are selected
var fid = formation.ids[0];
var cmpFormation = Engine.QueryInterface(+fid, IID_Formation);
if (cmpFormation && cmpFormation.GetMemberCount() == formation.members[fid].length
&& cmpFormation.GetMemberCount() == formation.entities.length)
{
// The whole formation was selected, so reuse its controller for this command
formationEnt = +fid;
}
}
if (!formationEnt)
{
// We need to give the selected units a new formation controller
// Remove selected units from their current formation
for (var fid in formation.members)
{
var cmpFormation = Engine.QueryInterface(+fid, IID_Formation);
if (cmpFormation)
cmpFormation.RemoveMembers(formation.members[fid]);
}
// Create the new controller
formationEnt = Engine.AddEntity("special/formation");
var cmpFormation = Engine.QueryInterface(formationEnt, IID_Formation);
cmpFormation.SetMembers(formation.entities);
var cmpOwnership = Engine.QueryInterface(formationEnt, IID_Ownership);
cmpOwnership.SetOwner(player);
// If all the selected units were previously in formations of the same shape,
// then set this new formation to that shape too; otherwise use the default shape
var lastFormationName = undefined;
for each (var ent in formation.entities)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
var name = cmpUnitAI.GetLastFormationName();
if (lastFormationName === undefined)
{
lastFormationName = name;
}
else if (lastFormationName != name)
{
lastFormationName = undefined;
break;
}
}
}
var formationName;
if (lastFormationName)
formationName = lastFormationName;
else
formationName = "Line Closed";
if (CanMoveEntsIntoFormation(formation.entities, formationName))
{
cmpFormation.LoadFormation(formationName);
}
else
{
cmpFormation.LoadFormation("Scatter");
}
}
return nonformedUnitAIs.concat(Engine.QueryInterface(formationEnt, IID_UnitAI));
}
function GetFormationRequirements(formationName)
{
var countRequired = 1;
var classesRequired;
switch(formationName)
{
case "Scatter":
case "Column Closed":
case "Line Closed":
case "Column Open":
case "Line Open":
case "Battle Line":
break;
case "Box":
countRequired = 4;
break;
case "Flank":
countRequired = 8;
break;
case "Skirmish":
classesRequired = ["Ranged"];
break;
case "Wedge":
countRequired = 3;
classesRequired = ["Cavalry"];
break;
case "Phalanx":
countRequired = 10;
classesRequired = ["Melee", "Infantry"];
break;
case "Syntagma":
countRequired = 9;
classesRequired = ["Melee", "Infantry"]; // TODO: pike only
break;
case "Testudo":
countRequired = 9;
classesRequired = ["Melee", "Infantry"];
break;
default:
// We encountered a unknown formation -> warn the user
warn("Commands.js: GetFormationRequirements: unknown formation: " + formationName);
return false;
}
return { "count": countRequired, "classesRequired": classesRequired };
}
function CanMoveEntsIntoFormation(ents, formationName)
{
var count = ents.length;
// TODO: should check the player's civ is allowed to use this formation
// See simulation/components/Player.js GetFormations() for a list of all allowed formations
var requirements = GetFormationRequirements(formationName);
if (!requirements)
return false;
if (count < requirements.count)
return false;
var scatterOnlyUnits = true;
for each (var ent in ents)
{
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (cmpIdentity)
{
var classes = cmpIdentity.GetClassesList();
if (scatterOnlyUnits && (classes.indexOf("Worker") == -1 || classes.indexOf("Support") == -1))
scatterOnlyUnits = false;
for each (var classRequired in requirements.classesRequired)
{
if (classes.indexOf(classRequired) == -1)
{
return false;
}
}
}
}
if (scatterOnlyUnits)
return false;
return true;
}
/**
* Check if player can control this entity
* returns: true if the entity is valid and owned by the player if
* or control all units is activated for the player, else false
*/
function CanControlUnit(entity, player, controlAll)
{
return (IsOwnedByPlayer(player, entity) || controlAll);
}
/**
* Filter entities which the player can control
*/
function FilterEntityList(entities, player, controlAll)
{
return entities.filter(function(ent) { return CanControlUnit(ent, player, controlAll);} );
}
/**
* Try to transform a wall to a gate
*/
function TryTransformWallToGate(ent, cmpPlayer, template)
{
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (!cmpIdentity)
return;
// Check if this is a valid long wall segment
if (!cmpIdentity.HasClass("LongWall"))
{
if (g_DebugCommands)
warn("Invalid command: invalid wall conversion to gate for player "+player+": "+uneval(cmd));
return;
}
var civ = cmpIdentity.GetCiv();
var gate = Engine.AddEntity(template);
var cmpCost = Engine.QueryInterface(gate, IID_Cost);
if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
{
if (g_DebugCommands)
warn("Invalid command: convert gate cost check failed for player "+player+": "+uneval(cmd));
Engine.DestroyEntity(gate);
return;
}
ReplaceBuildingWith(ent, gate);
}
/**
* Unconditionally replace a building with another one
*/
function ReplaceBuildingWith(ent, building)
{
// Move the building to the right place
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
var pos = cmpPosition.GetPosition2D();
cmpBuildingPosition.JumpTo(pos.x, pos.y);
var rot = cmpPosition.GetRotation();
cmpBuildingPosition.SetYRotation(rot.y);
cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
// Copy ownership
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner());
// Copy control groups
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
// Copy health level from the old entity to the new
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
var healthFraction = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
var buildingHitpoints = Math.round(cmpBuildingHealth.GetMaxHitpoints() * healthFraction);
cmpBuildingHealth.SetHitpoints(buildingHitpoints);
PlaySound("constructed", building);
Engine.PostMessage(ent, MT_ConstructionFinished,
{ "entity": ent, "newentity": building });
Engine.BroadcastMessage(MT_EntityRenamed, { entity: ent, newentity: building });
Engine.DestroyEntity(ent);
}
Engine.RegisterGlobal("GetFormationRequirements", GetFormationRequirements);
Engine.RegisterGlobal("CanMoveEntsIntoFormation", CanMoveEntsIntoFormation);
Engine.RegisterGlobal("ProcessCommand", ProcessCommand);
Index: ps/trunk/source/simulation2/TypeList.h
===================================================================
--- ps/trunk/source/simulation2/TypeList.h (revision 13224)
+++ ps/trunk/source/simulation2/TypeList.h (revision 13225)
@@ -1,166 +1,169 @@
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
// MESSAGE: message types
// INTERFACE: component interface types
// COMPONENT: component types
// Components intended only for use in test cases:
// (The tests rely on the enum IDs, so don't change the order of these)
INTERFACE(Test1)
COMPONENT(Test1A)
COMPONENT(Test1B)
COMPONENT(Test1Scripted)
INTERFACE(Test2)
COMPONENT(Test2A)
COMPONENT(Test2Scripted)
// Message types:
MESSAGE(TurnStart)
MESSAGE(Update)
MESSAGE(Update_MotionFormation)
MESSAGE(Update_MotionUnit)
MESSAGE(Update_Final)
MESSAGE(Interpolate) // non-deterministic (use with caution)
MESSAGE(RenderSubmit) // non-deterministic (use with caution)
MESSAGE(ProgressiveLoad) // non-deterministic (use with caution)
MESSAGE(Create)
MESSAGE(Destroy)
MESSAGE(OwnershipChanged)
MESSAGE(PositionChanged)
MESSAGE(MotionChanged)
MESSAGE(RangeUpdate)
MESSAGE(TerrainChanged)
MESSAGE(TerritoriesChanged)
MESSAGE(PathResult)
MESSAGE(TechnologyModification)
MESSAGE(VisionRangeChanged)
// TemplateManager must come before all other (non-test) components,
// so that it is the first to be (de)serialized
INTERFACE(TemplateManager)
COMPONENT(TemplateManager)
// Special component for script component types with no native interface
INTERFACE(UnknownScript)
COMPONENT(UnknownScript)
// In alphabetical order:
INTERFACE(AIInterface)
COMPONENT(AIInterfaceScripted)
INTERFACE(AIManager)
COMPONENT(AIManager)
INTERFACE(CommandQueue)
COMPONENT(CommandQueue)
INTERFACE(Decay)
COMPONENT(Decay)
// Note: The VisualActor component relies on this component being initialized before itself, in order to support using
// an entity's footprint shape for the selection boxes. This dependency is not strictly necessary, but it does avoid
// some extra plumbing code to set up on-demand initialization. If you find yourself forced to break this dependency,
// see VisualActor's Init method for a description of how you can avoid it.
INTERFACE(Footprint)
COMPONENT(Footprint)
INTERFACE(GuiInterface)
COMPONENT(GuiInterfaceScripted)
INTERFACE(Identity)
COMPONENT(IdentityScripted)
INTERFACE(Minimap)
COMPONENT(Minimap)
INTERFACE(Motion)
COMPONENT(MotionBall)
COMPONENT(MotionScripted)
INTERFACE(Obstruction)
COMPONENT(Obstruction)
INTERFACE(ObstructionManager)
COMPONENT(ObstructionManager)
INTERFACE(OverlayRenderer)
COMPONENT(OverlayRenderer)
INTERFACE(Ownership)
COMPONENT(Ownership)
INTERFACE(ParticleManager)
COMPONENT(ParticleManager)
INTERFACE(Pathfinder)
COMPONENT(Pathfinder)
INTERFACE(Player)
COMPONENT(PlayerScripted)
INTERFACE(PlayerManager)
COMPONENT(PlayerManagerScripted)
INTERFACE(Position)
COMPONENT(Position) // must be before VisualActor
INTERFACE(ProjectileManager)
COMPONENT(ProjectileManager)
INTERFACE(RallyPointRenderer)
COMPONENT(RallyPointRenderer)
INTERFACE(RangeManager)
COMPONENT(RangeManager)
INTERFACE(Selectable)
COMPONENT(Selectable)
INTERFACE(Settlement)
COMPONENT(SettlementScripted)
INTERFACE(SoundManager)
COMPONENT(SoundManager)
INTERFACE(TechnologyManager)
COMPONENT(TechnologyManagerScripted)
+INTERFACE(TechnologyTemplateManager)
+COMPONENT(TechnologyTemplateManagerScripted)
+
INTERFACE(Terrain)
COMPONENT(Terrain)
INTERFACE(TerritoryInfluence)
COMPONENT(TerritoryInfluence)
INTERFACE(TerritoryManager)
COMPONENT(TerritoryManager)
INTERFACE(UnitMotion)
COMPONENT(UnitMotion) // must be after Obstruction
COMPONENT(UnitMotionScripted)
INTERFACE(Vision)
COMPONENT(Vision)
// Note: this component relies on the Footprint component being initialized before itself. See the comments above for
// the Footprint component to find out why.
INTERFACE(Visual)
COMPONENT(VisualActor) // must be after Ownership (dependency in Deserialize) and Vision (dependency in Init)
INTERFACE(WaterManager)
COMPONENT(WaterManager)
Index: ps/trunk/source/simulation2/components/ICmpAIInterface.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpAIInterface.h (revision 13224)
+++ ps/trunk/source/simulation2/components/ICmpAIInterface.h (revision 13225)
@@ -1,35 +1,40 @@
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_ICMPAIINTERFACE
#define INCLUDED_ICMPAIINTERFACE
#include "simulation2/system/Interface.h"
class ICmpAIInterface : public IComponent
{
public:
/**
* Returns a script object that represents the current world state,
* to be passed to AI scripts.
*/
virtual CScriptVal GetRepresentation() = 0;
+ /**
+ * Returns a script object that represents the current world state,
+ * to be passed to AI scripts. No caching for initialization
+ */
+ virtual CScriptVal GetFullRepresentation() = 0;
DECLARE_INTERFACE_TYPE(AIInterface)
};
#endif // INCLUDED_ICMPAIINTERFACE
Index: ps/trunk/source/simulation2/components/ICmpTechnologyTemplateManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpTechnologyTemplateManager.cpp (nonexistent)
+++ ps/trunk/source/simulation2/components/ICmpTechnologyTemplateManager.cpp (revision 13225)
@@ -0,0 +1,39 @@
+/* Copyright (C) 2012 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "ICmpTechnologyTemplateManager.h"
+
+#include "simulation2/system/InterfaceScripted.h"
+#include "simulation2/scripting/ScriptComponent.h"
+
+BEGIN_INTERFACE_WRAPPER(TechnologyTemplateManager)
+END_INTERFACE_WRAPPER(TechnologyTemplateManager)
+
+class CCmpTechnologyTemplateManagerScripted : public ICmpTechnologyTemplateManager
+{
+public:
+ DEFAULT_SCRIPT_WRAPPER(TechnologyTemplateManagerScripted)
+
+ virtual CScriptVal GetAllTechs()
+ {
+ return m_Script.Call("GetAllTechs");
+ }
+};
+
+REGISTER_COMPONENT_SCRIPT_WRAPPER(TechnologyTemplateManagerScripted)
Property changes on: ps/trunk/source/simulation2/components/ICmpTechnologyTemplateManager.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/military.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/military.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/military.js (revision 13225)
@@ -1,641 +1,617 @@
/*
- * Military strategy:
- * * Try training an attack squad of a specified size
- * * When it's the appropriate size, send it to attack the enemy
- * * Repeat forever
- *
+ * Military Manager. NOT cleaned up from qBot, many of the functions here are deprecated for functions in attack_plan.js
*/
var MilitaryAttackManager = function() {
+
+ this.fortressStartTime = Config.Military.fortressStartTime * 1000;
+ this.fortressLapseTime = Config.Military.fortressLapseTime * 1000;
+ this.defenceBuildingTime = Config.Military.defenceBuildingTime * 1000;
+ this.advancedMilitaryStartTime = Config.Military.advancedMilitaryStartTime * 1000;
+ this.attackPlansStartTime = Config.Military.attackPlansStartTime * 1000;
this.defenceManager = new Defence();
+
this.TotalAttackNumber = 0;
this.upcomingAttacks = { "CityAttack" : [] };
this.startedAttacks = { "CityAttack" : [] };
};
MilitaryAttackManager.prototype.init = function(gameState) {
var civ = gameState.playerData.civ;
// load units and buildings from the config files
if (civ in Config.buildings.moderate){
this.bModerate = Config.buildings.moderate[civ];
}else{
this.bModerate = Config.buildings.moderate['default'];
}
if (civ in Config.buildings.advanced){
this.bAdvanced = Config.buildings.advanced[civ];
}else{
this.bAdvanced = Config.buildings.advanced['default'];
}
if (civ in Config.buildings.fort){
this.bFort = Config.buildings.fort[civ];
}else{
this.bFort = Config.buildings.fort['default'];
}
for (var i in this.bAdvanced){
this.bAdvanced[i] = gameState.applyCiv(this.bAdvanced[i]);
}
for (var i in this.bFort){
this.bFort[i] = gameState.applyCiv(this.bFort[i]);
}
this.getEconomicTargets = function(gameState, militaryManager){
return militaryManager.getEnemyBuildings(gameState, "Economic");
};
// TODO: figure out how to make this generic
for (var i in this.attackManagers){
this.availableAttacks[i] = new this.attackManagers[i](gameState, this);
}
var enemies = gameState.getEnemyEntities();
var filter = Filters.byClassesOr(["CitizenSoldier", "Champion", "Hero", "Siege"]);
this.enemySoldiers = enemies.filter(filter); // TODO: cope with diplomacy changes
this.enemySoldiers.registerUpdates();
// each enemy watchers keeps a list of entity collections about the enemy it watches
// It also keeps track of enemy armies, merging/splitting as needed
this.enemyWatchers = {};
this.ennWatcherIndex = [];
for (var i = 1; i <= 8; i++)
- if (gameState.player != i && gameState.isPlayerEnemy(i)) {
+ if (PlayerID != i && gameState.isPlayerEnemy(i)) {
this.enemyWatchers[i] = new enemyWatcher(gameState, i);
this.ennWatcherIndex.push(i);
+ this.defenceManager.enemyArmy[i] = [];
}
};
/**
* @param (GameState) gameState
* @param (string) soldierTypes
* @returns array of soldiers for which training buildings exist
*/
MilitaryAttackManager.prototype.findTrainableUnits = function(gameState, soldierType){
var allTrainable = [];
gameState.getOwnEntities().forEach(function(ent) {
var trainable = ent.trainableEntities();
for (var i in trainable){
if (allTrainable.indexOf(trainable[i]) === -1){
allTrainable.push(trainable[i]);
}
}
});
var ret = [];
for (var i in allTrainable){
var template = gameState.getTemplate(allTrainable[i]);
if (soldierType == this.getSoldierType(template)){
ret.push(allTrainable[i]);
}
}
return ret;
};
// Returns the type of a soldier, either citizenSoldier, advanced or siege
MilitaryAttackManager.prototype.getSoldierType = function(ent){
if (ent.hasClass("Hero")){
return undefined;
}
if (ent.hasClass("CitizenSoldier") && !ent.hasClass("Cavalry")){
return "citizenSoldier";
}else if (ent.hasClass("Champion") || ent.hasClass("CitizenSoldier")){
return "advanced";
}else if (ent.hasClass("Siege")){
return "siege";
}else{
return undefined;
}
};
/**
* Returns the unit type we should begin training. (Currently this is whatever
* we have least of.)
*/
MilitaryAttackManager.prototype.findBestNewUnit = function(gameState, queue, soldierType) {
var units = this.findTrainableUnits(gameState, soldierType);
// Count each type
var types = [];
for ( var tKey in units) {
var t = units[tKey];
types.push([t, gameState.countEntitiesAndQueuedByType(gameState.applyCiv(t))
+ queue.countAllByType(gameState.applyCiv(t)) ]);
}
// Sort by increasing count
types.sort(function(a, b) {
return a[1] - b[1];
});
if (types.length === 0){
return false;
}
return types[0][0];
};
// picks the best template based on parameters and classes
MilitaryAttackManager.prototype.findBestTrainableUnit = function(gameState, classes, parameters) {
var units = gameState.findTrainableUnits(classes);
if (units.length === 0)
return undefined;
units.sort(function(a, b) { //}) {
var aDivParam = 0, bDivParam = 0;
var aTopParam = 0, bTopParam = 0;
for (i in parameters) {
var param = parameters[i];
if (param[0] == "base") {
aTopParam = param[1];
bTopParam = param[1];
}
if (param[0] == "strength") {
- aTopParam += a[1].getMaxStrength() * param[1];
- bTopParam += b[1].getMaxStrength() * param[1];
+ aTopParam += getMaxStrength(a[1]) * param[1];
+ bTopParam += getMaxStrength(b[1]) * param[1];
+ }
+ if (param[0] == "siegeStrength") {
+ aTopParam += getMaxStrength(a[1], "Structure") * param[1];
+ bTopParam += getMaxStrength(b[1], "Structure") * param[1];
}
if (param[0] == "speed") {
aTopParam += a[1].walkSpeed() * param[1];
bTopParam += b[1].walkSpeed() * param[1];
}
if (param[0] == "cost") {
aDivParam += a[1].costSum() * param[1];
bDivParam += b[1].costSum() * param[1];
}
}
return -(aTopParam/(aDivParam+1)) + (bTopParam/(bDivParam+1));
});
return units[0][0];
};
MilitaryAttackManager.prototype.registerSoldiers = function(gameState) {
var soldiers = gameState.getOwnEntitiesByRole("soldier");
var self = this;
soldiers.forEach(function(ent) {
- ent.setMetadata("role", "military");
- ent.setMetadata("military", "unassigned");
+ ent.setMetadata(PlayerID, "role", "military");
+ ent.setMetadata(PlayerID, "military", "unassigned");
});
};
// return count of enemy buildings for a given building class
MilitaryAttackManager.prototype.getEnemyBuildings = function(gameState,cls) {
var targets = gameState.entities.filter(function(ent) {
return (gameState.isEntityEnemy(ent) && ent.hasClass("Structure") && ent.hasClass(cls) && ent.owner() !== 0 && ent.position());
});
return targets;
};
// return n available units and makes these units unavailable
MilitaryAttackManager.prototype.getAvailableUnits = function(n, filter) {
var ret = [];
var count = 0;
var units = undefined;
if (filter){
units = this.getUnassignedUnits().filter(filter);
}else{
units = this.getUnassignedUnits();
}
units.forEach(function(ent){
ret.push(ent.id());
- ent.setMetadata("military", "assigned");
- ent.setMetadata("role", "military");
+ ent.setMetadata(PlayerID, "military", "assigned");
+ ent.setMetadata(PlayerID, "role", "military");
count++;
if (count >= n) {
return;
}
});
return ret;
};
// Takes a single unit id, and marks it unassigned
MilitaryAttackManager.prototype.unassignUnit = function(unit){
- this.entity(unit).setMetadata("military", "unassigned");
+ this.entity(unit).setMetadata(PlayerID, "military", "unassigned");
};
// Takes an array of unit id's and marks all of them unassigned
MilitaryAttackManager.prototype.unassignUnits = function(units){
for (var i in units){
this.unassignUnit(units[i]);
}
};
MilitaryAttackManager.prototype.getUnassignedUnits = function(){
return this.gameState.getOwnEntitiesByMetadata("military", "unassigned");
};
MilitaryAttackManager.prototype.countAvailableUnits = function(filter){
var count = 0;
if (filter){
return this.getUnassignedUnits().filter(filter).length;
}else{
return this.getUnassignedUnits().length;
}
};
// Takes an entity id and returns an entity object or undefined if there is no entity with that id
// Also sends a debug message warning if the id has no entity
MilitaryAttackManager.prototype.entity = function(id) {
return this.gameState.getEntityById(id);
};
// Returns the military strength of unit
MilitaryAttackManager.prototype.getUnitStrength = function(ent){
var strength = 0.0;
var attackTypes = ent.attackTypes();
var armourStrength = ent.armourStrengths();
var hp = 2 * ent.hitpoints() / (160 + 1*ent.maxHitpoints()); //100 = typical number of hitpoints
for (var typeKey in attackTypes) {
var type = attackTypes[typeKey];
var attackStrength = ent.attackStrengths(type);
var attackRange = ent.attackRange(type);
var attackTimes = ent.attackTimes(type);
for (var str in attackStrength) {
var val = parseFloat(attackStrength[str]);
switch (str) {
case "crush":
strength += (val * 0.085) / 3;
break;
case "hack":
strength += (val * 0.075) / 3;
break;
case "pierce":
strength += (val * 0.065) / 3;
break;
}
}
if (attackRange){
strength += (attackRange.max * 0.0125) ;
}
for (var str in attackTimes) {
var val = parseFloat(attackTimes[str]);
switch (str){
case "repeat":
strength += (val / 100000);
break;
case "prepare":
strength -= (val / 100000);
break;
}
}
}
for (var str in armourStrength) {
var val = parseFloat(armourStrength[str]);
switch (str) {
case "crush":
strength += (val * 0.085) / 3;
break;
case "hack":
strength += (val * 0.075) / 3;
break;
case "pierce":
strength += (val * 0.065) / 3;
break;
}
}
return strength * hp;
};
// Returns the strength of the available units of ai army
MilitaryAttackManager.prototype.measureAvailableStrength = function(){
var strength = 0.0;
var self = this;
this.getUnassignedUnits(this.gameState).forEach(function(ent){
strength += self.getUnitStrength(ent);
});
return strength;
};
MilitaryAttackManager.prototype.getEnemySoldiers = function(){
return this.enemySoldiers;
};
// Returns the number of units in the largest enemy army
MilitaryAttackManager.prototype.measureEnemyCount = function(gameState){
// Measure enemy units
var isEnemy = gameState.playerData.isEnemy;
var enemyCount = [];
var maxCount = 0;
for ( var i = 1; i < isEnemy.length; i++) {
enemyCount[i] = 0;
}
// Loop through the enemy soldiers and add one to the count for that soldiers player's count
this.enemySoldiers.forEach(function(ent) {
enemyCount[ent.owner()]++;
if (enemyCount[ent.owner()] > maxCount) {
maxCount = enemyCount[ent.owner()];
}
});
return maxCount;
};
// Returns the strength of the largest enemy army
MilitaryAttackManager.prototype.measureEnemyStrength = function(gameState){
// Measure enemy strength
var isEnemy = gameState.playerData.isEnemy;
var enemyStrength = [];
var maxStrength = 0;
var self = this;
for ( var i = 1; i < isEnemy.length; i++) {
enemyStrength[i] = 0;
}
// Loop through the enemy soldiers and add the strength to that soldiers player's total strength
this.enemySoldiers.forEach(function(ent) {
enemyStrength[ent.owner()] += self.getUnitStrength(ent);
if (enemyStrength[ent.owner()] > maxStrength) {
maxStrength = enemyStrength[ent.owner()];
}
});
return maxStrength;
};
// Adds towers to the defenceBuilding queue
MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv('structures/{civ}_defense_tower'))
- + queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["DefenseTower"]) {
-
+ + queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["DefenseTower"]
+ && gameState.currentPhase() > 1) {
gameState.getOwnEntities().forEach(function(dropsiteEnt) {
- if (dropsiteEnt.resourceDropsiteTypes() && dropsiteEnt.getMetadata("defenseTower") !== true){
+ if (dropsiteEnt.resourceDropsiteTypes() && dropsiteEnt.getMetadata(PlayerID, "defenseTower") !== true){
var position = dropsiteEnt.position();
if (position){
queues.defenceBuilding.addItem(new BuildingConstructionPlan(gameState, 'structures/{civ}_defense_tower', position));
}
- dropsiteEnt.setMetadata("defenseTower", true);
+ dropsiteEnt.setMetadata(PlayerID, "defenseTower", true);
}
});
}
var numFortresses = 0;
for (var i in this.bFort){
numFortresses += gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bFort[i]));
}
- if (numFortresses + queues.defenceBuilding.totalLength() < 1){ //gameState.getEntityLimits()["Fortress"]) {
- if (gameState.getTimeElapsed() > 840 * 1000 + numFortresses * 300 * 1000){
+ if (numFortresses + queues.defenceBuilding.totalLength() < 1 && gameState.currentPhase() > 2)
+ {
+ if (gameState.getTimeElapsed() > this.fortressStartTime + numFortresses * this.fortressLapseTime){
if (gameState.ai.pathsToMe && gameState.ai.pathsToMe.length > 0){
var position = gameState.ai.pathsToMe.shift();
// TODO: pick a fort randomly from the list.
queues.defenceBuilding.addItem(new BuildingConstructionPlan(gameState, this.bFort[0], position));
}else{
queues.defenceBuilding.addItem(new BuildingConstructionPlan(gameState, this.bFort[0]));
}
}
}
};
MilitaryAttackManager.prototype.constructTrainingBuildings = function(gameState, queues) {
// Build more military buildings
// TODO: make military building better
Engine.ProfileStart("Build buildings");
- if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > 25) {
+ if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > 25 && gameState.currentPhase() > 1) {
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0]))
+ queues.militaryBuilding.totalLength() < 1) {
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0]));
}
}
//build advanced military buildings
- if (gameState.getTimeElapsed() > 720*1000){
+ if (gameState.getTimeElapsed() > this.advancedMilitaryStartTime && gameState.currentPhase() > 2){
if (queues.militaryBuilding.totalLength() === 0){
var inConst = 0;
for (var i in this.bAdvanced)
inConst += gameState.countFoundationsWithType(gameState.applyCiv(this.bAdvanced[i]));
if (inConst == 0 && this.bAdvanced !== undefined) {
var i = Math.floor(Math.random() * this.bAdvanced.length);
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bAdvanced[i])) < 1){
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bAdvanced[i]));
}
}
}
}
Engine.ProfileStop();
};
-
-MilitaryAttackManager.prototype.trainMilitaryUnits = function(gameState, queues){
- Engine.ProfileStart("Train Units");
- // Continually try training new units, in batches of 5
- if (queues.citizenSoldier.length() < 6) {
- var newUnit = this.findBestNewUnit(gameState, queues.citizenSoldier, "citizenSoldier");
- if (newUnit){
- queues.citizenSoldier.addItem(new UnitTrainingPlan(gameState, newUnit, {
- "role" : "soldier"
- }, 5));
- }
- }
- if (queues.advancedSoldier.length() < 2) {
- var newUnit = this.findBestNewUnit(gameState, queues.advancedSoldier, "advanced");
- if (newUnit){
- queues.advancedSoldier.addItem(new UnitTrainingPlan(gameState, newUnit, {
- "role" : "soldier"
- }, 5));
- }
- }
- if (queues.siege.length() < 4) {
- var newUnit = this.findBestNewUnit(gameState, queues.siege, "siege");
- if (newUnit){
- queues.siege.addItem(new UnitTrainingPlan(gameState, newUnit, {
- "role" : "soldier"
- }, 2));
- }
- }
- Engine.ProfileStop();
-};
MilitaryAttackManager.prototype.pausePlan = function(gameState, planName) {
for (attackType in this.upcomingAttacks) {
for (i in this.upcomingAttacks[attackType]) {
var attack = this.upcomingAttacks[attackType][i];
if (attack.getName() == planName)
attack.setPaused(gameState, true);
}
}
}
MilitaryAttackManager.prototype.unpausePlan = function(gameState, planName) {
for (attackType in this.upcomingAttacks) {
for (i in this.upcomingAttacks[attackType]) {
var attack = this.upcomingAttacks[attackType][i];
if (attack.getName() == planName)
attack.setPaused(gameState, false);
}
}
}
MilitaryAttackManager.prototype.pauseAllPlans = function(gameState) {
for (attackType in this.upcomingAttacks) {
for (i in this.upcomingAttacks[attackType]) {
var attack = this.upcomingAttacks[attackType][i];
attack.setPaused(gameState, true);
}
}
}
MilitaryAttackManager.prototype.unpauseAllPlans = function(gameState) {
for (attackType in this.upcomingAttacks) {
for (i in this.upcomingAttacks[attackType]) {
var attack = this.upcomingAttacks[attackType][i];
attack.setPaused(gameState, false);
}
}
}
MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
var self = this;
Engine.ProfileStart("military update");
this.gameState = gameState;
- //this.registerSoldiers(gameState);
-
- //this.trainMilitaryUnits(gameState, queues);
-
Engine.ProfileStart("Constructing military buildings and building defences");
this.constructTrainingBuildings(gameState, queues);
- if(gameState.getTimeElapsed() > 300*1000)
+ if(gameState.getTimeElapsed() > this.defenceBuildingTime)
this.buildDefences(gameState, queues);
Engine.ProfileStop();
- Engine.ProfileStart("Updating enemy watchers");
- this.enemyWatchers[ this.ennWatcherIndex[gameState.ai.playedTurn % this.ennWatcherIndex.length] ].detectArmies(gameState,this);
- Engine.ProfileStop();
+ //Engine.ProfileStart("Updating enemy watchers");
+ //this.enemyWatchers[ this.ennWatcherIndex[gameState.ai.playedTurn % this.ennWatcherIndex.length] ].detectArmies(gameState,this);
+ //Engine.ProfileStop();
this.defenceManager.update(gameState, events, this);
/*Engine.ProfileStart("Plan new attacks");
// Look for attack plans which can be executed, only do this once every minute
for (var i = 0; i < this.availableAttacks.length; i++){
if (this.availableAttacks[i].canExecute(gameState, this)){
this.availableAttacks[i].execute(gameState, this);
this.currentAttacks.push(this.availableAttacks[i]);
//debug("Attacking!");
}
this.availableAttacks.splice(i, 1, new this.attackManagers[i](gameState, this));
}
Engine.ProfileStop();
Engine.ProfileStart("Update attacks");
// Keep current attacks updated
for (var i in this.currentAttacks){
this.currentAttacks[i].update(gameState, this, events);
}
Engine.ProfileStop();*/
Engine.ProfileStart("Looping through attack plans");
// create plans if I'm at peace. I'm not starting plans if there is a sizable force in my realm (hence defcon 4+)
//if (gameState.defcon() >= 4 && this.canStartAttacks === true) {
//if ((this.preparingNormal) == 0 && this.BuildingInfoManager.getNumberBuiltByRole("Barracks") > 0) {
// this will updats plans. Plans can be updated up to defcon 2, where they'll be paused (TODO)
//if (0 == 1) // remove to activate attacks
//if (gameState.defcon() >= 3) {
if (1) {
for (attackType in this.upcomingAttacks) {
- for (var i = 0; i < this.upcomingAttacks[attackType].length; ++i) {
+ for (var i = 0;i < this.upcomingAttacks[attackType].length; ++i) {
var attack = this.upcomingAttacks[attackType][i];
// okay so we'll get the support plan
if (!attack.isStarted()) {
var updateStep = attack.updatePreparation(gameState, this,events);
// now we're gonna check if the preparation time is over
if (updateStep === 1 || attack.isPaused() ) {
// just chillin'
} else if (updateStep === 0 || updateStep === 3) {
debug ("Military Manager: " +attack.getType() +" plan " +attack.getName() +" aborted.");
if (updateStep === 3) {
this.attackPlansEncounteredWater = true;
debug("I dare not wet my feet");
}
attack.Abort(gameState, this);
//this.abortedAttacks.push(attack);
this.upcomingAttacks[attackType].splice(i--,1);
} else if (updateStep === 2) {
debug ("Military Manager: Starting " +attack.getType() +" plan " +attack.getName());
attack.StartAttack(gameState,this);
this.startedAttacks[attackType].push(attack);
this.upcomingAttacks[attackType].splice(i--,1);
}
} else {
debug ("Military Manager: Starting " +attack.getType() +" plan " +attack.getName());
this.startedAttacks[attackType].push(attack);
this.upcomingAttacks[attackType].splice(i--,1);
}
}
}
//if (this.abortedAttacks.length !== 0)
// this.abortedAttacks[gameState.ai.mainCounter % this.abortedAttacks.length].releaseAnyUnit(gameState);
}
for (attackType in this.startedAttacks) {
- for (i in this.startedAttacks[attackType]) {
+ for (var i = 0; i < this.startedAttacks[attackType].length; ++i) {
var attack = this.startedAttacks[attackType][i];
// okay so then we'll update the raid.
var remaining = attack.update(gameState,this,events);
if (remaining == 0 || remaining == undefined) {
debug ("Military Manager: " +attack.getType() +" plan " +attack.getName() +" is now finished.");
attack.Abort(gameState);
//this.abortedAttacks.push(attack);
this.startedAttacks[attackType].splice(i--,1);
}
}
}
// creating plans after updating because an aborted plan might be reused in that case.
- if (gameState.countEntitiesByType(gameState.applyCiv(this.bModerate[0])) >= 1 && !this.attackPlansEncounteredWater) {
- if (this.upcomingAttacks["CityAttack"].length == 0 && gameState.getTimeElapsed() < 25*60000) {
+ if (gameState.countEntitiesByType(gameState.applyCiv(this.bModerate[0])) >= 1 && !this.attackPlansEncounteredWater
+ && gameState.getTimeElapsed() > this.attackPlansStartTime) {
+ if (this.upcomingAttacks["CityAttack"].length == 0 && (gameState.getTimeElapsed() < 25*60000 || Config.difficulty < 2)) {
var Lalala = new CityAttack(gameState, this,this.TotalAttackNumber, -1);
debug ("Military Manager: Creating the plan " +this.TotalAttackNumber);
this.TotalAttackNumber++;
this.upcomingAttacks["CityAttack"].push(Lalala);
- } else if (this.upcomingAttacks["CityAttack"].length == 0) {
+ } else if (this.upcomingAttacks["CityAttack"].length == 0 && Config.difficulty !== 0) {
var Lalala = new CityAttack(gameState, this,this.TotalAttackNumber, -1, "superSized");
debug ("Military Manager: Creating the super sized plan " +this.TotalAttackNumber);
this.TotalAttackNumber++;
this.upcomingAttacks["CityAttack"].push(Lalala);
}
}
/*
if (this.HarassRaiding && this.preparingRaidNumber + this.startedRaidNumber < 1 && gameState.getTimeElapsed() < 780000) {
var Lalala = new CityAttack(gameState, this,this.totalStartedAttackNumber, -1, "harass_raid");
- if (!Lalala.createSupportPlans(gameState, this, queues.advancedSoldier)) {
+ if (!Lalala.createSupportPlans(gameState, this, )) {
debug ("Military Manager: harrassing plan not a valid option");
this.HarassRaiding = false;
} else {
debug ("Military Manager: Creating the harass raid plan " +this.totalStartedAttackNumber);
this.totalStartedAttackNumber++;
this.preparingRaidNumber++;
this.currentAttacks.push(Lalala);
}
}
*/
Engine.ProfileStop();
/*Engine.ProfileStart("Use idle military as workers");
// Set unassigned to be workers TODO: fix this so it doesn't scan all units every time
this.getUnassignedUnits(gameState).forEach(function(ent){
if (self.getSoldierType(ent) === "citizenSoldier"){
- ent.setMetadata("role", "worker");
+ ent.setMetadata(PlayerID, "role", "worker");
}
});
Engine.ProfileStop();*/
Engine.ProfileStop();
};
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/plan-research.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/plan-research.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/plan-research.js (revision 13225)
@@ -0,0 +1,47 @@
+var ResearchPlan = function(gameState, type) {
+ this.type = type;
+
+ this.template = gameState.getTemplate(this.type);
+ if (!this.template || this.template.researchTime === undefined) {
+ this.invalidTemplate = true;
+ this.template = undefined;
+ debug ("Invalid research");
+ return false;
+ }
+ this.category = "technology";
+ this.cost = new Resources(this.template.cost(),0);
+ this.number = 1; // Obligatory for compatibility
+ return true;
+};
+
+ResearchPlan.prototype.canExecute = function(gameState) {
+ if (this.invalidTemplate)
+ return false;
+
+ // also checks canResearch
+ return (gameState.findResearchers(this.type).length !== 0);
+};
+
+ResearchPlan.prototype.execute = function(gameState) {
+ var self = this;
+ //debug ("Starting the research plan for " + this.type);
+ var trainers = gameState.findResearchers(this.type).toEntityArray();
+
+ //for (i in trainers)
+ // warn (this.type + " - " +trainers[i].genericName());
+
+ // Prefer training buildings with short queues
+ // (TODO: this should also account for units added to the queue by
+ // plans that have already been executed this turn)
+ if (trainers.length > 0){
+ trainers.sort(function(a, b) {
+ return (a.trainingQueueTime() - b.trainingQueueTime());
+ });
+ trainers[0].research(this.type);
+ }
+};
+
+ResearchPlan.prototype.getCost = function(){
+ return this.cost;
+};
+
Property changes on: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/plan-research.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/qbot.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/qbot.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/qbot.js (revision 13225)
@@ -1,208 +1,272 @@
-
function QBotAI(settings) {
BaseAI.call(this, settings);
this.turn = 0;
this.playedTurn = 0;
this.modules = {
"economy": new EconomyManager(),
- "military": new MilitaryAttackManager(),
- "housing": new HousingManager()
+ "military": new MilitaryAttackManager()
};
// this.queues can only be modified by the queue manager or things will go awry.
this.queues = {
house : new Queue(),
citizenSoldier : new Queue(),
villager : new Queue(),
economicBuilding : new Queue(),
+ dropsites : new Queue(),
field : new Queue(),
- advancedSoldier : new Queue(),
- siege : new Queue(),
militaryBuilding : new Queue(),
defenceBuilding : new Queue(),
- civilCentre: new Queue()
+ civilCentre: new Queue(),
+ majorTech: new Queue(),
+ minorTech: new Queue()
};
-
+
this.productionQueues = [];
this.priorities = Config.priorities;
this.queueManager = new QueueManager(this.queues, this.priorities);
this.firstTime = true;
this.savedEvents = [];
+ this.waterMap = false;
+
this.defcon = 5;
this.defconChangeTime = -10000000;
}
QBotAI.prototype = new BaseAI();
-//Some modules need the gameState to fully initialise
-QBotAI.prototype.runInit = function(gameState){
- if (this.firstTime){
- for (var i in this.modules){
- if (this.modules[i].init){
- this.modules[i].init(gameState);
- }
- }
-
- this.timer = new Timer();
-
- this.firstTime = false;
-
- var myKeyEntities = gameState.getOwnEntities().filter(function(ent) {
+// Bit of a hack: I run the pathfinder early, before the map apears, to avoid a sometimes substantial lag right at the start.
+QBotAI.prototype.InitShared = function(gameState, sharedScript) {
+ var ents = gameState.getEntities().filter(Filters.byOwner(PlayerID));
+ var myKeyEntities = ents.filter(function(ent) {
return ent.hasClass("CivCentre");
- });
-
- if (myKeyEntities.length == 0){
- myKeyEntities = gameState.getOwnEntities();
- }
-
- // disband the walls themselves
- if (gameState.playerData.civ == "iber") {
- gameState.getOwnEntities().filter(function(ent) { //}){
- if (ent.hasClass("StoneWall") && !ent.hasClass("Tower"))
- ent.disband();
- });
- }
-
- var filter = Filters.byClass("CivCentre");
- var enemyKeyEntities = gameState.getEnemyEntities().filter(filter);
-
- if (enemyKeyEntities.length == 0){
- enemyKeyEntities = gameState.getEnemyEntities();
- }
-
- this.accessibility = new Accessibility(gameState, myKeyEntities.toEntityArray()[0].position());
-
- if (enemyKeyEntities.length == 0)
- return;
-
- var pathFinder = new PathFinder(gameState);
- this.pathsToMe = pathFinder.getPaths(enemyKeyEntities.toEntityArray()[0].position(), myKeyEntities.toEntityArray()[0].position(), 'entryPoints');
+ });
- this.templateManager = new TemplateManager(gameState);
-
- this.distanceFromMeMap = new Map(gameState);
- this.distanceFromMeMap.drawDistance(gameState,myKeyEntities.toEntityArray());
- //this.distanceFromMeMap.dumpIm("dumping.png", this.distanceFromMeMap.width*1.5);
+ if (myKeyEntities.length == 0){
+ myKeyEntities = gameState.getEntities().filter(Filters.byOwner(PlayerID));
+ }
+
+ var filter = Filters.byClass("CivCentre");
+ var enemyKeyEntities = gameState.getEntities().filter(Filters.not(Filters.byOwner(PlayerID))).filter(filter);
+
+ if (enemyKeyEntities.length == 0){
+ enemyKeyEntities = gameState.getEntities().filter(Filters.not(Filters.byOwner(PlayerID)));
+ }
+
+ this.pathFinder = new aStarPath(gameState, false, true);
+ this.pathsToMe = [];
+ this.pathInfo = { "angle" : 0, "needboat" : true, "mkeyPos" : myKeyEntities.toEntityArray()[0].position(), "ekeyPos" : enemyKeyEntities.toEntityArray()[0].position() };
+
+ var pos = [this.pathInfo.mkeyPos[0] + 140*Math.cos(this.pathInfo.angle),this.pathInfo.mkeyPos[1] + 140*Math.sin(this.pathInfo.angle)];
+ var path = this.pathFinder.getPath(this.pathInfo.ekeyPos, pos, 3, 3);// uncomment for debug:*/, 300000, gameState);
+
+ if (path !== undefined && path[1] !== undefined && path[1] == false) {
+ // path is viable and doesn't require boating.
+ // blackzone the last two waypoints.
+ this.pathFinder.markImpassableArea(path[0][0][0],path[0][0][1],20);
+ this.pathsToMe.push(path[0][0][0]);
+ this.pathInfo.needboat = false;
+ }
+
+ this.pathInfo.angle += Math.PI/3.0;
+}
+
+//Some modules need the gameState to fully initialise
+QBotAI.prototype.runInit = function(gameState, events){
+ for (var i in this.modules){
+ if (this.modules[i].init){
+ this.modules[i].init(gameState, events);
+ }
+ }
+ debug ("inited");
+ this.timer = new Timer();
+
+
+ var ents = gameState.getOwnEntities();
+ var myKeyEntities = gameState.getOwnEntities().filter(function(ent) {
+ return ent.hasClass("CivCentre");
+ });
+
+ if (myKeyEntities.length == 0){
+ myKeyEntities = gameState.getOwnEntities();
+ }
+
+ // disband the walls themselves
+ if (gameState.playerData.civ == "iber") {
+ gameState.getOwnEntities().filter(function(ent) { //}){
+ if (ent.hasClass("StoneWall") && !ent.hasClass("Tower"))
+ ent.destroy();
+ });
}
+
+ var filter = Filters.byClass("CivCentre");
+ var enemyKeyEntities = gameState.getEnemyEntities().filter(filter);
+
+ if (enemyKeyEntities.length == 0){
+ enemyKeyEntities = gameState.getEnemyEntities();
+ }
+
+ //this.accessibility = new Accessibility(gameState, myKeyEntities.toEntityArray()[0].position());
+
+ this.myIndex = this.accessibility.getAccessValue(myKeyEntities.toEntityArray()[0].position());
+
+ if (enemyKeyEntities.length == 0)
+ return;
+
+ this.templateManager = new TemplateManager(gameState);
+
+ this.distanceFromMeMap = new Map(gameState);
+ this.distanceFromMeMap.drawDistance(gameState,myKeyEntities.toEntityArray());
+
+ //this.distanceFromMeMap.dumpIm("dumping.png", this.distanceFromMeMap.width*1.5);
};
-QBotAI.prototype.OnUpdate = function() {
+QBotAI.prototype.OnUpdate = function(sharedScript) {
if (this.gameFinished){
return;
}
if (this.events.length > 0){
this.savedEvents = this.savedEvents.concat(this.events);
}
- if (this.turn == 0) {
- debug ("Initializing");
- var gameState = new GameState(this);
- this.runInit(gameState);
- }
- // Run the update every n turns, offset depending on player ID to balance
- // the load
- if ((this.turn + this.player) % 10 == 0) {
- Engine.ProfileStart("qBot-xp");
+
+ // Run the update every n turns, offset depending on player ID to balance the load
+ // this also means that init at turn 0 always happen and is never run in parallel to the first played turn so I use an else if.
+ if (this.turn == 0) {
+
+ //Engine.DumpImage("terrain.png", this.accessibility.map, this.accessibility.width,this.accessibility.height,255)
+ //Engine.DumpImage("Access.png", this.accessibility.passMap, this.accessibility.width,this.accessibility.height,this.accessibility.regionID+1)
+
+ var gameState = sharedScript.gameState[PlayerID];
+ gameState.ai = this;
+
+ this.runInit(gameState, this.savedEvents);
+
+ // Delete creation events
+ delete this.savedEvents;
+ this.savedEvents = [];
+ } else if ((this.turn + this.player) % 10 == 0) {
+
+ Engine.ProfileStart("Aegis bot");
this.playedTurn++;
- var gameState = new GameState(this);
+
+ var gameState = sharedScript.gameState[PlayerID];
+ gameState.ai = this;
if (gameState.getOwnEntities().length === 0){
Engine.ProfileStop();
return; // With no entities to control the AI cannot do anything
}
+
+ if (this.pathInfo !== undefined)
+ {
+ var pos = [this.pathInfo.mkeyPos[0] + 140*Math.cos(this.pathInfo.angle),this.pathInfo.mkeyPos[1] + 140*Math.sin(this.pathInfo.angle)];
+ var path = this.pathFinder.getPath(this.pathInfo.ekeyPos, pos, 6, 6);// uncomment for debug:*/, 300000, gameState);
+ if (path !== undefined && path[1] !== undefined && path[1] == false) {
+ // path is viable and doesn't require boating.
+ // blackzone the last two waypoints.
+ this.pathFinder.markImpassableArea(path[0][0][0],path[0][0][1],20);
+ this.pathsToMe.push(path[0][0][0]);
+ this.pathInfo.needboat = false;
+ }
+
+ this.pathInfo.angle += Math.PI/3.0;
+
+ if (this.pathInfo.angle > Math.PI*2.0)
+ {
+ if (this.pathInfo.needboat)
+ {
+ debug ("Assuming this is a water map");
+ this.waterMap = true;
+ }
+ delete this.pathFinder;
+ delete this.pathInfo;
+ }
+ }
+
+ // try going up phases.
+ if (gameState.canResearch("phase_town",true) && gameState.getTimeElapsed() > (Config.Economy.townPhase*1000)
+ && gameState.findResearchers("phase_town").length != 0 && this.queues.majorTech.length() === 0) {
+ this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_town"));
+ debug ("Trying to reach town phase");
+ } else if (gameState.canResearch("phase_city_generic",true) && gameState.getTimeElapsed() > (Config.Economy.cityPhase*1000)
+ && gameState.findResearchers("phase_city_generic").length != 0 && this.queues.majorTech.length() === 0) {
+ debug ("Trying to reach city phase");
+ this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_city_generic"));
+ }
+
// defcon cooldown
if (this.defcon < 5 && gameState.timeSinceDefconChange() > 20000)
+ {
this.defcon++;
-
- this.runInit(gameState);
+ debug ("updefconing to " +this.defcon);
+ }
for (var i in this.modules){
this.modules[i].update(gameState, this.queues, this.savedEvents);
}
- //this.updateDynamicPriorities(gameState, this.queues);
-
this.queueManager.update(gameState);
+ //if (this.playedTurn % 20 === 0)
+ // this.queueManager.printQueues(gameState);
+
// Generate some entropy in the random numbers (against humans) until the engine gets random initialised numbers
// TODO: remove this when the engine gives a random seed
var n = this.savedEvents.length % 29;
for (var i = 0; i < n; i++){
Math.random();
}
delete this.savedEvents;
this.savedEvents = [];
Engine.ProfileStop();
}
this.turn++;
};
-QBotAI.prototype.updateDynamicPriorities = function(gameState, queues){
- // Dynamically change priorities
- Engine.ProfileStart("Change Priorities");
- var females = gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen"));
- var femalesTarget = this.modules["economy"].targetNumWorkers;
- var enemyStrength = this.modules["military"].measureEnemyStrength(gameState);
- var availableStrength = this.modules["military"].measureAvailableStrength();
-
- var additionalPriority = (enemyStrength - availableStrength) * 5;
- additionalPriority = Math.min(Math.max(additionalPriority, -50), 220);
-
- var advancedProportion = (availableStrength / 40) * (females/femalesTarget);
- advancedProportion = Math.min(advancedProportion, 0.7);
-
- this.priorities.advancedSoldier = advancedProportion * (150 + additionalPriority) + 1;
-
- if (females/femalesTarget > 0.7){
- this.priorities.defenceBuilding = 70;
- }
- Engine.ProfileStop();
-};
-
// TODO: Remove override when the whole AI state is serialised
QBotAI.prototype.Deserialize = function(data)
{
BaseAI.prototype.Deserialize.call(this, data);
};
// Override the default serializer
QBotAI.prototype.Serialize = function()
{
- var ret = BaseAI.prototype.Serialize.call(this);
- ret._entityMetadata = {};
- return ret;
+ //var ret = BaseAI.prototype.Serialize.call(this);
+ return {};
};
function debug(output){
if (Config.debug){
if (typeof output === "string"){
warn(output);
}else{
warn(uneval(output));
}
}
}
function copyPrototype(descendant, parent) {
var sConstructor = parent.toString();
var aMatch = sConstructor.match( /\s*function (.*)\(/ );
if ( aMatch != null ) { descendant.prototype[aMatch[1]] = parent; }
for (var m in parent.prototype) {
descendant.prototype[m] = parent.prototype[m];
}
}
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/queue.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/queue.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/queue.js (revision 13225)
@@ -1,138 +1,144 @@
/*
* Holds a list of wanted items to train or construct
*/
var Queue = function() {
this.queue = [];
this.outQueue = [];
};
+Queue.prototype.empty = function() {
+ this.queue = [];
+ this.outQueue = [];
+};
+
+
Queue.prototype.addItem = function(plan) {
+ for (var i in this.queue)
+ {
+ if (plan.category === "unit" && this.queue[i].type == plan.type && this.queue[i].number + plan.number <= this.queue[i].maxMerge)
+ {
+ this.queue[i].addItem(plan.number)
+ return;
+ }
+ }
this.queue.push(plan);
};
Queue.prototype.getNext = function() {
if (this.queue.length > 0) {
return this.queue[0];
} else {
return null;
}
};
Queue.prototype.outQueueNext = function(){
if (this.outQueue.length > 0) {
return this.outQueue[0];
} else {
return null;
}
};
Queue.prototype.outQueueCost = function(){
var cost = new Resources();
for (var key in this.outQueue){
cost.add(this.outQueue[key].getCost());
}
return cost;
};
Queue.prototype.nextToOutQueue = function(){
if (this.queue.length > 0){
- if (this.outQueue.length > 0 &&
- this.getNext().category === "unit" &&
- this.outQueue[this.outQueue.length-1].type === this.getNext().type &&
- this.outQueue[this.outQueue.length-1].number < 5){
- this.queue.shift();
- this.outQueue[this.outQueue.length-1].addItem();
- }else{
- this.outQueue.push(this.queue.shift());
- }
+ this.outQueue.push(this.queue.shift());
}
};
Queue.prototype.executeNext = function(gameState) {
if (this.outQueue.length > 0) {
this.outQueue.shift().execute(gameState);
return true;
} else {
return false;
}
};
Queue.prototype.length = function() {
return this.queue.length;
};
Queue.prototype.countQueuedUnits = function(){
var count = 0;
for (var i in this.queue){
count += this.queue[i].number;
}
return count;
};
Queue.prototype.countOutQueuedUnits = function(){
var count = 0;
for (var i in this.outQueue){
count += this.outQueue[i].number;
}
return count;
};
Queue.prototype.countTotalQueuedUnits = function(){
var count = 0;
for (var i in this.queue){
count += this.queue[i].number;
}
for (var i in this.outQueue){
count += this.outQueue[i].number;
}
return count;
};
Queue.prototype.countTotalQueuedUnitsWithClass = function(classe){
var count = 0;
for (var i in this.queue){
if (this.queue[i].template && this.queue[i].template.hasClass(classe))
count += this.queue[i].number;
}
for (var i in this.outQueue){
if (this.outQueue[i].template && this.outQueue[i].template.hasClass(classe))
count += this.outQueue[i].number;
}
return count;
};
Queue.prototype.countTotalQueuedUnitsWithMetadata = function(data,value){
var count = 0;
for (var i in this.queue){
if (this.queue[i].metadata[data] && this.queue[i].metadata[data] == value)
count += this.queue[i].number;
}
for (var i in this.outQueue){
if (this.outQueue[i].metadata[data] && this.outQueue[i].metadata[data] == value)
count += this.outQueue[i].number;
}
return count;
};
Queue.prototype.totalLength = function(){
return this.queue.length + this.outQueue.length;
};
Queue.prototype.outQueueLength = function(){
return this.outQueue.length;
};
Queue.prototype.countAllByType = function(t){
var count = 0;
for (var i = 0; i < this.queue.length; i++){
if (this.queue[i].type === t){
count += this.queue[i].number;
}
}
for (var i = 0; i < this.outQueue.length; i++){
if (this.outQueue[i].type === t){
count += this.outQueue[i].number;
}
}
return count;
};
\ No newline at end of file
Index: ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/worker.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/worker.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/qbot-wc/worker.js (revision 13225)
@@ -1,352 +1,478 @@
/**
* This class makes a worker do as instructed by the economy manager
*/
var Worker = function(ent) {
this.ent = ent;
- this.approachCount = 0;
+ this.maxApproachTime = 45000;
+ this.unsatisfactoryResource = false; // if true we'll reguarly check if we can't have better now.
};
Worker.prototype.update = function(gameState) {
- var subrole = this.ent.getMetadata("subrole");
+
+
+ var subrole = this.ent.getMetadata(PlayerID, "subrole");
if (!this.ent.position()){
// If the worker has no position then no work can be done
return;
}
if (subrole === "gatherer"){
- if (!(this.ent.unitAIState().split(".")[1] === "GATHER" && this.ent.unitAIOrderData()[0].type
- && this.getResourceType(this.ent.unitAIOrderData()[0].type) === this.ent.getMetadata("gather-type"))
- && !(this.ent.unitAIState().split(".")[1] === "RETURNRESOURCE")){
+ if (this.ent.unitAIState().split(".")[1] !== "GATHER" && this.ent.unitAIState().split(".")[1] !== "COMBAT" && this.ent.unitAIState().split(".")[1] !== "RETURNRESOURCE"){
// TODO: handle combat for hunting animals
if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0 ||
- this.ent.resourceCarrying()[0].type === this.ent.getMetadata("gather-type")){
+ this.ent.resourceCarrying()[0].type === this.ent.getMetadata(PlayerID, "gather-type")){
Engine.ProfileStart("Start Gathering");
this.startGathering(gameState);
Engine.ProfileStop();
} else if (this.ent.unitAIState().split(".")[1] !== "RETURNRESOURCE") {
// Should deposit resources
Engine.ProfileStart("Return Resources");
- this.returnResources(gameState);
+ if (!this.returnResources(gameState))
+ {
+ // no dropsite, abandon cargo.
+
+ // if we have a new order
+ if (this.ent.resourceCarrying()[0].type !== this.ent.getMetadata(PlayerID, "gather-type"))
+ this.startGathering(gameState);
+ else {
+ this.ent.setMetadata(PlayerID, "gather-type",undefined);
+ this.ent.setMetadata(PlayerID, "subrole", "idle");
+ this.ent.stopMoving();
+ }
+ }
Engine.ProfileStop();
}
-
this.startApproachingResourceTime = gameState.getTimeElapsed();
//Engine.PostCommand({"type": "set-shading-color", "entities": [this.ent.id()], "rgb": [10,0,0]});
- }else{
- // If we haven't reached the resource in 1 minutes twice in a row and none of the resource has been
- // gathered then mark it as inaccessible.
- if (gameState.getTimeElapsed() - this.startApproachingResourceTime > 60000){
- if (this.gatheringFrom){
+ } else if (this.ent.unitAIState().split(".")[1] === "GATHER") {
+ if (this.unsatisfactoryResource && (this.ent.id() + gameState.ai.playedTurn) % 20 === 0)
+ {
+ Engine.ProfileStart("Start Gathering");
+ this.startGathering(gameState);
+ Engine.ProfileStop();
+ }
+ /*if (gameState.getTimeElapsed() - this.startApproachingResourceTime > this.maxApproachTime) {
+ if (this.gatheringFrom) {
var ent = gameState.getEntityById(this.gatheringFrom);
- if (ent && ent.resourceSupplyAmount() == ent.resourceSupplyMax()){
+ if ((ent && ent.resourceSupplyAmount() == ent.resourceSupplyMax())) {
// if someone gathers from it, it's only that the pathfinder sucks.
- if (this.approachCount > 0 && ent.getMetadata("gatherer-count") <= 2){
- ent.setMetadata("inaccessible", true);
- this.ent.setMetadata("subrole", "idle");
- this.ent.flee(ent);
- }
- this.approachCount++;
- }else{
- this.approachCount = 0;
+ debug (ent.toString() + " is inaccessible");
+ ent.setMetadata(PlayerID, "inaccessible", true);
+ this.ent.flee(ent);
+ this.ent.setMetadata(PlayerID, "subrole", "idle");
+ this.gatheringFrom = undefined;
}
-
- this.startApproachingResourceTime = gameState.getTimeElapsed();
}
- }
+ }*/
+ } else if (this.ent.unitAIState().split(".")[1] === "COMBAT") {
+ /*if (gameState.getTimeElapsed() - this.startApproachingResourceTime > this.maxApproachTime) {
+ var ent = gameState.getEntityById(this.ent.unitAIOrderData()[0].target);
+ if (ent && !ent.isHurt()) {
+ // if someone gathers from it, it's only that the pathfinder sucks.
+ debug (ent.toString() + " is inaccessible from Combat");
+ ent.setMetadata(PlayerID, "inaccessible", true);
+ this.ent.flee(ent);
+ this.ent.setMetadata(PlayerID, "subrole", "idle");
+ this.gatheringFrom = undefined;
+ }
+ }*/
+ } else {
+ this.startApproachingResourceTime = gameState.getTimeElapsed();
}
} else if(subrole === "builder") {
if (this.ent.unitAIState().split(".")[1] !== "REPAIR"){
- var target = this.ent.getMetadata("target-foundation");
+ var target = this.ent.getMetadata(PlayerID, "target-foundation");
if (target.foundationProgress() === undefined && target.needsRepair() == false)
- this.ent.setMetadata("subrole", "idle");
+ this.ent.setMetadata(PlayerID, "subrole", "idle");
else
this.ent.repair(target);
}
+ this.startApproachingResourceTime = gameState.getTimeElapsed();
//Engine.PostCommand({"type": "set-shading-color", "entities": [this.ent.id()], "rgb": [0,10,0]});
+ } else {
+ this.startApproachingResourceTime = gameState.getTimeElapsed();
}
Engine.ProfileStart("Update Gatherer Counts");
this.updateGathererCounts(gameState);
Engine.ProfileStop();
};
Worker.prototype.updateGathererCounts = function(gameState, dead){
// update gatherer counts for the resources
- if (this.ent.unitAIState().split(".")[2] === "GATHERING" && !dead){
+ if (this.ent.unitAIState().split(".")[1] === "GATHER" && !dead){
if (this.gatheringFrom !== this.ent.unitAIOrderData()[0].target){
if (this.gatheringFrom){
var ent = gameState.getEntityById(this.gatheringFrom);
if (ent && ent.resourceSupplyType()){
- ent.setMetadata("gatherer-count", ent.getMetadata("gatherer-count") - 1);
+ ent.setMetadata(PlayerID, "gatherer-count", ent.getMetadata(PlayerID, "gatherer-count") - 1);
this.markFull(gameState,ent);
}
}
this.gatheringFrom = this.ent.unitAIOrderData()[0].target;
if (this.gatheringFrom){
var ent = gameState.getEntityById(this.gatheringFrom);
if (ent && ent.resourceSupplyType()){
- ent.setMetadata("gatherer-count", (ent.getMetadata("gatherer-count") || 0) + 1);
+ ent.setMetadata(PlayerID, "gatherer-count", (ent.getMetadata(PlayerID, "gatherer-count") || 0) + 1);
this.markFull(gameState,ent);
}
}
- }
- } else if (this.ent.unitAIState().split(".")[2] === "RETURNRESOURCE" && !dead) {
+ this.startApproachingResourceTime = gameState.getTimeElapsed();
+ }
+ } else if (this.ent.unitAIState().split(".")[1] === "RETURNRESOURCE" && !dead) {
// We remove us from the counting is we have no following order or its not "return to collected resource".
if (this.ent.unitAIOrderData().length === 1) {
var ent = gameState.getEntityById(this.gatheringFrom);
if (ent && ent.resourceSupplyType()){
- ent.setMetadata("gatherer-count", ent.getMetadata("gatherer-count") - 1);
+ ent.setMetadata(PlayerID, "gatherer-count", ent.getMetadata(PlayerID, "gatherer-count") - 1);
this.markFull(gameState,ent);
}
this.gatheringFrom = undefined;
- }
- if (!this.ent.unitAIOrderData()[1].target || this.gatheringFrom !== this.ent.unitAIOrderData()[1].target){
+ } else if (!this.ent.unitAIOrderData()[1].target || this.gatheringFrom !== this.ent.unitAIOrderData()[1].target){
if (this.gatheringFrom){
var ent = gameState.getEntityById(this.gatheringFrom);
if (ent && ent.resourceSupplyType()){
- ent.setMetadata("gatherer-count", ent.getMetadata("gatherer-count") - 1);
+ ent.setMetadata(PlayerID, "gatherer-count", ent.getMetadata(PlayerID, "gatherer-count") - 1);
this.markFull(gameState,ent);
}
}
this.gatheringFrom = undefined;
}
} else {
if (this.gatheringFrom){
var ent = gameState.getEntityById(this.gatheringFrom);
if (ent && ent.resourceSupplyType()){
- ent.setMetadata("gatherer-count", ent.getMetadata("gatherer-count") - 1);
+ ent.setMetadata(PlayerID, "gatherer-count", ent.getMetadata(PlayerID, "gatherer-count") - 1);
this.markFull(gameState,ent);
}
this.gatheringFrom = undefined;
}
}
};
Worker.prototype.markFull = function(gameState,ent){
var maxCounts = {"food": 15, "wood": 6, "metal": 15, "stone": 15, "treasure": 1};
var resource = ent.resourceSupplyType().generic;
- if (ent.resourceSupplyType() && ent.getMetadata("gatherer-count") >= maxCounts[resource]){
- if (!ent.getMetadata("full")){
- ent.setMetadata("full", true);
+ if (ent.resourceSupplyType() && ent.getMetadata(PlayerID, "gatherer-count") >= maxCounts[resource]){
+ if (!ent.getMetadata(PlayerID, "full")){
+ ent.setMetadata(PlayerID, "full", true);
// update the dropsite
- var dropsite = gameState.getEntityById(ent.getMetadata("linked-dropsite"));
- if (dropsite == undefined || dropsite.getMetadata("linked-resources-" + resource) === undefined)
+ var dropsite = gameState.getEntityById(ent.getMetadata(PlayerID, "linked-dropsite"));
+ if (dropsite == undefined || dropsite.getMetadata(PlayerID, "linked-resources-" + resource) === undefined)
return;
- if (ent.getMetadata("linked-dropsite-nearby") == true) {
- dropsite.setMetadata("resource-quantity-" + resource, +dropsite.getMetadata("resource-quantity-" + resource) - (+ent.getMetadata("dp-update-value")));
- dropsite.getMetadata("linked-resources-" + resource).updateEnt(ent);
- dropsite.getMetadata("nearby-resources-" + resource).updateEnt(ent);
+ if (ent.getMetadata(PlayerID, "linked-dropsite-nearby") == true) {
+ dropsite.setMetadata(PlayerID, "resource-quantity-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) - (+ent.getMetadata(PlayerID, "dp-update-value")));
+ dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(ent);
+ dropsite.getMetadata(PlayerID, "nearby-resources-" + resource).updateEnt(ent);
} else {
- dropsite.setMetadata("resource-quantity-far-" + resource, +dropsite.getMetadata("resource-quantity-" + resource) - (+ent.getMetadata("dp-update-value")));
- dropsite.getMetadata("linked-resources-" + resource).updateEnt(ent);
+ dropsite.setMetadata(PlayerID, "resource-quantity-far-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) - (+ent.getMetadata(PlayerID, "dp-update-value")));
+ dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(ent);
}
}
}else{
- if (ent.getMetadata("full")){
- ent.setMetadata("full", false);
+ if (ent.getMetadata(PlayerID, "full")){
+ ent.setMetadata(PlayerID, "full", false);
// update the dropsite
- var dropsite = gameState.getEntityById(ent.getMetadata("linked-dropsite"));
- if (dropsite == undefined || dropsite.getMetadata("linked-resources-" + resource) === undefined)
+ var dropsite = gameState.getEntityById(ent.getMetadata(PlayerID, "linked-dropsite"));
+ if (dropsite == undefined || dropsite.getMetadata(PlayerID, "linked-resources-" + resource) === undefined)
return;
- if (ent.getMetadata("linked-dropsite-nearby") == true) {
- dropsite.setMetadata("resource-quantity-" + resource, +dropsite.getMetadata("resource-quantity-" + resource) + ent.resourceSupplyAmount());
- dropsite.getMetadata("linked-resources-" + resource).updateEnt(ent);
- dropsite.getMetadata("nearby-resources-" + resource).updateEnt(ent);
+ if (ent.getMetadata(PlayerID, "linked-dropsite-nearby") == true) {
+ dropsite.setMetadata(PlayerID, "resource-quantity-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) + ent.resourceSupplyAmount());
+ dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(ent);
+ dropsite.getMetadata(PlayerID, "nearby-resources-" + resource).updateEnt(ent);
} else {
- dropsite.setMetadata("resource-quantity-far-" + resource, +dropsite.getMetadata("resource-quantity-" + resource) + ent.resourceSupplyAmount());
- dropsite.getMetadata("linked-resources-" + resource).updateEnt(ent);
+ dropsite.setMetadata(PlayerID, "resource-quantity-far-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) + ent.resourceSupplyAmount());
+ dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(ent);
}
}
}
};
Worker.prototype.startGathering = function(gameState){
- var resource = this.ent.getMetadata("gather-type");
+ var resource = this.ent.getMetadata(PlayerID, "gather-type");
var ent = this.ent;
if (!ent.position()){
// TODO: work out what to do when entity has no position
return;
}
+ this.unsatisfactoryResource = false;
+
// TODO: this is not necessarily optimal.
// find closest dropsite which has nearby resources of the correct type
var minDropsiteDist = Math.min(); // set to infinity initially
var nearestResources = undefined;
var nearestDropsite = undefined;
- // first, look for nearby resources.
+ // first step: count how many dropsites we have that have enough resources "close" to them.
+ // TODO: this is a huge part of multi-base support. Count only those in the same base as the worker.
var number = 0;
- gameState.getOwnDropsites(resource).forEach(function (dropsite){ if (dropsite.getMetadata("linked-resources-" +resource) !== undefined
- && dropsite.getMetadata("linked-resources-" +resource).length > 3) { number++; } });
-
- gameState.getOwnDropsites(resource).forEach(function (dropsite){ //}){
- if (dropsite.getMetadata("resource-quantity-" +resource) == undefined)
- return;
- if (dropsite.position() && (dropsite.getMetadata("resource-quantity-" +resource) > 10 || number <= 1) ) {
- var dist = SquareVectorDistance(ent.position(), dropsite.position());
- if (dist < minDropsiteDist){
- minDropsiteDist = dist;
- nearestResources = dropsite.getMetadata("linked-resources-" + resource);
- nearestDropsite = dropsite;
- }
+ var ourDropsites = gameState.getOwnDropsites(resource);
+
+ if (ourDropsites.length === 0)
+ {
+ debug ("We do not have a dropsite for " + resource + ", aborting");
+ return;
+ }
+
+ ourDropsites.forEach(function (dropsite) {
+ if (dropsite.getMetadata(PlayerID, "linked-resources-" +resource) !== undefined
+ && dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) !== undefined && dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) > 200) {
+ number++;
}
});
- // none, check even low level of resources and far away
- if (!nearestResources || nearestResources.length === 0){
- gameState.getOwnDropsites(resource).forEach(function (dropsite){ //}){
- if (dropsite.position() &&
- (dropsite.getMetadata("resource-quantity-" +resource)+dropsite.getMetadata("resource-quantity-far-" +resource) > 10 || number <= 1)) {
+
+ //debug ("Available " +resource + " dropsites: " +ourDropsites.length);
+
+ // Allright second step, if there are any such dropsites, we pick the closest.
+ // we pick one with a lot of resource, or we pick the only one available (if it's high enough, otherwise we'll see with "far" below).
+ if (number > 0)
+ {
+ ourDropsites.forEach(function (dropsite) { //}){
+ if (dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) == undefined)
+ return;
+ if (dropsite.position() && (dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) > 700 || (number === 1 && dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) > 200) ) ) {
var dist = SquareVectorDistance(ent.position(), dropsite.position());
if (dist < minDropsiteDist){
minDropsiteDist = dist;
- nearestResources = dropsite.getMetadata("linked-resources-" + resource);
+ nearestResources = dropsite.getMetadata(PlayerID, "linked-resources-" + resource);
nearestDropsite = dropsite;
}
}
});
}
- // else, just get the closest to our closest dropsite.
- if (!nearestResources || nearestResources.length === 0){
- nearestResources = gameState.getResourceSupplies(resource);
- gameState.getOwnDropsites(resource).forEach(function (dropsite){
- if (dropsite.position()){
+ //debug ("Nearest dropsite: " +nearestDropsite);
+
+ // Now if we have no dropsites, we repeat the process with resources "far" from dropsites but still linked with them.
+ // I add the "close" value for code sanity.
+ // Again, we choose a dropsite with a lot of resources left, or we pick the only one available (in this case whatever happens).
+ if (!nearestResources || nearestResources.length === 0) {
+ //debug ("here(1)");
+ gameState.getOwnDropsites(resource).forEach(function (dropsite){ //}){
+ var quantity = dropsite.getMetadata(PlayerID, "resource-quantity-" +resource)+dropsite.getMetadata(PlayerID, "resource-quantity-far-" +resource);
+ if (dropsite.position() && (quantity) > 700 || number === 1) {
var dist = SquareVectorDistance(ent.position(), dropsite.position());
if (dist < minDropsiteDist){
minDropsiteDist = dist;
+ nearestResources = dropsite.getMetadata(PlayerID, "linked-resources-" + resource);
nearestDropsite = dropsite;
}
}
});
+ this.unsatisfactoryResource = true;
+ //debug ("Nearest dropsite: " +nearestDropsite);
+ }
+ // If we still haven't found any fitting dropsite...
+ // Then we'll just pick any resource, and we'll check for the closest dropsite to that one
+ if (!nearestResources || nearestResources.length === 0){
+ //debug ("No fitting dropsite for " + resource + " found, iterating the map.");
+ nearestResources = gameState.getResourceSupplies(resource);
+ this.unsatisfactoryResource = true;
}
if (nearestResources.length === 0){
- if (resource === "food" && !this.buildAnyField(gameState)) // try to go build a farm
+ if (resource === "food")
+ {
+ if (this.buildAnyField(gameState))
+ return;
debug("No " + resource + " found! (1)");
+ }
else
debug("No " + resource + " found! (1)");
return;
}
+ //debug("Found " + nearestResources.length + "spots for " + resource);
- if (!nearestDropsite) {
+ /*if (!nearestDropsite) {
debug ("No dropsite for " +resource);
return;
- }
+ }*/
var supplies = [];
var nearestSupplyDist = Math.min();
var nearestSupply = undefined;
+ // filter resources
+ // TODo: add a bonus for resources with a lot of resources left, perhaps, to spread gathering?
nearestResources.forEach(function(supply) { //}){
- // TODO: handle enemy territories
-
- if (!supply.position()){
+
+ // sanity check, perhaps sheep could be garrisoned?
+ if (!supply.position()) {
+ //debug ("noposition");
+ return;
+ }
+
+ if (supply.getMetadata(PlayerID, "inaccessible") === true) {
+ //debug ("inaccessible");
+ return;
+ }
+
+ // too many workers trying to gather from this resource
+ if (supply.getMetadata(PlayerID, "full") === true) {
+ //debug ("full");
return;
}
+
+ // Don't gather enemy farms
+ if (!supply.isOwn(PlayerID) && supply.owner() !== 0) {
+ //debug ("enemy");
+ return;
+ }
+ var territoryOwner = Map.createTerritoryMap(gameState).getOwner(supply.position());
+ if (territoryOwner != PlayerID && territoryOwner != 0) {
+ //debug ("enemy territory");
+ return;
+ }
+
+ // quickscope accessbility check.
+ if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position(), true)) {
+ //debug ("nopath");
+ return;
+ }
+ // some simple check for chickens: if they're in a square that's inaccessible, we won't gather from them.
+ if (supply.footprintRadius() < 1)
+ {
+ var fakeMap = new Map(gameState,gameState.getMap().data);
+ var id = fakeMap.gamePosToMapPos(supply.position())[0] + fakeMap.width*fakeMap.gamePosToMapPos(supply.position())[1];
+ if ( (gameState.sharedScript.passabilityClasses["pathfinderObstruction"] & gameState.getMap().data[id]) )
+ {
+ supply.setMetadata(PlayerID, "inaccessible", true)
+ return;
+ }
+ }
+
// measure the distance to the resource (largely irrelevant)
var dist = SquareVectorDistance(supply.position(), ent.position());
// Add on a factor for the nearest dropsite if one exists
- if (supply.getMetadata("linked-dropsite") !== undefined){
+ if (nearestDropsite !== undefined ){
dist += 4*SquareVectorDistance(supply.position(), nearestDropsite.position());
dist /= 5.0;
}
// Go for treasure as a priority
if (dist < 40000 && supply.resourceSupplyType().generic == "treasure"){
dist /= 1000;
}
-
- if (dist < nearestSupplyDist){
+
+ if (dist < nearestSupplyDist) {
nearestSupplyDist = dist;
nearestSupply = supply;
}
});
if (nearestSupply) {
var pos = nearestSupply.position();
+ // find a fitting dropsites in case we haven't already.
+ if (!nearestDropsite) {
+ ourDropsites.forEach(function (dropsite){ //}){
+ if (dropsite.position()){
+ var dist = SquareVectorDistance(pos, dropsite.position());
+ if (dist < minDropsiteDist){
+ minDropsiteDist = dist;
+ nearestDropsite = dropsite;
+ }
+ }
+ });
+ if (!nearestDropsite)
+ {
+ debug ("No dropsite for " +resource);
+ return;
+ }
+ }
+
// if the resource is far away, try to build a farm instead.
var tried = false;
if (resource === "food" && SquareVectorDistance(pos,this.ent.position()) > 22500)
+ {
tried = this.buildAnyField(gameState);
- if (!tried && SquareVectorDistance(pos,this.ent.position()) > 62500) {
- return; // wait. a farm should appear.
+ if (!tried && SquareVectorDistance(pos,this.ent.position()) > 62500) {
+ return; // wait. a farm should appear.
+ }
}
if (!tried) {
- var territoryOwner = gameState.getTerritoryMap().getOwner(pos);
- if (!gameState.ai.accessibility.isAccessible(pos) ||
- (territoryOwner != gameState.getPlayerID() && territoryOwner != 0)){
- nearestSupply.setMetadata("inaccessible", true);
- }else{
- ent.gather(nearestSupply);
- }
+ this.maxApproachTime = Math.max(25000, VectorDistance(pos,this.ent.position()) * 1000);
+ ent.gather(nearestSupply);
}
- }else{
+ } else {
+ if (resource === "food" && this.buildAnyField(gameState))
+ return;
+
debug("No " + resource + " found! (2)");
+ // If we had a fitting closest dropsite with a lot of resources, mark it as not good. It means it's probably full. Then retry.
+ // it'll be resetted next time it's counted anyway.
+ if (nearestDropsite && nearestDropsite.getMetadata(PlayerID, "resource-quantity-" +resource)+nearestDropsite.getMetadata(PlayerID, "resource-quantity-far-" +resource) > 400)
+ {
+ nearestDropsite.setMetadata(PlayerID, "resource-quantity-" +resource, 0);
+ nearestDropsite.setMetadata(PlayerID, "resource-quantity-far-" +resource, 0);
+ this.startGathering(gameState);
+ }
}
};
// Makes the worker deposit the currently carried resources at the closest dropsite
Worker.prototype.returnResources = function(gameState){
if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0){
- return;
+ return true; // assume we're OK.
}
var resource = this.ent.resourceCarrying()[0].type;
var self = this;
if (!this.ent.position()){
// TODO: work out what to do when entity has no position
- return;
+ return true;
}
var closestDropsite = undefined;
var dist = Math.min();
gameState.getOwnDropsites(resource).forEach(function(dropsite){
if (dropsite.position()){
var d = SquareVectorDistance(self.ent.position(), dropsite.position());
if (d < dist){
dist = d;
closestDropsite = dropsite;
}
}
});
if (!closestDropsite){
- debug("No dropsite found for " + resource);
- return;
+ debug("No dropsite found to deposit " + resource);
+ return false;
}
this.ent.returnResources(closestDropsite);
+ return true;
};
Worker.prototype.getResourceType = function(type){
if (!type || !type.generic){
return undefined;
}
if (type.generic === "treasure"){
return type.specific;
}else{
return type.generic;
}
};
Worker.prototype.buildAnyField = function(gameState){
var self = this;
var okay = false;
var foundations = gameState.getOwnFoundations();
foundations.filterNearest(this.ent.position(), foundations.length);
foundations.forEach(function (found) {
if (found._template.BuildRestrictions.Category === "Field" && !okay) {
self.ent.repair(found);
okay = true;
return;
}
});
return okay;
};
Index: ps/trunk/binaries/data/mods/public/simulation/components/TechnologyManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/TechnologyManager.js (revision 13224)
+++ ps/trunk/binaries/data/mods/public/simulation/components/TechnologyManager.js (revision 13225)
@@ -1,425 +1,447 @@
function TechnologyManager() {}
TechnologyManager.prototype.Schema =
"";
TechnologyManager.prototype.Serialize = function()
{
// The modifications cache will be affected by property reads from the GUI and other places so we shouldn't
// serialize it.
var ret = {};
for (var i in this)
{
if (this.hasOwnProperty(i))
ret[i] = this[i];
}
ret.modificationCache = {};
return ret;
};
TechnologyManager.prototype.Init = function ()
{
var cmpTechTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TechnologyTemplateManager);
this.allTechs = cmpTechTempMan.GetAllTechs();
this.researchedTechs = {}; // technologies which have been researched
this.researchQueued = {}; // technologies which are queued for research
this.researchStarted = {}; // technologies which are being researched currently (non-queued)
// This stores the modifications to unit stats from researched technologies
// Example data: {"ResourceGatherer/Rates/food.grain": [
// {"multiplier": 1.15, "affects": ["Female", "Infantry Swordsman"]},
// {"add": 2}
// ]}
this.modifications = {};
this.modificationCache = {}; // Caches the values after technologies have been applied
// e.g. { "Attack/Melee/Hack" : {5: 10, 7: 12, ...}, ...}
// where 5 and 7 are entity id's
this.typeCounts = {}; // stores the number of entities of each type
this.classCounts = {}; // stores the number of entities of each Class
this.typeCountsByClass = {}; // stores the number of entities of each type for each class i.e.
// {"someClass": {"unit/spearman": 2, "unit/cav": 5} "someOtherClass":...}
// Some technologies are automatically researched when their conditions are met. They have no cost and are
// researched instantly. This allows civ bonuses and more complicated technologies.
this.autoResearchTech = {};
for (var key in this.allTechs)
{
if (this.allTechs[key].autoResearch || this.allTechs[key].top)
this.autoResearchTech[key] = this.allTechs[key];
}
this.UpdateAutoResearch();
};
TechnologyManager.prototype.OnUpdate = function ()
{
this.UpdateAutoResearch();
}
// This function checks if the requirements of any autoresearch techs are met and if they are it researches them
TechnologyManager.prototype.UpdateAutoResearch = function ()
{
for (var key in this.autoResearchTech)
{
if ((this.allTechs[key].autoResearch && this.CanResearch(key))
|| (this.allTechs[key].top && (this.IsTechnologyResearched(this.allTechs[key].top) || this.IsTechnologyResearched(this.allTechs[key].bottom))))
{
delete this.autoResearchTech[key];
this.ResearchTechnology(key);
return; // We will have recursively handled any knock-on effects so can just return
}
}
}
TechnologyManager.prototype.GetTechnologyTemplate = function (tech)
{
return this.allTechs[tech];
};
// Checks an entity template to see if its technology requirements have been met
TechnologyManager.prototype.CanProduce = function (templateName)
{
var cmpTempManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTempManager.GetTemplate(templateName);
if (template.Identity && template.Identity.RequiredTechnology)
return this.IsTechnologyResearched(template.Identity.RequiredTechnology);
else
return true; // If there is no required technology then this entity can be produced
};
TechnologyManager.prototype.IsTechnologyResearched = function (tech)
{
return (this.researchedTechs[tech] !== undefined);
};
// Checks the requirements for a technology to see if it can be researched at the current time
TechnologyManager.prototype.CanResearch = function (tech)
{
var template = this.GetTechnologyTemplate(tech);
if (!template)
{
warn("Technology \"" + tech + "\" does not exist");
return false;
}
// The technology which this technology supersedes is required
if (template.supersedes && !this.IsTechnologyResearched(template.supersedes))
return false;
if (template.pair && !this.CanResearch(template.pair))
return false;
if (this.IsInProgress(tech))
return false;
return this.CheckTechnologyRequirements(template.requirements);
};
// Private function for checking a set of requirements is met
TechnologyManager.prototype.CheckTechnologyRequirements = function (reqs)
{
// If there are no requirements then all requirements are met
if (!reqs)
return true;
if (reqs.tech)
{
return this.IsTechnologyResearched(reqs.tech);
}
else if (reqs.all)
{
for (var i = 0; i < reqs.all.length; i++)
{
if (!this.CheckTechnologyRequirements(reqs.all[i]))
return false;
}
return true;
}
else if (reqs.any)
{
for (var i = 0; i < reqs.any.length; i++)
{
if (this.CheckTechnologyRequirements(reqs.any[i]))
return true;
}
return false;
}
else if (reqs.class)
{
if (reqs.numberOfTypes)
{
if (this.typeCountsByClass[reqs.class])
return (reqs.numberOfTypes <= Object.keys(this.typeCountsByClass[reqs.class]).length);
else
return false;
}
else if (reqs.number)
{
if (this.classCounts[reqs.class])
return (reqs.number <= this.classCounts[reqs.class]);
else
return false;
}
}
else if (reqs.civ)
{
var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
if (cmpPlayer && cmpPlayer.GetCiv() == reqs.civ)
return true;
else
return false;
}
// The technologies requirements are not a recognised format
error("Bad requirements " + uneval(reqs));
return false;
};
TechnologyManager.prototype.OnGlobalOwnershipChanged = function (msg)
{
// This automatically updates typeCounts, classCounts and typeCountsByClass
var playerID = (Engine.QueryInterface(this.entity, IID_Player)).GetPlayerID();
if (msg.to == playerID)
{
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateManager.GetCurrentTemplateName(msg.entity);
this.typeCounts[template] = this.typeCounts[template] || 0;
this.typeCounts[template] += 1;
var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
if (!cmpIdentity)
return;
var classes = cmpIdentity.GetClassesList();
// don't use foundations for the class counts but check if techs apply (e.g. health increase)
if (!Engine.QueryInterface(msg.entity, IID_Foundation))
{
for (var i in classes)
{
this.classCounts[classes[i]] = this.classCounts[classes[i]] || 0;
this.classCounts[classes[i]] += 1;
this.typeCountsByClass[classes[i]] = this.typeCountsByClass[classes[i]] || {};
this.typeCountsByClass[classes[i]][template] = this.typeCountsByClass[classes[i]][template] || 0;
this.typeCountsByClass[classes[i]][template] += 1;
}
}
// Newly created entity, check if any researched techs might apply
// (only do this for new entities because even if an entity is converted or captured,
// we want it to maintain whatever technologies previously applied)
if (msg.from == -1)
{
var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
var playerID = cmpPlayer.GetPlayerID();
var modifiedComponents = {};
for (var name in this.modifications)
{
// We only need to find one one tech per component for a match
var modifications = this.modifications[name];
var component = name.split("/")[0];
for (var i in modifications)
if (!modifiedComponents[component] && DoesModificationApply(modifications[i], classes))
modifiedComponents[component] = true;
}
// Send mesage(s) to the entity so it knows about researched techs
for (var component in modifiedComponents)
Engine.PostMessage(msg.entity, MT_TechnologyModification, { "component": component, "player": playerID });
}
}
if (msg.from == playerID)
{
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateManager.GetCurrentTemplateName(msg.entity);
this.typeCounts[template] -= 1;
if (this.typeCounts[template] <= 0)
delete this.typeCounts[template];
// don't use foundations for the class counts
if (!Engine.QueryInterface(msg.entity, IID_Foundation))
{
var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
if (cmpIdentity)
{
var classes = cmpIdentity.GetClassesList();
for (var i in classes)
{
this.classCounts[classes[i]] -= 1;
if (this.classCounts[classes[i]] <= 0)
delete this.classCounts[classes[i]];
this.typeCountsByClass[classes[i]][template] -= 1;
if (this.typeCountsByClass[classes[i]][template] <= 0)
delete this.typeCountsByClass[classes[i]][template];
}
}
}
this.clearModificationCache(msg.entity);
}
};
// Marks a technology as researched. Note that this does not verify that the requirements are met.
TechnologyManager.prototype.ResearchTechnology = function (tech)
{
this.StoppedResearch(tech); // The tech is no longer being currently researched
var template = this.GetTechnologyTemplate(tech);
if (!template)
{
error("Tried to research invalid techonology: " + uneval(tech));
return;
}
var modifiedComponents = {};
this.researchedTechs[tech] = template;
// store the modifications in an easy to access structure
if (template.modifications)
{
var affects = [];
if (template.affects && template.affects.length > 0)
{
for (var i in template.affects)
{
// Put the list of classes into an array for convenient access
affects.push(template.affects[i].split(/\s+/));
}
}
else
{
affects.push([]);
}
// We add an item to this.modifications for every modification in the template.modifications array
for (var i in template.modifications)
{
var modification = template.modifications[i];
if (!this.modifications[modification.value])
this.modifications[modification.value] = [];
var modAffects = [];
if (modification.affects)
{
for (var j in modification.affects)
modAffects.push(modification.affects[j].split(/\s+/));
}
var mod = {"affects": affects.concat(modAffects)};
// copy the modification data into our new data structure
for (var j in modification)
if (j !== "value" && j !== "affects")
mod[j] = modification[j];
this.modifications[modification.value].push(mod);
modifiedComponents[modification.value.split("/")[0]] = true;
this.modificationCache[modification.value] = {};
}
}
this.UpdateAutoResearch();
var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
var player = cmpPlayer.GetPlayerID();
// TODO: Handle technology broadcasting for autoresearch properly (some components might not be initialized currently)
if (player === undefined)
return;
for (var component in modifiedComponents){
Engine.BroadcastMessage(MT_TechnologyModification, { "component": component, "player": player });
}
};
// Clears the cached data for an entity from the modifications cache
TechnologyManager.prototype.clearModificationCache = function(ent)
{
for (var valueName in this.modificationCache)
delete this.modificationCache[valueName][ent];
};
// Caching layer in front of ApplyModificationsWorker
// Note: be careful with the type of curValue, if it should be a numerical
// value and is derived from template data, you must convert the string
// from the template to a number using the + operator, before calling
// this function!
TechnologyManager.prototype.ApplyModifications = function(valueName, curValue, ent)
{
if (!this.modificationCache[valueName])
this.modificationCache[valueName] = {};
if (!this.modificationCache[valueName][ent])
{
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var templateName = cmpTemplateManager.GetCurrentTemplateName(ent);
// Ensure that preview entites have the same properties as the final building
if (templateName.indexOf("preview|") != -1)
templateName = templateName.slice(8);
this.modificationCache[valueName][ent] = GetTechModifiedProperty(this.modifications, cmpTemplateManager.GetTemplate(templateName), valueName, curValue);
}
return this.modificationCache[valueName][ent];
};
// Alternative version of ApplyModifications, applies to templates instead of entities
TechnologyManager.prototype.ApplyModificationsTemplate = function(valueName, curValue, template)
{
return GetTechModifiedProperty(this.modifications, template, valueName, curValue);
};
// Marks a technology as being queued for research
TechnologyManager.prototype.QueuedResearch = function (tech, researcher)
{
this.researchQueued[tech] = researcher;
};
// Marks a technology as actively being researched
TechnologyManager.prototype.StartedResearch = function (tech)
{
this.researchStarted[tech] = true;
};
// Marks a technology as not being currently researched
TechnologyManager.prototype.StoppedResearch = function (tech)
{
delete this.researchQueued[tech];
delete this.researchStarted[tech];
};
// Checks whether a technology is set to be researched
TechnologyManager.prototype.IsInProgress = function(tech)
{
if (this.researchQueued[tech])
return true;
else
return false;
};
// Get all techs that are currently being researched
TechnologyManager.prototype.GetTechsStarted = function()
{
return this.researchStarted;
};
// Gets the entity currently researching a technology
TechnologyManager.prototype.GetResearcher = function(tech)
{
if (this.researchQueued[tech])
return this.researchQueued[tech];
return undefined;
};
// Get helper data for tech modifications
TechnologyManager.prototype.GetTechModifications = function()
{
return this.modifications;
};
+// called by GUIInterface for PlayerData. AI use.
+TechnologyManager.prototype.GetQueuedResearch = function()
+{
+ return this.researchQueued;
+};
+TechnologyManager.prototype.GetStartedResearch = function()
+{
+ return this.researchStarted;
+};
+TechnologyManager.prototype.GetResearchedTechs = function()
+{
+ return this.researchedTechs;
+};
+TechnologyManager.prototype.GetClassCounts = function()
+{
+ return this.classCounts;
+};
+TechnologyManager.prototype.GetTypeCountsByClass = function()
+{
+ return this.typeCountsByClass;
+};
+
Engine.RegisterComponentType(IID_TechnologyManager, "TechnologyManager", TechnologyManager);
Index: ps/trunk/source/simulation2/Simulation2.cpp
===================================================================
--- ps/trunk/source/simulation2/Simulation2.cpp (revision 13224)
+++ ps/trunk/source/simulation2/Simulation2.cpp (revision 13225)
@@ -1,904 +1,905 @@
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "Simulation2.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/system/ComponentManager.h"
#include "simulation2/system/ParamNode.h"
#include "simulation2/system/SimContext.h"
#include "simulation2/components/ICmpAIManager.h"
#include "simulation2/components/ICmpCommandQueue.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "graphics/MapReader.h"
#include "graphics/Terrain.h"
#include "lib/timer.h"
#include "lib/file/vfs/vfs_util.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/Loader.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "ps/XML/Xeromyces.h"
#include
#if MSC_VERSION
#include
#define getpid _getpid // use the non-deprecated function name
#endif
static std::string Hexify(const std::string& s) // TODO: shouldn't duplicate this function in so many places
{
std::stringstream str;
str << std::hex;
for (size_t i = 0; i < s.size(); ++i)
str << std::setfill('0') << std::setw(2) << (int)(unsigned char)s[i];
return str.str();
}
class CSimulation2Impl
{
public:
CSimulation2Impl(CUnitManager* unitManager, CTerrain* terrain) :
m_SimContext(), m_ComponentManager(m_SimContext),
m_EnableOOSLog(false), m_EnableSerializationTest(false)
{
m_SimContext.m_UnitManager = unitManager;
m_SimContext.m_Terrain = terrain;
m_ComponentManager.LoadComponentTypes();
RegisterFileReloadFunc(ReloadChangedFileCB, this);
// Tests won't have config initialised
if (CConfigDB::IsInitialised())
{
CFG_GET_VAL("ooslog", Bool, m_EnableOOSLog);
CFG_GET_VAL("serializationtest", Bool, m_EnableSerializationTest);
}
}
~CSimulation2Impl()
{
UnregisterFileReloadFunc(ReloadChangedFileCB, this);
}
void ResetState(bool skipScriptedComponents, bool skipAI)
{
m_DeltaTime = 0.0;
m_LastFrameOffset = 0.0f;
m_TurnNumber = 0;
ResetComponentState(m_ComponentManager, skipScriptedComponents, skipAI);
}
static void ResetComponentState(CComponentManager& componentManager, bool skipScriptedComponents, bool skipAI)
{
componentManager.ResetState();
CParamNode noParam;
CComponentManager::ComponentTypeId cid;
// Add native system components:
componentManager.AddComponent(SYSTEM_ENTITY, CID_TemplateManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_CommandQueue, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_ObstructionManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_ParticleManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_Pathfinder, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_ProjectileManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_RangeManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_SoundManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_Terrain, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_TerritoryManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_WaterManager, noParam);
- if (!skipAI)
- {
- componentManager.AddComponent(SYSTEM_ENTITY, CID_AIManager, noParam);
- }
-
// Add scripted system components:
if (!skipScriptedComponents)
{
#define LOAD_SCRIPTED_COMPONENT(name) \
cid = componentManager.LookupCID(name); \
if (cid == CID__Invalid) \
LOGERROR(L"Can't find component type " L##name); \
componentManager.AddComponent(SYSTEM_ENTITY, cid, noParam)
LOAD_SCRIPTED_COMPONENT("AIInterface");
LOAD_SCRIPTED_COMPONENT("Barter");
LOAD_SCRIPTED_COMPONENT("EndGameManager");
LOAD_SCRIPTED_COMPONENT("GuiInterface");
LOAD_SCRIPTED_COMPONENT("PlayerManager");
LOAD_SCRIPTED_COMPONENT("TechnologyTemplateManager");
LOAD_SCRIPTED_COMPONENT("Timer");
#undef LOAD_SCRIPTED_COMPONENT
+
+ if (!skipAI)
+ {
+ componentManager.AddComponent(SYSTEM_ENTITY, CID_AIManager, noParam);
+ }
+
}
}
static bool LoadDefaultScripts(CComponentManager& componentManager, std::set* loadedScripts);
static bool LoadScripts(CComponentManager& componentManager, std::set* loadedScripts, const VfsPath& path);
Status ReloadChangedFile(const VfsPath& path);
static Status ReloadChangedFileCB(void* param, const VfsPath& path)
{
return static_cast(param)->ReloadChangedFile(path);
}
int ProgressiveLoad();
void Update(int turnLength, const std::vector& commands);
static void UpdateComponents(CSimContext& simContext, fixed turnLengthFixed, const std::vector& commands);
void Interpolate(float simFrameLength, float frameOffset, float realFrameLength);
void DumpState();
CSimContext m_SimContext;
CComponentManager m_ComponentManager;
double m_DeltaTime;
float m_LastFrameOffset;
std::string m_StartupScript;
CScriptValRooted m_InitAttributes;
CScriptValRooted m_MapSettings;
std::set m_LoadedScripts;
uint32_t m_TurnNumber;
bool m_EnableOOSLog;
// Functions and data for the serialization test mode: (see Update() for relevant comments)
bool m_EnableSerializationTest;
struct SerializationTestState
{
std::stringstream state;
std::stringstream debug;
std::string hash;
};
void DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix);
void ReportSerializationFailure(
SerializationTestState* primaryStateBefore, SerializationTestState* primaryStateAfter,
SerializationTestState* secondaryStateBefore, SerializationTestState* secondaryStateAfter);
static std::vector CloneCommandsFromOtherContext(ScriptInterface& oldScript, ScriptInterface& newScript,
const std::vector& commands)
{
std::vector newCommands = commands;
for (size_t i = 0; i < newCommands.size(); ++i)
{
newCommands[i].data = CScriptValRooted(newScript.GetContext(),
newScript.CloneValueFromOtherContext(oldScript, newCommands[i].data.get()));
}
return newCommands;
}
};
bool CSimulation2Impl::LoadDefaultScripts(CComponentManager& componentManager, std::set* loadedScripts)
{
return (
LoadScripts(componentManager, loadedScripts, "simulation/components/interfaces/") &&
LoadScripts(componentManager, loadedScripts, L"simulation/helpers/") &&
LoadScripts(componentManager, loadedScripts, L"simulation/components/")
);
}
bool CSimulation2Impl::LoadScripts(CComponentManager& componentManager, std::set* loadedScripts, const VfsPath& path)
{
VfsPaths pathnames;
if (vfs::GetPathnames(g_VFS, path, L"*.js", pathnames) < 0)
return false;
bool ok = true;
for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
{
VfsPath filename = *it;
if (loadedScripts)
loadedScripts->insert(filename);
LOGMESSAGE(L"Loading simulation script '%ls'", filename.string().c_str());
if (! componentManager.LoadScript(filename))
ok = false;
}
return ok;
}
Status CSimulation2Impl::ReloadChangedFile(const VfsPath& path)
{
const VfsPath& filename = path;
// Ignore if this file wasn't loaded as a script
// (TODO: Maybe we ought to load in any new .js files that are created in the right directories)
if (m_LoadedScripts.find(filename) == m_LoadedScripts.end())
return INFO::OK;
// If the file doesn't exist (e.g. it was deleted), don't bother loading it since that'll give an error message.
// (Also don't bother trying to 'unload' it from the component manager, because that's not possible)
if (!VfsFileExists(path))
return INFO::OK;
LOGMESSAGE(L"Reloading simulation script '%ls'", filename.string().c_str());
if (!m_ComponentManager.LoadScript(filename, true))
return ERR::FAIL;
return INFO::OK;
}
int CSimulation2Impl::ProgressiveLoad()
{
// yield after this time is reached. balances increased progress bar
// smoothness vs. slowing down loading.
const double end_time = timer_Time() + 200e-3;
int ret;
do
{
bool progressed = false;
int total = 0;
int progress = 0;
CMessageProgressiveLoad msg(&progressed, &total, &progress);
m_ComponentManager.BroadcastMessage(msg);
if (!progressed || total == 0)
return 0; // we have nothing left to load
ret = Clamp(100*progress / total, 1, 100);
}
while (timer_Time() < end_time);
return ret;
}
void CSimulation2Impl::DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix)
{
if (!state.hash.empty())
{
std::ofstream file (OsString(path / (L"hash." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc);
file << Hexify(state.hash);
}
if (!state.debug.str().empty())
{
std::ofstream file (OsString(path / (L"debug." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc);
file << state.debug.str();
}
if (!state.state.str().empty())
{
std::ofstream file (OsString(path / (L"state." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
file << state.state.str();
}
}
void CSimulation2Impl::ReportSerializationFailure(
SerializationTestState* primaryStateBefore, SerializationTestState* primaryStateAfter,
SerializationTestState* secondaryStateBefore, SerializationTestState* secondaryStateAfter)
{
OsPath path = psLogDir() / "oos_log";
CreateDirectories(path, 0700);
// Clean up obsolete files from previous runs
wunlink(path / "hash.before.a");
wunlink(path / "hash.before.b");
wunlink(path / "debug.before.a");
wunlink(path / "debug.before.b");
wunlink(path / "state.before.a");
wunlink(path / "state.before.b");
wunlink(path / "hash.after.a");
wunlink(path / "hash.after.b");
wunlink(path / "debug.after.a");
wunlink(path / "debug.after.b");
wunlink(path / "state.after.a");
wunlink(path / "state.after.b");
if (primaryStateBefore)
DumpSerializationTestState(*primaryStateBefore, path, L"before.a");
if (primaryStateAfter)
DumpSerializationTestState(*primaryStateAfter, path, L"after.a");
if (secondaryStateBefore)
DumpSerializationTestState(*secondaryStateBefore, path, L"before.b");
if (secondaryStateAfter)
DumpSerializationTestState(*secondaryStateAfter, path, L"after.b");
debug_warn(L"Serialization test failure");
}
void CSimulation2Impl::Update(int turnLength, const std::vector& commands)
{
PROFILE3("sim update");
PROFILE2_ATTR("turn %d", (int)m_TurnNumber);
fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000;
/*
* In serialization test mode, we save the original (primary) simulation state before each turn update.
* We run the update, then load the saved state into a secondary context.
* We serialize that again and compare to the original serialization (to check that
* serialize->deserialize->serialize is equivalent to serialize).
* Then we run the update on the secondary context, and check that its new serialized
* state matches the primary context after the update (to check that the simulation doesn't depend
* on anything that's not serialized).
*/
const bool serializationTestDebugDump = false; // set true to save human-readable state dumps before an error is detected, for debugging (but slow)
const bool serializationTestHash = true; // set true to save and compare hash of state
SerializationTestState primaryStateBefore;
if (m_EnableSerializationTest)
{
ENSURE(m_ComponentManager.SerializeState(primaryStateBefore.state));
if (serializationTestDebugDump)
ENSURE(m_ComponentManager.DumpDebugState(primaryStateBefore.debug, false));
if (serializationTestHash)
ENSURE(m_ComponentManager.ComputeStateHash(primaryStateBefore.hash, false));
}
UpdateComponents(m_SimContext, turnLengthFixed, commands);
if (m_EnableSerializationTest)
{
// Initialise the secondary simulation
CTerrain secondaryTerrain;
CSimContext secondaryContext;
secondaryContext.m_Terrain = &secondaryTerrain;
CComponentManager secondaryComponentManager(secondaryContext);
secondaryComponentManager.LoadComponentTypes();
ENSURE(LoadDefaultScripts(secondaryComponentManager, NULL));
ResetComponentState(secondaryComponentManager, false, false);
// Load the map into the secondary simulation
LDR_BeginRegistering();
CMapReader* mapReader = new CMapReader; // automatically deletes itself
// TODO: this duplicates CWorld::RegisterInit and could probably be cleaned up a bit
std::string mapType;
m_ComponentManager.GetScriptInterface().GetProperty(m_InitAttributes.get(), "mapType", mapType);
if (mapType == "scenario")
{
// Load scenario attributes
std::wstring mapFile;
m_ComponentManager.GetScriptInterface().GetProperty(m_InitAttributes.get(), "map", mapFile);
VfsPath mapfilename(VfsPath("maps/scenarios") / (mapFile + L".pmp"));
mapReader->LoadMap(mapfilename, &secondaryTerrain, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, &secondaryContext, INVALID_PLAYER, true); // throws exception on failure
}
else
{
// TODO: support random map scripts
debug_warn(L"Serialization test mode only supports scenarios");
}
LDR_EndRegistering();
ENSURE(LDR_NonprogressiveLoad() == INFO::OK);
ENSURE(secondaryComponentManager.DeserializeState(primaryStateBefore.state));
SerializationTestState secondaryStateBefore;
ENSURE(secondaryComponentManager.SerializeState(secondaryStateBefore.state));
if (serializationTestDebugDump)
ENSURE(secondaryComponentManager.DumpDebugState(secondaryStateBefore.debug, false));
if (serializationTestHash)
ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateBefore.hash, false));
if (primaryStateBefore.state.str() != secondaryStateBefore.state.str() ||
primaryStateBefore.hash != secondaryStateBefore.hash)
{
ReportSerializationFailure(&primaryStateBefore, NULL, &secondaryStateBefore, NULL);
}
SerializationTestState primaryStateAfter;
ENSURE(m_ComponentManager.SerializeState(primaryStateAfter.state));
if (serializationTestHash)
ENSURE(m_ComponentManager.ComputeStateHash(primaryStateAfter.hash, false));
UpdateComponents(secondaryContext, turnLengthFixed,
CloneCommandsFromOtherContext(m_ComponentManager.GetScriptInterface(), secondaryComponentManager.GetScriptInterface(), commands));
SerializationTestState secondaryStateAfter;
ENSURE(secondaryComponentManager.SerializeState(secondaryStateAfter.state));
if (serializationTestHash)
ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateAfter.hash, false));
if (primaryStateAfter.state.str() != secondaryStateAfter.state.str() ||
primaryStateAfter.hash != secondaryStateAfter.hash)
{
// Only do the (slow) dumping now we know we're going to need to report it
ENSURE(m_ComponentManager.DumpDebugState(primaryStateAfter.debug, false));
ENSURE(secondaryComponentManager.DumpDebugState(secondaryStateAfter.debug, false));
ReportSerializationFailure(&primaryStateBefore, &primaryStateAfter, &secondaryStateBefore, &secondaryStateAfter);
}
}
// if (m_TurnNumber == 0)
// m_ComponentManager.GetScriptInterface().DumpHeap();
// Run the GC occasionally
// (TODO: we ought to schedule this for a frame where we're not
// running the sim update, to spread the load)
if (m_TurnNumber % 10 == 0)
m_ComponentManager.GetScriptInterface().MaybeGC();
if (m_EnableOOSLog)
DumpState();
// Start computing AI for the next turn
CmpPtr cmpAIManager(m_SimContext, SYSTEM_ENTITY);
if (cmpAIManager)
cmpAIManager->StartComputation();
++m_TurnNumber;
}
void CSimulation2Impl::UpdateComponents(CSimContext& simContext, fixed turnLengthFixed, const std::vector& commands)
{
// TODO: the update process is pretty ugly, with lots of messages and dependencies
// between different components. Ought to work out a nicer way to do this.
CComponentManager& componentManager = simContext.GetComponentManager();
CMessageTurnStart msgTurnStart;
componentManager.BroadcastMessage(msgTurnStart);
CmpPtr cmpPathfinder(simContext, SYSTEM_ENTITY);
if (cmpPathfinder)
cmpPathfinder->FinishAsyncRequests();
// Push AI commands onto the queue before we use them
CmpPtr cmpAIManager(simContext, SYSTEM_ENTITY);
if (cmpAIManager)
cmpAIManager->PushCommands();
CmpPtr cmpCommandQueue(simContext, SYSTEM_ENTITY);
if (cmpCommandQueue)
cmpCommandQueue->FlushTurn(commands);
// Process newly generated move commands so the UI feels snappy
if (cmpPathfinder)
cmpPathfinder->ProcessSameTurnMoves();
// Send all the update phases
{
CMessageUpdate msgUpdate(turnLengthFixed);
componentManager.BroadcastMessage(msgUpdate);
}
{
CMessageUpdate_MotionFormation msgUpdate(turnLengthFixed);
componentManager.BroadcastMessage(msgUpdate);
}
// Process move commands for formations (group proxy)
if (cmpPathfinder)
cmpPathfinder->ProcessSameTurnMoves();
{
CMessageUpdate_MotionUnit msgUpdate(turnLengthFixed);
componentManager.BroadcastMessage(msgUpdate);
}
{
CMessageUpdate_Final msgUpdate(turnLengthFixed);
componentManager.BroadcastMessage(msgUpdate);
}
// Process moves resulting from group proxy movement (unit needs to catch up or realign) and any others
if (cmpPathfinder)
cmpPathfinder->ProcessSameTurnMoves();
// Clean up any entities destroyed during the simulation update
componentManager.FlushDestroyedComponents();
}
void CSimulation2Impl::Interpolate(float simFrameLength, float frameOffset, float realFrameLength)
{
PROFILE3("sim interpolate");
m_LastFrameOffset = frameOffset;
CMessageInterpolate msg(simFrameLength, frameOffset, realFrameLength);
m_ComponentManager.BroadcastMessage(msg);
// Clean up any entities destroyed during interpolate (e.g. local corpses)
m_ComponentManager.FlushDestroyedComponents();
}
void CSimulation2Impl::DumpState()
{
PROFILE("DumpState");
std::stringstream pid;
pid << getpid();
std::stringstream name;\
name << std::setw(5) << std::setfill('0') << m_TurnNumber << ".txt";
OsPath path = psLogDir() / "sim_log" / pid.str() / name.str();
CreateDirectories(path.Parent(), 0700);
std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
file << "State hash: " << std::hex;
std::string hashRaw;
m_ComponentManager.ComputeStateHash(hashRaw, false);
for (size_t i = 0; i < hashRaw.size(); ++i)
file << std::setfill('0') << std::setw(2) << (int)(unsigned char)hashRaw[i];
file << std::dec << "\n";
file << "\n";
m_ComponentManager.DumpDebugState(file, true);
std::ofstream binfile (OsString(path.ChangeExtension(L".dat")).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
m_ComponentManager.SerializeState(binfile);
}
////////////////////////////////////////////////////////////////
CSimulation2::CSimulation2(CUnitManager* unitManager, CTerrain* terrain) :
m(new CSimulation2Impl(unitManager, terrain))
{
}
CSimulation2::~CSimulation2()
{
delete m;
}
// Forward all method calls to the appropriate CSimulation2Impl/CComponentManager methods:
void CSimulation2::EnableOOSLog()
{
m->m_EnableOOSLog = true;
}
void CSimulation2::EnableSerializationTest()
{
m->m_EnableSerializationTest = true;
}
entity_id_t CSimulation2::AddEntity(const std::wstring& templateName)
{
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity());
}
entity_id_t CSimulation2::AddEntity(const std::wstring& templateName, entity_id_t preferredId)
{
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity(preferredId));
}
entity_id_t CSimulation2::AddLocalEntity(const std::wstring& templateName)
{
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewLocalEntity());
}
void CSimulation2::DestroyEntity(entity_id_t ent)
{
m->m_ComponentManager.DestroyComponentsSoon(ent);
}
void CSimulation2::FlushDestroyedEntities()
{
m->m_ComponentManager.FlushDestroyedComponents();
}
IComponent* CSimulation2::QueryInterface(entity_id_t ent, int iid) const
{
return m->m_ComponentManager.QueryInterface(ent, iid);
}
void CSimulation2::PostMessage(entity_id_t ent, const CMessage& msg) const
{
m->m_ComponentManager.PostMessage(ent, msg);
}
void CSimulation2::BroadcastMessage(const CMessage& msg) const
{
m->m_ComponentManager.BroadcastMessage(msg);
}
CSimulation2::InterfaceList CSimulation2::GetEntitiesWithInterface(int iid)
{
return m->m_ComponentManager.GetEntitiesWithInterface(iid);
}
const CSimulation2::InterfaceListUnordered& CSimulation2::GetEntitiesWithInterfaceUnordered(int iid)
{
return m->m_ComponentManager.GetEntitiesWithInterfaceUnordered(iid);
}
const CSimContext& CSimulation2::GetSimContext() const
{
return m->m_SimContext;
}
ScriptInterface& CSimulation2::GetScriptInterface() const
{
return m->m_ComponentManager.GetScriptInterface();
}
void CSimulation2::InitGame(const CScriptVal& data)
{
GetScriptInterface().CallFunctionVoid(GetScriptInterface().GetGlobalObject(), "InitGame", data);
}
void CSimulation2::Update(int turnLength)
{
std::vector commands;
m->Update(turnLength, commands);
}
void CSimulation2::Update(int turnLength, const std::vector& commands)
{
m->Update(turnLength, commands);
}
void CSimulation2::Interpolate(float simFrameLength, float frameOffset, float realFrameLength)
{
m->Interpolate(simFrameLength, frameOffset, realFrameLength);
}
void CSimulation2::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
{
PROFILE3("sim submit");
CMessageRenderSubmit msg(collector, frustum, culling);
m->m_ComponentManager.BroadcastMessage(msg);
}
float CSimulation2::GetLastFrameOffset() const
{
return m->m_LastFrameOffset;
}
bool CSimulation2::LoadScripts(const VfsPath& path)
{
return m->LoadScripts(m->m_ComponentManager, &m->m_LoadedScripts, path);
}
bool CSimulation2::LoadDefaultScripts()
{
return m->LoadDefaultScripts(m->m_ComponentManager, &m->m_LoadedScripts);
}
void CSimulation2::SetStartupScript(const std::string& code)
{
m->m_StartupScript = code;
}
const std::string& CSimulation2::GetStartupScript()
{
return m->m_StartupScript;
}
void CSimulation2::SetInitAttributes(const CScriptValRooted& attribs)
{
m->m_InitAttributes = attribs;
}
CScriptValRooted CSimulation2::GetInitAttributes()
{
return m->m_InitAttributes;
}
void CSimulation2::SetMapSettings(const std::string& settings)
{
m->m_MapSettings = m->m_ComponentManager.GetScriptInterface().ParseJSON(settings);
}
void CSimulation2::SetMapSettings(const CScriptValRooted& settings)
{
m->m_MapSettings = settings;
}
std::string CSimulation2::GetMapSettingsString()
{
return m->m_ComponentManager.GetScriptInterface().StringifyJSON(m->m_MapSettings.get());
}
CScriptVal CSimulation2::GetMapSettings()
{
return m->m_MapSettings.get();
}
void CSimulation2::LoadPlayerSettings(bool newPlayers)
{
GetScriptInterface().CallFunctionVoid(GetScriptInterface().GetGlobalObject(), "LoadPlayerSettings", m->m_MapSettings, newPlayers);
}
void CSimulation2::LoadMapSettings()
{
// Initialize here instead of in Update()
GetScriptInterface().CallFunctionVoid(GetScriptInterface().GetGlobalObject(), "LoadMapSettings", m->m_MapSettings);
if (!m->m_StartupScript.empty())
GetScriptInterface().LoadScript(L"map startup script", m->m_StartupScript);
}
int CSimulation2::ProgressiveLoad()
{
return m->ProgressiveLoad();
}
Status CSimulation2::ReloadChangedFile(const VfsPath& path)
{
return m->ReloadChangedFile(path);
}
void CSimulation2::ResetState(bool skipScriptedComponents, bool skipAI)
{
m->ResetState(skipScriptedComponents, skipAI);
}
bool CSimulation2::ComputeStateHash(std::string& outHash, bool quick)
{
return m->m_ComponentManager.ComputeStateHash(outHash, quick);
}
bool CSimulation2::DumpDebugState(std::ostream& stream)
{
return m->m_ComponentManager.DumpDebugState(stream, true);
}
bool CSimulation2::SerializeState(std::ostream& stream)
{
return m->m_ComponentManager.SerializeState(stream);
}
bool CSimulation2::DeserializeState(std::istream& stream)
{
// TODO: need to make sure the required SYSTEM_ENTITY components get constructed
return m->m_ComponentManager.DeserializeState(stream);
}
std::string CSimulation2::GenerateSchema()
{
return m->m_ComponentManager.GenerateSchema();
}
std::vector CSimulation2::GetRMSData()
{
VfsPath path(L"maps/random/");
VfsPaths pathnames;
std::vector data;
// Find all ../maps/random/*.json
Status ret = vfs::GetPathnames(g_VFS, path, L"*.json", pathnames);
if (ret == INFO::OK)
{
for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
{
// Load JSON file
CVFSFile file;
PSRETURN ret = file.Load(g_VFS, *it);
if (ret != PSRETURN_OK)
{
LOGERROR(L"Failed to load file '%ls': %hs", path.string().c_str(), GetErrorString(ret));
}
else
{
data.push_back(file.DecodeUTF8()); // assume it's UTF-8
}
}
}
else
{
// Some error reading directory
wchar_t error[200];
LOGERROR(L"Error reading directory '%ls': %ls", path.string().c_str(), StatusDescription(ret, error, ARRAY_SIZE(error)));
}
return data;
}
std::vector CSimulation2::GetCivData()
{
VfsPath path(L"civs/");
VfsPaths pathnames;
std::vector data;
// Load all JSON files in civs directory
Status ret = vfs::GetPathnames(g_VFS, path, L"*.json", pathnames);
if (ret == INFO::OK)
{
for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
{
// Load JSON file
CVFSFile file;
PSRETURN ret = file.Load(g_VFS, *it);
if (ret != PSRETURN_OK)
{
LOGERROR(L"CSimulation2::GetCivData: Failed to load file '%ls': %hs", path.string().c_str(), GetErrorString(ret));
}
else
{
data.push_back(file.DecodeUTF8()); // assume it's UTF-8
}
}
}
else
{
// Some error reading directory
wchar_t error[200];
LOGERROR(L"CSimulation2::GetCivData: Error reading directory '%ls': %ls", path.string().c_str(), StatusDescription(ret, error, ARRAY_SIZE(error)));
}
return data;
}
std::string CSimulation2::GetPlayerDefaults()
{
return ReadJSON(L"simulation/data/player_defaults.json");
}
std::string CSimulation2::GetMapSizes()
{
return ReadJSON(L"simulation/data/map_sizes.json");
}
std::string CSimulation2::ReadJSON(VfsPath path)
{
std::string data;
if (!VfsFileExists(path))
{
LOGERROR(L"File '%ls' does not exist", path.string().c_str());
}
else
{
// Load JSON file
CVFSFile file;
PSRETURN ret = file.Load(g_VFS, path);
if (ret != PSRETURN_OK)
{
LOGERROR(L"Failed to load file '%ls': %hs", path.string().c_str(), GetErrorString(ret));
}
else
{
data = file.DecodeUTF8(); // assume it's UTF-8
}
}
return data;
}
std::string CSimulation2::GetAIData()
{
ScriptInterface& scriptInterface = GetScriptInterface();
std::vector aiData = ICmpAIManager::GetAIs(scriptInterface);
// Build single JSON string with array of AI data
CScriptValRooted ais;
if (!scriptInterface.Eval("({})", ais) || !scriptInterface.SetProperty(ais.get(), "AIData", aiData))
return std::string();
return scriptInterface.StringifyJSON(ais.get());
}
Index: ps/trunk/source/simulation2/components/ICmpAIInterface.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpAIInterface.cpp (revision 13224)
+++ ps/trunk/source/simulation2/components/ICmpAIInterface.cpp (revision 13225)
@@ -1,39 +1,44 @@
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "ICmpAIInterface.h"
#include "simulation2/system/InterfaceScripted.h"
#include "simulation2/scripting/ScriptComponent.h"
BEGIN_INTERFACE_WRAPPER(AIInterface)
END_INTERFACE_WRAPPER(AIInterface)
class CCmpAIInterfaceScripted : public ICmpAIInterface
{
public:
DEFAULT_SCRIPT_WRAPPER(AIInterfaceScripted)
virtual CScriptVal GetRepresentation()
{
return m_Script.Call ("GetRepresentation");
}
+ virtual CScriptVal GetFullRepresentation()
+ {
+ return m_Script.Call ("GetFullRepresentation");
+ }
+
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(AIInterfaceScripted)
Index: ps/trunk/source/simulation2/components/ICmpAIManager.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpAIManager.h (revision 13224)
+++ ps/trunk/source/simulation2/components/ICmpAIManager.h (revision 13225)
@@ -1,56 +1,58 @@
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_ICMPAIMANAGER
#define INCLUDED_ICMPAIMANAGER
#include "simulation2/system/Interface.h"
#include "simulation2/helpers/Player.h"
class ICmpAIManager : public IComponent
{
public:
/**
* Add a new AI player into the world, based on the AI script identified
* by @p id (corresponding to a subdirectory in simulation/ai/),
* to control player @p player.
*/
virtual void AddPlayer(std::wstring id, player_id_t player) = 0;
+ virtual void TryLoadSharedComponent() = 0;
+ virtual void RunGamestateInit() = 0;
/**
* Call this at the end of a turn, to trigger AI computation which will be
* ready for the next turn.
*/
virtual void StartComputation() = 0;
/**
* Call this at the start of a turn, to push the computed AI commands into
* the command queue.
*/
virtual void PushCommands() = 0;
/**
* Returns a vector of {"id":"value-for-AddPlayer", "name":"Human readable name"}
* objects, based on all the available AI scripts.
*/
static std::vector GetAIs(ScriptInterface& scriptInterface);
DECLARE_INTERFACE_TYPE(AIManager)
};
#endif // INCLUDED_ICMPAIMANAGER