diff --git a/binaries/data/mods/public/simulation/ai/aegis/aegis.js b/binaries/data/mods/public/simulation/ai/aegis/aegis.js index d7c3a12322..d4e2e9230f 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/aegis.js +++ b/binaries/data/mods/public/simulation/ai/aegis/aegis.js @@ -8,24 +8,12 @@ m.playerGlobals = []; m.AegisBot = function AegisBot(settings) { API3.BaseAI.call(this, settings); - this.Config = new m.Config(); - - this.Config.updateDifficulty(settings.difficulty); - this.turn = 0; - this.playedTurn = 0; - - this.priorities = this.Config.priorities; - // this.queues can only be modified by the queue manager or things will go awry. - this.queues = {}; - for (var i in this.priorities) - this.queues[i] = new m.Queue(); - - this.queueManager = new m.QueueManager(this.Config, this.queues, this.priorities); - - this.HQ = new m.HQ(this.Config); + this.Config = new m.Config(); + this.Config.updateDifficulty(settings.difficulty); + this.Config.personality = settings.personality; this.firstTime = true; @@ -38,6 +26,19 @@ m.AegisBot = function AegisBot(settings) { m.AegisBot.prototype = new API3.BaseAI(); m.AegisBot.prototype.CustomInit = function(gameState, sharedScript) { + + this.initPersonality(this.gameState); + + this.priorities = this.Config.priorities; + // this.queues can only be modified by the queue manager or things will go awry. + this.queues = {}; + for (var i in this.priorities) + this.queues[i] = new m.Queue(); + + this.queueManager = new m.QueueManager(this.Config, this.queues, this.priorities); + + this.HQ = new m.HQ(this.Config); + gameState.Config = this.Config; m.playerGlobals[PlayerID] = {}; m.playerGlobals[PlayerID].uniqueIDBOPlans = 0; // training/building/research plans @@ -85,8 +86,6 @@ m.AegisBot.prototype.CustomInit = function(gameState, sharedScript) { } this.pathInfo.angle += Math.PI/3.0; - - this.chooseRandomStrategy(); } m.AegisBot.prototype.OnUpdate = function(sharedScript) { @@ -142,19 +141,30 @@ m.AegisBot.prototype.OnUpdate = function(sharedScript) { var townPhase = this.gameState.townPhase(); var cityPhase = this.gameState.cityPhase(); + // try going up phases. - // TODO: softcode this. - if (this.gameState.canResearch(townPhase,true) && this.gameState.getTimeElapsed() > (this.Config.Economy.townPhase*1000) && this.gameState.getPopulation() > 40 - && this.gameState.findResearchers(townPhase,true).length != 0 && this.queues.majorTech.length() === 0 - && this.gameState.getOwnStructures().filter(API3.Filters.byClass("Village")).length > 5) + // TODO: softcode this more + if (this.gameState.canResearch(townPhase,true) && this.gameState.getPopulation() >= this.Config.Economy.villagePopCap - 10 + && this.gameState.findResearchers(townPhase,true).length != 0 && this.queues.majorTech.length() === 0) { - this.queueManager.pauseQueue("villager", true); - this.queueManager.pauseQueue("citizenSoldier", true); - this.queueManager.pauseQueue("house", true); - this.queues.majorTech.addItem(new m.ResearchPlan(this.gameState, townPhase,0,-1,true)); // we rush the town phase. - m.debug ("Trying to reach town phase"); - } - else if (this.gameState.canResearch(cityPhase,true) && this.gameState.getTimeElapsed() > (this.Config.Economy.cityPhase*1000) + var plan = new m.ResearchPlan(this.gameState, townPhase, true); + plan.lastIsGo = false; + plan.onStart = function (gameState) { gameState.ai.HQ.econState = "growth"; gameState.ai.HQ.OnTownPhase(gameState) }; + plan.isGo = function (gameState) { + var ret = gameState.getPopulation() >= gameState.Config.Economy.villagePopCap + if (ret && !this.lastIsGo) + this.onGo(gameState); + else if (!ret && this.lastIsGo) + this.onNotGo(gameState); + this.lastIsGo = ret; + return ret; + }; + plan.onGo = function (gameState) { gameState.ai.HQ.econState = "townPhasing"; m.debug ("Trying to reach TownPhase"); }; + plan.onNotGo = function (gameState) { gameState.ai.HQ.econState = "growth"; }; + + this.queues.majorTech.addItem(plan); + m.debug ("Planning Town Phase"); + } else if (this.gameState.canResearch(cityPhase,true) && this.gameState.getTimeElapsed() > (this.Config.Economy.cityPhase*1000) && this.gameState.getOwnEntitiesByRole("worker", true).length > 85 && this.gameState.findResearchers(cityPhase, true).length != 0 && this.queues.majorTech.length() === 0) { m.debug ("Trying to reach city phase"); @@ -222,20 +232,43 @@ m.AegisBot.prototype.OnUpdate = function(sharedScript) { this.turn++; }; -m.AegisBot.prototype.chooseRandomStrategy = function() +// defines our core components strategy-wise. +// TODO: the sky's the limit here. +m.AegisBot.prototype.initPersonality = function(gameState) { - // deactivated for now. - this.strategy = "normal"; - // rarely and if we can assume it's not a water map. - if (!this.pathInfo.needboat && 0)//Math.random() < 0.2 && this.Config.difficulty == 2) + this.aggressiveness = 0.5; // I'll try to keep this as a percent but it's basically arbitrary. + if (this.Config.difficulty >= 2) + this.aggressiveness = Math.random(); + + var agrThrsh = 0.8; // treshold for aggressiveness. + if (gameState.civ() == "athen") + agrThrsh = 0.6; // works very well with athens + + if (this.aggressiveness > agrThrsh) { - this.strategy = "rush"; - // going to rush. - this.HQ.targetNumWorkers = 0; - this.Config.Economy.townPhase = 480; + m.debug("Going the Rush route"); + this.aggressiveness = 1.0; + // we'll try to pull in an attack at village phase. + this.Config.Military.defenceBuildingTime = 900; + this.Config.Military.popForBarracks1 = 0; + this.Config.Economy.villagePopCap = 75; this.Config.Economy.cityPhase = 900; - this.Config.Economy.farmsteadStartTime = 600; - this.Config.Economy.femaleRatio = 0; // raise it since we'll want to rush age 2. + this.Config.Economy.popForMarket = 80; + this.Config.Economy.popForFarmstead = 50; + this.Config.Economy.targetNumBuilders = 2; + this.Config.Economy.femaleRatio = 0.6; + this.Config.Defence.prudence = 0.5; + } else if (this.aggressiveness < 0.15) { + m.debug("Going the Boom route"); + // Now and then Superboom + this.Config.Military.defenceBuildingTime = 600; + this.Config.Economy.cityPhase = 1000; + this.Config.Military.attackPlansStartTime = 1000; + this.Config.Military.popForBarracks1 = 39; + this.Config.Economy.villagePopCap = 50; + this.Config.Economy.femaleRatio = 1.0; + this.Config.Economy.popForMarket = 55; + this.Config.Economy.popForFarmstead = 55; } }; diff --git a/binaries/data/mods/public/simulation/ai/aegis/attack_plan.js b/binaries/data/mods/public/simulation/ai/aegis/attack_plan.js index a9d039e35b..59786d666d 100755 --- a/binaries/data/mods/public/simulation/ai/aegis/attack_plan.js +++ b/binaries/data/mods/public/simulation/ai/aegis/attack_plan.js @@ -19,6 +19,7 @@ m.CityAttack = function CityAttack(gameState, HQ, Config, uniqueID, targetEnemy, this.targetPlayer = targetEnemy; if (this.targetPlayer === -1 || this.targetPlayer === undefined) { // let's find our prefered target, basically counting our enemies units. + // TODO: improve this. var enemyCount = {}; for (var i = 1; i <=8; i++) enemyCount[i] = 0; @@ -53,9 +54,6 @@ m.CityAttack = function CityAttack(gameState, HQ, Config, uniqueID, targetEnemy, this.timeOfPlanStart = gameState.getTimeElapsed(); // we get the time at which we decided to start the attack this.maxPreparationTime = 210*1000; - // in this case we want to have the attack ready by the 13th minute. Countdown. Minimum 2 minutes. - if (type !== "superSized" && this.Config.difficulty >= 1) - this.maxPreparationTime = 780000 - gameState.getTimeElapsed() < 120000 ? 120000 : 780000 - gameState.getTimeElapsed(); this.pausingStart = 0; this.totalPausingTime = 0; @@ -63,6 +61,9 @@ m.CityAttack = function CityAttack(gameState, HQ, Config, uniqueID, targetEnemy, this.onArrivalReaction = "proceedOnTargets"; + // priority of the queues we'll create. + var priority = 70; + // priority is relative. If all are 0, the only relevant criteria is "currentsize/targetsize". // if not, this is a "bonus". The higher the priority, the faster this unit will get built. // Should really be clamped to [0.1-1.5] (assuming 1 is default/the norm) @@ -70,53 +71,42 @@ m.CityAttack = function CityAttack(gameState, HQ, Config, uniqueID, targetEnemy, // only once every other category is at least 50% of its target size. // note: siege build order is currently added by the military manager if a fortress is there. this.unitStat = {}; - this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 10, "batchSize" : 5, "classes" : ["Infantry","Ranged"], - "interests" : [ ["canGather", 2], ["strength",2], ["cost",1] ], "templates" : [] }; - this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 10, "batchSize" : 5, "classes" : ["Infantry","Melee"], - "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" : [] }; - var priority = 50; + this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 6, "targetSize" : 18, "batchSize" : 3, "classes" : ["Infantry","Ranged"], "interests" : [ ["canGather", 1], ["strength",1.6], ["cost",1.5], ["costsResource", 0.3, "stone"], ["costsResource", 0.3, "metal"] ], "templates" : [] }; + this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 6, "targetSize" : 18, "batchSize" : 3, "classes" : ["Infantry","Melee"], "interests" : [ ["canGather", 1], ["strength",1.6], ["cost",1.5], ["costsResource", 0.3, "stone"], ["costsResource", 0.3, "metal"] ], "templates" : [] }; + + var ats = Math.random() * 15000 - 15000; // attack time shuffle: move the exact attack time around a bit. - if (type === "rush") { + // in this case we want to have the attack ready by the 14th minute. Countdown. Minimum 2 minutes. + if (this.Config.difficulty >= 1) + this.maxPreparationTime = (800000+ats) - gameState.getTimeElapsed() < 120000 ? 120000 : 800000 + ats - gameState.getTimeElapsed(); + + if (type === "Rush") { // we have 3 minutes to train infantry. delete this.unitStat["RangedInfantry"]; delete this.unitStat["MeleeInfantry"]; - delete this.unitStat["MeleeCavalry"]; - delete this.unitStat["RangedCavalry"]; - this.unitStat["Infantry"] = { "priority" : 1, "minSize" : 10, "targetSize" : 30, "batchSize" : 1, "classes" : ["Infantry"], "interests" : [ ["strength",1], ["cost",1] ], "templates" : [] }; - this.maxPreparationTime = 150*1000; - priority = 120; + this.unitStat["Infantry"] = { "priority" : 1, "minSize" : 10, "targetSize" : 30, "batchSize" : 2, "classes" : ["Infantry"], "interests" : [ ["strength",1], ["cost",1], ["costsResource", 0.5, "stone"], ["costsResource", 0.6, "metal"] ], "templates" : [] }; + this.maxPreparationTime = (540000+ats) - gameState.getTimeElapsed() < 120000 ? 120000 : 540000 + ats - gameState.getTimeElapsed(); + priority = 250; } else if (type === "superSized") { // our first attack has started worst case at the 14th minute, we want to attack another time by the 21th minute, so we rock 6.5 minutes - this.maxPreparationTime = 480000; + this.maxPreparationTime = 480000; // 8 minutes // basically we want a mix of citizen soldiers so our barracks have a purpose, and champion units. - this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 20, "batchSize" : 5, "classes" : ["Infantry","Ranged", "CitizenSoldier"], - "interests" : [["strength",3], ["cost",1] ], "templates" : [] }; - this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 20, "batchSize" : 5, "classes" : ["Infantry","Melee", "CitizenSoldier" ], - "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] }; - this.unitStat["ChampRangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 15, "batchSize" : 5, "classes" : ["Infantry","Ranged", "Champion"], - "interests" : [["strength",3], ["cost",1] ], "templates" : [] }; - this.unitStat["ChampMeleeInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 15, "batchSize" : 5, "classes" : ["Infantry","Melee", "Champion" ], - "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] }; - this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 18, "batchSize" : 3, "classes" : ["Cavalry","Melee", "CitizenSoldier" ], - "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; - this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 18 , "batchSize" : 3, "classes" : ["Cavalry","Ranged", "CitizenSoldier"], - "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; - this.unitStat["ChampMeleeInfantry"] = { "priority" : 0.8, "minSize" : 3, "targetSize" : 12, "batchSize" : 3, "classes" : ["Infantry","Melee", "Champion" ], - "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] }; - this.unitStat["ChampMeleeCavalry"] = { "priority" : 0.8, "minSize" : 3, "targetSize" : 12, "batchSize" : 3, "classes" : ["Cavalry","Melee", "Champion" ], - "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; + this.unitStat["RangedInfantry"] = { "priority" : 0.7, "minSize" : 5, "targetSize" : 15, "batchSize" : 5, "classes" : ["Infantry","Ranged", "CitizenSoldier"], "interests" : [["strength",3], ["cost",1] ], "templates" : [] }; + this.unitStat["MeleeInfantry"] = { "priority" : 0.7, "minSize" : 5, "targetSize" : 15, "batchSize" : 5, "classes" : ["Infantry","Melee", "CitizenSoldier" ], "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] }; + this.unitStat["ChampRangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 25, "batchSize" : 5, "classes" : ["Infantry","Ranged", "Champion"], "interests" : [["strength",3], ["cost",1] ], "templates" : [] }; + this.unitStat["ChampMeleeInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 20, "batchSize" : 5, "classes" : ["Infantry","Melee", "Champion" ], "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] }; + this.unitStat["MeleeCavalry"] = { "priority" : 0.7, "minSize" : 3, "targetSize" : 15, "batchSize" : 3, "classes" : ["Cavalry","Melee", "CitizenSoldier" ], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; + this.unitStat["RangedCavalry"] = { "priority" : 0.7, "minSize" : 3, "targetSize" : 15, "batchSize" : 3, "classes" : ["Cavalry","Ranged", "CitizenSoldier"], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; + this.unitStat["ChampMeleeInfantry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 18, "batchSize" : 3, "classes" : ["Infantry","Melee", "Champion" ], "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] }; + this.unitStat["ChampMeleeCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 18, "batchSize" : 3, "classes" : ["Cavalry","Melee", "Champion" ], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; - priority = 70; + priority = 90; } // TODO: there should probably be one queue per type of training building gameState.ai.queueManager.addQueue("plan_" + this.name, priority); this.queue = gameState.ai.queues["plan_" + this.name]; - gameState.ai.queueManager.addQueue("plan_" + this.name +"_champ", priority); + gameState.ai.queueManager.addQueue("plan_" + this.name +"_champ", priority+1); this.queueChamp = gameState.ai.queues["plan_" + this.name +"_champ"]; /* this.unitStat["Siege"]["filter"] = function (ent) { @@ -271,7 +261,10 @@ m.CityAttack.prototype.canStart = function(gameState){ for (var unitCat in this.unitStat) { var Unit = this.unitStat[unitCat]; if (this.unit[unitCat].length < Unit["minSize"]) + { + m.debug(unitCat + " doesn't have enough units : " + this.unit[unitCat].length); return false; + } } return true; @@ -514,7 +507,7 @@ m.CityAttack.prototype.updatePreparation = function(gameState, HQ,events) { queue = this.queueChamp; if (this.buildOrder[0][0] < 1 && queue.length() <= 5) { - var template = HQ.findBestTrainableSoldier(gameState, this.buildOrder[0][1], this.buildOrder[0][3]["interests"] ); + var template = HQ.findBestTrainableUnit(gameState, this.buildOrder[0][1], this.buildOrder[0][3]["interests"] ); //m.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) { @@ -738,7 +731,6 @@ m.CityAttack.prototype.update = function(gameState, HQ, events){ // this actually doesn't do anything right now. if (this.state === "walking") { - var attackedNB = 0; var toProcess = {}; @@ -754,12 +746,7 @@ m.CityAttack.prototype.update = function(gameState, HQ, events){ var ourUnit = gameState.getEntityById(e.target); if (attacker && attacker.position() && attacker.hasClass("Unit") && attacker.owner() != 0 && attacker.owner() != PlayerID) { - - var territoryMap = m.createTerritoryMap(gameState); - if ( +territoryMap.point(attacker.position()) - 64 === +this.targetPlayer) - { - attackedNB++; - } + attackedNB++; //if (HQ.enemyWatchers[attacker.owner()]) { //toProcess[attacker.id()] = attacker; //var armyID = HQ.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id()); @@ -773,7 +760,8 @@ m.CityAttack.prototype.update = function(gameState, HQ, events){ } } - if (attackedNB > 4) { + var territoryMap = m.createTerritoryMap(gameState); + if ((territoryMap.getOwner(this.position) === this.targetPlayer && attackedNB > 1) || attackedNB > 4) { m.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"; @@ -851,7 +839,6 @@ m.CityAttack.prototype.update = function(gameState, HQ, events){ // basically haven't moved an inch: very likely stuck) if (API3.SquareVectorDistance(this.position, this.position5TurnsAgo) < 10 && this.path.length > 0 && gameState.ai.playedTurn % 5 === 0) { // check for stuck siege units - var sieges = this.unitCollection.filter(API3.Filters.byClass("Siege")); var farthest = 0; var farthestEnt = -1; @@ -889,17 +876,17 @@ m.CityAttack.prototype.update = function(gameState, HQ, events){ return 0; } } - + // check if our land units are close enough from the next waypoint. - if (API3.SquareVectorDistance(this.unitCollection.getCentrePosition(), this.targetPos) < 7500 || - API3.SquareVectorDistance(this.unitCollection.getCentrePosition(), this.path[0][0]) < 650) { + if (API3.SquareVectorDistance(this.position, this.targetPos) < 9000 || + API3.SquareVectorDistance(this.position, this.path[0][0]) < 650) { if (this.unitCollection.filter(API3.Filters.byClass("Siege")).length !== 0 - && API3.SquareVectorDistance(this.unitCollection.getCentrePosition(), this.targetPos) >= 7500 + && API3.SquareVectorDistance(this.position, this.targetPos) >= 9000 && API3.SquareVectorDistance(this.unitCollection.filter(API3.Filters.byClass("Siege")).getCentrePosition(), this.path[0][0]) >= 650) { } else { - // okay so here basically two cases. The first one is "we need a boat at this point". - // the second one is "we need to unload at this point". The third is "normal". + // okay so here basically two cases. First case is "we've arrived" + // Second case is "either we need a boat, or we need to unload" if (this.path[0][1] !== true) { this.path.shift(); @@ -975,8 +962,10 @@ m.CityAttack.prototype.update = function(gameState, HQ, events){ } } + // basic state of attacking. if (this.state === "") { - // Units attacked will target their attacker unless they're siege. Then we take another non-siege unit to attack them. + + // events watch: if siege units are attacked, we'll send some units to deal with enemies. var attackedEvents = events["Attacked"]; for (var key in attackedEvents) { var e = attackedEvents[key]; @@ -984,31 +973,20 @@ m.CityAttack.prototype.update = function(gameState, HQ, events){ var attacker = gameState.getEntityById(e.attacker); var ourUnit = gameState.getEntityById(e.target); - if (attacker && attacker.position() && attacker.hasClass("Unit") && attacker.owner() != 0 && attacker.owner() != PlayerID) { - if (ourUnit.hasClass("Siege")) - { - var collec = this.unitCollection.filterNearest(ourUnit.position(), 8).filter(Filters.not(Filters.byClass("Siege"))).toEntityArray(); - if (collec.length !== 0) - { - collec[0].attack(attacker.id()); - if (collec.length !== 1) - { - collec[1].attack(attacker.id()); - if (collec.length !== 2) - { - collec[2].attack(attacker.id()); - } - } - } - } else { - ourUnit.attack(attacker.id()); - } - } + if (!attacker || !attacker.position() || !attacker.hasClass("Unit") || attacker.owner() === 0 || attacker.owner() === PlayerID) + continue; + + if (!ourUnit.hasClass("Siege")) + continue; + var collec = this.unitCollection.filter(Filters.not(Filters.byClass("Siege"))).filterNearest(ourUnit.position(), 5).toEntityArray(); + if (collec.length === 0) + continue; + collec.attack(attacker.id()) } } - var enemyUnits = gameState.getGEC("player-" +this.targetPlayer + "-units"); - var enemyStructures = gameState.getGEC("player-" +this.targetPlayer + "-structures"); + var enemyUnits = gameState.getEnemyUnits(this.targetPlayer); + var enemyStructures = gameState.getEnemyStructures(this.targetPlayer); if (this.unitCollUpdateArray === undefined || this.unitCollUpdateArray.length === 0) { @@ -1025,69 +1003,108 @@ m.CityAttack.prototype.update = function(gameState, HQ, events){ var ent = gameState.getEntityById(this.unitCollUpdateArray[0]); if (!ent) continue; + + // if the unit is in my territory, make it move towards the target. + if (territoryMap.point(ent.position()) - 64 === PlayerID) { + ent.move(this.targetPos[0],this.targetPos[1]); + continue; + } + var orderData = ent.unitAIOrderData(); if (orderData.length !== 0) orderData = orderData[0]; else orderData = undefined; - - // if the unit is in my territory, make it move. - if (territoryMap.point(ent.position()) - 64 === PlayerID) - ent.move(this.targetPos[0],this.targetPos[1]); // update it. var needsUpdate = false; if (ent.isIdle()) needsUpdate = true; - if (ent.hasClass("Siege") && (!orderData || !orderData["target"] || !gameState.getEntityById(orderData["target"]) || !gameState.getEntityById(orderData["target"]).hasClass("ConquestCritical")) ) + else if (ent.hasClass("Siege") && (!orderData || !orderData["target"] || !gameState.getEntityById(orderData["target"]) || !gameState.getEntityById(orderData["target"]).hasClass("ConquestCritical")) ) needsUpdate = true; else if (!ent.hasClass("Siege") && orderData && orderData["target"] && gameState.getEntityById(orderData["target"]) && gameState.getEntityById(orderData["target"]).hasClass("Structure")) needsUpdate = true; // try to make it attack a unit instead + // don't update too soon. if (timeElapsed - ent.getMetadata(PlayerID, "lastAttackPlanUpdateTime") < 10000) - needsUpdate = false; + continue; - if (needsUpdate === true || arrivedthisTurn) - { - ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", timeElapsed); - var mStruct = enemyStructures.filter(function (enemy) { //}){ - if (!enemy.position() || (enemy.hasClass("StoneWall") && ent.canAttackClass("StoneWall"))) { - return false; - } - if (API3.SquareVectorDistance(enemy.position(),ent.position()) > 3000) { - return false; - } - return true; + if (needsUpdate === false && !arrivedthisTurn) + continue; + + ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", timeElapsed); + + // let's filter targets further based on this unit. + var mStruct = enemyStructures.filter(function (enemy) { //}){ + if (!enemy.position() || (enemy.hasClass("StoneWall") && ent.canAttackClass("StoneWall"))) { + return false; + } + if (API3.SquareVectorDistance(enemy.position(),ent.position()) > 3000) { + return false; + } + return true; + }); + var mUnit = enemyUnits.filter(function (enemy) { + if (!enemy.position()) + return false; + if (API3.SquareVectorDistance(enemy.position(),ent.position()) > 10000) + return false; + return true; + }); + // Checking for gates if we're a siege unit. + var isGate = false; + mUnit = mUnit.toEntityArray(); + mStruct = mStruct.toEntityArray(); + if (ent.hasClass("Siege")) { + mStruct.sort(function (structa,structb) { + var vala = structa.costSum(); + if (structa.hasClass("Gates") && ent.canAttackClass("StoneWall")) { // we hate gates + isGate = true; + vala += 10000; + } else if (structa.hasClass("ConquestCritical")) + vala += 200; + var valb = structb.costSum(); + if (structb.hasClass("Gates") && ent.canAttackClass("StoneWall")) { // we hate gates + isGate = true; + valb += 10000; + } else if (structb.hasClass("ConquestCritical")) + valb += 200; + //warn ("Structure " +structa.genericName() + " is worth " +vala); + //warn ("Structure " +structb.genericName() + " is worth " +valb); + return (valb - vala); }); - var mUnit; - if (ent.hasClass("Cavalry") && ent.countersClasses(["Support"])) { - mUnit = enemyUnits.filter(function (enemy) { //}){ - if (!enemy.position()) { - return false; - } - if (!enemy.hasClass("Support")) - return false; - if (API3.SquareVectorDistance(enemy.position(),ent.position()) > 10000) { - return false; - } - return true; - }); + // TODO: handle ballistas here + if (mStruct.length !== 0) { + if (isGate) + ent.attack(mStruct[0].id()); + else + { + var rand = Math.floor(Math.random() * mStruct.length*0.2); + ent.attack(mStruct[+rand].id()); + //m.debug ("Siege units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName()); + } + } else if (API3.SquareVectorDistance(self.targetPos, ent.position()) > 900 ) { + //m.debug ("Siege units moving to " + uneval(self.targetPos)); + ent.move(self.targetPos[0],self.targetPos[1]); } - if (!(ent.hasClass("Cavalry") && ent.countersClasses(["Support"])) || mUnit.length === 0) { - mUnit = enemyUnits.filter(function (enemy) { //}){ - if (!enemy.position()) { - return false; - } - if (API3.SquareVectorDistance(enemy.position(),ent.position()) > 10000) { - return false; - } - return true; + } else { + if (mUnit.length !== 0) { + mUnit.sort(function (unitA,unitB) { + var vala = unitA.hasClass("Support") ? 50 : 0; + if (ent.countersClasses(unitA.classes())) + vala += 100; + var valb = unitB.hasClass("Support") ? 50 : 0; + if (ent.countersClasses(unitB.classes())) + valb += 100; + return valb - vala; }); - } - var isGate = false; - mUnit = mUnit.toEntityArray(); - mStruct = mStruct.toEntityArray(); - if (ent.hasClass("Siege")) { + var rand = Math.floor(Math.random() * mUnit.length*0.1); + ent.attack(mUnit[(+rand)].id()); + //m.debug ("Units attacking a unit from " +mUnit[+rand].owner() + " , " +mUnit[+rand].templateName()); + } else if (API3.SquareVectorDistance(self.targetPos, ent.position()) > 900 ){ + //m.debug ("Units moving to " + uneval(self.targetPos)); + ent.move(self.targetPos[0],self.targetPos[1]); + } else if (mStruct.length !== 0) { mStruct.sort(function (structa,structb) { //}){ var vala = structa.costSum(); if (structa.hasClass("Gates") && ent.canAttackClass("StoneWall")) // we hate gates @@ -1095,66 +1112,23 @@ m.CityAttack.prototype.update = function(gameState, HQ, events){ isGate = true; vala += 10000; } else if (structa.hasClass("ConquestCritical")) - vala += 200; + vala += 100; var valb = structb.costSum(); if (structb.hasClass("Gates") && ent.canAttackClass("StoneWall")) // we hate gates { isGate = true; valb += 10000; } else if (structb.hasClass("ConquestCritical")) - valb += 200; - //warn ("Structure " +structa.genericName() + " is worth " +vala); - //warn ("Structure " +structb.genericName() + " is worth " +valb); + valb += 100; return (valb - vala); }); - // TODO: handle ballistas here - if (mStruct.length !== 0) { - if (isGate) - ent.attack(mStruct[0].id()); - else - { - var rand = Math.floor(Math.random() * mStruct.length*0.1); - ent.attack(mStruct[+rand].id()); - //m.debug ("Siege units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName()); - } - } else if (API3.SquareVectorDistance(self.targetPos, ent.position()) > 900 ){ - //m.debug ("Siege units moving to " + uneval(self.targetPos)); - ent.move(self.targetPos[0],self.targetPos[1]); - } - } else { - if (mUnit.length !== 0) { - var rand = Math.floor(Math.random() * mUnit.length*0.99); - ent.attack(mUnit[(+rand)].id()); - //m.debug ("Units attacking a unit from " +mUnit[+rand].owner() + " , " +mUnit[+rand].templateName()); - } else if (API3.SquareVectorDistance(self.targetPos, ent.position()) > 900 ){ - //m.debug ("Units moving to " + uneval(self.targetPos)); - ent.move(self.targetPos[0],self.targetPos[1]); - } else if (mStruct.length !== 0) { - mStruct.sort(function (structa,structb) { //}){ - var vala = structa.costSum(); - if (structa.hasClass("Gates") && ent.canAttackClass("StoneWall")) // we hate gates - { - isGate = true; - vala += 10000; - } else if (structa.hasClass("ConquestCritical")) - vala += 100; - var valb = structb.costSum(); - if (structb.hasClass("Gates") && ent.canAttackClass("StoneWall")) // we hate gates - { - isGate = true; - valb += 10000; - } else if (structb.hasClass("ConquestCritical")) - valb += 100; - return (valb - vala); - }); - if (isGate) - ent.attack(mStruct[0].id()); - else - { - var rand = Math.floor(Math.random() * mStruct.length*0.1); - ent.attack(mStruct[+rand].id()); - //m.debug ("Units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName()); - } + if (isGate) + ent.attack(mStruct[0].id()); + else + { + var rand = Math.floor(Math.random() * mStruct.length*0.1); + ent.attack(mStruct[+rand].id()); + //m.debug ("Units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName()); } } } diff --git a/binaries/data/mods/public/simulation/ai/aegis/base-manager.js b/binaries/data/mods/public/simulation/ai/aegis/base-manager.js index f1fb4eb5c6..1f89cd0ac6 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/base-manager.js +++ b/binaries/data/mods/public/simulation/ai/aegis/base-manager.js @@ -58,9 +58,9 @@ m.BaseManager.prototype.init = function(gameState, unconstructed){ // TODO: difficulty levels for this? // smallRadius is the distance necessary to mark a resource as linked to a dropsite. - this.smallRadius = { 'food':40*40,'wood':45*45,'stone':40*40,'metal':40*40 }; + this.smallRadius = { 'food':40*40,'wood':50*50,'stone':40*40,'metal':40*40 }; // medRadius is the maximal distance for a link, albeit one that would still make us want to build a new dropsite. - this.medRadius = { 'food':70*70,'wood':70*70,'stone':80*80,'metal':80*80 }; + this.medRadius = { 'food':70*70,'wood':55*55,'stone':80*80,'metal':80*80 }; // bigRadius is the distance for a weak link, mainly for optimizing search for resources when a DP is depleted. this.bigRadius = { 'food':70*70,'wood':200*200,'stone':200*200,'metal':200*200 }; }; @@ -115,6 +115,9 @@ m.BaseManager.prototype.initTerritory = function(HQ, gameState) { var width = gameState.getMap().width; for (var xi = -radius; xi <= radius; ++xi) for (var yi = -radius; yi <= radius; ++yi) + { + if (x+xi >= width || y+yi >= width) + continue; if (xi*xi+yi*yi < radius*radius && HQ.basesMap.map[(x+xi) + (y+yi)*width] === 0) { if (this.accessIndex == gameState.sharedScript.accessibility.landPassMap[x+xi + width*(y+yi)]) @@ -123,6 +126,7 @@ m.BaseManager.prototype.initTerritory = function(HQ, gameState) { HQ.basesMap.map[(x+xi) + (y+yi)*width] = this.ID; } } + } } m.BaseManager.prototype.initGatheringFunctions = function(HQ, gameState, specTypes) { @@ -189,10 +193,10 @@ m.BaseManager.prototype.checkEvents = function (gameState, events, queues) { if (ent.hasClass("CivCentre")) { // TODO: might want to tell the queue manager to pause other stuffs if we are the only base. - queues.civilCentre.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre", { "base" : this.ID, "baseAnchor" : true }, 0 , -1,ent.position())); + queues.civilCentre.addItem(gameState, new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre", { "base" : this.ID, "baseAnchor" : true }, ent.position())); } else { // TODO - queues.civilCentre.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre", { "base" : this.ID, "baseAnchor" : true },0,-1,ent.position())); + queues.civilCentre.addItem(gameState, new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre", { "base" : this.ID, "baseAnchor" : true }, ent.position())); } } @@ -600,7 +604,7 @@ m.BaseManager.prototype.checkResourceLevels = function (gameState,queues) { plan.isGo = function() { return false; }; // don't start right away. queues.field.addItem(plan); } - } else if (!this.isFarming && count < 650) + } else if (!this.isFarming && count < 400) { for (var i in queues.field.queue) queues.field.queue[i].isGo = function() { return true; }; // start them @@ -636,7 +640,7 @@ m.BaseManager.prototype.checkResourceLevels = function (gameState,queues) { // TODO: tell the HQ we'll be needing a new base for this resource, or tell it we've ran out of resource Z. } else { m.debug ("planning new dropsite for " + type); - queues.dropsites.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_storehouse",{ "base" : this.ID }, 0, -1, pos)); + queues.dropsites.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_storehouse",{ "base" : this.ID }, pos)); } } } @@ -660,6 +664,15 @@ m.BaseManager.prototype.getGatherRates = function(gameState, currentRates) { if (gRate !== undefined) currentRates[i] += Math.log(1+gRate)/1.1; }); + if (i === "food") + { + units = this.workers.filter(API3.Filters.byMetadata(PlayerID, "subrole", "hunter")); + units.forEach(function (ent) { + var gRate = ent.currentGatherRate() + if (gRate !== undefined) + currentRates[i] += Math.log(1+gRate)/1.1; + }); + } } }; @@ -799,7 +812,12 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) { // TODO: this is not perfect performance-wise. var foundations = this.buildings.filter(API3.Filters.and(API3.Filters.isFoundation(),API3.Filters.not(API3.Filters.byClass("Field")))).toEntityArray(); - var damagedBuildings = this.buildings.filter(function (ent) { if (ent.needsRepair() && ent.getMetadata(PlayerID, "plan") == undefined) { return true; } return false; }).toEntityArray(); + + var damagedBuildings = this.buildings.filter(function (ent) { + if (ent.foundationProgress() === undefined && ent.needsRepair()) + return true; + return false; + }).toEntityArray(); // Check if nothing to build if (!foundations.length && !damagedBuildings.length){ @@ -846,15 +864,18 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) { for (var i in foundations) { var target = foundations[i]; - // Removed: sometimes the AI would not notice it has empty unbuilt fields - //if (target._template.BuildRestrictions.Category === "Field") - // continue; // we do not build fields + + if (target.hasClass("Field")) + continue; // we do not build fields var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target.id()).length; - var targetNB = this.Config.Economy.targetNumBuilders; // TODO: dynamic that. - if (target.hasClass("CivCentre") || target.buildTime() > 150 || target.hasClass("House")) + if (target.hasClass("House")) targetNB *= 2; + else if (target.hasClass("Barracks")) + targetNB = 4; + else if (target.hasClass("Fortress")) + targetNB = 7; if (target.getMetadata(PlayerID, "baseAnchor") == true) targetNB = 15; @@ -886,6 +907,11 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) { } } } + + // auras/techs are buggy and the AI tries to repair healthy buildings. + // TODO: reimplement once that's fixed. + return; + // don't repair if we're still under attack, unless it's like a vital (civcentre or wall) building that's getting destroyed. for (var i in damagedBuildings) { var target = damagedBuildings[i]; @@ -935,9 +961,20 @@ m.BaseManager.prototype.update = function(gameState, queues, events) { { var terrMap = m.createTerritoryMap(gameState); if(terrMap.getOwner(this.anchor.position()) !== 0 && terrMap.getOwner(this.anchor.position()) !== PlayerID) - this.anchor.destroy(); + { + // we're in enemy territory. If we're too close from the enemy, destroy us. + var eEnts = gameState.getEnemyStructures().filter(API3.Filters.byClass("CivCentre")).toEntityArray(); + for (var i in eEnts) + { + var entPos = eEnts[i].position(); + entPos = [entPos[0]/4.0,entPos[1]/4.0]; + if (API3.SquareVectorDistance(entPos, pos) < 500) + this.anchor.destroy(); + } + } } + // if (!this.constructing) // { if (gameState.ai.playedTurn % 2 === 0) diff --git a/binaries/data/mods/public/simulation/ai/aegis/config.js b/binaries/data/mods/public/simulation/ai/aegis/config.js index 72d3662d32..ed26b1b650 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/config.js +++ b/binaries/data/mods/public/simulation/ai/aegis/config.js @@ -1,33 +1,32 @@ var AEGIS = function(m) { +// this defines the medium difficulty m.Config = function() { this.difficulty = 2; // 0 is sandbox, 1 is easy, 2 is medium, 3 is hard, 4 is very hard. - // overriden by the GUI, this defines the base difficulty. this.Military = { "fortressLapseTime" : 540, // Time to wait between building 2 fortresses "defenceBuildingTime" : 600, // Time to wait before building towers or fortresses - "attackPlansStartTime" : 0, // time to wait before attacking. Start as soon as possible (first barracks) + "attackPlansStartTime" : 0, // time to wait before attacking. Start as soon as possible. "techStartTime" : 120, // time to wait before teching. Will only start after town phase so it's irrelevant. - "popForBarracks1" : 20, + "popForBarracks1" : 25, "popForBarracks2" : 95, "timeForBlacksmith" : 900, }; this.Economy = { - "townPhase" : 180, // time to start trying to reach town phase (might be a while after. Still need the requirements + ress ) + "villagePopCap" : 40, // How many units we want before aging to town. "cityPhase" : 840, // time to start trying to reach city phase - "popForMarket" : 80, - "popForFarmstead" : 45, + "popForMarket" : 50, + "popForFarmstead" : 35, "dockStartTime" : 240, // Time to wait before building the dock "techStartTime" : 0, // time to wait before teching. "targetNumBuilders" : 1.5, // Base number of builders per foundation. - "femaleRatio" : 0.4, // percent of females among the workforce. + "femaleRatio" : 0.5, // percent of females among the workforce. "initialFields" : 2 }; - + // Note: attack settings are set directly in attack_plan.js - // defence this.Defence = { @@ -72,7 +71,7 @@ m.Config = function() { "dropsites" : 120, "field" : 500, "economicBuilding" : 90, - "militaryBuilding" : 140, // TODO: set to a lower value after the first barracks. + "militaryBuilding" : 240, // set to something lower after the first barracks. "defenceBuilding" : 70, "civilCentre" : 400, "majorTech" : 700, diff --git a/binaries/data/mods/public/simulation/ai/aegis/defence-helper.js b/binaries/data/mods/public/simulation/ai/aegis/defence-helper.js index 0f757173ef..bbb7f8b970 100755 --- a/binaries/data/mods/public/simulation/ai/aegis/defence-helper.js +++ b/binaries/data/mods/public/simulation/ai/aegis/defence-helper.js @@ -139,6 +139,8 @@ m.Army.prototype.recalculatePosition = function(gameState, force) for (var i in this.entities) { var ent = gameState.getEntityById(this.entities[i]); + if (!ent) // whaaat? + continue; var epos = ent.position(); if (epos == undefined) continue; @@ -503,6 +505,8 @@ m.Army.prototype.update = function (gameState) { var id = this.entities[i]; var ent = gameState.getEntityById(id); + if (!ent.position) // shouldn't be able to happen but apparently does. + continue; if (API3.SquareVectorDistance(ent.position(), this.position) > this.breakawaySize) { breakaways.push(id); diff --git a/binaries/data/mods/public/simulation/ai/aegis/defence.js b/binaries/data/mods/public/simulation/ai/aegis/defence.js index 04ad40372f..b19d61c56e 100755 --- a/binaries/data/mods/public/simulation/ai/aegis/defence.js +++ b/binaries/data/mods/public/simulation/ai/aegis/defence.js @@ -8,7 +8,7 @@ m.Defence = function(Config) } -m.Defence.prototype.init = function(gameState,events) +m.Defence.prototype.init = function(gameState) { this.armyMergeSize = this.Config.Defence.armyMergeSize; diff --git a/binaries/data/mods/public/simulation/ai/aegis/headquarters.js b/binaries/data/mods/public/simulation/ai/aegis/headquarters.js index 3590e77228..68cd1770b7 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/headquarters.js +++ b/binaries/data/mods/public/simulation/ai/aegis/headquarters.js @@ -25,13 +25,17 @@ m.HQ = function(Config) { this.dockFailed = false; // sanity check this.waterMap = false; // set by the aegis.js file. + this.econState = "growth"; // existing values: growth, townPhasing. + // tell if we can't gather from a resource type for sanity checks. this.outOf = { "food" : false, "wood" : false, "stone" : false, "metal" : false }; this.baseManagers = {}; - // cache the rates currently want for resource gathering. - this.wantedRates = {}; + // cache the rates. + this.wantedRates = { "food": 0, "wood": 0, "stone":0, "metal": 0 }; + this.currentRates = { "food": 0, "wood": 0, "stone":0, "metal": 0 }; + this.currentRateLastUpdateTime = 0; // this means we'll have about a big third of women, and thus we can maximize resource gathering rates. this.femaleRatio = this.Config.Economy.femaleRatio; @@ -40,13 +44,13 @@ m.HQ = function(Config) { this.fortressLapseTime = this.Config.Military.fortressLapseTime * 1000; this.defenceBuildingTime = this.Config.Military.defenceBuildingTime * 1000; this.attackPlansStartTime = this.Config.Military.attackPlansStartTime * 1000; + this.defenceManager = new m.Defence(this.Config); - this.navalManager = new m.NavalManager(); this.TotalAttackNumber = 0; - this.upcomingAttacks = { "CityAttack" : [] }; - this.startedAttacks = { "CityAttack" : [] }; + this.upcomingAttacks = { "CityAttack" : [], "Rush" : [] }; + this.startedAttacks = { "CityAttack" : [], "Rush" : [] }; }; // More initialisation for stuff that needs the gameState @@ -112,7 +116,7 @@ m.HQ.prototype.init = function(gameState, queues){ var pos = this.baseManagers[1].findBestDropsiteLocation(gameState, "wood"); if (pos) { - queues.dropsites.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_storehouse",{ "base" : 1 }, 0, -1, pos)); + queues.dropsites.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_storehouse",{ "base" : 1 }, pos)); queues.minorTech.addItem(new m.ResearchPlan(gameState, "gather_capacity_wheelbarrow")); } } @@ -125,6 +129,7 @@ m.HQ.prototype.init = function(gameState, queues){ //this.reassignIdleWorkers(gameState); this.navalManager.init(gameState, queues); + this.defenceManager.init(gameState); // TODO: change that to something dynamic. var civ = gameState.playerData.civ; @@ -184,11 +189,14 @@ m.HQ.prototype.checkEvents = function (gameState, events, queues) { // Let's get a few units out there to build this. // TODO: select the best base, or use multiple bases. var builders = this.bulkPickWorkers(gameState, bID, 10); - builders.forEach(function (worker) { - worker.setMetadata(PlayerID, "base", bID); - worker.setMetadata(PlayerID, "subrole", "builder"); - worker.setMetadata(PlayerID, "target-foundation", ent.id()); - }); + if (builders !== false) + { + builders.forEach(function (worker) { + worker.setMetadata(PlayerID, "base", bID); + worker.setMetadata(PlayerID, "subrole", "builder"); + worker.setMetadata(PlayerID, "target-foundation", ent.id()); + }); + } } } } @@ -217,6 +225,17 @@ m.HQ.prototype.checkEvents = function (gameState, events, queues) { } }; +// Called by the "town phase" research plan once it's started +m.HQ.prototype.OnTownPhase = function(gameState) +{ + if (this.Config.difficulty >= 2 && this.femaleRatio !== 0.4) + { + this.femaleRatio = 0.4; + gameState.ai.queues["villager"].empty(); + gameState.ai.queues["citizenSoldier"].empty(); + } +} + // This code trains females and citizen workers, trying to keep close to a ratio of females/CS // TODO: this should choose a base depending on which base need workers // TODO: also there are several things that could be greatly improved here. @@ -251,6 +270,9 @@ m.HQ.prototype.trainMoreWorkers = function(gameState, queues) if (numTotal > this.targetNumWorkers || numQueued > 50 || (numQueuedF > 20 && numQueuedS > 20) || numInTraining > 15) return; + if (numTotal >= this.Config.Economy.villagePopCap && gameState.currentPhase() === 1 && !gameState.isResearching(gameState.townPhase())) + return; + // default template and size var template = gameState.applyCiv("units/{civ}_support_female_citizen"); var size = Math.min(5, Math.ceil(numTotal / 10)); @@ -281,9 +303,9 @@ m.HQ.prototype.trainMoreWorkers = function(gameState, queues) // base "0" means "auto" if (template === gameState.applyCiv("units/{civ}_support_female_citizen")) - queues.villager.addItem(new m.TrainingPlan(gameState, template, { "role" : "worker", "base" : 0 }, size, 0, -1, size )); + queues.villager.addItem(new m.TrainingPlan(gameState, template, { "role" : "worker", "base" : 0 }, size, size )); else - queues.citizenSoldier.addItem(new m.TrainingPlan(gameState, template, { "role" : "worker", "base" : 0 }, size, 0, -1, size)); + queues.citizenSoldier.addItem(new m.TrainingPlan(gameState, template, { "role" : "worker", "base" : 0 }, size, size)); }; // picks the best template based on parameters and classes @@ -294,50 +316,6 @@ m.HQ.prototype.findBestTrainableUnit = function(gameState, classes, parameters) return undefined; units.sort(function(a, b) {// }) { - var aDivParam = 0, bDivParam = 0; - var aTopParam = 0, bTopParam = 0; - for (var i in parameters) { - var param = parameters[i]; - - if (param[0] == "base") { - aTopParam = param[1]; - bTopParam = param[1]; - } - if (param[0] == "strength") { - aTopParam += m.getMaxStrength(a[1]) * param[1]; - bTopParam += m.getMaxStrength(b[1]) * 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]; - } - // 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)); - }); - return units[0][0]; -}; - -// picks the best template based on parameters and classes -m.HQ.prototype.findBestTrainableSoldier = function(gameState, classes, parameters) { - var units = gameState.findTrainableUnits(classes); - - if (units.length === 0) - return undefined; - - - units.sort(function(a, b) { //}) { var aDivParam = 0, bDivParam = 0; var aTopParam = 0, bTopParam = 0; for (var i in parameters) { @@ -364,6 +342,13 @@ m.HQ.prototype.findBestTrainableSoldier = function(gameState, classes, parameter 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]; + } if (param[0] == "canGather") { // checking against wood, could be anything else really. if (a[1].resourceGatherRates() && a[1].resourceGatherRates()["wood.tree"]) @@ -377,7 +362,6 @@ m.HQ.prototype.findBestTrainableSoldier = function(gameState, classes, parameter return units[0][0]; }; - // Tries to research any available tech // Only one at once. Also does military tech (selection is completely random atm) // TODO: Lots, lots, lots here. @@ -450,14 +434,18 @@ m.HQ.prototype.bulkPickWorkers = function(gameState, newBaseID, number) { m.HQ.prototype.GetCurrentGatherRates = function(gameState) { var self = this; - var currentRates = {}; + if (gameState.getTimeElapsed() - this.currentRateLastUpdateTime < 10000 && this.currentRateLastUpdateTime !== 0 && gameState.ai.playedTurn > 3) + return this.currentRates; + + this.currentRateLastUpdateTime = gameState.getTimeElapsed(); + for (var type in this.wantedRates) - currentRates[type] = 0; + this.currentRates[type] = 0; for (var i in this.baseManagers) - this.baseManagers[i].getGatherRates(gameState, currentRates); + this.baseManagers[i].getGatherRates(gameState, this.currentRates); - return currentRates; + return this.currentRates; }; @@ -682,12 +670,15 @@ m.HQ.prototype.buildMarket = function(gameState, queues){ // Build a farmstead to go to town phase faster and prepare for research. Only really active on higher diff mode. m.HQ.prototype.buildFarmstead = function(gameState, queues){ - if (gameState.getPopulation() > this.Config.Economy.popForFarmstead) { + if (gameState.getPopulation() > this.Config.Economy.popForFarmstead + && (gameState.currentPhase() > 1 || gameState.isResearching(gameState.townPhase()))) { // achtung: "DropsiteFood" does not refer to CCs. if (queues.economicBuilding.countQueuedUnitsWithClass("DropsiteFood") === 0 && gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_farmstead"), true) === 0){ //only ever build one storehouse/CC/market at a time queues.economicBuilding.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_farmstead", { "base" : 1 })); + // add the farming plough to the research we want. + queues.minorTech.addItem(new m.ResearchPlan(gameState, "gather_farming_plough")); } } }; @@ -713,29 +704,83 @@ m.HQ.prototype.buildDock = function(gameState, queues){ } }; -// if Aegis has resources it doesn't need, it'll try to barter it for resources it needs -// once per turn because the info doesn't update between a turn and I don't want to fix it. -// Not sure how efficient it is but it seems to be sane, at least. -m.HQ.prototype.tryBartering = function(gameState){ - var done = false; - if (gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_market"), true) >= 1) { - - var needs = gameState.ai.queueManager.futureNeeds(gameState); - var ress = gameState.ai.queueManager.getAvailableResources(gameState); - - for (var sell in needs) { - for (var buy in needs) { - if (!done && buy != sell && needs[sell] <= 0 && ress[sell] > 400) { // if we don't need it and have a buffer - if ( (ress[buy] < 400) || needs[buy] > 0) { // if we need that other resource/ have too little of it - var markets = gameState.getOwnEntitiesByType(gameState.applyCiv("structures/{civ}_market"), true).toEntityArray(); - markets[0].barter(buy,sell,100); - //m.debug ("bartered " +sell +" for " + buy + ", value 100"); - done = true; - } +// Try to barter unneeded resources for needed resources. +// once per turn because the info doesn't update between a turn and fixing isn't worth it. +m.HQ.prototype.tryBartering = function(gameState) { + var markets = gameState.getOwnEntitiesByType(gameState.applyCiv("structures/{civ}_market"), true).toEntityArray(); + + if (markets.length === 0) + return false; + + // Available resources after account substraction + var available = gameState.ai.queueManager.getAvailableResources(gameState); + + var rates = this.GetCurrentGatherRates(gameState) + + var prices = gameState.getBarterPrices(); + // calculates conversion rates + var getBarterRate = function (prices,buy,sell) { return Math.round(100 * prices["sell"][sell] / prices["buy"][buy]); }; + + // loop through each queues checking if we could barter and finish a queue quickly. + for (var j in gameState.ai.queues) + { + var queue = gameState.ai.queues[j]; + if (queue.paused || queue.length() === 0) + continue; + + var account = gameState.ai.queueManager.accounts[j]; + var elem = queue.queue[0]; + var elemCost = elem.getCost(); + for each (var ress in elemCost.types) + { + if (available[ress] >= 0) + continue; // don't care if we still have available resources or our rate is good enough + var need = elemCost[ress] - account[ress]; + if (need <= 0 || rates[ress] >= need/50) // don't care if we don't need resources for our first item + continue; + + if (ress == "food" && need < 400) + continue; + + // pick the best resource to barter. + var bestToBarter = ""; + var bestRate = 0; + for each (var otherRess in elemCost.types) + { + if (ress === otherRess) + continue; + // I wanna keep some + if (available[otherRess] < 130 + need) + return false; + var barterRate = getBarterRate(prices, ress, otherRess); + if (barterRate > bestRate) + { + bestRate = barterRate; + bestToBarter = otherRess; + } + } + if (bestToBarter !== "") + { + markets[0].barter(buy,sell,100); + m.debug ("Snipe bartered " + sell +" for " + buy + ", value 100"); + return true; + } + } + } + // now barter for big needs. + var needs = gameState.ai.queueManager.currentNeeds(gameState); + for each (var sell in needs.types) { + for each (var buy in needs.types) { + if (buy != sell && needs[sell] <= 0 && available[sell] > 500) { // if we don't need it and have a buffer + if (needs[buy] > rates[buy]*80) { // if we need that other resource terribly. + markets[0].barter(buy,sell,100); + m.debug ("Gross bartered " +sell +" for " + buy + ", value 100"); + return true; } } } } + return false; }; // build more houses if needed. @@ -743,9 +788,7 @@ m.HQ.prototype.tryBartering = function(gameState){ m.HQ.prototype.buildMoreHouses = function(gameState,queues) { if (gameState.getPopulationLimit() < gameState.getPopulationMax()) { - var numPlanned = queues.house.length(); - if (numPlanned < 3 || (numPlanned < 5 && gameState.getPopulation() > 80)) { var plan = new m.ConstructionPlan(gameState, "structures/{civ}_house", { "base" : 1 }); @@ -762,13 +805,29 @@ m.HQ.prototype.buildMoreHouses = function(gameState,queues) { freeSlots = gameState.getPopulationLimit() + HouseNb*10 - gameState.getPopulation(); if (gameState.getPopulation() > 55 && difficulty > 1) return (freeSlots <= 21); - else if (gameState.getPopulation() >= 20 && difficulty !== 0) - return (freeSlots <= 16); + else if (gameState.getPopulation() >= 30 && difficulty !== 0) + return (freeSlots <= 15); else return (freeSlots <= 10); } queues.house.addItem(plan); } + if (numPlanned > 0 && this.econState == "townPhasing") + { + var houseQueue = queues.house.queue; + var count = gameState.getOwnStructures().filter(API3.Filters.byClass("Village")).length; + count += queues.militaryBuilding.length(); // barracks + for (var i = 0; i < numPlanned; ++i) + { + if (houseQueue[i].isGo(gameState)) + ++count; + else if (count < 5) + { + houseQueue[i].isGo = function () { return true; } + ++count; + } + } + } } }; @@ -809,7 +868,7 @@ m.HQ.prototype.checkBasesRessLevel = function(gameState,queues) { this.outOf[type] = true; } else { // base "-1" means new base. - queues.civilCentre.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre",{ "base" : -1 }, 0, -1, pos)); + queues.civilCentre.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre",{ "base" : -1 }, pos)); } } } @@ -824,7 +883,7 @@ m.HQ.prototype.buildDefences = function(gameState, queues){ var workersNumber = gameState.getOwnEntitiesByRole("worker", true).filter(API3.Filters.not(API3.Filters.byHasMetadata(PlayerID,"plan"))).length; if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv('structures/{civ}_defense_tower'), true) - + queues.defenceBuilding.length() < gameState.getEntityLimits()["DefenseTower"] && queues.defenceBuilding.length() < 4 && gameState.currentPhase() > 1) { + + queues.defenceBuilding.length() < gameState.getEntityLimits()["DefenseTower"] && queues.defenceBuilding.length() === 0 && gameState.currentPhase() > 1) { for (var i in this.baseManagers) { for (var j in this.baseManagers[i].dropsites) @@ -836,7 +895,7 @@ m.HQ.prototype.buildDefences = function(gameState, queues){ { var position = dpEnt.position(); if (position) { - queues.defenceBuilding.addItem(new m.ConstructionPlan(gameState, 'structures/{civ}_defense_tower', { "base" : i }, 0 , -1, position)); + queues.defenceBuilding.addItem(new m.ConstructionPlan(gameState, 'structures/{civ}_defense_tower', { "base" : i }, position)); } dpEnt.setMetadata(PlayerID, "defenseTower", true); } @@ -896,19 +955,26 @@ m.HQ.prototype.constructTrainingBuildings = function(gameState, queues) { Engine.ProfileStart("Build buildings"); var workersNumber = gameState.getOwnEntitiesByRole("worker", true).filter(API3.Filters.not(API3.Filters.byHasMetadata(PlayerID, "plan"))).length; - if (workersNumber > this.Config.Military.popForBarracks1) { - if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0]), true) + queues.militaryBuilding.length() < 1) { + var barrackNb = gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0]), true); + + // first barracks. + if (workersNumber > this.Config.Military.popForBarracks1 || (this.econState == "townPhasing" && gameState.getOwnStructures().filter(API3.Filters.byClass("Village")).length < 5)) { + if (barrackNb + queues.militaryBuilding.length() < 1) { m.debug ("Trying to build barracks"); - queues.militaryBuilding.addItem(new m.ConstructionPlan(gameState, this.bModerate[0], { "base" : 1 })); + var plan = new m.ConstructionPlan(gameState, this.bModerate[0], { "base" : 1 }); + plan.onStart = function(gameState) { gameState.ai.queueManager.changePriority("militaryBuilding", 130); }; + queues.militaryBuilding.addItem(plan); } } - - if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0]), true) < 2 && workersNumber > this.Config.Military.popForBarracks2) + + // second barracks. + if (barrackNb < 2 && workersNumber > this.Config.Military.popForBarracks2) if (queues.militaryBuilding.length() < 1) queues.militaryBuilding.addItem(new m.ConstructionPlan(gameState, this.bModerate[0], { "base" : 1 })); - if (gameState.countEntitiesByType(gameState.applyCiv(this.bModerate[0]), true) === 2 && gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0]), true) < 3 && workersNumber > 125) - if (queues.militaryBuilding.length() < 1) + // third barracks (optional 4th/5th for some civs as they rely on barracks more.) + if (barrackNb === 2 && barrackNb + queues.militaryBuilding.length() < 3 && workersNumber > 125) + if (queues.militaryBuilding.length() === 0) { queues.militaryBuilding.addItem(new m.ConstructionPlan(gameState, this.bModerate[0], { "base" : 1 })); if (gameState.civ() == "gaul" || gameState.civ() == "brit" || gameState.civ() == "iber") { @@ -916,6 +982,7 @@ m.HQ.prototype.constructTrainingBuildings = function(gameState, queues) { queues.militaryBuilding.addItem(new m.ConstructionPlan(gameState, this.bModerate[0], { "base" : 1 })); } } + //build advanced military buildings if (workersNumber >= this.Config.Military.popForBarracks2 - 15 && gameState.currentPhase() > 2){ if (queues.militaryBuilding.length() === 0){ @@ -930,6 +997,7 @@ m.HQ.prototype.constructTrainingBuildings = function(gameState, queues) { } } } + // build second advanced building except for some civs. if (gameState.civ() !== "gaul" && gameState.civ() !== "brit" && gameState.civ() !== "iber" && workersNumber > 130 && gameState.currentPhase() > 2) { @@ -1062,8 +1130,6 @@ m.HQ.prototype.update = function(gameState, queues, events) { this.buildMoreHouses(gameState,queues); - this.GetCurrentGatherRates(gameState); - if (gameState.getTimeElapsed() > this.techStartTime && gameState.currentPhase() > 2 ) this.tryResearchTechs(gameState,queues); @@ -1170,50 +1236,43 @@ m.HQ.prototype.update = function(gameState, queues, events) { } } + // creating plans after updating because an aborted plan might be reused in that case. + // TODO: remove the limitation to attacks when on water maps. - - // Note: these indications of "rush" are currently unused. - if (gameState.ai.strategy === "rush" && this.startedAttacks["CityAttack"].length !== 0) { - // and then we revert. - gameState.ai.strategy = "normal"; - this.Config.Economy.femaleRatio = 0.4; - gameState.ai.modules.economy.targetNumWorkers = Math.max(Math.floor(gameState.getPopulationMax()*0.55), 1); - } else if (gameState.ai.strategy === "rush" && this.upcomingAttacks["CityAttack"].length === 0) + if (!this.waterMap && !this.attackPlansEncounteredWater) { - Lalala = new m.CityAttack(gameState, this, this.Config, this.TotalAttackNumber, -1, "rush") - this.TotalAttackNumber++; - this.upcomingAttacks["CityAttack"].push(Lalala); - m.debug ("Starting a little something"); - } else if (gameState.ai.strategy !== "rush" && !this.waterMap) - { - // creating plans after updating because an aborted plan might be reused in that case. - if (gameState.countEntitiesByType(gameState.applyCiv(this.bModerate[0]), true) >= 1 && !this.attackPlansEncounteredWater - && gameState.getTimeElapsed() > this.attackPlansStartTime && gameState.currentPhase() > 1) { + if (gameState.ai.aggressiveness > 0.75 && gameState.countEntitiesByType(gameState.applyCiv(this.bModerate[0]), true) >= 1 + && gameState.getTimeElapsed() > this.attackPlansStartTime && gameState.getTimeElapsed() < 360000) + { + if (this.upcomingAttacks["Rush"].length === 0) + { + // we have a barracks and we want to rush, rush. + var AttackPlan = new m.CityAttack(gameState, this, this.Config, this.TotalAttackNumber, -1, "Rush"); + m.debug ("Headquarters: Rushing plan " +this.TotalAttackNumber); + this.TotalAttackNumber++; + this.upcomingAttacks["Rush"].push(AttackPlan); + } + } + // if we have a barracks, there's no water, we're at age >= 1 and we've decided to attack. + else if (gameState.countEntitiesByType(gameState.applyCiv(this.bModerate[0]), true) >= 1 && !this.attackPlansEncounteredWater + && gameState.getTimeElapsed() > this.attackPlansStartTime && (gameState.currentPhase() > 1 || gameState.isResearching(gameState.townPhase()))) { if (gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_dock"), true) === 0 && this.waterMap) { // wait till we get a dock. - } else { + } else if (this.upcomingAttacks["CityAttack"].length === 0) { // basically only the first plan, really. - if (this.upcomingAttacks["CityAttack"].length == 0 && gameState.getTimeElapsed() < 12*60000) { - var Lalala = new m.CityAttack(gameState, this, this.Config, this.TotalAttackNumber, -1); - if (Lalala.failed) - { - this.attackPlansEncounteredWater = true; // hack - } else { - m.debug ("Military Manager: Creating the plan " +this.TotalAttackNumber); - this.TotalAttackNumber++; - this.upcomingAttacks["CityAttack"].push(Lalala); - } - } else if (this.upcomingAttacks["CityAttack"].length == 0 && this.Config.difficulty !== 0) { - var Lalala = new m.CityAttack(gameState, this, this.Config, this.TotalAttackNumber, -1, "superSized"); - if (Lalala.failed) - { - this.attackPlansEncounteredWater = true; // hack - } else { - m.debug ("Military Manager: Creating the super sized plan " +this.TotalAttackNumber); - this.TotalAttackNumber++; - this.upcomingAttacks["CityAttack"].push(Lalala); - } + var Lalala = undefined; + if (gameState.getTimeElapsed() < 12*60000) + Lalala = new m.CityAttack(gameState, this, this.Config, this.TotalAttackNumber, -1); + else if (this.Config.difficulty !== 0) + Lalala = new m.CityAttack(gameState, this, this.Config, this.TotalAttackNumber, -1, "superSized"); + + if (Lalala.failed) + this.attackPlansEncounteredWater = true; // hack + else { + m.debug ("Military Manager: Creating the plan " +this.TotalAttackNumber); + this.TotalAttackNumber++; + this.upcomingAttacks["CityAttack"].push(Lalala); } } } diff --git a/binaries/data/mods/public/simulation/ai/aegis/naval-manager.js b/binaries/data/mods/public/simulation/ai/aegis/naval-manager.js index 4c22f9bf84..3464dcfa9a 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/naval-manager.js +++ b/binaries/data/mods/public/simulation/ai/aegis/naval-manager.js @@ -187,7 +187,7 @@ m.NavalManager.prototype.maintainFleet = function(gameState, queues, events) { && tpNb + queues.ships.length() === 0 && gameState.getTemplate(gameState.applyCiv("units/{civ}_ship_bireme")).available(gameState)) { // TODO: check our dock can build the wanted ship types, for Carthage. - queues.ships.addItem(new m.TrainingPlan(gameState, "units/{civ}_ship_bireme", { "sea" : i }, 1, 0, -1, 1 )); + queues.ships.addItem(new m.TrainingPlan(gameState, "units/{civ}_ship_bireme", { "sea" : i }, 1, 1 )); } } }; diff --git a/binaries/data/mods/public/simulation/ai/aegis/queue-manager.js b/binaries/data/mods/public/simulation/ai/aegis/queue-manager.js index 53c82ecd06..42ef11c4a5 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/queue-manager.js +++ b/binaries/data/mods/public/simulation/ai/aegis/queue-manager.js @@ -24,14 +24,12 @@ m.QueueManager = function(Config, queues, priorities) { this.Config = Config; this.queues = queues; this.priorities = priorities; - this.account = {}; this.accounts = {}; - // the sorting would need to be updated on priority change but there is currently none. + // the sorting is updated on priority change. var self = this; this.queueArrays = []; for (var p in this.queues) { - this.account[p] = 0; this.accounts[p] = new API3.Resources(); this.queueArrays.push([p,this.queues[p]]); } @@ -59,31 +57,22 @@ m.QueueManager.prototype.getTotalAccountedResources = function(gameState) { }; m.QueueManager.prototype.currentNeeds = function(gameState) { - var needs = new API3.Resources(); - // get out current resources, not removing accounts. - var current = this.getAvailableResources(gameState, true); + var needed = new API3.Resources(); //queueArrays because it's faster. for (var i in this.queueArrays) { var name = this.queueArrays[i][0]; var queue = this.queueArrays[i][1]; - if (queue.length() > 0 && queue.getNext().isGo(gameState)) - needs.add(queue.getNext().getCost()); - else if (queue.length() > 0 && !queue.getNext().isGo(gameState)) - { - var cost = queue.getNext().getCost(); - cost.multiply(0.5); - needs.add(cost); - } - if (queue.length() > 1 && queue.queue[1].isGo(gameState)) - needs.add(queue.queue[1].getCost()); + if (queue.length() == 0 || !queue.queue[0].isGo(gameState)) + continue; + // we need resource if the account is smaller than the cost + var costs = queue.queue[0].getCost(); + for each (var ress in costs.types) + costs[ress] = Math.max(0, costs[ress] - this.accounts[name][ress]); + + needed.add(costs); } - return { - "food" : Math.max(25 + needs.food - current.food, 0), - "wood" : Math.max(needs.wood - current.wood, 0), - "stone" : Math.max(needs.stone - current.stone, 0), - "metal" : Math.max(needs.metal - current.metal, 0) - }; + return needed; }; m.QueueManager.prototype.futureNeeds = function(gameState) { @@ -137,10 +126,6 @@ m.QueueManager.prototype.wantedGatherRates = function(gameState) { var elem = queue.queue[j]; var cost = elem.getCost(); - if (qTime < elem.startTime) - qTime = elem.startTime; - // TODO: what is the else case here? - if (!elem.isGo(gameState)) { // assume we'll be wanted in four minutes. @@ -150,21 +135,12 @@ m.QueueManager.prototype.wantedGatherRates = function(gameState) { qTime += 240000; break; // disregard other stuffs. } - if (!elem.endTime) - { - // Assume we want it in 30 seconds from current time. - // Costs are made higher based on priority and lower based on current time. - // TODO: work on this. - for (var type in qCosts) - qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name])) / (qTime/time); - qTime += 30000; - } else { - // TODO: work on this. - for (var type in qCosts) - qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name])) / (qTime/time); - // TODO: refine based on % completed. - qTime += (elem.endTime-elem.startTime); - } + // Assume we want it in 30 seconds from current time. + // Costs are made higher based on priority and lower based on current time. + // TODO: work on this. + for (var type in qCosts) + qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name])) / (qTime/time); + qTime += 30000; // TODO: this needs a lot more work. } for (var j in qCosts) { @@ -420,51 +396,45 @@ m.QueueManager.prototype.update = function(gameState) { var maxAdd = Math.floor(Math.min(maxNeed[j], toAdd)); this.accounts[j][ress] += maxAdd; } - }/* else if (ress != "population" && gameState.ai.playedTurn % 5 === 0) { - // okay here we haev no resource available. We'll try to shift resources to complete plans if possible. - // So basically if 2 queues have resources, and one is higher priority, and it needs resources - // We'll shift from the lower priority to the higher if we can complete it. - var queues = []; - for (var j in this.queues) { - if (this.queues[j].length() && this.queues[j].getNext().isGo(gameState) && this.accounts[j][ress] > 0) - queues.push(j); - } - if (queues.length > 1) + } else { + // We have no available resources, see if we can't "compact" them in one queue. + // compare queues 2 by 2, and if one with a higher priority could be completed by our amount, give it. + // TODO: this isn't perfect compression. + for (var j in this.queues) { - // we'll work from the bottom to the top. ie lowest priority will try to give to highest priority. - queues.sort(function (a,b) { return (self.priorities[a] < self.priorities[b]); }); - var under = 0, over = queues.length - 1; - while (under !== over) + var queue = this.queues[j]; + var queueCost = queue.maxAccountWanted(gameState); + if (this.queues[j].length() === 0 || this.queues[j].paused) + continue; + + for (var i in this.queues) { - var cost = this.queues[queues[over]].getNext().getCost()[ress]; - var totalCost = this.queues[queues[over]].maxAccountWanted(gameState)[ress]; - if (this.accounts[queues[over]] >= cost) - { - --over; // check the next one. + if (i === j) continue; - } - // need some discrepancy in priorities - if (this.priorities[queues[under]] < this.priorities[queues[over]] - 20) + var otherQueue = this.queues[i]; + if (this.priorities[i] >= this.priorities[j] || otherQueue.switched !== 0) + continue; + + for (var ress in queueCost) { - if (this.accounts[queues[under]] + this.accounts[queues[over]] >= cost) + if (this.accounts[j][ress] >= queueCost[ress]) + continue; + if (this.accounts[j][ress] + this.accounts[i][ress] >= queueCost[ress]) { - var amnt = cost - this.accounts[queues[over]]; - this.accounts[queues[under]] -= amnt; - this.accounts[queues[over]] += amnt; - --over; - m.debug ("Shifting " + amnt + " from " + queues[under] + " to " +queues[over]); - continue; - } else { - ++under; - continue; + // we would be helped by it. Check if it's worth it. + for (var otherRess in queueCost) + if (otherRess !== ress && queueCost[otherRess] + 100 >= queueCost[ress]) + continue; + var diff = Math.min(queueCost[ress] - this.accounts[j][ress],this.accounts[i][ress]); + this.accounts[j][ress] += diff; + this.accounts[i][ress] -= diff; + ++otherQueue.switched; + //warn ("switching " + ress + " from " + i + " to " + j + " in amount " + diff); } - } else { - break; } } - // okaaaay. } - }*/ + } } Engine.ProfileStart("Pick items from queues"); @@ -487,10 +457,12 @@ m.QueueManager.prototype.update = function(gameState) { { this.accounts[name].subtract(item.getCost()); queue.startNext(gameState); + otherQueue.switched = 0; } } } else if (queue.length() === 0) { this.accounts[name].reset(); + otherQueue.switched = 0; } } //m.debug (uneval(this.accounts)); @@ -538,7 +510,6 @@ m.QueueManager.prototype.addQueue = function(queueName, priority) { if (this.queues[queueName] == undefined) { this.queues[queueName] = new m.Queue(); this.priorities[queueName] = priority; - this.account[queueName] = 0; this.accounts[queueName] = new API3.Resources(); var self = this; @@ -555,7 +526,6 @@ m.QueueManager.prototype.removeQueue = function(queueName) { } delete this.queues[queueName]; delete this.priorities[queueName]; - delete this.account[queueName]; delete this.accounts[queueName]; var self = this; diff --git a/binaries/data/mods/public/simulation/ai/aegis/queue.js b/binaries/data/mods/public/simulation/ai/aegis/queue.js index fd478e8c1a..8ecf1dd353 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/queue.js +++ b/binaries/data/mods/public/simulation/ai/aegis/queue.js @@ -8,6 +8,7 @@ var AEGIS = function(m) m.Queue = function() { this.queue = []; this.paused = false; + this.switched = 0; }; m.Queue.prototype.empty = function() { @@ -52,7 +53,7 @@ m.Queue.prototype.maxAccountWanted = function(gameState) { if (this.queue.length > 1 && this.queue[1].isGo(gameState)) { var costs = this.queue[1].getCost(); - costs.multiply(0.8); + costs.multiply(0.4); cost.add(costs); } return cost; diff --git a/binaries/data/mods/public/simulation/ai/aegis/queueplan-building.js b/binaries/data/mods/public/simulation/ai/aegis/queueplan-building.js index 7128c9d256..3c2bf434ff 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/queueplan-building.js +++ b/binaries/data/mods/public/simulation/ai/aegis/queueplan-building.js @@ -1,42 +1,25 @@ var AEGIS = function(m) { -m.ConstructionPlan = function(gameState, type, metadata, startTime, expectedTime, position) { - this.type = gameState.applyCiv(type); - this.position = position; +// Defines a construction plan, ie a building. +// We'll try to fing a good position if non has been provided - this.metadata = metadata; - - this.ID = m.playerGlobals[PlayerID].uniqueIDBOPlans++; - - this.template = gameState.getTemplate(this.type); - if (!this.template) { +m.ConstructionPlan = function(gameState, type, metadata, position) { + if (!m.QueuePlan.call(this, gameState, type, metadata)) return false; - } - - this.category = "building"; - this.cost = new API3.Resources(this.template.cost()); - this.number = 1; // The number of buildings to build - - if (!startTime) - this.startTime = 0; - else - this.startTime = startTime; - if (!expectedTime) - this.expectedTime = -1; - else - this.expectedTime = expectedTime; + this.position = position ? position : 0; + + this.category = "building"; + return true; }; -// return true if we willstart amassing resource for this plan -m.ConstructionPlan.prototype.isGo = function(gameState) { - return (gameState.getTimeElapsed() > this.startTime); -}; +m.ConstructionPlan.prototype = Object.create(m.QueuePlan.prototype); // checks other than resource ones. // TODO: change this. +// TODO: if there are specific requirements here, maybe try to do them? m.ConstructionPlan.prototype.canStart = function(gameState) { if (gameState.buildingsBuilt > 0) return false; @@ -81,12 +64,7 @@ m.ConstructionPlan.prototype.start = function(gameState) { } } else builders[0].construct(this.type, pos.x, pos.z, pos.angle, this.metadata); -}; - -m.ConstructionPlan.prototype.getCost = function() { - var costs = new API3.Resources(); - costs.add(this.cost); - return costs; + this.onStart(gameState); }; m.ConstructionPlan.prototype.findGoodPosition = function(gameState) { @@ -119,54 +97,31 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState) { } else { // No position was specified so try and find a sensible place to build gameState.getOwnStructures().forEach(function(ent) { - var infl = 32; - if (ent.hasClass("CivCentre")) - infl *= 4; - var pos = ent.position(); var x = Math.round(pos[0] / cellSize); var z = Math.round(pos[1] / cellSize); - - if (ent.buildCategory() == "Wall") { // no real blockers, but can't build where they are - friendlyTiles.addInfluence(x, z, 2,-1000); - return; - } - if (template._template.BuildRestrictions.Category === "Field"){ - if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf("food") !== -1){ - if (ent.hasClass("CivCentre")) - friendlyTiles.addInfluence(x, z, infl/4, infl); - else - friendlyTiles.addInfluence(x, z, infl, infl); - - } - }else{ - if (template.genericName() == "House" && ent.genericName() == "House") { - friendlyTiles.addInfluence(x, z, 15.0,20,'linear'); // houses are close to other houses + if (template.hasClass("Field")) { + if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf("food") !== -1) + friendlyTiles.addInfluence(x, z, 20, 50); + } else if (template.hasClass("House")) { + if (ent.hasClass("House")) + { + friendlyTiles.addInfluence(x, z, 15,40); // houses are close to other houses alreadyHasHouses = true; - } else if (template.hasClass("GarrisonFortress") && ent.genericName() == "House") - { + } else { + friendlyTiles.addInfluence(x, z, 15, -40); // and further away from other stuffs + } + } else { + if (template.hasClass("GarrisonFortress") && ent.genericName() == "House") friendlyTiles.addInfluence(x, z, 30, -50); - } else if (template.genericName() == "House") { - friendlyTiles.addInfluence(x, z, Math.ceil(infl/4.0),-infl/2.0); // houses are farther away from other buildings but houses - } else if (template.hasClass("GarrisonFortress")) - { - friendlyTiles.addInfluence(x, z, 20, 10); - friendlyTiles.addInfluence(x, z, 10, -40, 'linear'); - } else if (ent.genericName() != "House") // houses have no influence on other buildings - { - friendlyTiles.addInfluence(x, z, infl); - //avoid building too close to each other if possible. - friendlyTiles.addInfluence(x, z, 5, -5, 'linear'); - } - // If this is not a field add a negative influence near the CivCentre because we want to leave this - // area for fields. - if (ent.hasClass("CivCentre") && template.genericName() != "House"){ - friendlyTiles.addInfluence(x, z, Math.floor(infl/8), Math.floor(-infl/2)); - } else if (ent.hasClass("CivCentre")) { - friendlyTiles.addInfluence(x, z, infl/3.0, infl + 1); - friendlyTiles.addInfluence(x, z, Math.ceil(infl/5.0), -(infl/2.0), 'linear'); - } + else if (template.hasClass("Military")) + friendlyTiles.addInfluence(x, z, 10, -40); + + // If this is not a field add a negative influence near the CivCentre because we want to leave this + // area for fields. + if (ent.hasClass("CivCentre")) + friendlyTiles.addInfluence(x, z, 20, -20); } }); if (this.metadata && this.metadata.base !== undefined) @@ -231,6 +186,5 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState) { }; }; - return m; }(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/queueplan-research.js b/binaries/data/mods/public/simulation/ai/aegis/queueplan-research.js index 8cefb2c621..81915fa4d0 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/queueplan-research.js +++ b/binaries/data/mods/public/simulation/ai/aegis/queueplan-research.js @@ -1,41 +1,21 @@ var AEGIS = function(m) { -m.ResearchPlan = function(gameState, type, startTime, expectedTime, rush) { - this.type = type; - - this.ID = m.playerGlobals[PlayerID].uniqueIDBOPlans++; - - this.template = gameState.getTemplate(this.type); - if (!this.template || this.template.researchTime === undefined) { +m.ResearchPlan = function(gameState, type, rush) { + if (!m.QueuePlan.call(this, gameState, type, {})) return false; - } - this.category = "technology"; - this.cost = new API3.Resources(this.template.cost(),0); - this.number = 1; // Obligatory for compatibility - - if (!startTime) - this.startTime = 0; - else - this.startTime = startTime; - - if (!expectedTime) - this.expectedTime = -1; - else - this.expectedTime = expectedTime; - if (rush) - this.rush = true; - else - this.rush = false; + if (this.template.researchTime === undefined) + return false; + + this.category = "technology"; + + this.rush = rush ? true : false; return true; }; -// return true if we willstart amassing resource for this plan -m.ResearchPlan.prototype.isGo = function(gameState) { - return (gameState.getTimeElapsed() > this.startTime); -}; +m.ResearchPlan.prototype = Object.create(m.QueuePlan.prototype); m.ResearchPlan.prototype.canStart = function(gameState) { // also checks canResearch @@ -45,10 +25,6 @@ m.ResearchPlan.prototype.canStart = function(gameState) { m.ResearchPlan.prototype.start = function(gameState) { var self = this; - // TODO: this is special cased for "rush" technologies, ie the town phase - // which currently is a 100% focus. - gameState.ai.queueManager.unpauseAll(); - //m.debug ("Starting the research plan for " + this.type); var trainers = gameState.findResearchers(this.type).toEntityArray(); @@ -67,14 +43,8 @@ m.ResearchPlan.prototype.start = function(gameState) { trainers[0].stopAllProduction(0.45); trainers[0].research(this.type); } + this.onStart(gameState); }; -m.ResearchPlan.prototype.getCost = function(){ - var costs = new API3.Resources(); - costs.add(this.cost); - return costs; -}; - - return m; }(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/queueplan-training.js b/binaries/data/mods/public/simulation/ai/aegis/queueplan-training.js index d4d3f7a538..d5e2648756 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/queueplan-training.js +++ b/binaries/data/mods/public/simulation/ai/aegis/queueplan-training.js @@ -1,44 +1,20 @@ var AEGIS = function(m) { -m.TrainingPlan = function(gameState, type, metadata, number, startTime, expectedTime, maxMerge) { - this.type = gameState.applyCiv(type); - this.metadata = metadata; - - this.ID = m.playerGlobals[PlayerID].uniqueIDBOPlans++; - - this.template = gameState.getTemplate(this.type); - if (!this.template) +m.TrainingPlan = function(gameState, type, metadata, number, maxMerge) { + if (!m.QueuePlan.call(this, gameState, type, metadata)) return false; this.category = "unit"; this.cost = new API3.Resources(this.template.cost(), this.template._template.Cost.Population); - if (!number) - this.number = 1; - else - this.number = number; - if (!maxMerge) - this.maxMerge = 5; - else - this.maxMerge = maxMerge; + this.number = number !== undefined ? number : 1; + this.maxMerge = maxMerge !== undefined ? maxMerge : 5; - if (!startTime) - this.startTime = 0; - else - this.startTime = startTime; - - if (!expectedTime) - this.expectedTime = -1; - else - this.expectedTime = expectedTime; return true; }; -// return true if we willstart amassing resource for this plan -m.TrainingPlan.prototype.isGo = function(gameState) { - return (gameState.getTimeElapsed() > this.startTime); -}; +m.TrainingPlan.prototype = Object.create(m.QueuePlan.prototype); m.TrainingPlan.prototype.canStart = function(gameState) { if (this.invalidTemplate) @@ -73,21 +49,14 @@ m.TrainingPlan.prototype.start = function(gameState) { this.metadata.base = trainers[0].getMetadata(PlayerID,"base"); trainers[0].train(this.type, this.number, this.metadata); } + this.onStart(gameState); }; -m.TrainingPlan.prototype.getCost = function(){ - var multCost = new API3.Resources(); - multCost.add(this.cost); - multCost.multiply(this.number); - return multCost; -}; - -m.TrainingPlan.prototype.addItem = function(amount){ +m.TrainingPlan.prototype.addItem = function(amount) { if (amount === undefined) amount = 1; this.number += amount; }; - return m; }(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/worker.js b/binaries/data/mods/public/simulation/ai/aegis/worker.js index a7c4598c50..6fb6c16bf5 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/worker.js +++ b/binaries/data/mods/public/simulation/ai/aegis/worker.js @@ -143,9 +143,9 @@ m.Worker.prototype.update = function(baseManager, gameState) { } else if(subrole === "builder") { // check for transport. - if (gameState.ai.playedTurn % 5 === 0) + /*if (gameState.ai.playedTurn % 5 === 0) { - if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[2] === "APPROACHING" && this.ent.unitAIOrderData()[0]["target"]) + if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[2] && this.ent.unitAIState().split(".")[2] === "APPROACHING" && this.ent.unitAIOrderData()[0]["target"]) { var ress = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]); if (ress !== undefined) @@ -154,12 +154,11 @@ m.Worker.prototype.update = function(baseManager, gameState) { var mIndex = gameState.ai.accessibility.getAccessValue(this.ent.position()); if (index !== mIndex && index !== 1) { - //gameState.ai.HQ.navalManager.askForTransport(this.ent.id(), this.ent.position(), ress.position()); + gameState.ai.HQ.navalManager.askForTransport(this.ent.id(), this.ent.position(), ress.position()); } } } - } - + }*/ if (this.ent.unitAIState().split(".")[1] !== "REPAIR") { var target = gameState.getEntityById(this.ent.getMetadata(PlayerID, "target-foundation")); @@ -169,8 +168,7 @@ m.Worker.prototype.update = function(baseManager, gameState) { { if (!this.ent.getMetadata(PlayerID, "keepSubrole")) this.ent.setMetadata(PlayerID, "subrole", "idle"); - } - else + } else this.ent.repair(target); } this.startApproachingResourceTime = gameState.getTimeElapsed(); diff --git a/binaries/data/mods/public/simulation/ai/common-api/baseAI.js b/binaries/data/mods/public/simulation/ai/common-api/baseAI.js index 4c0b89b1b1..560156c394 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/baseAI.js +++ b/binaries/data/mods/public/simulation/ai/common-api/baseAI.js @@ -55,6 +55,8 @@ m.BaseAI.prototype.Init = function(state, playerID, sharedAI) this.timeElapsed = sharedAI.timeElapsed; + this.barterPrices = sharedAI.barterPrices; + this.CustomInit(this.gameState, this.sharedScript); } diff --git a/binaries/data/mods/public/simulation/ai/common-api/entity.js b/binaries/data/mods/public/simulation/ai/common-api/entity.js index d559e4fdb3..6aa76033ca 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/entity.js +++ b/binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -142,7 +142,7 @@ m.EntityTemplate = m.Class({ maxHitpoints: function() { if (this._template.Health !== undefined) - return this._template.Health.Max; + return GetTechModifiedProperty(this._techModifications, this._template, "Health/Max",+this._template.Health.Max); return 0; }, isHealable: function() diff --git a/binaries/data/mods/public/simulation/ai/common-api/gamestate.js b/binaries/data/mods/public/simulation/ai/common-api/gamestate.js index 7b63e08951..a16cf09b5e 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/gamestate.js +++ b/binaries/data/mods/public/simulation/ai/common-api/gamestate.js @@ -25,6 +25,7 @@ m.GameState.prototype.init = function(SharedScript, state, player) { this.player = player; this.playerData = this.sharedScript.playersData[this.player]; this.techModifications = SharedScript._techModifications[this.player]; + this.barterPrices = SharedScript.barterPrices; }; m.GameState.prototype.update = function(SharedScript, state) { @@ -38,6 +39,7 @@ m.GameState.prototype.update = function(SharedScript, state) { this.entities = SharedScript.entities; this.playerData = SharedScript.playersData[this.player]; this.techModifications = SharedScript._techModifications[this.player]; + this.barterPrices = SharedScript.barterPrices; this.buildingsBuilt = 0; this.turnCache = {}; @@ -111,6 +113,11 @@ m.GameState.prototype.getTimeElapsed = function() return this.timeElapsed; }; +m.GameState.prototype.getBarterPrices = function() +{ + return this.barterPrices; +}; + m.GameState.prototype.getTemplate = function(type) { if (this.techTemplates[type] !== undefined) diff --git a/binaries/data/mods/public/simulation/ai/common-api/map-module.js b/binaries/data/mods/public/simulation/ai/common-api/map-module.js index f3d0258768..b01933c998 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/map-module.js +++ b/binaries/data/mods/public/simulation/ai/common-api/map-module.js @@ -37,16 +37,18 @@ m.Map.prototype.gamePosToMapPos = function(p){ m.Map.prototype.point = function(p){ var q = this.gamePosToMapPos(p); + q[0] = q[0] >= this.width ? this.width : (q[0] < 0 ? 0 : q[0]); + q[1] = q[1] >= this.width ? this.width : (q[1] < 0 ? 0 : q[1]); return this.map[q[0] + this.width * q[1]]; }; m.Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) { strength = strength ? +strength : +maxDist; type = type ? type : 'linear'; - var x0 = Math.max(0, cx - maxDist); - var y0 = Math.max(0, cy - maxDist); - var x1 = Math.min(this.width-1, cx + maxDist); - var y1 = Math.min(this.height-1, cy + maxDist); + var x0 = Math.floor(Math.max(0, cx - maxDist)); + var y0 = Math.floor(Math.max(0, cy - maxDist)); + var x1 = Math.floor(Math.min(this.width-1, cx + maxDist)); + var y1 = Math.floor(Math.min(this.height-1, cy + maxDist)); var maxDist2 = maxDist * maxDist; var str = 0.0; diff --git a/binaries/data/mods/public/simulation/ai/common-api/shared.js b/binaries/data/mods/public/simulation/ai/common-api/shared.js index 52fa69262a..bad69420fe 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/shared.js +++ b/binaries/data/mods/public/simulation/ai/common-api/shared.js @@ -125,6 +125,7 @@ m.SharedScript.prototype.init = function(state) { this.playersData = state.players; this.territoryMap = state.territoryMap; this.timeElapsed = state.timeElapsed; + this.barterPrices = state.barterPrices; for (var o in state.players) this._techModifications[o] = state.players[o].techModifications; @@ -175,6 +176,7 @@ m.SharedScript.prototype.onUpdate = function(state) this.playersData = state.players; this.territoryMap = state.territoryMap; this.timeElapsed = state.timeElapsed; + this.barterPrices = state.barterPrices; for (var o in state.players) this._techModifications[o] = state.players[o].techModifications; diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index b121514167..1620d1b46c 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -135,6 +135,10 @@ GuiInterface.prototype.GetExtendedSimulationState = function(player) ret.players[i].statistics = cmpPlayerStatisticsTracker.GetStatistics(); } + // Add bartering prices + var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter); + ret.barterPrices = cmpBarter.GetPrices(); + return ret; };