petra: better AI recovery after its economy is badly damaged + some fixes

This was SVN commit r15989.
This commit is contained in:
mimo 2014-11-18 19:56:23 +00:00
parent 3589aa39b9
commit da1af993f7
6 changed files with 237 additions and 119 deletions

View File

@ -632,7 +632,9 @@ m.BaseManager.prototype.reassignIdleWorkers = function(gameState)
if (ent.hasClass("Worker"))
{
if (self.anchor && self.anchor.needsRepair() === true)
// Just emergency repairing here. It is better managed in assignToFoundations
if (self.anchor && self.anchor.needsRepair() === true
&& gameState.getOwnEntitiesByMetadata("target-foundation", self.anchor.id()).length < 2)
ent.repair(self.anchor);
else
{
@ -732,7 +734,7 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
}
var workers = this.workers.filter(API3.Filters.not(API3.Filters.or(API3.Filters.byClass("Cavalry"), API3.Filters.byClass("Ship"))));
var builderWorkers = this.workersBySubrole(gameState, "builder");
var idleBuilderWorkers = this.workersBySubrole(gameState, "builder").filter(API3.Filters.isIdle());
var idleBuilderWorkers = builderWorkers.filter(API3.Filters.isIdle());
// if we're constructing and we have the foundations to our base anchor, only try building that.
if (this.constructing == true && this.buildings.filter(API3.Filters.and(API3.Filters.isFoundation(), API3.Filters.byMetadata(PlayerID, "baseAnchor", true))).length !== 0)
@ -763,11 +765,8 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
});
}
}
var addedWorkers = 0;
var maxTotalBuilders = Math.ceil(workers.length * 0.2);
if (this.constructing == true && maxTotalBuilders < 15)
maxTotalBuilders = 15;
var builderTot = builderWorkers.length - idleBuilderWorkers.length;
for (var target of foundations)
{
@ -775,94 +774,36 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
continue; // we do not build fields
if (gameState.ai.HQ.isDangerousLocation(target.position()))
if (!target.hasClass("CivCentre") && !target.hasClass("StoneWall"))
if (!target.hasClass("CivCentre") && !target.hasClass("StoneWall") && (!target.hasClass("Wonder") || gameState.getGameType() !== "wonder"))
continue;
var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target.id()).length;
var targetNB = this.Config.Economy.targetNumBuilders; // TODO: dynamic that.
var maxTotalBuilders = Math.ceil(workers.length * 0.2);
var targetNB = 2;
if (target.hasClass("House") || target.hasClass("Market"))
targetNB *= 2;
targetNB = 3;
else if (target.hasClass("Barracks") || target.hasClass("Tower"))
targetNB = 4;
else if (target.hasClass("Fortress"))
targetNB = 7;
if (target.getMetadata(PlayerID, "baseAnchor") == true || (target.hasClass("Wonder") && gameState.getGameType() === "wonder"))
{
targetNB = 15;
maxTotalBuilders = Math.max(maxTotalBuilders, 15);
}
if (assigned < targetNB)
{
if (builderWorkers.length - idleBuilderWorkers.length + addedWorkers < maxTotalBuilders) {
var addedToThis = 0;
idleBuilderWorkers.forEach(function(ent) {
if (ent.position() && API3.SquareVectorDistance(ent.position(), target.position()) < 10000 && assigned + addedToThis < targetNB)
{
addedWorkers++;
addedToThis++;
ent.setMetadata(PlayerID, "target-foundation", target.id());
}
});
if (assigned + addedToThis < targetNB)
{
var nonBuilderWorkers = workers.filter(function(ent) {
if (ent.getMetadata(PlayerID, "subrole") === "builder")
return false;
if (!ent.position())
return false;
if (ent.getMetadata(PlayerID, "plan") === -2 || ent.getMetadata(PlayerID, "plan") === -3)
return false;
if (ent.getMetadata(PlayerID, "transport"))
return false;
return true;
}).toEntityArray();
var time = target.buildTime();
nonBuilderWorkers.sort(function (workerA,workerB)
{
var coeffA = API3.SquareVectorDistance(target.position(),workerA.position());
// elephant moves slowly, so when far away they are only useful if build time is long
if (workerA.hasClass("Elephant"))
coeffA *= 0.5 * (1 + (Math.sqrt(coeffA)/150)*(30/time));
else if (workerA.getMetadata(PlayerID, "gather-type") === "food")
coeffA *= 3;
var coeffB = API3.SquareVectorDistance(target.position(),workerB.position());
if (workerB.hasClass("Elephant"))
coeffB *= 0.5 * (1 + (Math.sqrt(coeffB)/150)*(30/time));
else if (workerB.getMetadata(PlayerID, "gather-type") === "food")
coeffB *= 3;
return (coeffA - coeffB);
});
var current = 0;
while (assigned + addedToThis < targetNB && current < nonBuilderWorkers.length)
{
addedWorkers++;
addedToThis++;
var ent = nonBuilderWorkers[current++];
ent.stopMoving();
ent.setMetadata(PlayerID, "subrole", "builder");
ent.setMetadata(PlayerID, "target-foundation", target.id());
};
}
}
}
}
// 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 target of damagedBuildings)
{
if (gameState.ai.HQ.isDangerousLocation(target.position()))
if (target.healthLevel() > 0.5 || (!target.hasClass("CivCentre") && !target.hasClass("StoneWall")))
continue;
else if (noRepair && !target.hasClass("CivCentre"))
continue;
if (target.decaying())
continue;
var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target.id()).length;
if (assigned < targetNB/3)
{
if (builderWorkers.length + addedWorkers < targetNB*2)
idleBuilderWorkers.forEach(function(ent) {
if (ent.getMetadata(PlayerID, "target-foundation") !== undefined)
return;
if (assigned >= targetNB || !ent.position() || API3.SquareVectorDistance(ent.position(), target.position()) > 40000)
return;
assigned++;
builderTot++;
ent.setMetadata(PlayerID, "target-foundation", target.id());
});
if (assigned < targetNB && builderTot < maxTotalBuilders)
{
var nonBuilderWorkers = workers.filter(function(ent) {
if (ent.getMetadata(PlayerID, "subrole") === "builder")
@ -874,12 +815,99 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
if (ent.getMetadata(PlayerID, "transport"))
return false;
return true;
}).toEntityArray();
var time = target.buildTime();
nonBuilderWorkers.sort(function (workerA,workerB)
{
let coeffA = API3.SquareVectorDistance(target.position(),workerA.position());
// elephant moves slowly, so when far away they are only useful if build time is long
if (workerA.hasClass("Elephant"))
coeffA *= 0.5 * (1 + (Math.sqrt(coeffA)/150)*(30/time));
else if (workerA.getMetadata(PlayerID, "gather-type") === "food")
coeffA *= 3;
let coeffB = API3.SquareVectorDistance(target.position(),workerB.position());
if (workerB.hasClass("Elephant"))
coeffB *= 0.5 * (1 + (Math.sqrt(coeffB)/150)*(30/time));
else if (workerB.getMetadata(PlayerID, "gather-type") === "food")
coeffB *= 3;
return (coeffA - coeffB);
});
var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), targetNB/3 - assigned);
let current = 0;
let nonBuilderTot = nonBuilderWorkers.length;
while (assigned < targetNB && builderTot < maxTotalBuilders && current < nonBuilderTot)
{
assigned++;
builderTot++;
var ent = nonBuilderWorkers[current++];
ent.stopMoving();
ent.setMetadata(PlayerID, "subrole", "builder");
ent.setMetadata(PlayerID, "target-foundation", target.id());
};
}
}
}
for (var target of damagedBuildings)
{
// don't repair if we're still under attack, unless it's a vital (civcentre or wall) building that's getting destroyed.
if (gameState.ai.HQ.isDangerousLocation(target.position()))
if (target.healthLevel() > 0.5 ||
(!target.hasClass("CivCentre") && !target.hasClass("StoneWall") && (!target.hasClass("Wonder") || gameState.getGameType() !== "wonder")))
continue;
else if (noRepair && !target.hasClass("CivCentre"))
continue;
if (target.decaying())
continue;
var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target.id()).length;
var maxTotalBuilders = Math.ceil(workers.length * 0.2);
var targetNB = 1;
if (target.hasClass("Fortress"))
targetNB = 3;
if (target.getMetadata(PlayerID, "baseAnchor") == true || (target.hasClass("Wonder") && gameState.getGameType() === "wonder"))
{
maxTotalBuilders = Math.ceil(workers.length * 0.3);
targetNB = 5;
if (target.healthLevel() < 0.3)
{
maxTotalBuilders = Math.ceil(workers.length * 0.6);
targetNB = 7;
}
}
if (assigned < targetNB)
{
idleBuilderWorkers.forEach(function(ent) {
if (ent.getMetadata(PlayerID, "target-foundation") !== undefined)
return;
if (assigned >= targetNB || !ent.position() || API3.SquareVectorDistance(ent.position(), target.position()) > 40000)
return;
assigned++;
builderTot++;
ent.setMetadata(PlayerID, "target-foundation", target.id());
});
if (assigned < targetNB && builderTot < maxTotalBuilders)
{
let nonBuilderWorkers = workers.filter(function(ent) {
if (ent.getMetadata(PlayerID, "subrole") === "builder")
return false;
if (!ent.position())
return false;
if (ent.getMetadata(PlayerID, "plan") === -2 || ent.getMetadata(PlayerID, "plan") === -3)
return false;
if (ent.getMetadata(PlayerID, "transport"))
return false;
return true;
});
let num = Math.min(nonBuilderWorkers.length, targetNB-assigned);
let nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), num);
nearestNonBuilders.forEach(function(ent) {
assigned++;
builderTot++;
ent.stopMoving();
addedWorkers++;
ent.setMetadata(PlayerID, "subrole", "builder");
ent.setMetadata(PlayerID, "target-foundation", target.id());
});

