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:
parent
2b503e68f7
commit
07ea313ad6
@ -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){
|
||||
|
@ -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;
|
@ -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])
|
||||
|
@ -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")){
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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){
|
||||
|
@ -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": []};
|
||||
};
|
||||
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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) ||
|
||||
|
@ -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){
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user