From 2c17ab70accfd534447d0e9de92b993ec70238f3 Mon Sep 17 00:00:00 2001 From: wraitii Date: Sat, 9 Mar 2013 14:09:06 +0000 Subject: [PATCH] Add support for -autostart-civ to set civilizations when quickstarting. Put the AI memory heap back to 32 MB to avoid OOM errors with numerous AIs in late game. Fix a bug that made ProductionQueue not broadcast progress. Fix many issues with Aegis in defense, pathfinding, foundation construction, training building choice, strength calculations, building placement and mostly attack. Aegis should be much more aggressive. This was SVN commit r13247. --- .../simulation/ai/common-api-v3/entity.js | 2 +- .../simulation/ai/common-api-v3/gamestate.js | 25 +- .../terrain-analysis-pathfinder.js | 2 +- .../simulation/ai/qbot-wc/attack_plan.js | 221 ++++++++++++------ .../public/simulation/ai/qbot-wc/config.js | 14 +- .../public/simulation/ai/qbot-wc/defence.js | 172 +++++++------- .../public/simulation/ai/qbot-wc/economy.js | 130 +++++++---- .../simulation/ai/qbot-wc/enemy-watcher.js | 2 +- .../simulation/ai/qbot-wc/entity-extend.js | 5 +- .../public/simulation/ai/qbot-wc/military.js | 101 +++++--- .../simulation/ai/qbot-wc/plan-building.js | 8 +- .../simulation/ai/qbot-wc/plan-training.js | 18 +- .../mods/public/simulation/ai/qbot-wc/qbot.js | 17 ++ .../simulation/ai/qbot-wc/queue-manager.js | 8 +- .../public/simulation/ai/qbot-wc/worker.js | 5 +- .../simulation/components/ProductionQueue.js | 2 + source/ps/GameSetup/GameSetup.cpp | 21 ++ .../simulation2/components/CCmpAIManager.cpp | 6 +- 18 files changed, 501 insertions(+), 258 deletions(-) diff --git a/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js b/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js index 110c99dc9b..b79f91c61a 100644 --- a/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js +++ b/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js @@ -676,7 +676,7 @@ var Entity = Class({ for (i in queue) { if (queue[i].progress < percentToStopAt) - Engine.PostCommand({ "type": "stop-production", "entity": this.id(), "id": queue[i].id }); + Engine.PostCommand({ "type": "stop-production", "entity": this.id(), "id": queue[i].id }); } return this; } diff --git a/binaries/data/mods/public/simulation/ai/common-api-v3/gamestate.js b/binaries/data/mods/public/simulation/ai/common-api-v3/gamestate.js index 5c755792c6..169fecbd93 100644 --- a/binaries/data/mods/public/simulation/ai/common-api-v3/gamestate.js +++ b/binaries/data/mods/public/simulation/ai/common-api-v3/gamestate.js @@ -109,6 +109,11 @@ GameState.prototype.isResearched = function(template) { return this.playerData.researchedTechs[template] !== undefined; }; +// true if started or queued +GameState.prototype.isResearching = function(template) +{ + return (this.playerData.researchStarted[template] !== undefined || this.playerData.researchQueued[template] !== undefined); +}; // this is an absolute check that doesn't check if we have a building to research from. GameState.prototype.canResearch = function(techTemplateName, noRequirementCheck) @@ -475,14 +480,26 @@ GameState.prototype.findResearchers = function(templateName) { // let's check we can research the tech. if (!this.canResearch(templateName)) return []; - + var template = this.getTemplate(templateName); + var self = this; return this.getOwnResearchFacilities().filter(function(ent) { //}){ var techs = ent.researchableTechs(); - if (!techs || (template.pair() && techs.indexOf(template.pair()) === -1) || (!template.pair() && techs.indexOf(templateName) === -1)) - return false; - return true; + for (i in techs) + { + var thisTemp = self.getTemplate(techs[i]); + if (thisTemp.pairDef()) + { + var pairedTechs = thisTemp.getPairedTechs(); + if (pairedTechs[0]._templateName == templateName || pairedTechs[1]._templateName == templateName) + return true; + } else { + if (techs[i] == templateName) + return true; + } + } + return false; }); }; diff --git a/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis-pathfinder.js b/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis-pathfinder.js index 90f5b078bf..5ae2d2be7f 100644 --- a/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis-pathfinder.js +++ b/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis-pathfinder.js @@ -75,7 +75,7 @@ aStarPath.prototype.getPath = function(start, end, Sampling, minWidth, iteration if (start[0] < 0 || this.gamePosToMapPos(start)[0] >= this.width || start[1] < 0 || this.gamePosToMapPos(start)[1] >= this.height) return undefined; - var s = this.terrainAnalyzer.findClosestPassablePoint(this.gamePosToMapPos(start), !this.waterPathfinder,500,true); + var s = this.terrainAnalyzer.findClosestPassablePoint(this.gamePosToMapPos(start), !this.waterPathfinder,500,false); var e = this.terrainAnalyzer.findClosestPassablePoint(this.gamePosToMapPos(end), !this.waterPathfinder,500,true); var w = this.width; 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 c7c9b526fa..8453a7299c 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 @@ -22,14 +22,19 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta } if (this.targetPlayer === undefined || this.targetPlayer === -1) return false; - debug ("Target = " +this.targetPlayer); + + var CCs = gameState.getOwnEntities().filter(Filters.byClass("CivCentre")); + if (CCs.length === 0) + return false; + + debug ("Target (" + PlayerID +") = " +this.targetPlayer); this.targetFinder = targetFinder || this.defaultTargetFinder; this.type = type || "normal"; this.name = uniqueID; this.healthRecord = []; this.timeOfPlanStart = gameState.getTimeElapsed(); // we get the time at which we decided to start the attack - this.maxPreparationTime = 300*1000; + this.maxPreparationTime = 180*1000; this.pausingStart = 0; this.totalPausingTime = 0; @@ -44,14 +49,14 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta // 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"], - "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; + "interests" : [ ["canGather", 2], ["strength",2], ["cost",1] ], "templates" : [] }; this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 10, "batchSize" : 5, "classes" : ["Infantry","Melee"], - "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; + "interests" : [ ["canGather", 2], ["strength",2], ["cost",1] ], "templates" : [] }; this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 8 , "batchSize" : 3, "classes" : ["Cavalry","Melee"], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 8 , "batchSize" : 3, "classes" : ["Cavalry","Ranged"], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; - this.unitStat["Siege"] = { "priority" : 0.5, "minSize" : 0, "targetSize" : 3 , "batchSize" : 1, "classes" : ["Siege"], "interests" : [ ["siegeStrength", 3], ["cost",1] ], "templates" : [] }; + this.unitStat["Siege"] = { "priority" : 0.8, "minSize" : 0, "targetSize" : 3 , "batchSize" : 2, "classes" : ["Siege"], "interests" : [ ["siegeStrength", 3], ["cost",1] ], "templates" : [] }; var priority = 60; if (type === "rush") { @@ -65,16 +70,28 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta this.maxPreparationTime = 150*1000; priority = 150; } else if (type === "superSized") { - this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 18, "batchSize" : 5, "classes" : ["Infantry","Ranged"], + + this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 16, "batchSize" : 5, "classes" : ["Infantry","Ranged"], + "interests" : [["strength",3], ["cost",1] ], "templates" : [] }; + this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 6, "targetSize" : 20, "batchSize" : 5, "classes" : ["Infantry","Melee"], + "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] }; + this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 3, "classes" : ["Cavalry","Melee"], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; - this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 6, "targetSize" : 24, "batchSize" : 5, "classes" : ["Infantry","Melee"], + this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 3, "classes" : ["Cavalry","Ranged"], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; - this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 5, "classes" : ["Cavalry","Melee"], - "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; - this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 5, "classes" : ["Cavalry","Ranged"], - "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; - this.unitStat["Siege"] = { "priority" : 0.5, "minSize" : 3, "targetSize" : 6 , "batchSize" : 3, "classes" : ["Siege"],"interests" : [ ["siegeStrength",3], ["cost",1] ], "templates" : [] }; - this.maxPreparationTime = 450*1000; + this.unitStat["Siege"] = { "priority" : 1, "minSize" : 2, "targetSize" : 6 , "batchSize" : 2, "classes" : ["Siege"],"interests" : [ ["siegeStrength",3], ["cost",1] ], "templates" : [] }; + + // Okay so here there is some civ specificity: some have buildings to train champions (or can even in the barracks) so they don't have to have tons of fortresses. + // Some don't, so we can't really rely on champions for the armies to a great extent. + if (gameState.civ() == "gaul" || gameState.civ() == "brit") + { + this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 16, "batchSize" : 5, "classes" : ["Infantry","Ranged"], "interests" : [["strength",3], ["cost",1] ], "templates" : [] }; + this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 15, "batchSize" : 5, "classes" : ["Infantry","Melee", "CitizenSoldier" ], "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] }; + this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 2, "targetSize" : 9 , "batchSize" : 5, "classes" : ["Cavalry","Melee", "CitizenSoldier" ], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; + this.unitStat["ChampMeleeInfantry"] = { "priority" : 0.8, "minSize" : 3, "targetSize" : 6, "batchSize" : 3, "classes" : ["Infantry","Melee", "Champion" ], "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] }; + this.unitStat["ChampMeleeCavalry"] = { "priority" : 0.8, "minSize" : 2, "targetSize" : 3 , "batchSize" : 3, "classes" : ["Cavalry","Melee", "Champion" ], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; + this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 3, "classes" : ["Cavalry","Ranged"], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; + } priority = 70; } else if (gameState.getTimeElapsed() > 15*60*1000) this.unitStat["Siege"]["minSize"] = 2; @@ -161,7 +178,7 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta else var position = [-1,-1]; - var CCs = gameState.getOwnEntities().filter(Filters.byClass("CivCentre")); + // abort. var nearestCCArray = CCs.filterNearest(position, 1).toEntityArray(); var CCpos = nearestCCArray[0].position(); this.rallyPoint = [0,0]; @@ -179,6 +196,7 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta } // some variables for during the attack + this.position20TurnsAgo = [0,0]; this.lastPosition = [0,0]; this.position = [0,0]; @@ -249,7 +267,7 @@ CityAttack.prototype.mustStart = function(gameState){ MaxReachedEverywhere = false; } } - if (MaxReachedEverywhere) + if (MaxReachedEverywhere || (gameState.getPopulationMax() - gameState.getPopulation() < 10 && this.canStart(gameState))) return true; return (this.maxPreparationTime + this.timeOfPlanStart + this.totalPausingTime < gameState.getTimeElapsed()); }; @@ -294,7 +312,8 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve debug ("This is actually a water map."); gameState.ai.waterMap = true; } - this.unitStat["TransportShip"] = { "priority" : 1.1, "minSize" : 2, "targetSize" : 2, "batchSize" : 1, "classes" : ["Warship"], "templates" : [] }; + this.unitStat["TransportShip"] = { "priority" : 1.1, "minSize" : 2, "targetSize" : 2, "batchSize" : 1, "classes" : ["Warship"], + "interests" : [ ["strength",1], ["cost",1] ] ,"templates" : [] }; if (type === "superSized") { this.unitStat["TransportShip"]["minSize"] = 4; this.unitStat["TransportShip"]["targetSize"] = 4; @@ -319,10 +338,15 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve Engine.ProfileStart("Update Preparation"); // keep on while the units finish being trained. - if (this.mustStart(gameState) && (gameState.countOwnQueuedEntitiesWithMetadata("plan", +this.name) + this.queue.countTotalQueuedUnits()) > 0 ) { + if (this.mustStart(gameState) && (gameState.countOwnQueuedEntitiesWithMetadata("plan", +this.name) > 0 || (gameState.getPopulationMax() - gameState.getPopulation() < 10) )) { this.assignUnits(gameState); + + if (this.queue.length()) + this.queue.empty(); + if ( (gameState.ai.turn + gameState.ai.player) % 40 == 0) { - this.AllToRallyPoint(gameState, true); // gain some time, start regrouping + this.AllToRallyPoint(gameState, false); + // TODO: should use this time to let gatherers deposit resources. } Engine.ProfileStop(); return 1; @@ -333,26 +357,25 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve // 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]); + var aQueued = gameState.countOwnQueuedEntitiesWithMetadata("special","Plan_"+self.name+"_"+a[4]); + aQueued += self.queue.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+a[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]); + var bQueued = gameState.countOwnQueuedEntitiesWithMetadata("special","Plan_"+self.name+"_"+b[4]); + bQueued += self.queue.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+b[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) { + if ( (gameState.ai.turn + gameState.ai.player) % 15 == 0) { this.AllToRallyPoint(gameState, false); - this.unitCollection.setStance("defensive"); // make sure units won't disperse out of control + this.unitCollection.setStance("standground"); // make sure units won't disperse out of control } } @@ -361,13 +384,17 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve // 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) { + + if (this.queue.countTotalQueuedUnitsWithMetadata("special",specialData) + inTraining + this.buildOrder[0][2].length <= this.buildOrder[0][3]["targetSize"]) { + if (this.buildOrder[0][0] < 1 && this.queue.length() <= 4) { var template = militaryManager.findBestTrainableUnit(gameState, this.buildOrder[0][1], this.buildOrder[0][3]["interests"] ); //debug ("tried " + uneval(this.buildOrder[0][1]) +", and " + template); // HACK (TODO replace) : if we have no trainable template... Then we'll simply remove the buildOrder, effectively removing the unit from the plan. if (template === undefined) { + // TODO: this is a complete hack. + if (this.needsShip && this.buildOrder[0][4] == "TransportShip") + return 0; delete this.unitStat[this.buildOrder[0][4]]; // deleting the associated unitstat. this.buildOrder.splice(0,1); } else { @@ -444,11 +471,14 @@ CityAttack.prototype.assignUnits = function(gameState){ if (this.type === "rush") NoRole = gameState.getOwnEntitiesByRole("worker"); NoRole.forEach(function(ent) { - if (ent.hasClasses(["CitizenSoldier", "Infantry"])) - ent.setMetadata(PlayerID, "role", "worker"); - else - ent.setMetadata(PlayerID, "role", "attack"); - ent.setMetadata(PlayerID, "plan", self.name); + if (ent.hasClass("Unit") && ent.attackTypes() !== undefined) + { + if (ent.hasClasses(["CitizenSoldier", "Infantry"])) + ent.setMetadata(PlayerID, "role", "worker"); + else + ent.setMetadata(PlayerID, "role", "attack"); + ent.setMetadata(PlayerID, "plan", self.name); + } }); }; @@ -472,7 +502,7 @@ CityAttack.prototype.AllToRallyPoint = function(gameState, evenWorkers) { } else { for (unitCat in this.unit) { this.unit[unitCat].forEach(function (ent) { - if (ent.getMetadata(PlayerID, "role") != "worker" && ent.getMetadata(PlayerID, "role") != "defence" && !ent.hasClass("Warship")) + if (ent.getMetadata(PlayerID, "role") != "worker" && ent.getMetadata(PlayerID, "role") != "defence" && !ent.hasClass("Warship")) ent.move(self.rallyPoint[0],self.rallyPoint[1]); }); } @@ -496,7 +526,7 @@ CityAttack.prototype.defaultTargetFinder = function(gameState, militaryManager){ } // 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")); + targets = gameState.getEnemyEntities().filter(Filters.and( Filters.byOwner(this.targetPlayer),Filters.byClass("ConquestCritical"))); } debug ("target is " + targets); return targets; @@ -556,7 +586,6 @@ CityAttack.prototype.StartAttack = function(gameState, militaryManager){ this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]); this.unitCollection.setStance("aggressive"); // make sure units won't disperse out of control - debug ("Started to attack with the plan " + this.name); this.state = "walking"; } else { gameState.ai.gameFinished = true; @@ -586,6 +615,8 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ // this actually doesn't do anything right now. if (this.state === "walking") { + var attackedNB = 0; + var toProcess = {}; var armyToProcess = {}; // Let's check if any of our unit has been attacked. In case yes, we'll determine if we're simply off against an enemy army, a lone unit/builing @@ -602,9 +633,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ 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"; + attackedNB++; } //if (militaryManager.enemyWatchers[attacker.owner()]) { //toProcess[attacker.id()] = attacker; @@ -619,15 +648,11 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ } } } - - // I don't process attacks if I'm in their base because I'll have already gone to "attacking" mode. - // I'll process by army - var total = 0; - for (armyID in armyToProcess) { - total += armyToProcess[armyID].length; - // TODO: if it's a big army, we may want to refer the scouting/defense manager + if (attackedNB > 4) { + 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"; } - /* @@ -697,8 +722,13 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ return undefined; // should spawn an error. // basically haven't moved an inch: very likely stuck) - if (this.position[0] == this.lastPosition[0] && this.position[1] == this.lastPosition[1] && this.path.length > 0) { + if (SquareVectorDistance(this.position, this.position20TurnsAgo) < 10 && this.path.length > 0 && gameState.ai.playedTurn % 100 === 0) { + + if (gameState.ai.playedTurn % 100 === 0) + this.position20TurnsAgo = this.position; + // check for stuck siege units + var sieges = this.unitCollection.filter(Filters.byClass("Siege")); var farthest = 0; var farthestEnt = -1; @@ -713,7 +743,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ farthestEnt.destroy(); } - if (SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) { + if (this.lastPosition && SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) { this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).moveIndiv(this.path[0][0][0], this.path[0][0][1]); // We're stuck, presumably. Check if there are no walls just close to us. If so, we're arrived, and we're gonna tear down some serious stone. var walls = gameState.getEnemyEntities().filter(Filters.and(Filters.byOwner(this.targetPlayer), Filters.byClass("StoneWall"))); @@ -735,8 +765,10 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ } // check if our land units are close enough from the next waypoint. - if (SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.path[0][0]) < 600) { + if (SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.targetPos) < 8000 || + SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.path[0][0]) < 600) { if (this.unitCollection.filter(Filters.byClass("Siege")).length !== 0 + && SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.targetPos) > 8000 && SquareVectorDistance(this.unitCollection.filter(Filters.byClass("Siege")).getCentrePosition(), this.path[0][0]) > 600) { } else { @@ -767,6 +799,9 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ } } else if (this.state === "shipping") { this.position = this.unitCollection.filter(Filters.byClass("Warship")).getCentrePosition(); + + if (!this.lastPosition) + this.lastPosition = [0,0]; if (SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) { this.unitCollection.filter(Filters.byClass("Warship")).moveIndiv(this.path[0][0][0], this.path[0][0][1]); @@ -905,13 +940,15 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ { this.tactics = new Tactics(gameState,HeadQuarters, this.idList,targetList); this.state = "huntVillagers"; + var arrivedthisTurn = true; } else { this.state = ""; + var arrivedthisTurn = true; } } } - if (this.state === "" && gameState.ai.playedTurn % 3 === 0) { + if (this.state === "") { // Units attacked will target their attacker unless they're siege. Then we take another non-siege unit to attack them. for (var key in events) { @@ -928,9 +965,10 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ if (help.length === 0) help = this.unitCollection.filter(Filters.not(Filters.byClass("Siege"))); if (help.length > 0) - { help.toEntityArray()[0].attack(attacker.id()); - } + if (help.length > 1) + help.toEntityArray()[1].attack(attacker.id()); + } else { ourUnit.attack(attacker.id()); } @@ -955,20 +993,22 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ // if the unit is not in my territory, make it move. var territoryMap = Map.createTerritoryMap(gameState); if (territoryMap.point(ent.position()) - 64 === PlayerID) - ent.move((this.targetPos[0] + ent.position()[0])/2,(this.targetPos[1] + ent.position()[1])/2); + ent.move(this.targetPos[0],this.targetPos[1]); // update it. var needsUpdate = false; if (ent.isIdle()) needsUpdate = true; - // always update siege units, they tend to target nothing we want. if (ent.hasClass("Siege")) needsUpdate = true; else if (ent.unitAIOrderData() && ent.unitAIOrderData()["target"] && gameState.getEntityById(ent.unitAIOrderData()["target"]).hasClass("Structure")) needsUpdate = true; // try to make it attack a unit instead - if (needsUpdate) + if (gameState.getTimeElapsed() - ent.getMetadata(PlayerID, "lastAttackPlanUpdateTime") < 10000) + needsUpdate = false; + + if (needsUpdate || arrivedthisTurn) { - //warn ("Entity " +ent.id() + ", a " + ent._templateName + " needs updating."); + ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", gameState.getTimeElapsed()); var mStruct = enemyStructures.filter(function (enemy) { //}){ if (!enemy.position() || (enemy.hasClass("StoneWall") && ent.canAttackClass("StoneWall"))) { return false; @@ -978,15 +1018,31 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ } return true; }); - var mUnit = enemyUnits.filter(function (enemy) { //}){ - if (!enemy.position()) { - return false; - } - if (SquareVectorDistance(enemy.position(),ent.position()) > 2000) { - return false; - } - return true; - }); + var mUnit; + if (ent.hasClass("Cavalry")) { + mUnit = enemyUnits.filter(function (enemy) { //}){ + if (!enemy.position()) { + return false; + } + if (!enemy.hasClass("Support")) + return false; + if (SquareVectorDistance(enemy.position(),ent.position()) > 2000) { + return false; + } + return true; + }); + } + if (!ent.hasClass("Cavalry") || mUnit.length === 0) { + mUnit = enemyUnits.filter(function (enemy) { //}){ + if (!enemy.position()) { + return false; + } + if (SquareVectorDistance(enemy.position(),ent.position()) > 2000) { + return false; + } + return true; + }); + } var isGate = false; mUnit = mUnit.toEntityArray(); mStruct = mStruct.toEntityArray(); @@ -1022,7 +1078,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ } } 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); + ent.move(self.targetPos[0],self.targetPos[1]); } } else { if (mUnit.length !== 0 && !isGate) { @@ -1057,7 +1113,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ } } 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); + ent.move(self.targetPos[0],self.targetPos[1]); } } } @@ -1065,6 +1121,35 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){ this.unitCollUpdateArray.splice(0,1); } } + // updating targets. + if (!gameState.getEntityById(this.target.id())) + { + debug ("Seems like our target has been destroyed. Switching."); + + var targets = this.targetFinder(gameState, militaryManager); + if (targets.length === 0){ + targets = this.defaultTargetFinder(gameState, militaryManager); + } + if (targets.length) { + // picking a target + var rand = Math.floor((Math.random()*targets.length)); + this.targetPos = undefined; + var count = 0; + while (!this.targetPos){ + this.target = targets.toEntityArray()[rand]; + this.targetPos = this.target.position(); + count++; + if (count > 1000){ + debug("No target with a valid position found"); + return false; + } + } + } + } + + // regularly update the target position in case it's a unit. + if (this.target.hasClass("Unit")) + this.targetPos = this.target.position(); } /* if (this.state === "huntVillagers") 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 553be5294f..f052e4c4ad 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/config.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/config.js @@ -8,14 +8,14 @@ var baseConfig = { "attackPlansStartTime" : 0 // time to wait before attacking. Start as soon as possible (first barracks) }, "Economy" : { - "townPhase" : 230, // time to start trying to reach town phase (might be a while after. Still need the requirements + ress ) + "townPhase" : 270, // time to start trying to reach town phase (might be a while after. Still need the requirements + ress ) "cityPhase" : 720, // time to start trying to reach city phase "farmsteadStartTime" : 400, // Time to wait before building a farmstead. "marketStartTime" : 480, // Time to wait before building the market. "dockStartTime" : 240, // Time to wait before building the dock "techStartTime" : 600, // time to wait before teching. "targetNumBuilders" : 1.5, // Base number of builders per foundation. Later updated, but this remains a multiplier. - "femaleRatio" : 0.4 // percent of females among the workforce. + "femaleRatio" : 0.6 // percent of females among the workforce. }, // Note: attack settings are set directly in attack_plan.js @@ -59,7 +59,7 @@ var baseConfig = { "dropsites" : 160, "field" : 1000, "militaryBuilding" : 90, - "defenceBuilding" : 20, + "defenceBuilding" : 45, "majorTech" : 250, "minorTech" : 40, "civilCentre" : 10000 // will hog all resources @@ -87,13 +87,14 @@ if (Config.difficulty === 1) "attackPlansStartTime" : 600 }; Config["Economy"] = { - "townPhase" : 300, + "townPhase" : 360, "cityPhase" : 900, "farmsteadStartTime" : 600, "marketStartTime" : 800, "techStartTime" : 1320, "targetNumBuilders" : 2, - "femaleRatio" : 0.5 + "femaleRatio" : 0.5, + "targetNumWorkers" : 100 }; Config["Defence"] = { "armyCompactSize" : 700, @@ -115,7 +116,8 @@ if (Config.difficulty === 1) "marketStartTime" : 1000, "techStartTime" : 600000, // never "targetNumBuilders" : 1, - "femaleRatio" : 0.0 + "femaleRatio" : 0.0, + "targetNumWorkers" : 50 }; Config["Defence"] = { "defenceRatio" : 1.0, 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 2371a3320a..44b24032db 100755 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/defence.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/defence.js @@ -172,7 +172,7 @@ Defence.prototype.armify = function(gameState, entity, militaryManager) { { var self = this; militaryManager.enemyWatchers[entity.owner()].enemySoldiers.forEach(function (ent) { //}){ - if (ent.position() !== undefined && SquareVectorDistance(entity.position(), ent.position()) < self.armyCompactSize) + if (ent.position() !== undefined && SquareVectorDistance(entity.position(), ent.position()) < self.armyCompactSize && self.evaluateEntity(gameState, ent)) { ent.setMetadata(PlayerID, "inArmy", self.totalArmyNB); self.enemyArmy[ent.owner()][self.totalArmyNB].addEnt(ent); @@ -190,8 +190,20 @@ Defence.prototype.evaluateRawEntity = function(gameState, entity) { return false; } Defence.prototype.evaluateEntity = function(gameState, entity) { - if (entity.position() && +this.territoryMap.point(entity.position()) - 64 === +PlayerID && entity.attackTypes() !== undefined) - return true; + if (!entity.position()) + return false; + if (this.territoryMap.point(entity.position()) - 64 === entity.owner() || entity.attackTypes() === undefined) + return false; + + var closeToMe = false; + + for (i in this.myBuildings._entities) + { + if (!this.myBuildings._entities[i].hasClass("ConquestCritical")) + continue; + if (SquareVectorDistance(this.myBuildings._entities[i].position(), entity.position()) < 4000) + return true; + } return false; } // returns false if the unit is in its territory @@ -228,7 +240,8 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage } delete this.listOfEnemies[evt.msg.entity]; this.nbAttackers--; - } else if (evt.msg.entityObj && evt.msg.entityObj.owner() === PlayerID && evt.msg.metadata[PlayerID] && evt.msg.metadata[PlayerID]["role"] === "defence") + } else if (evt.msg.entityObj && evt.msg.entityObj.owner() === PlayerID && evt.msg.metadata[PlayerID] && evt.msg.metadata[PlayerID]["role"] + && evt.msg.metadata[PlayerID]["role"] === "defence") { // lost a brave man there. this.nbDefenders--; @@ -251,7 +264,7 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage // Note: given the way memory works, if the entity has been recently deleted, its reference may still exist. // and this.enemyUnits[enemyID][0] may still point to that reference, "reviving" the unit. // So we've got to make sure it's not supposed to be dead. - for (var check = 0; check < 15; check++) + for (var check = 0; check < 20; check++) { if (this.enemyUnits[enemyID].length > 0 && gameState.getEntityById(this.enemyUnits[enemyID][0].id()) !== undefined) { @@ -346,8 +359,17 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage this.attackerCacheLoopIndicator++; this.attackerCacheLoopIndicator = this.attackerCacheLoopIndicator % 2; - if (this.nbAttackers === 0 && this.nbDefenders === 0) { + // Release all our units + this.myUnits.filter(Filters.byMetadata(PlayerID, "role","defence")).forEach(function (defender) { //}){ + defender.stopMoving(); + if (defender.getMetadata(PlayerID, "formerrole")) + defender.setMetadata(PlayerID, "role", defender.getMetadata(PlayerID, "formerrole") ); + else + defender.setMetadata(PlayerID, "role", "worker"); + defender.setMetadata(PlayerID, "subrole", undefined); + self.nbDefenders--; + }); return; } else if (this.nbAttackers === 0 && this.nbDefenders !== 0) { // Release all our units @@ -360,6 +382,7 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage defender.setMetadata(PlayerID, "subrole", undefined); self.nbDefenders--; }); + militaryManager.ungarrisonAll(gameState); militaryManager.unpauseAllPlans(gameState); return; } @@ -372,10 +395,10 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage // and then I'll assign my units. // and then rock on. - if (this.nbAttackers < 8){ - gameState.setDefcon(4); // few local units - } else if (this.nbAttackers >= 8){ + if (this.nbAttackers > 15){ gameState.setDefcon(3); + } else if (this.nbAttackers > 5){ + gameState.setDefcon(4); } // we're having too many. Release those that attack units already dealt with, or idle ones. @@ -405,7 +428,7 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage //debug ("nonDefenders.length "+ nonDefenders.length); // todo: this is suboptimal. Should allow the maximum number of unit, right now it just lowers the ratio. - if (newEnemies.length * (defenceRatio) > nonDefenders.length) + if (newEnemies.length * (defenceRatio - 1) > nonDefenders.length && this.nbAttackers > 5) { defenceRatio--; gameState.setDefcon(2); @@ -413,16 +436,27 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage if (defenceRatio < 1) defenceRatio = 1; - if (nonDefenders.length*2.0 < newEnemies.length) + if (nonDefenders.length*2.0 < newEnemies.length && this.nbAttackers > 5) gameState.setDefcon(1); + if ( (nonDefenders.length + this.nbDefenders > newEnemies.length + this.nbAttackers) + || this.nbDefenders + nonDefenders.length < 4) + { + var buildings = gameState.getOwnEntities().filter(Filters.byCanGarrison()).toEntityArray(); + buildings.forEach( function (struct) { + if (struct.garrisoned() && struct.garrisoned().length) + struct.unloadAll(); + }); + }; + + /* if (gameState.defcon() < 2 && (this.nbAttackers-this.nbDefenders) > 15) { - militaryManager.garrisonAllFemales(gameState); militaryManager.pauseAllPlans(gameState); } else if (gameState.defcon() < 3 && this.nbDefenders === 0 && newEnemies.length === 0) { militaryManager.ungarrisonAll(gameState); - } + }*/ + // For each enemy, we'll pick two units. for each (enemy in newEnemies) { if (nonDefenders.length === 0) @@ -473,73 +507,42 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage assigned++; self.nbDefenders++; }); - } - - /* - // yes. We'll pick new units (pretty randomly for now, todo) - // first from attack plans, then from workers. - var newSoldiers = gameState.getOwnEntities().filter(function (ent) { - if (ent.getMetadata(PlayerID, "plan") != undefined && ent.getMetadata(PlayerID, "role") != "defence") - return true; - return false; - }); - newSoldiers.forEach(function(ent) { - if (ent.getMetadata(PlayerID, "subrole","attacking")) // gone with the wind to avenge their brothers. - return; - if (nbOfAttackers <= 0) - return; - militaryManager.pausePlan(gameState,ent.getMetadata(PlayerID, "plan")); - ent.setMetadata(PlayerID, "formerrole", ent.getMetadata(PlayerID, "role")); - ent.setMetadata(PlayerID, "role","defence"); - ent.setMetadata(PlayerID, "subrole","newdefender"); - nbOfAttackers--; - }); - - - if (nbOfAttackers > 0) { - newSoldiers = gameState.getOwnEntitiesByRole("worker"); - newSoldiers.forEach(function(ent) { - if (nbOfAttackers <= 0) - return; - // If we're not female, we attack - // and if we're not already assigned from above (might happen, not sure, rather be cautious) - if (ent.hasClass("CitizenSoldier") && ent.getMetadata(PlayerID, "subrole") != "newdefender") { - ent.setMetadata(PlayerID, "formerrole", "worker"); - ent.setMetadata(PlayerID, "role","defence"); - ent.setMetadata(PlayerID, "subrole","newdefender"); - nbOfAttackers--; - } - }); - } - // okay - newSoldiers = gameState.getOwnEntitiesByMetadata("subrole","newdefender"); - - // we're okay, but there's a big amount of units - // todo: check against total number of soldiers - if (nbOfAttackers <= 0 && newSoldiers.length > 35) - gameState.setDefcon(2); - else if (nbOfAttackers > 0) { - // we are actually lacking units - gameState.setDefcon(1); - } - // TODO. For now, each unit will pick the closest unit that is attacked by only one/zero guy, or any if there is none. - // ought to regroup them first for optimization. - - newSoldiers.forEach(function(ent) { //}) { - var enemies = self.listedEnemyCollection.filterNearest(ent.position()).toEntityArray(); - var target = -1; - var secondaryTarget = enemies[0]; // second best pick - for (o in enemies) { - var enemy = enemies[o]; - if (self.attackerCache[enemy.id()].length < 2) { - target = +enemy.id(); - break; - } + + if (gameState.defcon() <= 3) + { + // let's try to garrison neighboring females. + var buildings = gameState.getOwnEntities().filter(Filters.byCanGarrison()).toEntityArray(); + var females = gameState.getOwnEntities().filter(Filters.byClass("Support")); + + var cache = {}; + var garrisoned = false; + females.forEach( function (ent) { //}){ + garrisoned = false; + if (ent.position()) + { + if (SquareVectorDistance(ent.position(), enemy.position()) < 3500) + { + for (i in buildings) + { + var struct = buildings[i]; + if (!cache[struct.id()]) + cache[struct.id()] = 0; + if (struct.garrisoned() && struct.garrisonMax() - struct.garrisoned().length - cache[struct.id()] > 0) + { + garrisoned = true; + ent.garrison(struct); + cache[struct.id()]++; + break; + } + } + if (!garrisoned) + ent.flee(enemy); + } + } + }); } - ent.setMetadata(PlayerID, "subrole","defending"); - ent.attack(+target); - }); -*/ + } + return; } @@ -596,6 +599,14 @@ Defence.prototype.MessageProcess = function(gameState,events, militaryManager) { // units in attack plans will react independently, but we still list the attacks here. if (attacker.hasClass("Structure")) { // todo: we ultimately have to check wether it's a danger point or an isolated area, and if it's a danger point, mark it as so. + + // Right now, to make the AI less gameable, we'll mark any surrounding resource as inaccessible. + // usual tower range is 80. Be on the safe side. + var close = gameState.getResourceSupplies("wood").filter(Filters.byDistance(attacker.position(), 90)); + close.forEach(function (supply) { //}){ + supply.setMetadata(PlayerID, "inaccessible", true); + }); + } else { // TODO: right now a soldier always retaliate... Perhaps it should be set in "Defence" mode. if (!attacker.hasClass("Female") && !attacker.hasClass("Ship")) { @@ -611,7 +622,7 @@ Defence.prototype.MessageProcess = function(gameState,events, militaryManager) { var position = attacker.position(); var close = militaryManager.enemyWatchers[attacker.owner()].enemySoldiers.filter(Filters.byDistance(position, self.armyCompactSize)); - if (close.length > 15) + if (close.length > 5) { close.forEach(function (ent) { //}){ if (SquareVectorDistance(position, ent.position()) < self.armyCompactSize) @@ -621,6 +632,7 @@ Defence.prototype.MessageProcess = function(gameState,events, militaryManager) { } }); } + return; // don't use too much processing power. If there are other cases, they'll be processed soon enough. // Defencemanager will deal with them in the next turn. /* // we register this unit as wanted, TODO register the whole army 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 935121919d..0200d18a8a 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/economy.js @@ -39,18 +39,37 @@ var EconomyManager = function() { }; // More initialisation for stuff that needs the gameState EconomyManager.prototype.init = function(gameState, events){ - if (this.targetNumWorkers === undefined) + if (Config.Economy.targetNumWorkers) + this.targetNumWorkers = Config.Economy.targetNumWorkers; + else if (this.targetNumWorkers === undefined) this.targetNumWorkers = Math.max(Math.floor(gameState.getPopulationMax()*0.55), 1); - // Athen's fastest Citizen soldier requires stone. - if (gameState.civ() == "athen" && !gameState.ai.strategy === "rush") + var availableRess = 0; + availableRess += gameState.ai.queueManager.getAvailableResources(gameState,false).toInt(); + + var ents = gameState.getEntities().filter(Filters.byOwner(PlayerID)); + ents = ents.filter(Filters.byClass("CivCentre")).toEntityArray(); + if (ents.length > 0) { - this.baseNeed["food"] = 140; - this.baseNeed["wood"] = 100; - this.baseNeed["stone"] = 50; + gameState.getResourceSupplies("food").forEach( function (ent) { + if (SquareVectorDistance(ent.position(), ents[0].position()) < 5000) + availableRess += ent.resourceSupplyMax(); + }); + gameState.getResourceSupplies("stone").forEach( function (ent) { + if (SquareVectorDistance(ent.position(), ents[0].position()) < 5000) + availableRess += ent.resourceSupplyMax(); + }); + gameState.getResourceSupplies("metal").forEach( function (ent) { + if (SquareVectorDistance(ent.position(), ents[0].position()) < 5000) + availableRess += ent.resourceSupplyMax(); + }); + gameState.getResourceSupplies("wood").forEach( function (ent) { + if (SquareVectorDistance(ent.position(), ents[0].position()) < 5000) + availableRess += ent.resourceSupplyMax(); + }); } - - + if (availableRess > 2000) + this.fastStart = true; // initialize once all the resource maps. this.updateResourceMaps(gameState, events); this.updateResourceConcentrations(gameState,"food"); @@ -103,16 +122,20 @@ EconomyManager.prototype.trainMoreWorkers = function(gameState, queues) { // If we have too few, train more // should plan enough to always have females… - if (numTotal < this.targetNumWorkers && numQueued < 3 && (numQueued+numInTraining) < 15) { + if (numTotal < this.targetNumWorkers && numQueued < 2 && (numQueued+numInTraining) < 15) { var template = gameState.applyCiv("units/{civ}_support_female_citizen"); var size = Math.min(Math.ceil(gameState.getTimeElapsed() / 30000),5); if (numFemales/numTotal > this.femaleRatio && gameState.getTimeElapsed() > 60*1000) { - template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], [ ["cost",1], ["speed",0.5]]); + template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], [ ["cost",1], ["speed",0.5], ["costsResource", 0.5, "stone"], ["costsResource", 0.5, "metal"]]); if (!template) template = gameState.applyCiv("units/{civ}_support_female_citizen"); else size = Math.min(Math.ceil(gameState.getTimeElapsed() / 90000),5); } + + if (gameState.getTimeElapsed() < 60000 && gameState.ai.queueManager.getAvailableResources(gameState, true)["food"] > 250 || this.fastStart) + size = 5; + if (template === gameState.applyCiv("units/{civ}_support_female_citizen")) queues.villager.addItem(new UnitTrainingPlan(gameState, template, { "role" : "worker" }, size, size )); else @@ -165,6 +188,13 @@ EconomyManager.prototype.findBestTrainableUnit = function(gameState, classes, pa aDivParam += a[1].costSum() * param[1]; bDivParam += b[1].costSum() * param[1]; } + // requires a third parameter which is the resource + if (param[0] == "costsResource") { + if (a[1].cost()[param[2]]) + aTopParam *= param[1]; + if (b[1].cost()[param[2]]) + bTopParam *= param[1]; + } } return -(aTopParam/(aDivParam+1)) + (bTopParam/(bDivParam+1)); }); @@ -207,10 +237,6 @@ EconomyManager.prototype.reassignRolelessUnits = function(gameState) { roleless.forEach(function(ent) { if (ent.hasClass("Worker")){ ent.setMetadata(PlayerID, "role", "worker"); - }else if(ent.hasClass("CitizenSoldier") || ent.hasClass("Champion")){ - ent.setMetadata(PlayerID, "role", "soldier"); - }else{ - ent.setMetadata(PlayerID, "role", "unknown"); } }); }; @@ -300,32 +326,37 @@ EconomyManager.prototype.assignToFoundations = function(gameState, noRepair) { for (i in foundations) { var target = foundations[i]; - if (target._template.BuildRestrictions.Category === "Field") - continue; // we do not build fields + // Removed: sometimes the AI would not notice it has empty unbuilt fields + //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(4.0,gameState.getTimeElapsed()/60000)) { var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.getMetadata(PlayerID, "gather-type") !== "food" && ent.position() !== undefined); }); var nearestNonBuilders = null; - if (target.hasClass("CivCentre")) + if (target.hasClass("CivCentre") || (target.hasClass("House") && gameState.getTimeElapsed() < 300000) ) nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders*2.0 - assigned); else nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders - assigned); nearestNonBuilders.forEach(function(ent) { addedWorkers++; + ent.stopMoving(); ent.setMetadata(PlayerID, "subrole", "builder"); ent.setMetadata(PlayerID, "target-foundation", target); }); - if (this.targetNumBuilders - assigned - nearestNonBuilders.length > 0) { +/* if (this.targetNumBuilders - assigned - nearestNonBuilders.length > 0) { var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.position() !== undefined); }); var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders - assigned); nearestNonBuilders.forEach(function(ent) { + ent.stopMoving(); addedWorkers++; ent.setMetadata(PlayerID, "subrole", "builder"); ent.setMetadata(PlayerID, "target-foundation", target); }); - } + }*/ } } } @@ -340,7 +371,7 @@ EconomyManager.prototype.assignToFoundations = function(gameState, noRepair) { continue; var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target).length; if (assigned < this.targetNumBuilders) { - if (builderWorkers.length + addedWorkers < this.targetNumBuilders*2.5) { + if (builderWorkers.length + addedWorkers < this.targetNumBuilders*4) { var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.position() !== undefined); }); if (gameState.defcon() < 5) @@ -348,6 +379,7 @@ EconomyManager.prototype.assignToFoundations = function(gameState, noRepair) { var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders - assigned); nearestNonBuilders.forEach(function(ent) { + ent.stopMoving(); addedWorkers++; ent.setMetadata(PlayerID, "subrole", "builder"); ent.setMetadata(PlayerID, "target-foundation", target); @@ -409,10 +441,10 @@ EconomyManager.prototype.updateResourceMaps = function(gameState, events) { var radius = {'wood':10.0, 'stone': 24.0, 'metal': 24.0, 'food': 24.0}; // smallRadius is the distance necessary to mark a resource as linked to a dropsite. - var smallRadius = { 'food':80*80,'wood':60*60,'stone':70*70,'metal':70*70 }; + var smallRadius = { 'food':60*60,'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 }; + var bigRadius = { 'food':80*80,'wood':140*140,'stone':140*140,'metal':140*140 }; var self = this; @@ -525,6 +557,8 @@ EconomyManager.prototype.updateResourceMaps = function(gameState, events) { var addToMap = true; var dropsites = gameState.getOwnDropsites(resource); dropsites.forEach( function (otherDropsite) { //}) { + if (!otherDropsite.position()) + return; var distance = SquareVectorDistance(ent.position(), otherDropsite.position()); if (ent.getMetadata(PlayerID, "linked-dropsite") == undefined || ent.getMetadata(PlayerID, "linked-dropsite-dist") > distance) { if (distance < bigRadius[resource]) { @@ -631,6 +665,7 @@ EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource friendlyTiles = new Map(gameState); var ents = gameState.getOwnEntities().filter(Filters.byClass("CivCentre")); + var eEnts = gameState.getEnemyEntities().filter(Filters.byClass("CivCentre")); // This uses a different resource maps,where the point is basically to try to have as many resources as possible in the CC's territory. for (var j = 0; j < friendlyTiles.length; ++j) @@ -658,7 +693,19 @@ EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource friendlyTiles.map[j] = 0; continue; } - + // Checking for enemy CCs + mindist = 7101; + eEnts.forEach( function (cc) { + var dist = SquareVectorDistance(friendlyTiles.gamePosToMapPos(cc.position()),pos); + if (dist < mindist) + mindist = dist; + }); + if (mindist < 3500) // cannot build too close to each other. + { + friendlyTiles.map[j] = 0; + continue; + } + friendlyTiles.map[j] += this.CCResourceMaps[resource].map[j] * 1.5; for (i in this.CCResourceMaps) @@ -675,8 +722,6 @@ EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource //friendlyTiles.dumpIm(gameState.getTimeElapsed() + "_" + resource + "_density_fade_final2.png", 5000); } - debug ("Have " + best[1] + " for " + resource); - // tell the dropsite builder we haven't found anything satisfactory. if (best[1] < 250) return [false, [-1,0]]; @@ -723,10 +768,10 @@ EconomyManager.prototype.updateNearbyResources = function(gameState,resource){ var radius = {'wood':10.0, 'stone': 24.0, 'metal': 24.0, 'food': 24.0}; // smallRadius is the distance necessary to mark a resource as linked to a dropsite. - var smallRadius = { 'food':80*80,'wood':60*60,'stone':70*70,'metal':70*70 }; + var smallRadius = { 'food':60*60,'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 }; + var bigRadius = { 'food':80*80,'wood':140*140,'stone':140*140,'metal':140*140 }; gameState.getOwnDropsites(resource).forEach(function(ent) { //}){ @@ -946,8 +991,13 @@ EconomyManager.prototype.buildMoreHouses = function(gameState, queues) { var numConstructing = gameState.countEntitiesByType(gameState.applyCiv("foundation|structures/{civ}_house")); var numPlanned = queues.house.totalLength(); - var additional = Math.ceil((20 - (gameState.getPopulationLimit() - gameState.getPopulation())) / 10) - - numConstructing - numPlanned; + if (gameState.getTimeElapsed() < 300000 && numConstructing + numPlanned !== 0) + return; + var additional = 0; + if (gameState.civ() == "gaul" || gameState.civ() == "brit" || gameState.civ() == "iber") + additional = Math.ceil((20 - (gameState.getPopulationLimit() - gameState.getPopulation())) / 5) - numConstructing - numPlanned; + else + additional = Math.ceil((20 - (gameState.getPopulationLimit() - gameState.getPopulation())) / 10) - numConstructing - numPlanned; for ( var i = 0; i < additional; i++) { queues.house.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_house")); @@ -1020,13 +1070,13 @@ EconomyManager.prototype.update = function(gameState, queues, events) { if (gameState.ai.playedTurn % 20 === 0){ this.setWorkersIdleByPriority(gameState); } else { - Engine.ProfileStart("Assign builders"); - this.assignToFoundations(gameState, false); - Engine.ProfileStop(); - Engine.ProfileStart("Reassign Idle Workers"); this.reassignIdleWorkers(gameState); Engine.ProfileStop(); + + Engine.ProfileStart("Assign builders"); + this.assignToFoundations(gameState, false); + Engine.ProfileStop(); } // TODO: do this incrementally a la defence.js @@ -1081,8 +1131,10 @@ EconomyManager.prototype.update = function(gameState, queues, events) { } else { this.targetNumBuilders = Config.Economy.targetNumBuilders; } - if (gameState.currentPhase() == 1) - this.femaleRatio = Config.Economy.femaleRatio * 1.5; + if (gameState.currentPhase() == 1 && !this.fastStart) + this.femaleRatio = Config.Economy.femaleRatio * 1.25; + else if (gameState.currentPhase() == 1 && this.fastStart) + this.femaleRatio = Config.Economy.femaleRatio * 0.9; // might expect a rush and build some CS to counter. else this.femaleRatio = Config.Economy.femaleRatio; @@ -1124,10 +1176,6 @@ EconomyManager.prototype.update = function(gameState, queues, events) { this.setWorkersIdleByPriority(gameState); } - Engine.ProfileStart("Assign builders"); - this.assignToFoundations(gameState); - Engine.ProfileStop(); - Engine.ProfileStart("Reassign Idle Workers"); this.reassignIdleWorkers(gameState); Engine.ProfileStop(); @@ -1162,6 +1210,10 @@ EconomyManager.prototype.update = function(gameState, queues, events) { Engine.ProfileStop(); } + Engine.ProfileStart("Assign builders"); + this.assignToFoundations(gameState); + Engine.ProfileStop(); + // TODO: do this incrementally a la defence.js (Changed slightly to be faster already). Engine.ProfileStart("Run Workers"); gameState.getOwnEntitiesByRole("worker").forEach(function(ent){ diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/enemy-watcher.js b/binaries/data/mods/public/simulation/ai/qbot-wc/enemy-watcher.js index 80edf3a775..24484159d0 100755 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/enemy-watcher.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/enemy-watcher.js @@ -52,7 +52,7 @@ enemyWatcher.prototype.getEnemyBuildings = function(gameState, specialClass, One else if (OneTime) return this.enemyBuildings.filter(filter); - return gameState.updatingGlobalCollection("player-" +this.watched + "-structures-" +specialClass, filter, gameState.getGEC("player-" +this.watched + "-structure")); + return gameState.updatingGlobalCollection("player-" +this.watched + "-structures-" +specialClass, filter, gameState.getGEC("player-" +this.watched + "-structures")); }; enemyWatcher.prototype.getDangerousArmies = function() { var toreturn = {}; 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 d0dea787b3..382b32b869 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 @@ -1,13 +1,16 @@ // returns some sort of DPS * health factor. If you specify a class, it'll use the modifiers against that class too. function getMaxStrength(ent, againstClass) { - var strength = 0.0; var attackTypes = ent.attackTypes(); var armourStrength = ent.armourStrengths(); var hp = ent.maxHitpoints() / 100.0; // some normalization for (var typeKey in attackTypes) { var type = attackTypes[typeKey]; + + if (type == "Slaughter" || type == "Charged") + continue; + var attackStrength = ent.attackStrengths(type); var attackRange = ent.attackRange(type); var attackTimes = ent.attackTimes(type); 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 9c2d7d11ac..d1f5b7a42b 100755 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/military.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/military.js @@ -148,35 +148,42 @@ MilitaryAttackManager.prototype.findBestTrainableUnit = function(gameState, clas units.sort(function(a, b) { //}) { - var aDivParam = 0, bDivParam = 0; - var aTopParam = 0, bTopParam = 0; - for (i in parameters) { - var param = parameters[i]; - - if (param[0] == "base") { - aTopParam = param[1]; - bTopParam = param[1]; - } - if (param[0] == "strength") { - aTopParam += getMaxStrength(a[1]) * param[1]; - bTopParam += getMaxStrength(b[1]) * param[1]; - } - if (param[0] == "siegeStrength") { - aTopParam += getMaxStrength(a[1], "Structure") * param[1]; - bTopParam += getMaxStrength(b[1], "Structure") * param[1]; - } - if (param[0] == "speed") { - aTopParam += a[1].walkSpeed() * param[1]; - bTopParam += b[1].walkSpeed() * param[1]; - } - - if (param[0] == "cost") { - aDivParam += a[1].costSum() * param[1]; - bDivParam += b[1].costSum() * param[1]; - } - } - return -(aTopParam/(aDivParam+1)) + (bTopParam/(bDivParam+1)); - }); + var aDivParam = 0, bDivParam = 0; + var aTopParam = 0, bTopParam = 0; + for (i in parameters) { + var param = parameters[i]; + + if (param[0] == "base") { + aTopParam = param[1]; + bTopParam = param[1]; + } + if (param[0] == "strength") { + aTopParam += getMaxStrength(a[1]) * param[1]; + bTopParam += getMaxStrength(b[1]) * param[1]; + } + if (param[0] == "siegeStrength") { + aTopParam += getMaxStrength(a[1], "Structure") * param[1]; + bTopParam += getMaxStrength(b[1], "Structure") * param[1]; + } + if (param[0] == "speed") { + aTopParam += a[1].walkSpeed() * param[1]; + bTopParam += b[1].walkSpeed() * param[1]; + } + + if (param[0] == "cost") { + aDivParam += a[1].costSum() * param[1]; + bDivParam += b[1].costSum() * param[1]; + } + if (param[0] == "canGather") { + // checking against wood, could be anything else really. + if (a[1].resourceGatherRates() && a[1].resourceGatherRates()["wood.tree"]) + aTopParam *= param[1]; + if (b[1].resourceGatherRates() && b[1].resourceGatherRates()["wood.tree"]) + bTopParam *= param[1]; + } + } + return -(aTopParam/(aDivParam+1)) + (bTopParam/(bDivParam+1)); + }); return units[0][0]; }; @@ -376,8 +383,8 @@ MilitaryAttackManager.prototype.measureEnemyStrength = function(gameState){ // Adds towers to the defenceBuilding queue MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){ if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv('structures/{civ}_defense_tower')) - + queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["DefenseTower"] - && gameState.currentPhase() > 1) { + + queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["DefenseTower"] && queues.defenceBuilding.totalLength() < 4 + && gameState.currentPhase() > 1 && queues.defenceBuilding.totalLength() < 3) { gameState.getOwnEntities().forEach(function(dropsiteEnt) { if (dropsiteEnt.resourceDropsiteTypes() && dropsiteEnt.getMetadata(PlayerID, "defenseTower") !== true && !dropsiteEnt.hasClass("CivCentre")){ var position = dropsiteEnt.position(); @@ -394,7 +401,7 @@ MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){ numFortresses += gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bFort[i])); } - if (numFortresses + queues.defenceBuilding.totalLength() < 1 && gameState.currentPhase() > 2) + if (numFortresses + queues.defenceBuilding.totalLength() < 2 && gameState.currentPhase() > 2) { if (gameState.getTimeElapsed() > this.fortressStartTime + numFortresses * this.fortressLapseTime){ if (gameState.ai.pathsToMe && gameState.ai.pathsToMe.length > 0){ @@ -412,12 +419,22 @@ 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")) > 15 && gameState.currentPhase() > 1) { - if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0])) - + queues.militaryBuilding.totalLength() < 1) { + if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > 25 && (gameState.currentPhase() > 1 || gameState.isResearching("phase_town"))) { + if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0])) + queues.militaryBuilding.totalLength() < 1) { + debug ("Trying to build barracks"); queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0])); } } + + if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0])) < 2 && gameState.getTimeElapsed() > 15*60*1000) + if (queues.militaryBuilding.totalLength() < 1) + queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0])); + + if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0])) < 3 && gameState.getTimeElapsed() > 23*60*1000 && + (gameState.civ() == "gaul" || gameState.civ() == "brit" || gameState.civ() == "iber")) + if (queues.militaryBuilding.totalLength() < 1) + queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0])); + //build advanced military buildings if (gameState.getTimeElapsed() > this.advancedMilitaryStartTime && gameState.currentPhase() > 2){ if (queues.militaryBuilding.totalLength() === 0){ @@ -432,6 +449,20 @@ MilitaryAttackManager.prototype.constructTrainingBuildings = function(gameState, } } } + if (gameState.civ() !== "gaul" && gameState.civ() !== "brit" && gameState.civ() !== "iber" && + gameState.getTimeElapsed() > this.advancedMilitaryStartTime && gameState.currentPhase() > 2 && gameState.getTimeElapsed() > 25*60*1000) + { + var Const = 0; + for (var i in this.bAdvanced) + Const += gameState.countEntitiesByType(gameState.applyCiv(this.bAdvanced[i])); + if (inConst == 1) { + var i = Math.floor(Math.random() * this.bAdvanced.length); + if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bAdvanced[i])) < 1){ + queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bAdvanced[i])); + } + } + } + Engine.ProfileStop(); }; 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 c2ec1cde8c..5a3d56ffe4 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 @@ -125,12 +125,14 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) { friendlyTiles.addInfluence(x, z, infl/3.0, infl + 1); friendlyTiles.addInfluence(x, z, Math.ceil(infl/5.0), -(infl/2.0), 'linear'); } + //avoid building too close to each other if possible. + friendlyTiles.addInfluence(x, z, 5, -5, 'linear'); } } }); } - //friendlyTiles.dumpIm(template.buildCategory() + "_" +gameState.getTimeElapsed() + ".png", 200); + friendlyTiles.dumpIm(template.buildCategory() + "_" +gameState.getTimeElapsed() + ".png", 200); // Find target building's approximate obstruction radius, and expand by a bit to make sure we're not too close, this // allows room for units to walk between buildings. @@ -142,7 +144,9 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) { else if (template.buildCategory() === "Dock") radius = 1;//Math.floor(template.obstructionRadius() / cellSize); else if (template.genericName() != "House" && !template.hasClass("DropsiteWood") && !template.hasClass("DropsiteStone") && !template.hasClass("DropsiteMetal")) - radius = Math.ceil(template.obstructionRadius() / cellSize) + 1; + radius = Math.ceil(template.obstructionRadius() / cellSize + 0.5); + else if (gameState.civ() === "iber" || gameState.civ() === "gaul" || gameState.civ() === "brit") + radius = Math.ceil(template.obstructionRadius() / cellSize); else radius = Math.ceil(template.obstructionRadius() / cellSize); diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/plan-training.js b/binaries/data/mods/public/simulation/ai/qbot-wc/plan-training.js index 00294e0c6e..bd976bb714 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/plan-training.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/plan-training.js @@ -42,17 +42,13 @@ 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(); + var aa = a.trainingQueueTime(); + var bb = b.trainingQueueTime(); + if (a.hasClass("Civic") && !self.template.hasClass("Support")) + aa += 0.9; + if (b.hasClass("Civic") && !self.template.hasClass("Support")) + bb += 0.9; + return (aa - bb); }); trainers[0].train(this.type, this.number, this.metadata); diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/qbot.js b/binaries/data/mods/public/simulation/ai/qbot-wc/qbot.js index 4a3682ce8e..9508081f0d 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/qbot.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/qbot.js @@ -202,6 +202,7 @@ QBotAI.prototype.OnUpdate = function(sharedScript) { this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_town",true)); // we rush the town phase. debug ("Trying to reach town phase"); } else if (gameState.canResearch("phase_city_generic",true) && gameState.getTimeElapsed() > (Config.Economy.cityPhase*1000) + && gameState.getOwnEntitiesByRole("worker").length > 90 && gameState.findResearchers("phase_city_generic").length != 0 && this.queues.majorTech.length() === 0) { debug ("Trying to reach city phase"); this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_city_generic")); @@ -223,6 +224,22 @@ QBotAI.prototype.OnUpdate = function(sharedScript) { this.queueManager.update(gameState); + /* + if (this.playedTurn % 80 === 0) + { + // some debug informations about units. + var units = gameState.getOwnEntities().filter(Filters.byClass("Unit")); + for (var i in units._entities) + { + var ent = units._entities[i]; + debug ("Unit " + ent.id() + " is a " + ent._templateName); + debug ("It is a " + uneval(ent.getMetadata(PlayerID, "role")) + ", "+ uneval(ent.getMetadata(PlayerID, "subrole"))); + if (ent.getMetadata(PlayerID, "plan") != undefined) + debug ("it is part of the plan " + uneval(ent.getMetadata(PlayerID, "plan"))); + } + }*/ + + //if (this.playedTurn % 15 === 0) // this.queueManager.printQueues(gameState); diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/queue-manager.js b/binaries/data/mods/public/simulation/ai/qbot-wc/queue-manager.js index cf6721eb2d..a5392cdd7e 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/queue-manager.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/queue-manager.js @@ -132,10 +132,10 @@ QueueManager.prototype.futureNeeds = function(gameState, EcoManager) { } else { // Return predicted values minus the current stockpiles along with a base rater for all resources return { - "food" : Math.max(needs.food - current.food, 0) + EcoManager.baseNeed["food"]*2, - "wood" : Math.max(needs.wood - current.wood, 0) + EcoManager.baseNeed["wood"]*2, - "stone" : Math.max(needs.stone - current.stone, 0) + EcoManager.baseNeed["stone"]*2, - "metal" : Math.max(needs.metal - current.metal, 0) + EcoManager.baseNeed["metal"]*2 + "food" : Math.max(needs.food - current.food, 0) + EcoManager.baseNeed["food"], + "wood" : Math.max(needs.wood - current.wood, 0) + EcoManager.baseNeed["wood"], + "stone" : Math.max(needs.stone - current.stone, 0) + EcoManager.baseNeed["stone"], + "metal" : Math.max(needs.metal - current.metal, 0) + EcoManager.baseNeed["metal"] }; } }; 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 429e57d1fc..eda10d2da2 100644 --- a/binaries/data/mods/public/simulation/ai/qbot-wc/worker.js +++ b/binaries/data/mods/public/simulation/ai/qbot-wc/worker.js @@ -17,8 +17,8 @@ Worker.prototype.update = function(gameState) { // If the worker has no position then no work can be done return; } - - if (subrole === "gatherer"){ + + if (subrole === "gatherer") { if (this.ent.unitAIState().split(".")[1] !== "GATHER" && this.ent.unitAIState().split(".")[1] !== "COMBAT" && this.ent.unitAIState().split(".")[1] !== "RETURNRESOURCE"){ // TODO: handle combat for hunting animals if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0 || @@ -400,6 +400,7 @@ Worker.prototype.startGathering = function(gameState){ if (!tried) { this.maxApproachTime = Math.max(25000, VectorDistance(pos,this.ent.position()) * 1000); ent.gather(nearestSupply); + ent.setMetadata(PlayerID, "target-foundation", undefined); } } else { if (resource === "food" && this.buildAnyField(gameState)) diff --git a/binaries/data/mods/public/simulation/components/ProductionQueue.js b/binaries/data/mods/public/simulation/components/ProductionQueue.js index 3d474d6814..8d586cad21 100644 --- a/binaries/data/mods/public/simulation/components/ProductionQueue.js +++ b/binaries/data/mods/public/simulation/components/ProductionQueue.js @@ -580,6 +580,8 @@ ProductionQueue.prototype.ProgressTimeout = function(data) if (item.timeRemaining > time) { item.timeRemaining -= time; + // send a message for the AIs. + Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { }); break; } diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index 4c9aeb466d..2848e61071 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -1196,6 +1196,27 @@ bool Autostart(const CmdLineArgs& args) scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player); } } + + // Set player data for Civs + if (args.Has("autostart-civ")) + { + std::vector civArgs = args.GetMultiple("autostart-civ"); + for (size_t i = 0; i < civArgs.size(); ++i) + { + // Instead of overwriting existing player data, modify the array + CScriptVal player; + if (!scriptInterface.GetPropertyInt(playerData.get(), i, player) || player.undefined()) + { + scriptInterface.Eval("({})", player); + } + + int playerID = civArgs[i].BeforeFirst(":").ToInt(); + CStr name = civArgs[i].AfterFirst(":"); + + scriptInterface.SetProperty(player.get(), "Civ", std::string(name)); + scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player); + } + } // Add player data to map settings scriptInterface.SetProperty(settings.get(), "PlayerData", playerData); diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp index c03fe4cc68..ec13442bee 100644 --- a/source/simulation2/components/CCmpAIManager.cpp +++ b/source/simulation2/components/CCmpAIManager.cpp @@ -291,11 +291,11 @@ public: }; CAIWorker() : - // TODO: Passing a 24 MB argument to CreateRuntime() is a temporary fix + // TODO: Passing a 32 MB argument to CreateRuntime() is a temporary fix // to prevent frequent AI out-of-memory crashes. The argument should be // removed as soon whenever the new pathfinder is committed // And the AIs can stop relying on their own little hands. - m_ScriptRuntime(ScriptInterface::CreateRuntime(25165824)), + m_ScriptRuntime(ScriptInterface::CreateRuntime(33554432)), m_ScriptInterface("Engine", "AI", m_ScriptRuntime), m_TurnNum(0), m_CommandsComputed(true), @@ -726,7 +726,7 @@ private: } // Run GC if we are about to overflow - if (JS_GetGCParameter(m_ScriptInterface.GetRuntime(), JSGC_BYTES) > 24000000) + if (JS_GetGCParameter(m_ScriptInterface.GetRuntime(), JSGC_BYTES) > 33000000) { PROFILE3("AI compute GC");