View File

@ -25,7 +25,6 @@ m.Config = function(difficulty)
"cityPhase" : 840, // time to start trying to reach city phase
"popForMarket" : 50,
"popForDock" : 25,
"targetNumBuilders" : 1.5, // Base number of builders per foundation.
"targetNumTraders" : 4, // Target number of traders
"targetNumFishers" : 1, // Target number of fishers per sea
"femaleRatio" : 0.5, // percent of females among the workforce.

View File

@ -18,8 +18,6 @@ m.HQ = function(Config)
{
this.Config = Config;
this.targetNumBuilders = this.Config.Economy.targetNumBuilders; // number of workers we want building stuff
this.econState = "growth"; // existing values: growth, townPhasing.
this.phaseStarted = undefined;
@ -534,16 +532,25 @@ m.HQ.prototype.trainMoreWorkers = function(gameState, queues)
});
});
// Adapt the batch size of the first queued workers and females to the present population
// Adapt the batch size of the first and second queued workers and females to the present population
// to ease a possible recovery if our population was drastically reduced by an attack
// (need to go up to second queued as it is accounted in queueManager)
if (numWorkers < 12)
var size = 1;
else
var size = Math.min(5, Math.ceil(numWorkers / 10));
if (queues.villager.queue[0])
{
queues.villager.queue[0].number = Math.min(queues.villager.queue[0].number, size);
if (queues.villager.queue[1])
queues.villager.queue[1].number = Math.min(queues.villager.queue[1].number, size);
}
if (queues.citizenSoldier.queue[0])
{
queues.citizenSoldier.queue[0].number = Math.min(queues.citizenSoldier.queue[0].number, size);
if (queues.citizenSoldier.queue[1])
queues.citizenSoldier.queue[1].number = Math.min(queues.citizenSoldier.queue[1].number, size);
}
var numQueuedF = queues.villager.countQueuedUnits();
var numQueuedS = queues.citizenSoldier.countQueuedUnits();
@ -1633,26 +1640,18 @@ m.HQ.prototype.trainEmergencyUnits = function(gameState, positions)
if (gameState.isDisabledTemplates(trainable))
continue;
var template = gameState.getTemplate(trainable);
if (!template.hasClass("Infantry") || !template.hasClass("Ranged")
|| !template.hasClass("CitizenSoldier"))
if (!template.hasClass("Infantry") || !template.hasClass("CitizenSoldier"))
continue;
// Keep non-ranged units only as long as no ranged one found
if (rangedUnit && !template.hasClass("Ranged"))
continue;
if (!total.canAfford(new API3.Resources(template.cost())))
{
if (rangedUnit)
continue;
// if we still have no ranged units, but can afford a melee unit, let's try it
var template = gameState.getTemplate(trainable);
if (!template.hasClass("Infantry") || !template.hasClass("Melee")
|| !template.hasClass("CitizenSoldier"))
continue;
if (!total.canAfford(new API3.Resources(template.cost())))
continue;
}
else
rangedUnit = true;
continue;
templateFound = [trainable, template];
if (rangedUnit)
break;
if (!template.hasClass("Ranged"))
continue;
rangedUnit = true;
break;
}
if (!templateFound)
continue;
@ -1698,8 +1697,8 @@ m.HQ.prototype.trainEmergencyUnits = function(gameState, positions)
break;
}
}
// Check first if we can afford it without touching other the accounts
// and if not, take some of ther accounted resources
// Check first if we can afford it without touching the other accounts
// and if not, take some of other accounted resources
// TODO sort the queues to be substracted
var cost = new API3.Resources(templateAnchor[1].cost());
if (!gameState.ai.queueManager.accounts["emergency"].canAfford(cost))
@ -2000,7 +1999,6 @@ m.HQ.prototype.update = function(gameState, queues, events)
m.HQ.prototype.Serialize = function()
{
let properties = {
"targetNumBuilders": this.targetNumBuilders,
"econState": this.econState,
"phaseStarted": this.phaseStarted,
"wantedRates": this.wantedRates,

View File

@ -423,6 +423,9 @@ m.QueueManager.prototype.update = function(gameState)
this.priorities[i] = 1; // TODO: make the Queue Manager not die when priorities are zero.
}
// Pause or unpause queues depending on the situation
this.checkPausedQueues(gameState);
// Let's assign resources to plans that need them
this.distributeResources(gameState);
@ -435,6 +438,85 @@ m.QueueManager.prototype.update = function(gameState)
Engine.ProfileStop();
};
// Recovery system: if short of workers after an attack, pause (and reset) some queues to favor worker training
m.QueueManager.prototype.checkPausedQueues = function(gameState)
{
let numWorkers = 0;
gameState.getOwnUnits().forEach (function (ent) {
if (ent.getMetadata(PlayerID, "role") == "worker")
numWorkers++;
});
gameState.getOwnTrainingFacilities().forEach(function(ent) {
ent.trainingQueue().forEach(function(item) {
if (item.metadata && item.metadata.role && item.metadata.role == "worker")
numWorkers += item.count;
});
});
if (numWorkers < 8)
{
for (let q in this.queues)
{
let queue = this.queues[q];
if (!queue.paused
&& q != "citizenSoldier" && q != "villager"
&& (q != "civilCentre" || gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")) > 0))
{
queue.paused = true;
this.accounts[q].reset();
}
else if (queue.paused)
queue.paused = false;
}
}
else if (numWorkers < 16)
{
for (let q in this.queues)
{
let queue = this.queues[q];
if (!queue.paused
&& (q == "economicBuilding" || q == "militaryBuilding" || q == "defenseBuilding"
|| (q == "civilCentre" && gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")) > 0)
|| q == "majorTech" || q == "minorTech" || q.indexOf("plan_") != -1))
{
queue.paused = true;
this.accounts[q].reset();
}
else if (queue.paused)
queue.paused = false;
}
}
else if (numWorkers < 24)
{
for (let q in this.queues)
{
let queue = this.queues[q];
if (!queue.paused
&& (q == "defenseBuilding"
|| (q == "civilCentre" && gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")) > 0)
|| q == "majorTech" || q.indexOf("_siege") != -1 || q.indexOf("_champ") != -1))
{
queue.paused = true;
this.accounts[q].reset();
}
else if (queue.paused)
queue.paused = false;
// And reduce the batch sizes of attack queues
if (q.indexOf("plan_") != -1 && queue.queue[0])
{
queue.queue[0].number = 1;
if (queue.queue[1])
queue.queue[1].number = 1;
}
}
}
else
for (let q in this.queues)
if (this.queues[q].paused)
this.queues[q].paused = false;
};
m.QueueManager.prototype.pauseQueue = function(queue, scrapAccounts)
{
if (this.queues[queue])

View File

@ -476,12 +476,12 @@ m.ConstructionPlan.prototype.Deserialize = function(gameState, data)
this.cost = cost;
// TODO find a way to properly serialize functions. For the time being, they are manually added
if (this.type == "structures/{civ}_house")
if (this.type == gameState.applyCiv("structures/{civ}_house"))
{
var difficulty = gameState.ai.Config.difficulty;
// change the starting condition according to the situation.
this.isGo = function (gameState) {
if (!self.canBuild(gameState, "structures/{civ}_house"))
if (!gameState.ai.HQ.canBuild(gameState, "structures/{civ}_house"))
return false;
if (gameState.getPopulationMax() <= gameState.getPopulationLimit())
return false;
@ -501,12 +501,12 @@ m.ConstructionPlan.prototype.Deserialize = function(gameState, data)
return (freeSlots <= 10);
};
}
else if (this.type == "structures/{civ}_market")
else if (this.type == gameState.applyCiv("structures/{civ}_market"))
{
let priority = gameState.ai.Config.priorities.economicBuilding;
this.onStart = function(gameState) { gameState.ai.queueManager.changePriority("economicBuilding", priority); };
}
else if (this.type == "structures/{civ}_barracks")
else if (this.type == gameState.applyCiv("structures/{civ}_barracks"))
{
let priority = gameState.ai.Config.priorities.militaryBuilding;
this.onStart = function(gameState) { gameState.ai.queueManager.changePriority("militaryBuilding", priority); };

View File

@ -65,10 +65,11 @@ m.TrainingPlan.prototype.start = function(gameState)
var wantedIndex = undefined;
if (this.metadata && this.metadata.index)
wantedIndex = this.metadata.index;
var workerUnit = (this.metadata && this.metadata.role && this.metadata.role == "worker");
var supportUnit = this.template.hasClass("Support");
trainers.sort(function(a, b) {
var aa = a.trainingQueueTime();
var bb = b.trainingQueueTime();
let aa = a.trainingQueueTime();
let bb = b.trainingQueueTime();
if (a.hasClass("Civic") && !supportUnit)
aa += 10;
if (b.hasClass("Civic") && !supportUnit)
@ -80,18 +81,28 @@ m.TrainingPlan.prototype.start = function(gameState)
if (gameState.ai.HQ.isDangerousLocation(b.position()))
bb += 50;
}
let aBase = a.getMetadata(PlayerID, "base");
let bBase = b.getMetadata(PlayerID, "base");
if (wantedIndex)
{
var aBase = a.getMetadata(PlayerID, "base");
if (!aBase || gameState.ai.HQ.baseManagers[aBase].accessIndex !== wantedIndex)
if (!aBase || gameState.ai.HQ.baseManagers[aBase].accessIndex != wantedIndex)
aa += 30;
var bBase = b.getMetadata(PlayerID, "base");
if (!bBase || gameState.ai.HQ.baseManagers[bBase].accessIndex !== wantedIndex)
if (!bBase || gameState.ai.HQ.baseManagers[bBase].accessIndex != wantedIndex)
bb += 30;
}
// then, if worker, small preference for bases with less workers
if (workerUnit && aBase && bBase && aBase != bBase)
{
let apop = gameState.ai.HQ.baseManagers[aBase].workers.length;
let bpop = gameState.ai.HQ.baseManagers[bBase].workers.length;
if (apop > bpop)
aa++;
else if (bpop > apop)
bb++;
}
return (aa - bb);
});
if (this.metadata && this.metadata.base !== undefined && this.metadata.base === 0)
if (this.metadata && this.metadata.base !== undefined && this.metadata.base == 0)
this.metadata.base = trainers[0].getMetadata(PlayerID, "base");
trainers[0].train(this.type, this.number, this.metadata, this.promotedTypes(gameState));
}