Various improvements to AI's early game. Simplify some code and improve on other. Add a few different strategies (Rush/normal/boom, quite basic for now, refs #2344).

Aegis should mostly respect tech limitations so I'll ref #1964.
I'm going to go with #2364 is fixed, the AI should be more efficient in
early-game, and late-game is a known problem.
Fixes #2274 and fixes #2379.
Refs #2372 as it should fix several of those warnings for AIs.
Fixes #2256 with a new bartering system, in parts taken from mimo's
patch.

This was SVN commit r14582.
This commit is contained in:
wraitii 2014-01-14 19:54:31 +00:00
parent e98478b68c
commit d23b7deb98
20 changed files with 603 additions and 618 deletions

View file

@ -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;
}
};

View file

@ -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());
}
}
}

View file

@ -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)

View file

@ -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,

View file

@ -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);

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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 ));
}
}
};

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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();

View file

@ -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);
}

View file

@ -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()

View file

@ -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)

View file

@ -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;

View file

@ -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;

View file

@ -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;
};