diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/attack_plan.js b/binaries/data/mods/public/simulation/ai/qbot-wc/attack_plan.js index 23b8e7a813..56517af3b1 100755 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/attack_plan.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/attack_plan.js @@ -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); }; diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/config.js b/binaries/data/mods/public/simulation/ai/qbot-wc/config.js index 213077af2b..e7cc7f37b6 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/config.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/config.js @@ -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; \ No newline at end of file diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/defence.js b/binaries/data/mods/public/simulation/ai/qbot-wc/defence.js index 92925dd487..9f890f15ac 100755 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/defence.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/defence.js @@ -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 diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js b/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js index 16b195cf07..03a996a2be 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js @@ -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}; diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/entity-extend.js b/binaries/data/mods/public/simulation/ai/qbot-wc/entity-extend.js index 81092d8be7..1f9920859b 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/entity-extend.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/entity-extend.js @@ -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}); } diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/entitycollection-extend.js b/binaries/data/mods/public/simulation/ai/qbot-wc/entitycollection-extend.js index 67dc80a72f..68a01e653f 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/entitycollection-extend.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/entitycollection-extend.js @@ -43,4 +43,27 @@ EntityCollection.prototype.getCentrePosition = function(){ }else{ return [sumPos[0]/count, sumPos[1]/count]; } -}; \ No newline at end of file +}; + +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); +}; diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/housing.js b/binaries/data/mods/public/simulation/ai/qbot-wc/housing.js index 3b07026863..74ddae70a3 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/housing.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/housing.js @@ -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 diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/military.js b/binaries/data/mods/public/simulation/ai/qbot-wc/military.js index 7cc5a4bd33..c6d183f393 100755 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/military.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/military.js @@ -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); diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/plan-building.js b/binaries/data/mods/public/simulation/ai/qbot-wc/plan-building.js index 5c6512739b..69580ff7e0 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/plan-building.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/plan-building.js @@ -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); diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/readme.txt b/binaries/data/mods/public/simulation/ai/qbot-wc/readme.txt index 0ccb974440..fe0b79adc5 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/readme.txt +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/readme.txt @@ -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.) \ No newline at end of file diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/worker.js b/binaries/data/mods/public/simulation/ai/qbot-wc/worker.js index 2a3772bd1a..12a89bb1db 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/worker.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/worker.js @@ -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