1
0
forked from 0ad/0ad

Major update for qBot-xp, with slight changes to unitAI (that broke qBot, and fix for that).

This was SVN commit r12343.
This commit is contained in:
wraitii 2012-08-10 16:33:58 +00:00
parent 2b503e68f7
commit 07ea313ad6
20 changed files with 1562 additions and 560 deletions

View File

@ -1,5 +1,5 @@
// basically an attack plan. The name is an artifact.
function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , targetFinder){
function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , targetFinder) {
//This is the list of IDs of the units in the plan
this.idList=[];
@ -35,12 +35,33 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
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" : 1, "minSize" : 0, "targetSize" : 3 , "batchSize" : 1, "classes" : ["Siege"], "templates" : [] };
this.unitStat["Siege"] = { "priority" : 0.5, "minSize" : 0, "targetSize" : 3 , "batchSize" : 1, "classes" : ["Siege"], "templates" : [] };
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.maxPreparationTime = 450*1000;
}
/*
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));
this.unitCollection = gameState.getOwnEntities().filter(filter);
@ -58,7 +79,7 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
var cat = unitCat;
var Unit = this.unitStat[cat];
var 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("plan",this.name),Filters.byOwner(gameState.player)));
this.unit[cat] = gameState.getOwnEntities().filter(filter);
this.unit[cat].registerUpdates();
this.unit[cat].length;
@ -110,15 +131,22 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
// 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
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];
this.rallyPoint[0] = (position[0]*3 + CCpos[0]) / 4.0;
this.rallyPoint[1] = (position[1]*3 + CCpos[1]) / 4.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;
@ -132,12 +160,13 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
this.threatList = []; // sounds so FBI
this.tactics = undefined;
gameState.ai.queueManager.addQueue("plan_" + this.name, 130); // high priority: some may gather anyway
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);
// get a good path to an estimated target.
this.pathFinder = new aStarPath(gameState,false);
};
CityAttack.prototype.getName = function(){
@ -160,6 +189,8 @@ CityAttack.prototype.canStart = function(gameState){
// 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");
};
@ -178,6 +209,8 @@ CityAttack.prototype.setPaused = function(gameState, boolValue){
}
};
CityAttack.prototype.mustStart = function(gameState){
if (this.isPaused())
return false;
var MaxReachedEverywhere = true;
for (unitCat in this.unitStat) {
var Unit = this.unitStat[unitCat];
@ -191,74 +224,152 @@ CityAttack.prototype.mustStart = function(gameState){
};
// 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) {
if (this.isPaused())
return 1; // continue
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) {
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;
}
this.path = this.path[0];
} 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");
// let's sort by training advancement, ie 'current size / target size'
this.buildOrder.sort(function (a,b) {
a[0] = a[2].length/a[3]["targetSize"];
b[0] = b[2].length/b[3]["targetSize"];
return (a[0]) - (b[0]);
});
this.assignUnits(gameState);
if ( (gameState.ai.turn + gameState.ai.player) % 40 == 0)
this.AllToRallyPoint(gameState, false);
var canstart = this.canStart(gameState);
Engine.ProfileStart("Creating units and looking through events");
// 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.countTotalQueuedUnits() < 5) {
var template = militaryManager.findBestTrainableUnit(gameState, this.buildOrder[0][1], [ ["strength",1], ["cost",1] ] );
//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 {
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"] ) );
else
this.queue.addItem( new UnitTrainingPlan(gameState,template, { "role" : "attack", "plan" : this.name, "special" : specialData },this.buildOrder[0][3]["batchSize"] ) );
// keep on while the units finish being trained.
if (this.mustStart(gameState) && gameState.countOwnQueuedEntitiesWithMetadata("plan", +this.name) ) {
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
}
}
}
// 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
}
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 = gameState.getEntityById(e.msg.attacker);
if (attacker && attacker.position()) {
this.unitCollection.attack(e.msg.attacker);
break;
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] ] );
//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 {
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"] ) );
else
this.queue.addItem( new UnitTrainingPlan(gameState,template, { "role" : "attack", "plan" : this.name, "special" : specialData },this.buildOrder[0][3]["batchSize"] ) );
}
}
}
/*
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;
}
Engine.ProfileStop();
// we count our units by triggering "canStart"
// returns false if we can no longer have time and cannot start.
// returns 0 if I must start and can't, returns 1 if I don't have to start, and returns 2 if I must start and can
if (!this.mustStart(gameState))
return 1;
else if (canstart)
// 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;
@ -268,21 +379,13 @@ CityAttack.prototype.assignUnits = function(gameState){
var self = this;
// TODO: assign myself units that fit only, right now I'm getting anything.
/*
// I'll take any unit set to "Defense" that has no subrole (ie is set to be a defensive unit, but has no particular task)
// I assign it to myself, and then it's mine, the entity collection will detect it.
var Defenders = gameState.getOwnEntitiesByRole("defence");
Defenders.forEach(function(ent) {
if (ent.getMetadata("subrole") == "idle" || !ent.getMetadata("subrole")) {
ent.setMetadata("role", "attack");
ent.setMetadata("plan", self.name);
}
});*/
// Assign all no-roles that fit (after a plan aborts, for example).
var NoRole = gameState.getOwnEntitiesByRole(undefined);
NoRole.forEach(function(ent) {
ent.setMetadata("role", "attack");
if (ent.hasClasses(["CitizenSoldier", "Infantry"]))
ent.setMetadata("role", "worker");
else
ent.setMetadata("role", "attack");
ent.setMetadata("plan", self.name);
});
@ -314,7 +417,10 @@ CityAttack.prototype.AllToRallyPoint = function(gameState, evenWorkers) {
CityAttack.prototype.defaultTargetFinder = function(gameState, militaryManager){
var targets = undefined;
targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings("ConquestCritical");
targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings("CivCentre");
if (targets.length == 0) {
targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings("ConquestCritical");
}
// If there's nothing, attack anything else that's less critical
if (targets.length == 0) {
targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings("Town");
@ -322,6 +428,10 @@ CityAttack.prototype.defaultTargetFinder = function(gameState, militaryManager){
if (targets.length == 0) {
targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings("Village");
}
// 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"));
}
return targets;
};
@ -355,59 +465,21 @@ CityAttack.prototype.raidingTargetFinder = function(gameState, militaryManager,
// 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){
var targets = [];
if (this.type === "harass_raid")
targets = this.targetFinder(gameState, militaryManager, "villager");
else
{
targets = this.targetFinder(gameState, militaryManager);
if (targets.length === 0){
targets = this.defaultTargetFinder(gameState, militaryManager);
}
}
// check we have a target and a path.
// If we have a target, move to it
if (targets.length) {
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();
// 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 false;
}
}
// Find possible distinct paths to the enemy
var pathFinder = new PathFinder(gameState);
var pathsToEnemy = pathFinder.getPaths(curPos, this.targetPos);
if (! pathsToEnemy){
pathsToEnemy = [[this.targetPos]];
}
this.path = [];
if (this.type !== "harass_raid")
{
var rand = Math.floor(Math.random() * pathsToEnemy.length);
this.path = pathsToEnemy[rand];
} else {
this.path = pathsToEnemy[Math.min(2,pathsToEnemy.length-1)];
}
this.unitCollection.forEach(function(ent) { ent.setMetadata("subrole", "attacking"); ent.setMetadata("role", "attack") ;});
// filtering by those that started to attack only
var filter = Filters.byMetadata("subrole","attacking");
this.unitCollection = this.unitCollection.filter(filter);
//this.unitCollection.registerUpdates();
this.unitCollection.registerUpdates();
//this.unitCollection.length;
for (unitCat in this.unitStat) {
@ -418,7 +490,7 @@ CityAttack.prototype.StartAttack = function(gameState, militaryManager){
this.unitCollection.move(this.path[0][0], this.path[0][1]);
debug ("Started to attack with the plan " + this.name);
this.state = "walking";
} else if (targets.length == 0 ) {
} else {
gameState.ai.gameFinished = true;
debug ("I do not have any target. So I'll just assume I won the game.");
return true;
@ -428,6 +500,7 @@ CityAttack.prototype.StartAttack = function(gameState, militaryManager){
// 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
@ -545,10 +618,10 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}*/
}
if (this.state === "walking"){
if (SquareVectorDistance(this.position, this.lastPosition) < 400 && this.path.length > 0) {
if (SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) {
this.unitCollection.move(this.path[0][0], this.path[0][1]);
}
if (SquareVectorDistance(this.unitCollection.getCentrePosition(), this.path[0]) < 400){
if (SquareVectorDistance(this.unitCollection.getCentrePosition(), this.path[0]) < 900){
this.path.shift();
if (this.path.length > 0){
this.unitCollection.move(this.path[0][0], this.path[0][1]);
@ -564,31 +637,10 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
// let's proceed on with whatever happens now.
// There's a ton of TODOs on this part.
if (this.onArrivalReaction == "proceedOnTargets") {
// 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.byStaticDistance(this.unitCollection.getCentrePosition(), 100), Filters.byClass("Unit")));
enemyUnits = enemyUnits.filter(Filters.byOwner(this.targetEnemy));
var enemyStructures = gameState.getEnemyEntities().filter(Filters.and(Filters.byStaticDistance(this.unitCollection.getCentrePosition(), 150), Filters.byClass("Structure")));
enemyStructures = enemyStructures.filter(Filters.byOwner(this.targetEnemy));
enemyUnits = enemyUnits.toEntityArray();
enemyStructures = enemyStructures.toEntityArray();
this.unitCollection.forEach( function (ent) { //}) {
if (ent.hasClass("Siege")) {
if (enemyStructures.length !== 0) {
var rand = Math.floor(Math.random() * enemyStructures.length*0.99);
ent.attack(enemyStructures[+rand].id());
} else
ent.stopMoving();
} else {
if (enemyUnits.length !== 0) {
var rand = Math.floor(Math.random() * enemyUnits.length*0.99);
ent.attack(enemyUnits[(+rand)].id());
} else
ent.stopMoving();
}
});
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) {
@ -608,30 +660,56 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}
}
}
if (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.byStaticDistance(this.unitCollection.getCentrePosition(), 200), Filters.byClass("Unit")));
enemyUnits = enemyUnits.filter(Filters.byOwner(this.targetEnemy));
var enemyStructures = gameState.getEnemyEntities().filter(Filters.and(Filters.byStaticDistance(this.unitCollection.getCentrePosition(), 250), Filters.byClass("Structure")));
enemyStructures = enemyStructures.filter(Filters.byOwner(this.targetEnemy));
enemyUnits = enemyUnits.toEntityArray();
enemyStructures = enemyStructures.toEntityArray();
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) {
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) {
return false;
}
return true;
});
mUnit = mUnit.toEntityArray();
mStruct = mStruct.toEntityArray();
if (ent.hasClass("Siege")) {
if (enemyStructures.length !== 0) {
var rand = Math.floor(Math.random() * enemyStructures.length*0.99);
ent.attack(enemyStructures[+rand].id());
} else
ent.stopMoving();
if (mStruct.length !== 0) {
var rand = Math.floor(Math.random() * mStruct.length*0.99);
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 (enemyUnits.length !== 0) {
var rand = Math.floor(Math.random() * enemyUnits.length*0.99);
ent.attack(enemyUnits[(+rand)].id());
} else
ent.stopMoving();
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);
}
}
}
});
@ -654,6 +732,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}*/
this.lastPosition = this.position;
Engine.ProfileStop();
return this.unitCollection.length;
};
CityAttack.prototype.totalCountUnits = function(gameState){

View File

@ -43,13 +43,13 @@ var baseConfig = {
// qbot
"priorities" : { // Note these are dynamic, you are only setting the initial values
"house" : 500,
"citizenSoldier" : 100,
"villager" : 150,
"economicBuilding" : 50,
"citizenSoldier" : 65,
"villager" : 95,
"economicBuilding" : 80,
"field" : 20,
"advancedSoldier" : 30,
"siege" : 10,
"militaryBuilding" : 80,
"militaryBuilding" : 90,
"defenceBuilding" : 17,
"civilCentre" : 1000
},
@ -58,7 +58,7 @@ var baseConfig = {
};
var Config = {
"debug": false
"debug": true
};
Config.__proto__ = baseConfig;

View File

@ -28,12 +28,11 @@ function Defence(){
// This allows the Defence manager to take units from the plans for Defence.
// Defcon levels
// 6 (or more): no danger whatsoever detected
// 5: local zones of danger (ie a tower somewhere, things like that)
// 4: a few enemy units inbound, like a scout or something. (local danger). Usually seen as the last level before a true "attack"
// 3: reasonnably sized enemy army inbound, local danger
// 2: well sized enemy army inbound, general danger
// 1: Sizable enemy army inside of my base, general danger.
// 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){
@ -150,6 +149,7 @@ Defence.prototype.defendFromEnemyArmies = function(gameState, events, militaryMa
var nbOfAttackers = 0;
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) {
@ -164,6 +164,9 @@ Defence.prototype.defendFromEnemyArmies = function(gameState, events, militaryMa
var enemyWatcher = militaryManager.enemyWatchers[unit.owner()];
if (enemyWatcher.isPartOfDangerousArmy(unit.id())) {
nbOfAttackers++;
if (this.attackerCache[unit.id()].length == 0) {
newEnemies.push(unit);
}
} else {
// if we had defined the attackerCache, ie if we had tried to attack this unit.
if (this.attackerCache[unit.id()] != undefined) {
@ -175,7 +178,6 @@ Defence.prototype.defendFromEnemyArmies = function(gameState, events, militaryMa
}
}
}
// okay so now, for every dangerous armies, we loop.
for (armyID in dangerArmies) {
// looping through army units
@ -196,6 +198,7 @@ Defence.prototype.defendFromEnemyArmies = function(gameState, events, militaryMa
self.attackerCache[ent.id()] = self.myUnits.filter(filter);
self.attackerCache[ent.id()].registerUpdates();
nbOfAttackers++;
newEnemies.push(ent);
}
});
}
@ -214,46 +217,77 @@ Defence.prototype.defendFromEnemyArmies = function(gameState, events, militaryMa
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 === 0) {
return;
} else if (nbOfAttackers < 5){
gameState.upDefcon(4); // few local units
} else if (nbOfAttackers >= 5){
gameState.upDefcon(3); // local attack, dangerous but not hugely threatening for my survival
if (nbOfAttackers < 10){
gameState.setDefcon(4); // few local units
} else if (nbOfAttackers >= 10){
gameState.setDefcon(3);
}
if (this.idleDefs.length < nbOfAttackers) {
gameState.upDefcon(2); // general danger
}
*/
var nonDefenders = this.myUnits.filter(Filters.or( Filters.not(Filters.byMetadata("role","defence")),Filters.isIdle()));
nonDefenders = nonDefenders.filter(Filters.not(Filters.byClass("Female")));
// todo: improve the logic against attackers.
var defenceRatio = this.defenceRatio;
if (newEnemies.length * defenceRatio> nonDefenders.length) {
defenceRatio = 1;
}
// For each enemy, we'll pick two units.
for each (enemy in newEnemies) {
if (nonDefenders.length === 0)
break;
// reupdate the existing defenders.
this.idleDefs.forEach(function(ent) {
ent.setMetadata("subrole","newdefender");
});
var assigned = self.attackerCache[enemy.id()].length;
if (assigned >= defenceRatio)
return;
nbOfAttackers *= this.defenceRatio;
// Assume those taken care of.
nbOfAttackers -= +(this.defenders.length);
// need new units?
if (nbOfAttackers <= 0)
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"));
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"));
defender.setMetadata("formerrole", defender.getMetadata("role"));
defender.setMetadata("role","defence");
defender.setMetadata("subrole","defending");
defender.attack(+enemy.id());
nonDefenders.updateEnt(defender);
assigned++;
});
}
}
/*
// 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)
if (ent.getMetadata("plan") != undefined && ent.getMetadata("role") != "defence")
return true;
return false;
});
@ -287,6 +321,15 @@ Defence.prototype.defendFromEnemyArmies = function(gameState, events, militaryMa
}
// 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.
@ -304,7 +347,7 @@ Defence.prototype.defendFromEnemyArmies = function(gameState, events, militaryMa
ent.setMetadata("subrole","defending");
ent.attack(+target);
});
*/
return;
}
@ -324,6 +367,11 @@ Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
// 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"))
continue;
// let's check for animals
if (attacker.owner() == 0) {
// if our unit is still alive, we make it react
@ -371,7 +419,7 @@ Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
this.WantedUnitsAttacker[attacker.id()].registerUpdates();
this.WantedUnitsAttacker[attacker.id()].length;
}
if (ourUnit && ourUnit.hasClass("Unit") && ourUnit.getMetadata("role") != "attack") {
if (ourUnit && ourUnit.hasClass("Unit")) {
if (ourUnit.hasClass("Support")) {
// TODO: it's a villager. Garrison it.
// TODO: make other neighboring villagers garrison
@ -393,7 +441,7 @@ Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
}
}
};
// At most, this will put defcon to 5
// At most, this will put defcon to 4
Defence.prototype.DealWithWantedUnits = function(gameState, events, militaryManager) {
//if (gameState.defcon() < 3)
// return;
@ -424,8 +472,8 @@ Defence.prototype.DealWithWantedUnits = function(gameState, events, militaryMana
if (nbOfAttackers === 0)
return;
// at most, we'll deal with two enemies at once.
if (nbOfDealtWith >= 2)
// 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
@ -434,11 +482,14 @@ Defence.prototype.DealWithWantedUnits = function(gameState, events, militaryMana
// 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 < 2 && nbOfAttackers > 0 && ent.getMetadata("plan") == undefined)
if (nbOfDealtWith < 3 && nbOfAttackers > 0 && ent.getMetadata("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.attack(+o);
if (addedto[o])
@ -455,17 +506,19 @@ Defence.prototype.DealWithWantedUnits = function(gameState, events, militaryMana
// still some undealt with attackers, recruit citizen soldiers
if (nbOfAttackers > 0 && nbOfDealtWith < 2) {
//gameState.upDefcon(5);
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 < 2 && nbOfAttackers > 0)
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.attack(+o);
if (addedto[o])

View File

@ -7,15 +7,27 @@ var EconomyManager = function() {
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.farmingFields = false;
this.dropsiteNumbers = {wood: 2, stone: 1, metal: 1};
this.dropsiteNumbers = {"wood": 1, "stone": 0.5, "metal": 0.5};
};
// More initialisation for stuff that needs the gameState
EconomyManager.prototype.init = function(gameState){
this.targetNumWorkers = Math.max(Math.floor(gameState.getPopulationMax()/2.5), 1);
this.targetNumWorkers = Math.max(Math.floor(gameState.getPopulationMax()*0.55), 1);
// initialize once all the resource maps.
this.updateResourceMaps(gameState, ["food","wood","stone","metal"]);
this.updateResourceConcentrations(gameState,"food");
this.updateResourceConcentrations(gameState,"wood");
this.updateResourceConcentrations(gameState,"stone");
this.updateResourceConcentrations(gameState,"metal");
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.
@ -29,26 +41,45 @@ 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();
var numWorkers = gameState.countOwnEntitiesAndQueuedWithRole("worker");
numWorkers += queues.citizenSoldier.countTotalQueuedUnits();
var numTotal = numWorkers + queues.villager.countTotalQueuedUnits() + queues.citizenSoldier.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)
numWorkers++;
});
gameState.getOwnTrainingFacilities().forEach(function(ent) {
ent.trainingQueue().forEach(function(item) {
if (item.metadata && item.metadata.role == "worker" && item.metadata.plan == undefined)
numWorkers += item.count;
});
});
var numQueued = queues.villager.countTotalQueuedUnits() + queues.citizenSoldier.countTotalQueuedUnits();
var numTotal = numWorkers + numQueued;
this.targetNumFields = numFemales/15;
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};
}
//debug (numTotal + "/" +this.targetNumWorkers + ", " +numFemales +"/" +numTotal);
// If we have too few, train more
if (numTotal < this.targetNumWorkers && (queues.villager.countTotalQueuedUnits() < 10 || queues.citizenSoldier.countTotalQueuedUnits() < 10) ) {
var template = "units/{civ}_support_female_citizen";
var size = 1;
if (numFemales/numWorkers > this.femaleRatio && gameState.getTimeElapsed() > 60*1000) {
// should plan enough to always have females…
if (numTotal < this.targetNumWorkers && numQueued < 20) {
var template = gameState.applyCiv("units/{civ}_support_female_citizen");
var size = Math.min(Math.ceil(gameState.getTimeElapsed() / 240000),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]]);
size = 5;
if (!template) {
template = "units/{civ}_support_female_citizen";
size = 1;
template = gameState.applyCiv("units/{civ}_support_female_citizen");
}
}
if (size == 5)
queues.citizenSoldier.addItem(new UnitTrainingPlan(gameState, template, { "role" : "worker" },size ));
else
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 ));
}
};
// picks the best template based on parameters and classes
@ -101,7 +132,7 @@ EconomyManager.prototype.pickMostNeededResources = function(gameState) {
}
var numGatherers = {};
for ( var type in this.gatherWeights){
for (type in this.gatherWeights){
numGatherers[type] = gameState.updatingCollection("workers-gathering-" + type,
Filters.byMetadata("gather-type", type), gameState.getOwnEntitiesByRole("worker")).length;
}
@ -200,44 +231,77 @@ EconomyManager.prototype.assignToFoundations = function(gameState) {
// 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();
var foundations = gameState.getOwnFoundations().toEntityArray();
var damagedBuildings = gameState.getOwnEntities().filter(function (ent) { if (ent.needsRepair() && ent.getMetadata("plan") == undefined) { return true; } return false; }).toEntityArray();
// Check if nothing to build
if (!foundations.length){
if (!foundations.length && !damagedBuildings.length){
return;
}
var workers = gameState.getOwnEntitiesByRole("worker");
var builderWorkers = this.workersBySubrole(gameState, "builder");
// Check if enough builders
var extraNeeded = this.targetNumBuilders*foundations.length - builderWorkers.length;
if (extraNeeded <= 0){
return;
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);
nearestNonBuilders.forEach(function(ent) {
addedWorkers++;
ent.setMetadata("subrole", "builder");
ent.setMetadata("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 nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders - assigned);
nearestNonBuilders.forEach(function(ent) {
addedWorkers++;
ent.setMetadata("subrole", "builder");
ent.setMetadata("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;
}
}
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); });
if (gameState.defcon() < 5)
nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata("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);
});
}
}
}
// Pick non-builders who are closest to the first foundation,
// and tell them to start building it
var target = foundations.toEntityArray()[0];
var nonBuilderWorkers = workers.filter(function(ent) {
// check position so garrisoned units aren't tasked
return (ent.getMetadata("subrole") !== "builder" && ent.position() !== undefined);
});
var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), extraNeeded);
// Order each builder individually, not as a formation
nearestNonBuilders.forEach(function(ent) {
ent.setMetadata("subrole", "builder");
ent.setMetadata("target-foundation", target);
});
};
EconomyManager.prototype.buildMoreFields = function(gameState, queues) {
if (this.farmingFields) {
if (this.farmingFields === true) {
var numFarms = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_field"));
numFarms += queues.field.countTotalQueuedUnits();
@ -245,12 +309,13 @@ EconomyManager.prototype.buildMoreFields = function(gameState, queues) {
queues.field.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_field"));
} else {
var foodAmount = 0;
gameState.updatingCollection("active-dropsite-food", Filters.byMetadata("active-dropsite-food", true),
gameState.getOwnDropsites("food")).forEach(function (dropsite){
dropsite.getMetadata("nearby-resources-food").forEach(function (supply) {
foodAmount += supply.resourceSupplyAmount();
});
});
gameState.getOwnDropsites("food").forEach( function (ent) { //}){
if (ent.getMetadata("resource-quantity-food") != undefined) {
foodAmount += ent.getMetadata("resource-quantity-food");
} else {
foodAmount = 300; // wait till we initialize
}
});
if (foodAmount < 300)
this.farmingFields = true;
}
@ -266,13 +331,22 @@ EconomyManager.prototype.buildNewCC= function(gameState, queues) {
}
};
//creates and maintains a map of tree density
EconomyManager.prototype.updateResourceMaps = function(gameState, events){
// The weight of the influence function is amountOfResource/decreaseFactor
var decreaseFactor = {'wood': 15, 'stone': 100, 'metal': 100, 'food': 20};
// This is the maximum radius of the influence
var radius = {'wood':9, 'stone': 10, 'metal': 10, 'food': 12};
// 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};
// This is the maximum radius of the influence
var radius = {'wood':25.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 };
// 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 self = this;
for (var resource in radius){
@ -291,25 +365,84 @@ EconomyManager.prototype.updateResourceMaps = function(gameState, events){
self.resourceMaps[resource].addInfluence(x, z, radius[resource], strength);
});
}
// TODO: fix for treasure and move out of loop
// Look for destroy events and subtract the entities original influence from the resourceMap
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 === resource){
}
// 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"]) {
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);
}
}
}else if (e.type === "Create") {
if (e.msg.entityObj){
var ent = e.msg.entityObj;
if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic === resource){
if (ent && ent.owner() == gameState.player && 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()];
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 (distance < bigRadius[resource]) {
supply.setMetadata("linked-dropsite", otherDropsite.id() );
supply.setMetadata("linked-dropsite-dist", +distance);
if (distance < smallRadius[resource]) {
takenOver = true;
supply.setMetadata("linked-dropsite-nearby", true );
} else {
supply.setMetadata("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);
}
});
}
}
}
} else if (e.type === "Create") {
if (e.msg.entityObj){
var ent = e.msg.entityObj;
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 (distance < bigRadius[resource]) {
if (distance < smallRadius[resource]) {
if (ent.getMetadata("linked-dropsite") == undefined)
addToMap = false;
ent.setMetadata("linked-dropsite-nearby", true );
} else {
ent.setMetadata("linked-dropsite-nearby", false );
}
ent.setMetadata("linked-dropsite", otherDropsite.id() );
ent.setMetadata("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]);
@ -318,140 +451,197 @@ EconomyManager.prototype.updateResourceMaps = function(gameState, events){
}
}
}
}
}
//this.resourceMaps['wood'].dumpIm("tree_density.png");
};
// Returns the position of the best place to build a new dropsite for the specified resource
EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource){
// A map which gives a positive weight for all CCs and adds a negative weight near all dropsites
var friendlyTiles = new Map(gameState);
gameState.getOwnEntities().forEach(function(ent) {
// We want to build near a CC of ours
if (ent.hasClass("CivCentre")){
var infl = 200;
friendlyTiles.add(this.resourceMaps[resource]);
for (i in this.resourceMaps)
if (i !== "food")
friendlyTiles.multiply(this.resourceMaps[i],true,100,1.5);
//friendlyTiles.dumpIm(gameState.getTimeElapsed() + "_" + resource + "_density_fade_base.png", 65000);
var territory = Map.createTerritoryMap(gameState);
friendlyTiles.multiplyTerritory(gameState,territory);
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.addInfluence(x, z, infl, 0.1 * infl);
friendlyTiles.addInfluence(x, z, infl/2, 0.1 * infl);
}
// We don't want multiple dropsites at one spot so add a negative for all dropsites
if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){
var infl = 20;
var pos = ent.position();
var x = Math.round(pos[0] / gameState.cellSize);
var z = Math.round(pos[1] / gameState.cellSize);
friendlyTiles.addInfluence(x, z, infl, -50, 'quadratic');
}
});
// Multiply by tree density to get a combination of the two maps
friendlyTiles.multiply(this.resourceMaps[resource]);
//friendlyTiles.dumpIm(resource + "_density_fade.png", 10000);
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);
var obstructions = Map.createObstructionMap(gameState);
obstructions.expandInfluences();
var bestIdx = friendlyTiles.findBestTile(4, obstructions)[0];
// Convert from 1d map pixel coordinates to game engine coordinates
var bestIdx = friendlyTiles.findBestTile(2, obstructions)[0];
var x = ((bestIdx % friendlyTiles.width) + 0.5) * gameState.cellSize;
var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * gameState.cellSize;
return [x,z];
};
EconomyManager.prototype.updateResourceConcentrations = function(gameState){
var self = this;
var resources = ["food", "wood", "stone", "metal"];
for (var key in resources){
var resource = resources[key];
gameState.getOwnEntities().forEach(function(ent) {
if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){
var radius = 14;
var pos = ent.position();
var x = Math.round(pos[0] / gameState.cellSize);
var z = Math.round(pos[1] / gameState.cellSize);
var quantity = self.resourceMaps[resource].sumInfluence(x, z, radius);
ent.setMetadata("resourceQuantity_" + resource, quantity);
}
});
if (territory.getOwner([x,z]) === 0) {
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 [true, [x,z]];
}
return [false, [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)
return;
dropsite.getMetadata("linked-resources-" + resource).forEach(function(supply){ //}){
if (supply.getMetadata("full") == true)
return;
if (supply.getMetadata("linked-dropsite-nearby") == true)
amount += supply.resourceSupplyAmount();
else
amountFar += supply.resourceSupplyAmount();
supply.setMetadata("dp-update-value",supply.resourceSupplyAmount());
});
dropsite.setMetadata("resource-quantity-" + resource, amount);
dropsite.setMetadata("resource-quantity-far-" + resource, amountFar);
});
};
// Stores lists of nearby resources
EconomyManager.prototype.updateNearbyResources = function(gameState){
EconomyManager.prototype.updateNearbyResources = function(gameState,resource){
var self = this;
var resources = ["food", "wood", "stone", "metal"];
var resourceSupplies;
var radius = 100;
for (var key in resources){
var resource = resources[key];
// 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};
// This is the maximum radius of the influence
var radius = {'wood':25.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) { //}){
gameState.getOwnDropsites(resource).forEach(function(ent) {
if (ent.getMetadata("nearby-resources-" + resource) === undefined){
var filterPos = Filters.byStaticDistance(ent.position(), radius);
if (ent.getMetadata("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.
var collection = gameState.getResourceSupplies(resource).filter(filterPos);
collection.registerUpdates();
ent.setMetadata("nearby-resources-" + resource, collection);
ent.setMetadata("active-dropsite-" + resource, true);
}
if (supply.getMetadata("linked-dropsite") == undefined || supply.getMetadata("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) {
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);
}
supply.setMetadata("linked-dropsite-nearby", true );
} else {
supply.setMetadata("linked-dropsite-nearby", false );
}
supply.setMetadata("linked-dropsite", ent.id() );
supply.setMetadata("linked-dropsite-dist", +distance);
}
}
});
// This one is both for the nearby and the linked
var filter = Filters.byMetadata("linked-dropsite", ent.id());
var collection = gameState.getResourceSupplies(resource).filter(filter);
collection.registerUpdates();
ent.setMetadata("linked-resources-" + resource, collection);
if (ent.getMetadata("nearby-resources-" + resource).length === 0){
ent.setMetadata("active-dropsite-" + resource, false);
}else{
ent.setMetadata("active-dropsite-" + resource, true);
}
/*
// Make resources glow wildly
if (resource == "food"){
ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [10,0,0]});
});
}
if (resource == "wood"){
ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,10,0]});
});
}
if (resource == "metal"){
ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,0,10]});
});
}*/
});
}
filter = Filters.byMetadata("linked-dropsite-nearby",true);
var collection2 = collection.filter(filter);
collection2.registerUpdates();
ent.setMetadata("nearby-resources-" + resource, collection2);
}
/*
// Make resources glow wildly
if (resource == "food"){
ent.getMetadata("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){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [10,0,0]});
});
}
if (resource == "wood"){
ent.getMetadata("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){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,10,0]});
});
}
if (resource == "metal"){
ent.getMetadata("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){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,0,10]});
});
}
if (resource == "stone"){
ent.getMetadata("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){
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: 1400, stone: 200, metal: 200};
//TODO: make these values adaptive
var requiredInfluence = {"wood": 2500, "stone": 600, "metal": 600};
var count = 0;
gameState.getOwnEntities().forEach(function(ent) {
if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){
var quantity = ent.getMetadata("resourceQuantity_" + resource);
if (quantity >= requiredInfluence[resource]){
count ++;
}
gameState.getOwnDropsites(resource).forEach(function(ent) { //}){
if (ent.getMetadata("resource-quantity-" + resource) == undefined || typeof(ent.getMetadata("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);
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.buildMarket = function(gameState, queues){
if (gameState.getTimeElapsed() > 360 * 1000){
if (gameState.getTimeElapsed() > 620 * 1000){
if (queues.economicBuilding.countTotalQueuedUnitsWithClass("BarterMarket") === 0 &&
gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_market")) === 0){
//only ever build one mill/CC/market at a time
@ -485,29 +675,19 @@ EconomyManager.prototype.tryBartering = function(gameState){
};
// so this always try to build dropsites.
EconomyManager.prototype.buildDropsites = function(gameState, queues){
if (queues.economicBuilding.totalLength() === 0 &&
gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_mill")) === 0 &&
if (queues.economicBuilding.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);
var myCivCentres = gameState.getOwnEntities().filter(function(ent) {
if (!ent.hasClass("CivCentre") || ent.position() === undefined){
return false;
}
var dx = (spot[0]-ent.position()[0]);
var dy = (spot[1]-ent.position()[1]);
var dist2 = dx*dx + dy*dy;
return (ent.hasClass("CivCentre") && dist2 < 180*180);
});
if (myCivCentres.length === 0){
queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre", spot));
}else{
queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_mill", spot));
if (spot[0] === true){
queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre", spot[1]));
} else {
queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_mill", spot[1]));
}
break;
}
@ -524,38 +704,38 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
this.buildNewCC(gameState,queues);
// this function also deals with a few things that are number-of-workers related
Engine.ProfileStart("Train workers and build farms");
this.trainMoreWorkers(gameState, queues);
if (gameState.getTimeElapsed() > 5000)
if ((gameState.ai.playedTurn+1) % 3 === 0)
this.buildMoreFields(gameState,queues);
Engine.ProfileStop();
//Later in the game we want to build stuff faster.
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.5) {
if (gameState.getTimeElapsed() > 15*60*1000) {
this.targetNumBuilders = 6;
}else{
this.targetNumBuilders = 3;
}
if (gameState.getTimeElapsed() > 20*60*1000) {
this.dropsiteNumbers = {wood: 3, stone: 2, metal: 2};
}else{
this.dropsiteNumbers = {wood: 2, stone: 1, metal: 1};
}
Engine.ProfileStart("Update Resource Maps and Concentrations");
this.updateResourceMaps(gameState, events);
if (gameState.ai.playedTurn % 2 === 0) {
this.updateResourceMaps(gameState, events);
this.updateResourceConcentrations(gameState);
this.updateNearbyResources(gameState);
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();
Engine.ProfileStart("Build new Dropsites");
this.buildDropsites(gameState, queues);
Engine.ProfileStop();
if (gameState.ai.playedTurn % 8 === 0) {
Engine.ProfileStart("Build new Dropsites");
this.buildDropsites(gameState, queues);
Engine.ProfileStop();
}
this.tryBartering(gameState);
this.buildMarket(gameState, queues);
@ -575,32 +755,33 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
this.reassignIdleWorkers(gameState);
Engine.ProfileStop();
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);
}
});
for (var i in gathererGroups){
for (var j in gathererGroups){
var a = eval(i);
var b = eval(j);
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");
// this is pretty slow, run it once in a while
if (gameState.ai.playedTurn % 4 === 0) {
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);
}
});
for (var i in gathererGroups){
for (var j in gathererGroups){
var a = eval(i);
var b = eval(j);
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");
}
}
}
}
Engine.ProfileStop();
}
Engine.ProfileStop();
Engine.ProfileStart("Run Workers");
gameState.getOwnEntitiesByRole("worker").forEach(function(ent){
if (!ent.getMetadata("worker-object")){

View File

@ -177,6 +177,9 @@ enemyWatcher.prototype.splitArmies = function(gameState){
army.forEach( function (enemy) {
if (enemy.position() == undefined)
return;
// debug ("entity " +enemy.templateName() + " is currently " +enemy.visibility(gameState.player));
if (!inRange(enemy.position(),centre, 3500) ) {
var newArmyID = uneval( gameState.player + "" + self.totalNBofArmies);
if (self.dangerousArmies.indexOf(armyID) !== -1)

View File

@ -44,6 +44,23 @@ EntityTemplate.prototype.hasClasses = function(array) {
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) {
Classes.push([this._template.Attack[i].Bonuses[o].Classes.split(" "), +this._template.Attack[i].Bonuses[o].Multiplier]);
}
}
return Classes;
};
EntityTemplate.prototype.getMaxStrength = function()
{
var strength = 0.0;
@ -113,11 +130,18 @@ EntityTemplate.prototype.costSum = function() {
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;
@ -158,3 +182,4 @@ Entity.prototype.barter = function(buyType, sellType, amount) {
Engine.PostCommand({"type": "barter", "sell" : sellType, "buy" : buyType, "amount" : amount });
return this;
};

View File

@ -10,13 +10,12 @@ EntityCollection.prototype.attack = function(unit)
Engine.PostCommand({"type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false});
return this;
};
EntityCollection.prototype.attackMove = function(x, z){
Engine.PostCommand({"type": "attack-move", "entities": this.toIdArray(), "x": x, "z": z, "queued": false});
// 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){

View File

@ -30,4 +30,13 @@ Filters["byTerritory"] = function(Map, territoryIndex){
}
},
"dynamicProperties": ['position']};
};
};
Filters["isDropsite"] = function(resourceType){
return {"func": function(ent){
return (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resourceType) !== -1
&& ent.foundationProgress() === undefined);
},
"dynamicProperties": []};
};

View File

@ -256,7 +256,7 @@ GameState.prototype.countOwnQueuedEntitiesWithMetadata = function(data, value) {
var count = 0;
this.getOwnTrainingFacilities().forEach(function(ent) {
ent.trainingQueue().forEach(function(item) {
if (item.metadata && item.metadata.data && item.metadata.data == value)
if (item.metadata && item.metadata[data] && item.metadata[data] == value)
count += item.count;
});
});
@ -344,15 +344,29 @@ GameState.prototype.findTrainableUnits = function(classes){
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;
};

View File

@ -1,15 +1,20 @@
const TERRITORY_PLAYER_MASK = 0x3F;
//TODO: Make this cope with negative cell values
function Map(gameState, originalMap){
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){
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{
} else {
this.map = new Uint16Array(this.length);
}
this.cellSize = gameState.cellSize;
@ -99,12 +104,28 @@ Map.createTerritoryMap = function(gameState) {
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);
}
}
};
Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) {
strength = strength ? strength : maxDist;
strength = strength ? +strength : +maxDist;
type = type ? type : 'linear';
var x0 = Math.max(0, cx - maxDist);
@ -113,19 +134,18 @@ Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) {
var y1 = Math.min(this.height, cy + maxDist);
var maxDist2 = maxDist * maxDist;
var str = 0;
var str = 0.0;
switch (type){
case 'linear':
str = strength / maxDist;
str = +strength / +maxDist;
break;
case 'quadratic':
str = strength / maxDist2;
str = +strength / +maxDist2;
break;
case 'constant':
str = strength;
str = +strength;
break;
}
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
@ -145,7 +165,6 @@ Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) {
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{
@ -156,6 +175,79 @@ Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) {
}
};
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);
@ -249,15 +341,37 @@ Map.prototype.findBestTile = function(radius, obstructionTiles){
return [bestIdx, bestVal];
};
// Multiplies current map by the parameter map pixelwise
Map.prototype.multiply = function(map){
for (var i = 0; i < this.length; i++){
this.map[i] *= map.map[i];
// Multiplies current map by 3 if in my territory
Map.prototype.multiplyTerritory = function(gameState,map){
for (var i = 0; i < this.length; ++i){
if (map.getOwnerIndex(i) === gameState.player)
this.map[i] *= 2.5;
}
};
// 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
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;
}
};
Map.prototype.dumpIm = function(name, threshold){
name = name ? name : "default.png";
threshold = threshold ? threshold : 256;
threshold = threshold ? threshold : 65500;
Engine.DumpImage(name, this.map, this.width, this.height, threshold);
};

View File

@ -7,16 +7,8 @@
*/
var MilitaryAttackManager = function() {
// these use the structure soldiers[unitId] = true|false to register the units
this.attackManagers = [AttackMoveToLocation];
this.availableAttacks = [];
this.currentAttacks = [];
// Counts how many attacks we have sent at the enemy.
this.attackCount = 0;
this.lastAttackTime = 0;
this.defenceManager = new Defence();
this.TotalAttackNumber = 0;
this.upcomingAttacks = { "CityAttack" : [] };
this.startedAttacks = { "CityAttack" : [] };
@ -144,9 +136,11 @@ MilitaryAttackManager.prototype.findBestNewUnit = function(gameState, queue, sol
// 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;
@ -407,7 +401,7 @@ MilitaryAttackManager.prototype.constructTrainingBuildings = function(gameState,
// Build more military buildings
// TODO: make military building better
Engine.ProfileStart("Build buildings");
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > 35) {
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > 25) {
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0]))
+ queues.militaryBuilding.totalLength() < 1) {
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0]));
@ -543,37 +537,36 @@ MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
var attack = this.upcomingAttacks[attackType][i];
if (!attack.isPaused()) {
// okay so we'll get the support plan
if (!attack.isStarted()) {
if (1) { //gameState.ai.status["underAttack"] == false) {
var updateStep = attack.updatePreparation(gameState, this,events);
// now we're gonna check if the preparation time is over
if (updateStep === 1) {
// just chillin'
} else if (updateStep === 0) {
debug ("Military Manager: " +attack.getType() +" plan " +attack.getName() +" aborted.");
attack.Abort(gameState, this);
//this.abortedAttacks.push(attack);
this.upcomingAttacks[attackType].splice(i,1);
i--;
} 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);
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");
}
} else {
debug ("Military Manager: Starting " +attack.getType() +" plan " +attack.getName());
this.startedAttacks[attackType].push(attack);
this.upcomingAttacks[attackType].splice(i,1);
attack.Abort(gameState, this);
//this.abortedAttacks.push(attack);
i--;
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);
i--;
this.upcomingAttacks[attackType].splice(i-1,1);
}
} else {
debug ("Military Manager: Starting " +attack.getType() +" plan " +attack.getName());
this.startedAttacks[attackType].push(attack);
i--;
this.upcomingAttacks[attackType].splice(i-1,1);
}
}
}
@ -596,12 +589,17 @@ MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
}
}
// creating plans after updating because an aborted plan might be reused in that case.
if (gameState.countEntitiesByType(gameState.applyCiv(this.bModerate[0])) >= 1) {
if (this.upcomingAttacks["CityAttack"].length == 0) {
if (gameState.countEntitiesByType(gameState.applyCiv(this.bModerate[0])) >= 1 && !this.attackPlansEncounteredWater) {
if (this.upcomingAttacks["CityAttack"].length == 0 && gameState.getTimeElapsed() < 25*60000) {
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) {
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);
}
}
/*

View File

@ -56,7 +56,7 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
var obstructionMap = Map.createObstructionMap(gameState,template);
//obstructionMap.dumpIm("obstructions.png");
///obstructionMap.dumpIm("obstructions.png");
obstructionMap.expandInfluences();
@ -64,12 +64,13 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
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);
//friendlyTiles.dumpIm("pos.png", 200);
}else{
// No position was specified so try and find a sensible place to build
gameState.getOwnEntities().forEach(function(ent) {
@ -96,9 +97,10 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
}
}else{
if (template.genericName() == "House" && ent.genericName() == "House")
friendlyTiles.addInfluence(x, z, infl*2.0); // houses are close to other houses
else if (template.genericName() == "House") {
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
@ -108,14 +110,17 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
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, Math.floor(infl/3.0), infl + 1);
friendlyTiles.addInfluence(x, z, Math.floor(infl/4), -Math.floor(infl));
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);
// 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.
@ -125,18 +130,26 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
else if (template.buildCategory() === "Dock")
var radius = 0;
else if (template.genericName() != "House" && !template.hasClass("DropsiteWood") && !template.hasClass("DropsiteStone") && !template.hasClass("DropsiteMetal"))
var radius = Math.ceil(template.obstructionRadius() / cellSize) + 2;
var radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
else
var radius = Math.ceil(template.obstructionRadius() / cellSize);
// further contract cause walls
if (gameState.playerData.civ == "iber")
radius *= 0.95;
// Find the best non-obstructed tile
var bestTile = friendlyTiles.findBestTile(radius, obstructionMap);
var bestIdx = bestTile[0];
var bestVal = bestTile[1];
// 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];
}
if (bestVal === undefined || bestVal === -1) {
var bestTile = friendlyTiles.findBestTile(radius, obstructionMap);
var bestIdx = bestTile[0];
var bestVal = bestTile[1];
}
if (bestVal === -1){
return false;
}

View File

@ -30,7 +30,7 @@ UnitTrainingPlan.prototype.canExecute = function(gameState) {
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
@ -38,6 +38,16 @@ UnitTrainingPlan.prototype.execute = function(gameState) {
// 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();
});

View File

@ -35,6 +35,9 @@ function QBotAI(settings) {
this.firstTime = true;
this.savedEvents = [];
this.defcon = 5;
this.defconChangeTime = -10000000;
}
QBotAI.prototype = new BaseAI();
@ -78,8 +81,9 @@ QBotAI.prototype.runInit = function(gameState){
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);
}
};
@ -92,6 +96,12 @@ QBotAI.prototype.OnUpdate = function() {
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) {
@ -105,13 +115,17 @@ QBotAI.prototype.OnUpdate = function() {
return; // With no entities to control the AI cannot do anything
}
// defcon cooldown
if (this.defcon < 5 && gameState.timeSinceDefconChange() > 20000)
this.defcon++;
this.runInit(gameState);
for (var i in this.modules){
this.modules[i].update(gameState, this.queues, this.savedEvents);
}
this.updateDynamicPriorities(gameState, this.queues);
//this.updateDynamicPriorities(gameState, this.queues);
this.queueManager.update(gameState);

View File

@ -100,6 +100,18 @@ Queue.prototype.countTotalQueuedUnitsWithClass = function(classe){
}
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;

View File

@ -8,7 +8,9 @@ var TemplateManager = function(gameState) {
this.knownTemplatesList = [];
this.buildingTemplates = [];
this.unitTemplates = [];
this.templateCounters = {};
this.templateCounteredBy = {};
// this will store templates that exist
this.AcknowledgeTemplates(gameState);
this.getBuildableSubtemplates(gameState);
@ -17,6 +19,8 @@ var TemplateManager = function(gameState) {
this.getTrainableSubtemplates(gameState);
// should be enough in 100% of the cases.
this.getTemplateCounters(gameState);
};
TemplateManager.prototype.AcknowledgeTemplates = function(gameState)
{
@ -74,3 +78,38 @@ TemplateManager.prototype.getTrainableSubtemplates = function(gameState)
}
}
}
TemplateManager.prototype.getTemplateCounters = function(gameState)
{
for (i in this.unitTemplates)
{
var tp = gameState.getTemplate(this.unitTemplates[i]);
var tpname = this.unitTemplates[i];
this.templateCounters[tpname] = tp.getCounteredClasses();
}
}
// features auto-caching
TemplateManager.prototype.getCountersToClasses = function(gameState,classes,templateName)
{
if (templateName !== undefined && this.templateCounteredBy[templateName])
return this.templateCounteredBy[templateName];
var templates = [];
for (i in this.templateCounters) {
var okay = false;
for each (ticket in this.templateCounters[i]) {
var okaya = true;
for (a in ticket[0]) {
if (classes.indexOf(ticket[0][a]) === -1)
okaya = false;
}
if (okaya && templates.indexOf(i) === -1)
templates.push([i, ticket[1]]);
}
}
templates.sort (function (a,b) { return -a[1] + b[1]; });
if (templateName !== undefined)
this.templateCounteredBy[templateName] = templates;
return templates;
}

View File

@ -116,7 +116,10 @@ PathFinder.prototype.getPaths = function(start, end, mode){
}
var paths = [];
var i = 0;
while (true){
i++;
//this.dumpIm("terrainanalysis_"+i+".png", 511);
this.makeGradient(s,e);
var curPath = this.walkGradient(e, mode);
@ -134,7 +137,7 @@ PathFinder.prototype.getPaths = function(start, end, mode){
if (paths.length > 0){
return paths;
}else{
return undefined;
return [];
}
};
@ -292,7 +295,7 @@ function Accessibility(gameState, location){
start = this.findClosestPassablePoint(this.gamePosToMapPos(location));
iterations += 1;
}
//this.dumpIm("accessibility.png");
}
copyPrototype(Accessibility, TerrainAnalysis);
@ -347,4 +350,362 @@ Accessibility.prototype.floodFill = function(start){
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.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.fCostArray[index]*2;
} else if (this.onWater && this.passabilityMap.map[index] !== 200) {
this.gCostArray[index] += this.fCostArray[index]*2;
} 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.fCostArray[index]*2;
} else if (this.onWater && this.passabilityMap.map[index] !== 200) {
addCost = this.fCostArray[index]*2;
} 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');
}
}
}
if (gamestate !== undefined)
this.TotorMap.dumpIm("Path From " +s +" to " +e +".png",255);
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;
}
}
};

View File

@ -16,8 +16,8 @@ Worker.prototype.update = function(gameState) {
}
if (subrole === "gatherer"){
if (!(this.ent.unitAIState().split(".")[1] === "GATHER" && this.ent.unitAIOrderData().type
&& this.getResourceType(this.ent.unitAIOrderData().type) === this.ent.getMetadata("gather-type"))
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")){
// TODO: handle combat for hunting animals
if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0 ||
@ -42,9 +42,11 @@ Worker.prototype.update = function(gameState) {
if (this.gatheringFrom){
var ent = gameState.getEntityById(this.gatheringFrom);
if (ent && ent.resourceSupplyAmount() == ent.resourceSupplyMax()){
if (this.approachCount > 0){
// 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{
@ -55,7 +57,7 @@ Worker.prototype.update = function(gameState) {
}
}
}
}else if(subrole === "builder"){
} else if(subrole === "builder") {
if (this.ent.unitAIState().split(".")[1] !== "REPAIR"){
var target = this.ent.getMetadata("target-foundation");
this.ent.repair(target);
@ -72,44 +74,89 @@ Worker.prototype.update = function(gameState) {
Worker.prototype.updateGathererCounts = function(gameState, dead){
// update gatherer counts for the resources
if (this.ent.unitAIState().split(".")[2] === "GATHERING" && !dead){
if (this.gatheringFrom !== this.ent.unitAIOrderData().target){
if (this.gatheringFrom !== this.ent.unitAIOrderData()[0].target){
if (this.gatheringFrom){
var ent = gameState.getEntityById(this.gatheringFrom);
if (ent){
if (ent && ent.resourceSupplyType()){
ent.setMetadata("gatherer-count", ent.getMetadata("gatherer-count") - 1);
this.markFull(ent);
this.markFull(gameState,ent);
}
}
this.gatheringFrom = this.ent.unitAIOrderData().target;
this.gatheringFrom = this.ent.unitAIOrderData()[0].target;
if (this.gatheringFrom){
var ent = gameState.getEntityById(this.gatheringFrom);
if (ent){
if (ent && ent.resourceSupplyType()){
ent.setMetadata("gatherer-count", (ent.getMetadata("gatherer-count") || 0) + 1);
this.markFull(ent);
this.markFull(gameState,ent);
}
}
}
}else{
} else if (this.ent.unitAIState().split(".")[2] === "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);
this.markFull(gameState,ent);
}
this.gatheringFrom = undefined;
}
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);
this.markFull(gameState,ent);
}
}
this.gatheringFrom = undefined;
}
} else {
if (this.gatheringFrom){
var ent = gameState.getEntityById(this.gatheringFrom);
if (ent){
if (ent && ent.resourceSupplyType()){
ent.setMetadata("gatherer-count", ent.getMetadata("gatherer-count") - 1);
this.markFull(ent);
this.markFull(gameState,ent);
}
this.gatheringFrom = undefined;
}
}
};
Worker.prototype.markFull = function(ent){
var maxCounts = {"food": 15, "wood": 5, "metal": 15, "stone": 15, "treasure": 1};
if (ent.resourceSupplyType() && ent.getMetadata("gatherer-count") >= maxCounts[ent.resourceSupplyType().generic]){
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);
// update the dropsite
var dropsite = gameState.getEntityById(ent.getMetadata("linked-dropsite"));
if (dropsite == undefined || dropsite.getMetadata("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);
} else {
dropsite.setMetadata("resource-quantity-far-" + resource, +dropsite.getMetadata("resource-quantity-" + resource) - (+ent.getMetadata("dp-update-value")));
dropsite.getMetadata("linked-resources-" + resource).updateEnt(ent);
}
}
}else{
if (ent.getMetadata("full")){
ent.setMetadata("full", false);
// update the dropsite
var dropsite = gameState.getEntityById(ent.getMetadata("linked-dropsite"));
if (dropsite == undefined || dropsite.getMetadata("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);
} else {
dropsite.setMetadata("resource-quantity-far-" + resource, +dropsite.getMetadata("resource-quantity-" + resource) + ent.resourceSupplyAmount());
dropsite.getMetadata("linked-resources-" + resource).updateEnt(ent);
}
}
}
};
@ -123,23 +170,45 @@ Worker.prototype.startGathering = function(gameState){
return;
}
// 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;
gameState.updatingCollection("active-dropsite-" + resource, Filters.byMetadata("active-dropsite-" + resource, true),
gameState.getOwnDropsites(resource)).forEach(function (dropsite){
if (dropsite.position()){
// first, look for nearby resources.
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("nearby-resources-" + resource);
nearestResources = dropsite.getMetadata("linked-resources-" + resource);
nearestDropsite = dropsite;
}
}
});
// 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)) {
var dist = SquareVectorDistance(ent.position(), dropsite.position());
if (dist < minDropsiteDist){
minDropsiteDist = dist;
nearestResources = dropsite.getMetadata("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){
@ -161,28 +230,32 @@ Worker.prototype.startGathering = function(gameState){
return;
}
if (!nearestDropsite) {
debug ("No dropsite for " +resource);
return;
}
var supplies = [];
var nearestSupplyDist = Math.min();
var nearestSupply = undefined;
nearestResources.forEach(function(supply) {
nearestResources.forEach(function(supply) { //}){
// TODO: handle enemy territories
if (!supply.position()){
return;
}
if (supply.getMetadata("full") == true) {
return;
}
// measure the distance to the resource
var dist = VectorDistance(supply.position(), ent.position());
// 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 (nearestDropsite){
dist += 5 * VectorDistance(supply.position(), nearestDropsite.position());
if (supply.getMetadata("linked-dropsite") !== undefined){
dist += 4*SquareVectorDistance(supply.position(), nearestDropsite.position());
dist /= 5.0;
}
// Go for treasure as a priority
if (dist < 1000 && supply.resourceSupplyType().generic == "treasure"){
if (dist < 40000 && supply.resourceSupplyType().generic == "treasure"){
dist /= 1000;
}
@ -197,8 +270,11 @@ Worker.prototype.startGathering = function(gameState){
// if the resource is far away, try to build a farm instead.
var tried = false;
if (resource === "food" && SquareVectorDistance(pos,this.ent.position()) > 50000)
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) {
var territoryOwner = gameState.getTerritoryMap().getOwner(pos);
if (!gameState.ai.accessibility.isAccessible(pos) ||

View File

@ -16,8 +16,8 @@ Worker.prototype.update = function(gameState) {
}
if (subrole === "gatherer"){
if (!(this.ent.unitAIState().split(".")[1] === "GATHER" && this.ent.unitAIOrderData().type
&& this.getResourceType(this.ent.unitAIOrderData().type) === this.ent.getMetadata("gather-type"))
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")){
// TODO: handle combat for hunting animals
if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0 ||
@ -72,7 +72,7 @@ Worker.prototype.update = function(gameState) {
Worker.prototype.updateGathererCounts = function(gameState, dead){
// update gatherer counts for the resources
if (this.ent.unitAIState().split(".")[2] === "GATHERING" && !dead){
if (this.gatheringFrom !== this.ent.unitAIOrderData().target){
if (this.gatheringFrom !== this.ent.unitAIOrderData()[0].target){
if (this.gatheringFrom){
var ent = gameState.getEntityById(this.gatheringFrom);
if (ent){
@ -80,7 +80,7 @@ Worker.prototype.updateGathererCounts = function(gameState, dead){
this.markFull(ent);
}
}
this.gatheringFrom = this.ent.unitAIOrderData().target;
this.gatheringFrom = this.ent.unitAIOrderData()[0].target;
if (this.gatheringFrom){
var ent = gameState.getEntityById(this.gatheringFrom);
if (ent){

View File

@ -2171,10 +2171,12 @@ UnitAI.prototype.AddOrders = function(orders)
UnitAI.prototype.GetOrderData = function()
{
if (this.order && this.order.data)
return deepcopy(this.order.data);
else
return undefined;
var orders = [];
for (i in this.orderQueue) {
if (this.orderQueue[i].data)
orders.push(deepcopy(this.orderQueue[i].data));
}
return orders;
};
UnitAI.prototype.TimerHandler = function(data, lateness)