1
0
forked from 0ad/0ad

General improvements to the attack plans and the defense manager. Slight econ improvements (better early game priorities, faster play, better farming, tentative to get qBot to crowd resources less, tweaking of dropsites variable). Many bugfixes, should be stable enough for alpha 11.

This was SVN commit r12283.
This commit is contained in:
wraitii 2012-08-05 11:19:31 +00:00
parent 4a1b085df0
commit 36cc1766a4
11 changed files with 272 additions and 111 deletions

View File

@ -38,9 +38,9 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
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","Ranged"], "templates" : [] };
this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 8 , "batchSize" : 3, "classes" : ["Cavalry","Melee"], "templates" : [] };
this.unitStat["Siege"] = { "priority" : 1, "minSize" : 0, "targetSize" : 2 , "batchSize" : 1, "classes" : ["Siege"], "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" : [] };
var filter = Filters.and(Filters.byMetadata("plan",this.name),Filters.byOwner(gameState.player));
this.unitCollection = gameState.getOwnEntities().filter(filter);
@ -132,7 +132,7 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
this.threatList = []; // sounds so FBI
this.tactics = undefined;
gameState.ai.queueManager.addQueue("plan_" + this.name, 100);
gameState.ai.queueManager.addQueue("plan_" + this.name, 130); // high priority: some may gather anyway
this.queue = gameState.ai.queues["plan_" + this.name];
@ -181,8 +181,9 @@ CityAttack.prototype.mustStart = function(gameState){
var MaxReachedEverywhere = true;
for (unitCat in this.unitStat) {
var Unit = this.unitStat[unitCat];
if (this.unit[unitCat].length < Unit["targetSize"])
if (this.unit[unitCat].length < Unit["targetSize"]) {
MaxReachedEverywhere = false;
}
}
if (MaxReachedEverywhere)
return true;
@ -192,7 +193,7 @@ CityAttack.prototype.mustStart = function(gameState){
// Three returns possible: 1 is "keep going", 0 is "failed plan", 2 is "start"
CityAttack.prototype.updatePreparation = function(gameState, militaryManager,events) {
if (this.isPaused())
return 0; // continue
return 1; // continue
Engine.ProfileStart("Update Preparation");
@ -206,7 +207,7 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
this.assignUnits(gameState);
if ( (gameState.ai.turn + gameState.ai.player) % 40 == 0)
this.AllToRallyPoint(gameState);
this.AllToRallyPoint(gameState, false);
var canstart = this.canStart(gameState);
@ -222,15 +223,20 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
//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) {
debug ("Abandonning the idea of recruiting " + uneval(this.buildOrder[0][1]) + " as no recruitable template were found" );
delete this.unitStat[this.buildOrder[0][4]]; // deleting the associated unitstat.
this.buildOrder.splice(0,1);
} else {
this.queue.addItem( new UnitTrainingPlan(gameState,template, { "role" : "attack", "plan" : this.name, "special" : specialData },this.buildOrder[0][3]["batchSize"] ) );
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"] ) );
}
}
}
// 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
}
@ -288,9 +294,19 @@ CityAttack.prototype.ToRallyPoint = function(gameState,id)
gameState.getEntityById(id).move(this.rallyPoint[0],this.rallyPoint[1]);
}
// this sends all units back to the "rally point" by entity collections.
CityAttack.prototype.AllToRallyPoint = function(gameState) {
for (unitCat in this.unit) {
this.unit[unitCat].move(this.rallyPoint[0],this.rallyPoint[1]);
CityAttack.prototype.AllToRallyPoint = function(gameState, evenWorkers) {
var self = this;
if (evenWorkers) {
for (unitCat in this.unit) {
this.unit[unitCat].move(this.rallyPoint[0],this.rallyPoint[1]);
}
} else {
for (unitCat in this.unit) {
this.unit[unitCat].forEach(function (ent) {
if (ent.getMetadata("role") != "worker")
ent.move(self.rallyPoint[0],self.rallyPoint[1]);
});
}
}
}
@ -386,7 +402,7 @@ CityAttack.prototype.StartAttack = function(gameState, militaryManager){
this.path = pathsToEnemy[Math.min(2,pathsToEnemy.length-1)];
}
this.unitCollection.forEach(function(ent) { ent.setMetadata("subrole", "attacking");});
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");
@ -435,13 +451,27 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
if (e.type === "Attacked" && e.msg) {
if (IDs.indexOf(e.msg.target) !== -1) {
var attacker = gameState.getEntityById(e.msg.attacker);
if (attacker && attacker.position() && attacker.hasClass("Unit") && attacker.owner() != 0) {
var ourUnit = gameState.getEntityById(e.msg.target);
if (attacker && attacker.position() && attacker.hasClass("Unit") && attacker.owner() != 0 && attacker.owner() != gameState.player) {
var territoryMap = Map.createTerritoryMap(gameState);
if ( +territoryMap.point(attacker.position()) - 64 === +this.targetPlayer)
{
debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination.");
// we must assume we've arrived at the end of the trail.
this.state = "arrived";
}
if (militaryManager.enemyWatchers[attacker.owner()]) {
toProcess[attacker.id()] = attacker;
var armyID = militaryManager.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id());
armyToProcess[armyID[0]] = armyID[1];
}
}
// if we're being attacked by a building, flee.
if (attacker && ourUnit && attacker.hasClass("Structure")) {
ourUnit.flee(attacker);
}
}
}
}
@ -515,6 +545,9 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}*/
}
if (this.state === "walking"){
if (SquareVectorDistance(this.position, this.lastPosition) < 400 && this.path.length > 0) {
this.unitCollection.move(this.path[0][0], this.path[0][1]);
}
if (SquareVectorDistance(this.unitCollection.getCentrePosition(), this.path[0]) < 400){
this.path.shift();
if (this.path.length > 0){
@ -525,16 +558,38 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
this.state = "arrived";
}
}
if (this.position == this.lastPosition)
this.unitCollection.move(this.path[0][0], this.path[0][1]);
}
// todo: re-implement raiding
/*
if (this.state === "arrived"){
// let's proceed on with whatever happens now.
// There's a ton of TODOs on this part.
if (this.onArrivalReaction == "huntVillagers")
{
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 = "";
} else if (this.onArrivalReaction == "huntVillagers") {
// let's get any villager and target them with a tactics manager
var enemyCitizens = gameState.entities.filter(function(ent) {
return (gameState.isEntityEnemy(ent) && ent.hasClass("Support") && ent.owner() !== 0 && ent.position());
@ -553,6 +608,36 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}
}
}
if (this.state === ""){
// 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();
this.unitCollection.forEach( function (ent) { //}) {
if (ent.isIdle()) {
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();
}
}
});
}
/*
if (this.state === "huntVillagers")
{
this.tactics.eventMetadataCleanup(events,HeadQuarters);
@ -586,8 +671,10 @@ CityAttack.prototype.Abort = function(gameState){
ent.setMetadata("subrole",undefined);
ent.setMetadata("plan",undefined);
});
for (unitCat in this.unitStat)
for (unitCat in this.unitStat) {
delete this.unitStat[unitCat];
delete this.unit[unitCat];
}
delete this.unitCollection;
gameState.ai.queueManager.removeQueue("plan_" + this.name);
};

View File

@ -45,20 +45,20 @@ var baseConfig = {
"house" : 500,
"citizenSoldier" : 100,
"villager" : 150,
"economicBuilding" : 30,
"economicBuilding" : 50,
"field" : 20,
"advancedSoldier" : 30,
"siege" : 10,
"militaryBuilding" : 50,
"militaryBuilding" : 80,
"defenceBuilding" : 17,
"civilCentre" : 1000
},
"debug" : true
"debug" : false
};
var Config = {
"debug": true
"debug": false
};
Config.__proto__ = baseConfig;

View File

@ -1,16 +1,17 @@
// directly imported from Marilyn, with slight modifications to work with qBot.
function Defence(){
this.DefenceRatio = 1.35; // How many defenders we want per attacker. Need to balance fewer losses vs. lost economy
this.defenceRatio = 1.8; // How many defenders we want per attacker. Need to balance fewer losses vs. lost economy
// note: the choice should be a no-brainer most of the time: better deflect the attack.
this.totalAttackNb = 0; // used for attack IDs
this.attacks = [];
this.toKill = [];
this.defenders = {};
// keeps a list of targeted enemy at instant T
this.attackerCache = {};
this.listOfEnemies = {};
this.listedEnemyCollection = null; // entity collection of this.listOfEnemies
// boolean 0/1 that's for optimization
this.attackerCacheLoopIndicator = 0;
@ -19,6 +20,7 @@ function Defence(){
this.listOfWantedUnits = {};
this.WantedUnitsAttacker = {}; // same as attackerCache.
this.defenders = null;
this.idleDefs = null;
}
@ -44,6 +46,16 @@ Defence.prototype.update = function(gameState, events, militaryManager){
this.idleDefs = gameState.getOwnEntities().filter(filter);
this.idleDefs.registerUpdates();
}
if (!this.defenders) {
var filter = Filters.byMetadata("role", "defence");
this.defenders = gameState.getOwnEntities().filter(filter);
this.defenders.registerUpdates();
}
if (!this.listedEnemyCollection) {
var filter = Filters.byMetadata("listed-enemy", true);
this.listedEnemyCollection = gameState.getEnemyEntities().filter(filter);
this.listedEnemyCollection.registerUpdates();
}
this.myBuildings = gameState.getOwnEntities().filter(Filters.byClass("Structure")).toEntityArray();
this.myUnits = gameState.getOwnEntities().filter(Filters.byClass("Unit"));
@ -73,7 +85,7 @@ Defence.prototype.reevaluateDangerousArmies = function(gameState, armies) {
var stillDangerousArmies = {};
for (i in armies) {
var pos = armies[i].getCentrePosition();
if (+this.territoryMap.point(armies[i].getCentrePosition()) - 64 === +gameState.player) {
if (armies[i].getCentrePosition() && +this.territoryMap.point(armies[i].getCentrePosition()) - 64 === +gameState.player) {
stillDangerousArmies[i] = armies[i];
continue;
}
@ -158,6 +170,7 @@ Defence.prototype.defendFromEnemyArmies = function(gameState, events, militaryMa
this.attackerCache[unit.id()].forEach(function(ent) { ent.stopMoving(); });
delete this.attackerCache[unit.id()];
}
this.listOfEnemies[unit.id()].toEntityArray()[0].setMetadata("listed-enemy",undefined);
delete this.listOfEnemies[unit.id()];
}
}
@ -176,7 +189,8 @@ Defence.prototype.defendFromEnemyArmies = function(gameState, events, militaryMa
self.listOfEnemies[ent.id()] = self.enemyUnits[owner].filter(filter);
self.listOfEnemies[ent.id()].registerUpdates();
self.listOfEnemies[ent.id()].length;
self.listOfEnemies[ent.id()].toEntityArray()[0].setMetadata("listed-enemy",true);
// let's also register an entity collection for units attacking this unit (so we can new if it's attacked)
filter = Filters.and(Filters.byOwner(gameState.player),Filters.byTargetedEntity(ent.id()));
self.attackerCache[ent.id()] = self.myUnits.filter(filter);
@ -218,62 +232,79 @@ Defence.prototype.defendFromEnemyArmies = function(gameState, events, militaryMa
}
*/
// if here, there are known enemy attackers.
this.idleDefs.forEach(function(ent) {
// okaaay we attack
for (o in self.listOfEnemies) {
if (self.attackerCache[o].length === 0) {
ent.setMetadata("subrole", "defending");
ent.attack(+o);
nbOfAttackers--;
break;
}
}
});
// todo: improve the logic against attackers.
// reupdate the existing defenders.
// okay so if we lacked units with the Defence role that could be used, we'll sort through those from attack plans, pausing the plans if needed.
var newSoldiers = gameState.getOwnEntitiesByRole("attack");
newSoldiers.forEach(function(ent) {
if (ent.getMetadata("subrole","attacking"))
return;
if (nbOfAttackers <= 0)
return;
// okaaay we attack
for (o in self.listOfEnemies) {
if (self.attackerCache[o].length === 0) {
if (ent.getMetadata("plan") != undefined)
militaryManager.pausePlan(gameState,ent.getMetadata("plan"));
ent.setMetadata("formerrole", "attack");
ent.setMetadata("role","defence");
ent.setMetadata("subrole","defending");
ent.attack(+o);
nbOfAttackers--;
break;
}
}
this.idleDefs.forEach(function(ent) {
ent.setMetadata("subrole","newdefender");
});
// still some undealt with attackers, launch the citizen soldiers
if (nbOfAttackers > 0) {
var newSoldiers = gameState.getOwnEntitiesByRole("worker");
nbOfAttackers *= this.defenceRatio;
// Assume those taken care of.
nbOfAttackers -= +(this.defenders.length);
// need new units?
if (nbOfAttackers <= 0)
return;
// 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)
return true;
return false;
});
newSoldiers.forEach(function(ent) {
if (ent.getMetadata("subrole","attacking")) // gone with the wind to avenge their brothers.
return;
if (nbOfAttackers <= 0)
return;
// If we're not female, we attack
if (ent.hasClass("CitizenSoldier"))
for (o in self.listOfEnemies) {
if (self.attackerCache[o].length === 0) {
ent.setMetadata("formerrole", "worker");
ent.setMetadata("role","defence");
ent.setMetadata("subrole","defending");
ent.attack(+o);
nbOfAttackers--;
break;
}
militaryManager.pausePlan(gameState,ent.getMetadata("plan"));
ent.setMetadata("formerrole", ent.getMetadata("role"));
ent.setMetadata("role","defence");
ent.setMetadata("subrole","newdefender");
nbOfAttackers--;
});
if (nbOfAttackers > 0) {
newSoldiers = gameState.getOwnEntitiesByRole("worker");
newSoldiers.forEach(function(ent) {
if (nbOfAttackers <= 0)
return;
// If we're not female, we attack
// and if we're not already assigned from above (might happen, not sure, rather be cautious)
if (ent.hasClass("CitizenSoldier") && ent.getMetadata("subrole") != "newdefender") {
ent.setMetadata("formerrole", "worker");
ent.setMetadata("role","defence");
ent.setMetadata("subrole","newdefender");
nbOfAttackers--;
}
});
});
}
// okay
newSoldiers = gameState.getOwnEntitiesByMetadata("subrole","newdefender");
// TODO. For now, each unit will pick the closest unit that is attacked by only one/zero guy, or any if there is none.
// ought to regroup them first for optimization.
newSoldiers.forEach(function(ent) { //}) {
var enemies = self.listedEnemyCollection.filterNearest(ent.position()).toEntityArray();
var target = -1;
var secondaryTarget = enemies[0]; // second best pick
for (o in enemies) {
var enemy = enemies[o];
if (self.attackerCache[enemy.id()].length < 2) {
target = +enemy.id();
break;
}
}
ent.setMetadata("subrole","defending");
ent.attack(+target);
});
return;
}
@ -287,8 +318,8 @@ Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
if (gameState.isEntityOwn(gameState.getEntityById(e.msg.target))) {
var attacker = gameState.getEntityById(e.msg.attacker);
var ourUnit = gameState.getEntityById(e.msg.target);
// the attacker must not be already dead
if (attacker !== undefined) {
// the attacker must not be already dead, and it must not be me (think catapults that miss).
if (attacker !== undefined && attacker.owner() !== gameState.player) {
// note: our unit can already by dead by now... We'll then have to rely on the enemy to react.
// if we're not on enemy territory
var territory = +this.territoryMap.point(attacker.position()) - 64;
@ -340,7 +371,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") && ourUnit.getMetadata("role") != "attack") {
if (ourUnit.hasClass("Support")) {
// TODO: it's a villager. Garrison it.
// TODO: make other neighboring villagers garrison

View File

@ -8,6 +8,8 @@ var EconomyManager = function() {
//turns before trying to reassign them.
this.femaleRatio = 0.4;
this.farmingFields = false;
this.dropsiteNumbers = {wood: 2, stone: 1, metal: 1};
};
@ -235,20 +237,23 @@ EconomyManager.prototype.assignToFoundations = function(gameState) {
};
EconomyManager.prototype.buildMoreFields = function(gameState, queues) {
var numFood = 0;
if (this.farmingFields) {
var numFarms = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_field"));
numFarms += queues.field.countTotalQueuedUnits();
gameState.updatingCollection("active-dropsite-food", Filters.byMetadata("active-dropsite-food", true),
gameState.getOwnDropsites("food")).forEach(function (dropsite){
numFood += dropsite.getMetadata("nearby-resources-food").length;
});
var numFarms = gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_field"));
numFarms += queues.field.countTotalQueuedUnits();
if (gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_field")) == 0)
numFood = Math.floor(numFood/1.5);
if (numFood+numFarms < this.targetNumFields)
queues.field.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_field"));
if (numFarms < this.targetNumFields + Math.floor(gameState.getTimeElapsed() / 900000))
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();
});
});
if (foodAmount < 300)
this.farmingFields = true;
}
};
// If all the CC's are destroyed then build a new one
@ -266,7 +271,7 @@ 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':13, 'stone': 10, 'metal': 10, 'food': 10};
var radius = {'wood':9, 'stone': 10, 'metal': 10, 'food': 12};
var self = this;
@ -431,7 +436,7 @@ EconomyManager.prototype.updateNearbyResources = function(gameState){
//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: 16000, stone: 300, metal: 300};
var requiredInfluence = {wood: 1400, stone: 200, metal: 200};
var count = 0;
gameState.getOwnEntities().forEach(function(ent) {
if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){
@ -446,7 +451,7 @@ EconomyManager.prototype.checkResourceConcentrations = function(gameState, resou
};
EconomyManager.prototype.buildMarket = function(gameState, queues){
if (gameState.getTimeElapsed() > 600 * 1000){
if (gameState.getTimeElapsed() > 360 * 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
@ -478,7 +483,7 @@ 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 &&
@ -522,7 +527,8 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
Engine.ProfileStart("Train workers and build farms");
this.trainMoreWorkers(gameState, queues);
this.buildMoreFields(gameState,queues);
if (gameState.getTimeElapsed() > 5000)
this.buildMoreFields(gameState,queues);
Engine.ProfileStop();
//Later in the game we want to build stuff faster.
@ -532,7 +538,7 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
this.targetNumBuilders = 3;
}
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.8) {
if (gameState.getTimeElapsed() > 20*60*1000) {
this.dropsiteNumbers = {wood: 3, stone: 2, metal: 2};
}else{
this.dropsiteNumbers = {wood: 2, stone: 1, metal: 1};

View File

@ -33,6 +33,17 @@ EntityTemplate.prototype.garrisonMax = function() {
return undefined;
return this._template.GarrisonHolder.Max;
};
EntityTemplate.prototype.hasClasses = function(array) {
var classes = this.classes();
if (!classes)
return false;
for (i in array)
if (classes.indexOf(array[i]) === -1)
return false;
return true;
};
EntityTemplate.prototype.getMaxStrength = function()
{
var strength = 0.0;
@ -126,12 +137,6 @@ Entity.prototype.garrison = function(target) {
return this;
};
Entity.prototype.attack = function(unitId)
{
Engine.PostCommand({"type": "attack", "entities": [this.id()], "target": unitId, "queued": false});
return this;
};
Entity.prototype.stopMoving = function() {
if (this.position() !== undefined)
Engine.PostCommand({"type": "walk", "entities": [this.id()], "x": this.position()[0], "z": this.position()[1], "queued": false});
@ -140,6 +145,9 @@ Entity.prototype.stopMoving = function() {
Entity.prototype.flee = function(unitToFleeFrom) {
if (this.position() !== undefined && unitToFleeFrom.position() !== undefined) {
var FleeDirection = [unitToFleeFrom.position()[0] - this.position()[0],unitToFleeFrom.position()[1] - this.position()[1]];
var dist = VectorDistance(unitToFleeFrom.position(), this.position() );
FleeDirection[0] = (FleeDirection[0]/dist) * 5;
FleeDirection[1] = (FleeDirection[1]/dist) * 5;
Engine.PostCommand({"type": "walk", "entities": [this.id()], "x": this.position()[0] + FleeDirection[0]*5, "z": this.position()[1] + FleeDirection[1]*5, "queued": false});
}

View File

@ -43,4 +43,27 @@ EntityCollection.prototype.getCentrePosition = function(){
}else{
return [sumPos[0]/count, sumPos[1]/count];
}
};
};
EntityCollection.prototype.filterNearest = function(targetPos, n)
{
// Compute the distance of each entity
var data = []; // [ [id, ent, distance], ... ]
for (var id in this._entities)
{
var ent = this._entities[id];
if (ent.position())
data.push([id, ent, SquareVectorDistance(targetPos, ent.position())]);
}
// Sort by increasing distance
data.sort(function (a, b) { return (a[2] - b[2]); });
if (n === undefined)
n = this._length;
// Extract the first n
var ret = {};
for each (var val in data.slice(0, n))
ret[val[0]] = val[1];
return new EntityCollection(this._ai, ret);
};

View File

@ -4,6 +4,8 @@ var HousingManager = function() {
};
HousingManager.prototype.buildMoreHouses = function(gameState, queues) {
if (gameState.getTimeElapsed() < 25000)
return;
// temporary 'remaining population space' based check, need to do
// predictive in future
if (gameState.getPopulationLimit() - gameState.getPopulation() < 20

View File

@ -374,7 +374,6 @@ MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv('structures/{civ}_defense_tower'))
+ queues.defenceBuilding.totalLength() < gameState.getBuildLimits()["DefenseTower"]) {
gameState.getOwnEntities().forEach(function(dropsiteEnt) {
if (dropsiteEnt.resourceDropsiteTypes() && dropsiteEnt.getMetadata("defenseTower") !== true){
var position = dropsiteEnt.position();
@ -503,7 +502,8 @@ MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
this.constructTrainingBuildings(gameState, queues);
this.buildDefences(gameState, queues);
if(gameState.getTimeElapsed() > 300*1000)
this.buildDefences(gameState, queues);
for (watcher in this.enemyWatchers)
this.enemyWatchers[watcher].detectArmies(gameState,this);

View File

@ -128,6 +128,9 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
var radius = Math.ceil(template.obstructionRadius() / cellSize) + 2;
else
var radius = Math.ceil(template.obstructionRadius() / cellSize);
if (gameState.playerData.civ == "iber")
radius *= 0.95;
// Find the best non-obstructed tile
var bestTile = friendlyTiles.findBestTile(radius, obstructionMap);

View File

@ -5,4 +5,3 @@ Install by placing the files into the data/mods/public/simulation/ai/qbot folder
If you are developing you might find it helpful to change the debugOn line in qBot.js. This will make it spew random warnings depending on what I have been working on. Use the debug() function to make your own warnings.
Addendum for the experimental version: this is the regular qBot, with improvements here and there. It's "experimental" as it is currently in "test" phase, and if it proves to work and be more efficient than the normal qBot, will replace it in the next alpha. Please report any error to the wildfire games forum ( http://www.wildfiregames.com/forum/index.php?act=idx ), thanks for playing the bot!
(note:debug is activated by default on this one. Deactivate it in the config file.)

View File

@ -36,9 +36,9 @@ Worker.prototype.update = function(gameState) {
//Engine.PostCommand({"type": "set-shading-color", "entities": [this.ent.id()], "rgb": [10,0,0]});
}else{
// If we haven't reached the resource in 2 minutes twice in a row and none of the resource has been
// If we haven't reached the resource in 1 minutes twice in a row and none of the resource has been
// gathered then mark it as inaccessible.
if (gameState.getTimeElapsed() - this.startApproachingResourceTime > 120000){
if (gameState.getTimeElapsed() - this.startApproachingResourceTime > 60000){
if (this.gatheringFrom){
var ent = gameState.getEntityById(this.gatheringFrom);
if (ent && ent.resourceSupplyAmount() == ent.resourceSupplyMax()){
@ -102,7 +102,7 @@ Worker.prototype.updateGathererCounts = function(gameState, dead){
};
Worker.prototype.markFull = function(ent){
var maxCounts = {"food": 20, "wood": 5, "metal": 20, "stone": 20, "treasure": 1};
var maxCounts = {"food": 15, "wood": 5, "metal": 15, "stone": 15, "treasure": 1};
if (ent.resourceSupplyType() && ent.getMetadata("gatherer-count") >= maxCounts[ent.resourceSupplyType().generic]){
if (!ent.getMetadata("full")){
ent.setMetadata("full", true);
@ -171,7 +171,9 @@ Worker.prototype.startGathering = function(gameState){
if (!supply.position()){
return;
}
if (supply.getMetadata("full") == true) {
return;
}
// measure the distance to the resource
var dist = VectorDistance(supply.position(), ent.position());
// Add on a factor for the nearest dropsite if one exists