Add support for -autostart-civ to set civilizations when quickstarting.

Put the AI memory heap back to 32 MB to avoid OOM errors with numerous
AIs in late game.
Fix a bug that made ProductionQueue not broadcast progress.
Fix many issues with Aegis in defense, pathfinding, foundation
construction, training building choice, strength calculations, building
placement and mostly attack. Aegis should be much more aggressive.

This was SVN commit r13247.
This commit is contained in:
wraitii 2013-03-09 14:09:06 +00:00
parent 0572ffd3d2
commit 2c17ab70ac
18 changed files with 501 additions and 258 deletions

View File

@ -676,7 +676,7 @@ var Entity = Class({
for (i in queue)
{
if (queue[i].progress < percentToStopAt)
Engine.PostCommand({ "type": "stop-production", "entity": this.id(), "id": queue[i].id });
Engine.PostCommand({ "type": "stop-production", "entity": this.id(), "id": queue[i].id });
}
return this;
}

View File

@ -109,6 +109,11 @@ GameState.prototype.isResearched = function(template)
{
return this.playerData.researchedTechs[template] !== undefined;
};
// true if started or queued
GameState.prototype.isResearching = function(template)
{
return (this.playerData.researchStarted[template] !== undefined || this.playerData.researchQueued[template] !== undefined);
};
// this is an absolute check that doesn't check if we have a building to research from.
GameState.prototype.canResearch = function(techTemplateName, noRequirementCheck)
@ -475,14 +480,26 @@ GameState.prototype.findResearchers = function(templateName) {
// let's check we can research the tech.
if (!this.canResearch(templateName))
return [];
var template = this.getTemplate(templateName);
var self = this;
return this.getOwnResearchFacilities().filter(function(ent) { //}){
var techs = ent.researchableTechs();
if (!techs || (template.pair() && techs.indexOf(template.pair()) === -1) || (!template.pair() && techs.indexOf(templateName) === -1))
return false;
return true;
for (i in techs)
{
var thisTemp = self.getTemplate(techs[i]);
if (thisTemp.pairDef())
{
var pairedTechs = thisTemp.getPairedTechs();
if (pairedTechs[0]._templateName == templateName || pairedTechs[1]._templateName == templateName)
return true;
} else {
if (techs[i] == templateName)
return true;
}
}
return false;
});
};

View File

@ -75,7 +75,7 @@ aStarPath.prototype.getPath = function(start, end, Sampling, minWidth, iteration
if (start[0] < 0 || this.gamePosToMapPos(start)[0] >= this.width || start[1] < 0 || this.gamePosToMapPos(start)[1] >= this.height)
return undefined;
var s = this.terrainAnalyzer.findClosestPassablePoint(this.gamePosToMapPos(start), !this.waterPathfinder,500,true);
var s = this.terrainAnalyzer.findClosestPassablePoint(this.gamePosToMapPos(start), !this.waterPathfinder,500,false);
var e = this.terrainAnalyzer.findClosestPassablePoint(this.gamePosToMapPos(end), !this.waterPathfinder,500,true);
var w = this.width;

View File

@ -22,14 +22,19 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
}
if (this.targetPlayer === undefined || this.targetPlayer === -1)
return false;
debug ("Target = " +this.targetPlayer);
var CCs = gameState.getOwnEntities().filter(Filters.byClass("CivCentre"));
if (CCs.length === 0)
return false;
debug ("Target (" + PlayerID +") = " +this.targetPlayer);
this.targetFinder = targetFinder || this.defaultTargetFinder;
this.type = type || "normal";
this.name = uniqueID;
this.healthRecord = [];
this.timeOfPlanStart = gameState.getTimeElapsed(); // we get the time at which we decided to start the attack
this.maxPreparationTime = 300*1000;
this.maxPreparationTime = 180*1000;
this.pausingStart = 0;
this.totalPausingTime = 0;
@ -44,14 +49,14 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
// only once every other category is at least 50% of its target size.
this.unitStat = {};
this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 10, "batchSize" : 5, "classes" : ["Infantry","Ranged"],
"interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
"interests" : [ ["canGather", 2], ["strength",2], ["cost",1] ], "templates" : [] };
this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 10, "batchSize" : 5, "classes" : ["Infantry","Melee"],
"interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
"interests" : [ ["canGather", 2], ["strength",2], ["cost",1] ], "templates" : [] };
this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 8 , "batchSize" : 3, "classes" : ["Cavalry","Melee"],
"interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 8 , "batchSize" : 3, "classes" : ["Cavalry","Ranged"],
"interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
this.unitStat["Siege"] = { "priority" : 0.5, "minSize" : 0, "targetSize" : 3 , "batchSize" : 1, "classes" : ["Siege"], "interests" : [ ["siegeStrength", 3], ["cost",1] ], "templates" : [] };
this.unitStat["Siege"] = { "priority" : 0.8, "minSize" : 0, "targetSize" : 3 , "batchSize" : 2, "classes" : ["Siege"], "interests" : [ ["siegeStrength", 3], ["cost",1] ], "templates" : [] };
var priority = 60;
if (type === "rush") {
@ -65,16 +70,28 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
this.maxPreparationTime = 150*1000;
priority = 150;
} else if (type === "superSized") {
this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 18, "batchSize" : 5, "classes" : ["Infantry","Ranged"],
this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 16, "batchSize" : 5, "classes" : ["Infantry","Ranged"],
"interests" : [["strength",3], ["cost",1] ], "templates" : [] };
this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 6, "targetSize" : 20, "batchSize" : 5, "classes" : ["Infantry","Melee"],
"interests" : [ ["strength",3], ["cost",1] ], "templates" : [] };
this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 3, "classes" : ["Cavalry","Melee"],
"interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 6, "targetSize" : 24, "batchSize" : 5, "classes" : ["Infantry","Melee"],
this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 3, "classes" : ["Cavalry","Ranged"],
"interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 5, "classes" : ["Cavalry","Melee"],
"interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 5, "classes" : ["Cavalry","Ranged"],
"interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
this.unitStat["Siege"] = { "priority" : 0.5, "minSize" : 3, "targetSize" : 6 , "batchSize" : 3, "classes" : ["Siege"],"interests" : [ ["siegeStrength",3], ["cost",1] ], "templates" : [] };
this.maxPreparationTime = 450*1000;
this.unitStat["Siege"] = { "priority" : 1, "minSize" : 2, "targetSize" : 6 , "batchSize" : 2, "classes" : ["Siege"],"interests" : [ ["siegeStrength",3], ["cost",1] ], "templates" : [] };
// Okay so here there is some civ specificity: some have buildings to train champions (or can even in the barracks) so they don't have to have tons of fortresses.
// Some don't, so we can't really rely on champions for the armies to a great extent.
if (gameState.civ() == "gaul" || gameState.civ() == "brit")
{
this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 16, "batchSize" : 5, "classes" : ["Infantry","Ranged"], "interests" : [["strength",3], ["cost",1] ], "templates" : [] };
this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 15, "batchSize" : 5, "classes" : ["Infantry","Melee", "CitizenSoldier" ], "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] };
this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 2, "targetSize" : 9 , "batchSize" : 5, "classes" : ["Cavalry","Melee", "CitizenSoldier" ], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
this.unitStat["ChampMeleeInfantry"] = { "priority" : 0.8, "minSize" : 3, "targetSize" : 6, "batchSize" : 3, "classes" : ["Infantry","Melee", "Champion" ], "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] };
this.unitStat["ChampMeleeCavalry"] = { "priority" : 0.8, "minSize" : 2, "targetSize" : 3 , "batchSize" : 3, "classes" : ["Cavalry","Melee", "Champion" ], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
this.unitStat["RangedCavalry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 12 , "batchSize" : 3, "classes" : ["Cavalry","Ranged"], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
}
priority = 70;
} else if (gameState.getTimeElapsed() > 15*60*1000)
this.unitStat["Siege"]["minSize"] = 2;
@ -161,7 +178,7 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
else
var position = [-1,-1];
var CCs = gameState.getOwnEntities().filter(Filters.byClass("CivCentre"));
// abort.
var nearestCCArray = CCs.filterNearest(position, 1).toEntityArray();
var CCpos = nearestCCArray[0].position();
this.rallyPoint = [0,0];
@ -179,6 +196,7 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
}
// some variables for during the attack
this.position20TurnsAgo = [0,0];
this.lastPosition = [0,0];
this.position = [0,0];
@ -249,7 +267,7 @@ CityAttack.prototype.mustStart = function(gameState){
MaxReachedEverywhere = false;
}
}
if (MaxReachedEverywhere)
if (MaxReachedEverywhere || (gameState.getPopulationMax() - gameState.getPopulation() < 10 && this.canStart(gameState)))
return true;
return (this.maxPreparationTime + this.timeOfPlanStart + this.totalPausingTime < gameState.getTimeElapsed());
};
@ -294,7 +312,8 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
debug ("This is actually a water map.");
gameState.ai.waterMap = true;
}
this.unitStat["TransportShip"] = { "priority" : 1.1, "minSize" : 2, "targetSize" : 2, "batchSize" : 1, "classes" : ["Warship"], "templates" : [] };
this.unitStat["TransportShip"] = { "priority" : 1.1, "minSize" : 2, "targetSize" : 2, "batchSize" : 1, "classes" : ["Warship"],
"interests" : [ ["strength",1], ["cost",1] ] ,"templates" : [] };
if (type === "superSized") {
this.unitStat["TransportShip"]["minSize"] = 4;
this.unitStat["TransportShip"]["targetSize"] = 4;
@ -319,10 +338,15 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
Engine.ProfileStart("Update Preparation");
// keep on while the units finish being trained.
if (this.mustStart(gameState) && (gameState.countOwnQueuedEntitiesWithMetadata("plan", +this.name) + this.queue.countTotalQueuedUnits()) > 0 ) {
if (this.mustStart(gameState) && (gameState.countOwnQueuedEntitiesWithMetadata("plan", +this.name) > 0 || (gameState.getPopulationMax() - gameState.getPopulation() < 10) )) {
this.assignUnits(gameState);
if (this.queue.length())
this.queue.empty();
if ( (gameState.ai.turn + gameState.ai.player) % 40 == 0) {
this.AllToRallyPoint(gameState, true); // gain some time, start regrouping
this.AllToRallyPoint(gameState, false);
// TODO: should use this time to let gatherers deposit resources.
}
Engine.ProfileStop();
return 1;
@ -333,26 +357,25 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
// count the number of queued units too.
// substract priority.
this.buildOrder.sort(function (a,b) { //}) {
var aQueued = gameState.countOwnQueuedEntitiesWithMetadata("special","Plan_"+self.name+"_"+self.buildOrder[0][4]);
aQueued += self.queue.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+self.buildOrder[0][4]);
var aQueued = gameState.countOwnQueuedEntitiesWithMetadata("special","Plan_"+self.name+"_"+a[4]);
aQueued += self.queue.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+a[4]);
a[0] = (a[2].length + aQueued)/a[3]["targetSize"];
var bQueued = gameState.countOwnQueuedEntitiesWithMetadata("special","Plan_"+self.name+"_"+self.buildOrder[0][4]);
bQueued += self.queue.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+self.buildOrder[0][4]);
var bQueued = gameState.countOwnQueuedEntitiesWithMetadata("special","Plan_"+self.name+"_"+b[4]);
bQueued += self.queue.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+b[4]);
b[0] = (b[2].length + bQueued)/b[3]["targetSize"];
a[0] -= a[3]["priority"];
b[0] -= b[3]["priority"];
return (a[0]) - (b[0]);
});
if (!this.isPaused()) {
this.assignUnits(gameState);
if ( (gameState.ai.turn + gameState.ai.player) % 40 == 0) {
if ( (gameState.ai.turn + gameState.ai.player) % 15 == 0) {
this.AllToRallyPoint(gameState, false);
this.unitCollection.setStance("defensive"); // make sure units won't disperse out of control
this.unitCollection.setStance("standground"); // make sure units won't disperse out of control
}
}
@ -361,13 +384,17 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
// gets the number in training of the same kind as the first one.
var specialData = "Plan_"+this.name+"_"+this.buildOrder[0][4];
var inTraining = gameState.countOwnQueuedEntitiesWithMetadata("special",specialData);
if (this.queue.countTotalQueuedUnits() + inTraining + this.buildOrder[0][2].length < Math.min(15,this.buildOrder[0][3]["targetSize"]) ) {
if (this.buildOrder[0][0] < 1 && this.queue.length() < 4) {
if (this.queue.countTotalQueuedUnitsWithMetadata("special",specialData) + inTraining + this.buildOrder[0][2].length <= this.buildOrder[0][3]["targetSize"]) {
if (this.buildOrder[0][0] < 1 && this.queue.length() <= 4) {
var template = militaryManager.findBestTrainableUnit(gameState, this.buildOrder[0][1], this.buildOrder[0][3]["interests"] );
//debug ("tried " + uneval(this.buildOrder[0][1]) +", and " + template);
// HACK (TODO replace) : if we have no trainable template... Then we'll simply remove the buildOrder, effectively removing the unit from the plan.
if (template === undefined) {
// TODO: this is a complete hack.
if (this.needsShip && this.buildOrder[0][4] == "TransportShip")
return 0;
delete this.unitStat[this.buildOrder[0][4]]; // deleting the associated unitstat.
this.buildOrder.splice(0,1);
} else {
@ -444,11 +471,14 @@ CityAttack.prototype.assignUnits = function(gameState){
if (this.type === "rush")
NoRole = gameState.getOwnEntitiesByRole("worker");
NoRole.forEach(function(ent) {
if (ent.hasClasses(["CitizenSoldier", "Infantry"]))
ent.setMetadata(PlayerID, "role", "worker");
else
ent.setMetadata(PlayerID, "role", "attack");
ent.setMetadata(PlayerID, "plan", self.name);
if (ent.hasClass("Unit") && ent.attackTypes() !== undefined)
{
if (ent.hasClasses(["CitizenSoldier", "Infantry"]))
ent.setMetadata(PlayerID, "role", "worker");
else
ent.setMetadata(PlayerID, "role", "attack");
ent.setMetadata(PlayerID, "plan", self.name);
}
});
};
@ -472,7 +502,7 @@ CityAttack.prototype.AllToRallyPoint = function(gameState, evenWorkers) {
} else {
for (unitCat in this.unit) {
this.unit[unitCat].forEach(function (ent) {
if (ent.getMetadata(PlayerID, "role") != "worker" && ent.getMetadata(PlayerID, "role") != "defence" && !ent.hasClass("Warship"))
if (ent.getMetadata(PlayerID, "role") != "worker" && ent.getMetadata(PlayerID, "role") != "defence" && !ent.hasClass("Warship"))
ent.move(self.rallyPoint[0],self.rallyPoint[1]);
});
}
@ -496,7 +526,7 @@ CityAttack.prototype.defaultTargetFinder = function(gameState, militaryManager){
}
// no buildings, attack anything conquest critical, even units (it's assuming it won't move).
if (targets.length == 0) {
targets = gameState.getEnemyEntities().filter(Filters.byClass("ConquestCritical"));
targets = gameState.getEnemyEntities().filter(Filters.and( Filters.byOwner(this.targetPlayer),Filters.byClass("ConquestCritical")));
}
debug ("target is " + targets);
return targets;
@ -556,7 +586,6 @@ CityAttack.prototype.StartAttack = function(gameState, militaryManager){
this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]);
this.unitCollection.setStance("aggressive"); // make sure units won't disperse out of control
debug ("Started to attack with the plan " + this.name);
this.state = "walking";
} else {
gameState.ai.gameFinished = true;
@ -586,6 +615,8 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
// this actually doesn't do anything right now.
if (this.state === "walking") {
var attackedNB = 0;
var toProcess = {};
var armyToProcess = {};
// Let's check if any of our unit has been attacked. In case yes, we'll determine if we're simply off against an enemy army, a lone unit/builing
@ -602,9 +633,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
var territoryMap = Map.createTerritoryMap(gameState);
if ( +territoryMap.point(attacker.position()) - 64 === +this.targetPlayer)
{
debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination.");
// we must assume we've arrived at the end of the trail.
this.state = "arrived";
attackedNB++;
}
//if (militaryManager.enemyWatchers[attacker.owner()]) {
//toProcess[attacker.id()] = attacker;
@ -619,15 +648,11 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}
}
}
// I don't process attacks if I'm in their base because I'll have already gone to "attacking" mode.
// I'll process by army
var total = 0;
for (armyID in armyToProcess) {
total += armyToProcess[armyID].length;
// TODO: if it's a big army, we may want to refer the scouting/defense manager
if (attackedNB > 4) {
debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination.");
// we must assume we've arrived at the end of the trail.
this.state = "arrived";
}
/*
@ -697,8 +722,13 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
return undefined; // should spawn an error.
// basically haven't moved an inch: very likely stuck)
if (this.position[0] == this.lastPosition[0] && this.position[1] == this.lastPosition[1] && this.path.length > 0) {
if (SquareVectorDistance(this.position, this.position20TurnsAgo) < 10 && this.path.length > 0 && gameState.ai.playedTurn % 100 === 0) {
if (gameState.ai.playedTurn % 100 === 0)
this.position20TurnsAgo = this.position;
// check for stuck siege units
var sieges = this.unitCollection.filter(Filters.byClass("Siege"));
var farthest = 0;
var farthestEnt = -1;
@ -713,7 +743,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
farthestEnt.destroy();
}
if (SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) {
if (this.lastPosition && SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) {
this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).moveIndiv(this.path[0][0][0], this.path[0][0][1]);
// We're stuck, presumably. Check if there are no walls just close to us. If so, we're arrived, and we're gonna tear down some serious stone.
var walls = gameState.getEnemyEntities().filter(Filters.and(Filters.byOwner(this.targetPlayer), Filters.byClass("StoneWall")));
@ -735,8 +765,10 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}
// check if our land units are close enough from the next waypoint.
if (SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.path[0][0]) < 600) {
if (SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.targetPos) < 8000 ||
SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.path[0][0]) < 600) {
if (this.unitCollection.filter(Filters.byClass("Siege")).length !== 0
&& SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.targetPos) > 8000
&& SquareVectorDistance(this.unitCollection.filter(Filters.byClass("Siege")).getCentrePosition(), this.path[0][0]) > 600)
{
} else {
@ -767,6 +799,9 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}
} else if (this.state === "shipping") {
this.position = this.unitCollection.filter(Filters.byClass("Warship")).getCentrePosition();
if (!this.lastPosition)
this.lastPosition = [0,0];
if (SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) {
this.unitCollection.filter(Filters.byClass("Warship")).moveIndiv(this.path[0][0][0], this.path[0][0][1]);
@ -905,13 +940,15 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
{
this.tactics = new Tactics(gameState,HeadQuarters, this.idList,targetList);
this.state = "huntVillagers";
var arrivedthisTurn = true;
} else {
this.state = "";
var arrivedthisTurn = true;
}
}
}
if (this.state === "" && gameState.ai.playedTurn % 3 === 0) {
if (this.state === "") {
// Units attacked will target their attacker unless they're siege. Then we take another non-siege unit to attack them.
for (var key in events) {
@ -928,9 +965,10 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
if (help.length === 0)
help = this.unitCollection.filter(Filters.not(Filters.byClass("Siege")));
if (help.length > 0)
{
help.toEntityArray()[0].attack(attacker.id());
}
if (help.length > 1)
help.toEntityArray()[1].attack(attacker.id());
} else {
ourUnit.attack(attacker.id());
}
@ -955,20 +993,22 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
// if the unit is not in my territory, make it move.
var territoryMap = Map.createTerritoryMap(gameState);
if (territoryMap.point(ent.position()) - 64 === PlayerID)
ent.move((this.targetPos[0] + ent.position()[0])/2,(this.targetPos[1] + ent.position()[1])/2);
ent.move(this.targetPos[0],this.targetPos[1]);
// update it.
var needsUpdate = false;
if (ent.isIdle())
needsUpdate = true;
// always update siege units, they tend to target nothing we want.
if (ent.hasClass("Siege"))
needsUpdate = true;
else if (ent.unitAIOrderData() && ent.unitAIOrderData()["target"] && gameState.getEntityById(ent.unitAIOrderData()["target"]).hasClass("Structure"))
needsUpdate = true; // try to make it attack a unit instead
if (needsUpdate)
if (gameState.getTimeElapsed() - ent.getMetadata(PlayerID, "lastAttackPlanUpdateTime") < 10000)
needsUpdate = false;
if (needsUpdate || arrivedthisTurn)
{
//warn ("Entity " +ent.id() + ", a " + ent._templateName + " needs updating.");
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", gameState.getTimeElapsed());
var mStruct = enemyStructures.filter(function (enemy) { //}){
if (!enemy.position() || (enemy.hasClass("StoneWall") && ent.canAttackClass("StoneWall"))) {
return false;
@ -978,15 +1018,31 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}
return true;
});
var mUnit = enemyUnits.filter(function (enemy) { //}){
if (!enemy.position()) {
return false;
}
if (SquareVectorDistance(enemy.position(),ent.position()) > 2000) {
return false;
}
return true;
});
var mUnit;
if (ent.hasClass("Cavalry")) {
mUnit = enemyUnits.filter(function (enemy) { //}){
if (!enemy.position()) {
return false;
}
if (!enemy.hasClass("Support"))
return false;
if (SquareVectorDistance(enemy.position(),ent.position()) > 2000) {
return false;
}
return true;
});
}
if (!ent.hasClass("Cavalry") || mUnit.length === 0) {
mUnit = enemyUnits.filter(function (enemy) { //}){
if (!enemy.position()) {
return false;
}
if (SquareVectorDistance(enemy.position(),ent.position()) > 2000) {
return false;
}
return true;
});
}
var isGate = false;
mUnit = mUnit.toEntityArray();
mStruct = mStruct.toEntityArray();
@ -1022,7 +1078,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}
} else if (SquareVectorDistance(self.targetPos, ent.position()) > 900 ){
//debug ("Siege units moving to " + uneval(self.targetPos));
ent.move((self.targetPos[0] + ent.position()[0])/2,(self.targetPos[1] + ent.position()[1])/2);
ent.move(self.targetPos[0],self.targetPos[1]);
}
} else {
if (mUnit.length !== 0 && !isGate) {
@ -1057,7 +1113,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}
} else if (SquareVectorDistance(self.targetPos, ent.position()) > 900 ){
//debug ("Units moving to " + uneval(self.targetPos));
ent.move((self.targetPos[0] + ent.position()[0])/2,(self.targetPos[1] + ent.position()[1])/2);
ent.move(self.targetPos[0],self.targetPos[1]);
}
}
}
@ -1065,6 +1121,35 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
this.unitCollUpdateArray.splice(0,1);
}
}
// updating targets.
if (!gameState.getEntityById(this.target.id()))
{
debug ("Seems like our target has been destroyed. Switching.");
var targets = this.targetFinder(gameState, militaryManager);
if (targets.length === 0){
targets = this.defaultTargetFinder(gameState, militaryManager);
}
if (targets.length) {
// picking a target
var rand = Math.floor((Math.random()*targets.length));
this.targetPos = undefined;
var count = 0;
while (!this.targetPos){
this.target = targets.toEntityArray()[rand];
this.targetPos = this.target.position();
count++;
if (count > 1000){
debug("No target with a valid position found");
return false;
}
}
}
}
// regularly update the target position in case it's a unit.
if (this.target.hasClass("Unit"))
this.targetPos = this.target.position();
}
/*
if (this.state === "huntVillagers")

View File

@ -8,14 +8,14 @@ var baseConfig = {
"attackPlansStartTime" : 0 // time to wait before attacking. Start as soon as possible (first barracks)
},
"Economy" : {
"townPhase" : 230, // time to start trying to reach town phase (might be a while after. Still need the requirements + ress )
"townPhase" : 270, // time to start trying to reach town phase (might be a while after. Still need the requirements + ress )
"cityPhase" : 720, // time to start trying to reach city phase
"farmsteadStartTime" : 400, // Time to wait before building a farmstead.
"marketStartTime" : 480, // Time to wait before building the market.
"dockStartTime" : 240, // Time to wait before building the dock
"techStartTime" : 600, // time to wait before teching.
"targetNumBuilders" : 1.5, // Base number of builders per foundation. Later updated, but this remains a multiplier.
"femaleRatio" : 0.4 // percent of females among the workforce.
"femaleRatio" : 0.6 // percent of females among the workforce.
},
// Note: attack settings are set directly in attack_plan.js
@ -59,7 +59,7 @@ var baseConfig = {
"dropsites" : 160,
"field" : 1000,
"militaryBuilding" : 90,
"defenceBuilding" : 20,
"defenceBuilding" : 45,
"majorTech" : 250,
"minorTech" : 40,
"civilCentre" : 10000 // will hog all resources
@ -87,13 +87,14 @@ if (Config.difficulty === 1)
"attackPlansStartTime" : 600
};
Config["Economy"] = {
"townPhase" : 300,
"townPhase" : 360,
"cityPhase" : 900,
"farmsteadStartTime" : 600,
"marketStartTime" : 800,
"techStartTime" : 1320,
"targetNumBuilders" : 2,
"femaleRatio" : 0.5
"femaleRatio" : 0.5,
"targetNumWorkers" : 100
};
Config["Defence"] = {
"armyCompactSize" : 700,
@ -115,7 +116,8 @@ if (Config.difficulty === 1)
"marketStartTime" : 1000,
"techStartTime" : 600000, // never
"targetNumBuilders" : 1,
"femaleRatio" : 0.0
"femaleRatio" : 0.0,
"targetNumWorkers" : 50
};
Config["Defence"] = {
"defenceRatio" : 1.0,

View File

@ -172,7 +172,7 @@ Defence.prototype.armify = function(gameState, entity, militaryManager) {
{
var self = this;
militaryManager.enemyWatchers[entity.owner()].enemySoldiers.forEach(function (ent) { //}){
if (ent.position() !== undefined && SquareVectorDistance(entity.position(), ent.position()) < self.armyCompactSize)
if (ent.position() !== undefined && SquareVectorDistance(entity.position(), ent.position()) < self.armyCompactSize && self.evaluateEntity(gameState, ent))
{
ent.setMetadata(PlayerID, "inArmy", self.totalArmyNB);
self.enemyArmy[ent.owner()][self.totalArmyNB].addEnt(ent);
@ -190,8 +190,20 @@ Defence.prototype.evaluateRawEntity = function(gameState, entity) {
return false;
}
Defence.prototype.evaluateEntity = function(gameState, entity) {
if (entity.position() && +this.territoryMap.point(entity.position()) - 64 === +PlayerID && entity.attackTypes() !== undefined)
return true;
if (!entity.position())
return false;
if (this.territoryMap.point(entity.position()) - 64 === entity.owner() || entity.attackTypes() === undefined)
return false;
var closeToMe = false;
for (i in this.myBuildings._entities)
{
if (!this.myBuildings._entities[i].hasClass("ConquestCritical"))
continue;
if (SquareVectorDistance(this.myBuildings._entities[i].position(), entity.position()) < 4000)
return true;
}
return false;
}
// returns false if the unit is in its territory
@ -228,7 +240,8 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
}
delete this.listOfEnemies[evt.msg.entity];
this.nbAttackers--;
} else if (evt.msg.entityObj && evt.msg.entityObj.owner() === PlayerID && evt.msg.metadata[PlayerID] && evt.msg.metadata[PlayerID]["role"] === "defence")
} else if (evt.msg.entityObj && evt.msg.entityObj.owner() === PlayerID && evt.msg.metadata[PlayerID] && evt.msg.metadata[PlayerID]["role"]
&& evt.msg.metadata[PlayerID]["role"] === "defence")
{
// lost a brave man there.
this.nbDefenders--;
@ -251,7 +264,7 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
// Note: given the way memory works, if the entity has been recently deleted, its reference may still exist.
// and this.enemyUnits[enemyID][0] may still point to that reference, "reviving" the unit.
// So we've got to make sure it's not supposed to be dead.
for (var check = 0; check < 15; check++)
for (var check = 0; check < 20; check++)
{
if (this.enemyUnits[enemyID].length > 0 && gameState.getEntityById(this.enemyUnits[enemyID][0].id()) !== undefined)
{
@ -346,8 +359,17 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
this.attackerCacheLoopIndicator++;
this.attackerCacheLoopIndicator = this.attackerCacheLoopIndicator % 2;
if (this.nbAttackers === 0 && this.nbDefenders === 0) {
// Release all our units
this.myUnits.filter(Filters.byMetadata(PlayerID, "role","defence")).forEach(function (defender) { //}){
defender.stopMoving();
if (defender.getMetadata(PlayerID, "formerrole"))
defender.setMetadata(PlayerID, "role", defender.getMetadata(PlayerID, "formerrole") );
else
defender.setMetadata(PlayerID, "role", "worker");
defender.setMetadata(PlayerID, "subrole", undefined);
self.nbDefenders--;
});
return;
} else if (this.nbAttackers === 0 && this.nbDefenders !== 0) {
// Release all our units
@ -360,6 +382,7 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
defender.setMetadata(PlayerID, "subrole", undefined);
self.nbDefenders--;
});
militaryManager.ungarrisonAll(gameState);
militaryManager.unpauseAllPlans(gameState);
return;
}
@ -372,10 +395,10 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
// and then I'll assign my units.
// and then rock on.
if (this.nbAttackers < 8){
gameState.setDefcon(4); // few local units
} else if (this.nbAttackers >= 8){
if (this.nbAttackers > 15){
gameState.setDefcon(3);
} else if (this.nbAttackers > 5){
gameState.setDefcon(4);
}
// we're having too many. Release those that attack units already dealt with, or idle ones.
@ -405,7 +428,7 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
//debug ("nonDefenders.length "+ nonDefenders.length);
// todo: this is suboptimal. Should allow the maximum number of unit, right now it just lowers the ratio.
if (newEnemies.length * (defenceRatio) > nonDefenders.length)
if (newEnemies.length * (defenceRatio - 1) > nonDefenders.length && this.nbAttackers > 5)
{
defenceRatio--;
gameState.setDefcon(2);
@ -413,16 +436,27 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
if (defenceRatio < 1)
defenceRatio = 1;
if (nonDefenders.length*2.0 < newEnemies.length)
if (nonDefenders.length*2.0 < newEnemies.length && this.nbAttackers > 5)
gameState.setDefcon(1);
if ( (nonDefenders.length + this.nbDefenders > newEnemies.length + this.nbAttackers)
|| this.nbDefenders + nonDefenders.length < 4)
{
var buildings = gameState.getOwnEntities().filter(Filters.byCanGarrison()).toEntityArray();
buildings.forEach( function (struct) {
if (struct.garrisoned() && struct.garrisoned().length)
struct.unloadAll();
});
};
/*
if (gameState.defcon() < 2 && (this.nbAttackers-this.nbDefenders) > 15)
{
militaryManager.garrisonAllFemales(gameState);
militaryManager.pauseAllPlans(gameState);
} else if (gameState.defcon() < 3 && this.nbDefenders === 0 && newEnemies.length === 0) {
militaryManager.ungarrisonAll(gameState);
}
}*/
// For each enemy, we'll pick two units.
for each (enemy in newEnemies) {
if (nonDefenders.length === 0)
@ -473,73 +507,42 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
assigned++;
self.nbDefenders++;
});
}
/*
// yes. We'll pick new units (pretty randomly for now, todo)
// first from attack plans, then from workers.
var newSoldiers = gameState.getOwnEntities().filter(function (ent) {
if (ent.getMetadata(PlayerID, "plan") != undefined && ent.getMetadata(PlayerID, "role") != "defence")
return true;
return false;
});
newSoldiers.forEach(function(ent) {
if (ent.getMetadata(PlayerID, "subrole","attacking")) // gone with the wind to avenge their brothers.
return;
if (nbOfAttackers <= 0)
return;
militaryManager.pausePlan(gameState,ent.getMetadata(PlayerID, "plan"));
ent.setMetadata(PlayerID, "formerrole", ent.getMetadata(PlayerID, "role"));
ent.setMetadata(PlayerID, "role","defence");
ent.setMetadata(PlayerID, "subrole","newdefender");
nbOfAttackers--;
});
if (nbOfAttackers > 0) {
newSoldiers = gameState.getOwnEntitiesByRole("worker");
newSoldiers.forEach(function(ent) {
if (nbOfAttackers <= 0)
return;
// If we're not female, we attack
// and if we're not already assigned from above (might happen, not sure, rather be cautious)
if (ent.hasClass("CitizenSoldier") && ent.getMetadata(PlayerID, "subrole") != "newdefender") {
ent.setMetadata(PlayerID, "formerrole", "worker");
ent.setMetadata(PlayerID, "role","defence");
ent.setMetadata(PlayerID, "subrole","newdefender");
nbOfAttackers--;
}
});
}
// okay
newSoldiers = gameState.getOwnEntitiesByMetadata("subrole","newdefender");
// we're okay, but there's a big amount of units
// todo: check against total number of soldiers
if (nbOfAttackers <= 0 && newSoldiers.length > 35)
gameState.setDefcon(2);
else if (nbOfAttackers > 0) {
// we are actually lacking units
gameState.setDefcon(1);
}
// TODO. For now, each unit will pick the closest unit that is attacked by only one/zero guy, or any if there is none.
// ought to regroup them first for optimization.
newSoldiers.forEach(function(ent) { //}) {
var enemies = self.listedEnemyCollection.filterNearest(ent.position()).toEntityArray();
var target = -1;
var secondaryTarget = enemies[0]; // second best pick
for (o in enemies) {
var enemy = enemies[o];
if (self.attackerCache[enemy.id()].length < 2) {
target = +enemy.id();
break;
}
if (gameState.defcon() <= 3)
{
// let's try to garrison neighboring females.
var buildings = gameState.getOwnEntities().filter(Filters.byCanGarrison()).toEntityArray();
var females = gameState.getOwnEntities().filter(Filters.byClass("Support"));
var cache = {};
var garrisoned = false;
females.forEach( function (ent) { //}){
garrisoned = false;
if (ent.position())
{
if (SquareVectorDistance(ent.position(), enemy.position()) < 3500)
{
for (i in buildings)
{
var struct = buildings[i];
if (!cache[struct.id()])
cache[struct.id()] = 0;
if (struct.garrisoned() && struct.garrisonMax() - struct.garrisoned().length - cache[struct.id()] > 0)
{
garrisoned = true;
ent.garrison(struct);
cache[struct.id()]++;
break;
}
}
if (!garrisoned)
ent.flee(enemy);
}
}
});
}
ent.setMetadata(PlayerID, "subrole","defending");
ent.attack(+target);
});
*/
}
return;
}
@ -596,6 +599,14 @@ Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
// units in attack plans will react independently, but we still list the attacks here.
if (attacker.hasClass("Structure")) {
// todo: we ultimately have to check wether it's a danger point or an isolated area, and if it's a danger point, mark it as so.
// Right now, to make the AI less gameable, we'll mark any surrounding resource as inaccessible.
// usual tower range is 80. Be on the safe side.
var close = gameState.getResourceSupplies("wood").filter(Filters.byDistance(attacker.position(), 90));
close.forEach(function (supply) { //}){
supply.setMetadata(PlayerID, "inaccessible", true);
});
} else {
// TODO: right now a soldier always retaliate... Perhaps it should be set in "Defence" mode.
if (!attacker.hasClass("Female") && !attacker.hasClass("Ship")) {
@ -611,7 +622,7 @@ Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
var position = attacker.position();
var close = militaryManager.enemyWatchers[attacker.owner()].enemySoldiers.filter(Filters.byDistance(position, self.armyCompactSize));
if (close.length > 15)
if (close.length > 5)
{
close.forEach(function (ent) { //}){
if (SquareVectorDistance(position, ent.position()) < self.armyCompactSize)
@ -621,6 +632,7 @@ Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
}
});
}
return; // don't use too much processing power. If there are other cases, they'll be processed soon enough.
// Defencemanager will deal with them in the next turn.
/*
// we register this unit as wanted, TODO register the whole army

View File

@ -39,18 +39,37 @@ var EconomyManager = function() {
};
// More initialisation for stuff that needs the gameState
EconomyManager.prototype.init = function(gameState, events){
if (this.targetNumWorkers === undefined)
if (Config.Economy.targetNumWorkers)
this.targetNumWorkers = Config.Economy.targetNumWorkers;
else if (this.targetNumWorkers === undefined)
this.targetNumWorkers = Math.max(Math.floor(gameState.getPopulationMax()*0.55), 1);
// Athen's fastest Citizen soldier requires stone.
if (gameState.civ() == "athen" && !gameState.ai.strategy === "rush")
var availableRess = 0;
availableRess += gameState.ai.queueManager.getAvailableResources(gameState,false).toInt();
var ents = gameState.getEntities().filter(Filters.byOwner(PlayerID));
ents = ents.filter(Filters.byClass("CivCentre")).toEntityArray();
if (ents.length > 0)
{
this.baseNeed["food"] = 140;
this.baseNeed["wood"] = 100;
this.baseNeed["stone"] = 50;
gameState.getResourceSupplies("food").forEach( function (ent) {
if (SquareVectorDistance(ent.position(), ents[0].position()) < 5000)
availableRess += ent.resourceSupplyMax();
});
gameState.getResourceSupplies("stone").forEach( function (ent) {
if (SquareVectorDistance(ent.position(), ents[0].position()) < 5000)
availableRess += ent.resourceSupplyMax();
});
gameState.getResourceSupplies("metal").forEach( function (ent) {
if (SquareVectorDistance(ent.position(), ents[0].position()) < 5000)
availableRess += ent.resourceSupplyMax();
});
gameState.getResourceSupplies("wood").forEach( function (ent) {
if (SquareVectorDistance(ent.position(), ents[0].position()) < 5000)
availableRess += ent.resourceSupplyMax();
});
}
if (availableRess > 2000)
this.fastStart = true;
// initialize once all the resource maps.
this.updateResourceMaps(gameState, events);
this.updateResourceConcentrations(gameState,"food");
@ -103,16 +122,20 @@ EconomyManager.prototype.trainMoreWorkers = function(gameState, queues) {
// If we have too few, train more
// should plan enough to always have females…
if (numTotal < this.targetNumWorkers && numQueued < 3 && (numQueued+numInTraining) < 15) {
if (numTotal < this.targetNumWorkers && numQueued < 2 && (numQueued+numInTraining) < 15) {
var template = gameState.applyCiv("units/{civ}_support_female_citizen");
var size = Math.min(Math.ceil(gameState.getTimeElapsed() / 30000),5);
if (numFemales/numTotal > this.femaleRatio && gameState.getTimeElapsed() > 60*1000) {
template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], [ ["cost",1], ["speed",0.5]]);
template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], [ ["cost",1], ["speed",0.5], ["costsResource", 0.5, "stone"], ["costsResource", 0.5, "metal"]]);
if (!template)
template = gameState.applyCiv("units/{civ}_support_female_citizen");
else
size = Math.min(Math.ceil(gameState.getTimeElapsed() / 90000),5);
}
if (gameState.getTimeElapsed() < 60000 && gameState.ai.queueManager.getAvailableResources(gameState, true)["food"] > 250 || this.fastStart)
size = 5;
if (template === gameState.applyCiv("units/{civ}_support_female_citizen"))
queues.villager.addItem(new UnitTrainingPlan(gameState, template, { "role" : "worker" }, size, size ));
else
@ -165,6 +188,13 @@ EconomyManager.prototype.findBestTrainableUnit = function(gameState, classes, pa
aDivParam += a[1].costSum() * param[1];
bDivParam += b[1].costSum() * param[1];
}
// requires a third parameter which is the resource
if (param[0] == "costsResource") {
if (a[1].cost()[param[2]])
aTopParam *= param[1];
if (b[1].cost()[param[2]])
bTopParam *= param[1];
}
}
return -(aTopParam/(aDivParam+1)) + (bTopParam/(bDivParam+1));
});
@ -207,10 +237,6 @@ EconomyManager.prototype.reassignRolelessUnits = function(gameState) {
roleless.forEach(function(ent) {
if (ent.hasClass("Worker")){
ent.setMetadata(PlayerID, "role", "worker");
}else if(ent.hasClass("CitizenSoldier") || ent.hasClass("Champion")){
ent.setMetadata(PlayerID, "role", "soldier");
}else{
ent.setMetadata(PlayerID, "role", "unknown");
}
});
};
@ -300,32 +326,37 @@ EconomyManager.prototype.assignToFoundations = function(gameState, noRepair) {
for (i in foundations) {
var target = foundations[i];
if (target._template.BuildRestrictions.Category === "Field")
continue; // we do not build fields
// Removed: sometimes the AI would not notice it has empty unbuilt fields
//if (target._template.BuildRestrictions.Category === "Field")
// continue; // we do not build fields
var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target).length;
if (assigned < this.targetNumBuilders) {
if (builderWorkers.length + addedWorkers < this.targetNumBuilders*Math.min(4.0,gameState.getTimeElapsed()/60000)) {
var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.getMetadata(PlayerID, "gather-type") !== "food" && ent.position() !== undefined); });
var nearestNonBuilders = null;
if (target.hasClass("CivCentre"))
if (target.hasClass("CivCentre") || (target.hasClass("House") && gameState.getTimeElapsed() < 300000) )
nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders*2.0 - assigned);
else
nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders - assigned);
nearestNonBuilders.forEach(function(ent) {
addedWorkers++;
ent.stopMoving();
ent.setMetadata(PlayerID, "subrole", "builder");
ent.setMetadata(PlayerID, "target-foundation", target);
});
if (this.targetNumBuilders - assigned - nearestNonBuilders.length > 0) {
/* if (this.targetNumBuilders - assigned - nearestNonBuilders.length > 0) {
var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.position() !== undefined); });
var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders - assigned);
nearestNonBuilders.forEach(function(ent) {
ent.stopMoving();
addedWorkers++;
ent.setMetadata(PlayerID, "subrole", "builder");
ent.setMetadata(PlayerID, "target-foundation", target);
});
}
}*/
}
}
}
@ -340,7 +371,7 @@ EconomyManager.prototype.assignToFoundations = function(gameState, noRepair) {
continue;
var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target).length;
if (assigned < this.targetNumBuilders) {
if (builderWorkers.length + addedWorkers < this.targetNumBuilders*2.5) {
if (builderWorkers.length + addedWorkers < this.targetNumBuilders*4) {
var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.position() !== undefined); });
if (gameState.defcon() < 5)
@ -348,6 +379,7 @@ EconomyManager.prototype.assignToFoundations = function(gameState, noRepair) {
var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders - assigned);
nearestNonBuilders.forEach(function(ent) {
ent.stopMoving();
addedWorkers++;
ent.setMetadata(PlayerID, "subrole", "builder");
ent.setMetadata(PlayerID, "target-foundation", target);
@ -409,10 +441,10 @@ EconomyManager.prototype.updateResourceMaps = function(gameState, events) {
var radius = {'wood':10.0, 'stone': 24.0, 'metal': 24.0, 'food': 24.0};
// smallRadius is the distance necessary to mark a resource as linked to a dropsite.
var smallRadius = { 'food':80*80,'wood':60*60,'stone':70*70,'metal':70*70 };
var smallRadius = { 'food':60*60,'wood':60*60,'stone':70*70,'metal':70*70 };
// bigRadius is the distance for a weak link (resources are considered when building other dropsites)
// and their resource amount is divided by 3 when checking for dropsite resource level.
var bigRadius = { 'food':140*140,'wood':140*140,'stone':140*140,'metal':140*140 };
var bigRadius = { 'food':80*80,'wood':140*140,'stone':140*140,'metal':140*140 };
var self = this;
@ -525,6 +557,8 @@ EconomyManager.prototype.updateResourceMaps = function(gameState, events) {
var addToMap = true;
var dropsites = gameState.getOwnDropsites(resource);
dropsites.forEach( function (otherDropsite) { //}) {
if (!otherDropsite.position())
return;
var distance = SquareVectorDistance(ent.position(), otherDropsite.position());
if (ent.getMetadata(PlayerID, "linked-dropsite") == undefined || ent.getMetadata(PlayerID, "linked-dropsite-dist") > distance) {
if (distance < bigRadius[resource]) {
@ -631,6 +665,7 @@ EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource
friendlyTiles = new Map(gameState);
var ents = gameState.getOwnEntities().filter(Filters.byClass("CivCentre"));
var eEnts = gameState.getEnemyEntities().filter(Filters.byClass("CivCentre"));
// This uses a different resource maps,where the point is basically to try to have as many resources as possible in the CC's territory.
for (var j = 0; j < friendlyTiles.length; ++j)
@ -658,7 +693,19 @@ EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource
friendlyTiles.map[j] = 0;
continue;
}
// Checking for enemy CCs
mindist = 7101;
eEnts.forEach( function (cc) {
var dist = SquareVectorDistance(friendlyTiles.gamePosToMapPos(cc.position()),pos);
if (dist < mindist)
mindist = dist;
});
if (mindist < 3500) // cannot build too close to each other.
{
friendlyTiles.map[j] = 0;
continue;
}
friendlyTiles.map[j] += this.CCResourceMaps[resource].map[j] * 1.5;
for (i in this.CCResourceMaps)
@ -675,8 +722,6 @@ EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource
//friendlyTiles.dumpIm(gameState.getTimeElapsed() + "_" + resource + "_density_fade_final2.png", 5000);
}
debug ("Have " + best[1] + " for " + resource);
// tell the dropsite builder we haven't found anything satisfactory.
if (best[1] < 250)
return [false, [-1,0]];
@ -723,10 +768,10 @@ EconomyManager.prototype.updateNearbyResources = function(gameState,resource){
var radius = {'wood':10.0, 'stone': 24.0, 'metal': 24.0, 'food': 24.0};
// smallRadius is the distance necessary to mark a resource as linked to a dropsite.
var smallRadius = { 'food':80*80,'wood':60*60,'stone':70*70,'metal':70*70 };
var smallRadius = { 'food':60*60,'wood':60*60,'stone':70*70,'metal':70*70 };
// bigRadius is the distance for a weak link (resources are considered when building other dropsites)
// and their resource amount is divided by 3 when checking for dropsite resource level.
var bigRadius = { 'food':140*140,'wood':140*140,'stone':140*140,'metal':140*140 };
var bigRadius = { 'food':80*80,'wood':140*140,'stone':140*140,'metal':140*140 };
gameState.getOwnDropsites(resource).forEach(function(ent) { //}){
@ -946,8 +991,13 @@ EconomyManager.prototype.buildMoreHouses = function(gameState, queues) {
var numConstructing = gameState.countEntitiesByType(gameState.applyCiv("foundation|structures/{civ}_house"));
var numPlanned = queues.house.totalLength();
var additional = Math.ceil((20 - (gameState.getPopulationLimit() - gameState.getPopulation())) / 10)
- numConstructing - numPlanned;
if (gameState.getTimeElapsed() < 300000 && numConstructing + numPlanned !== 0)
return;
var additional = 0;
if (gameState.civ() == "gaul" || gameState.civ() == "brit" || gameState.civ() == "iber")
additional = Math.ceil((20 - (gameState.getPopulationLimit() - gameState.getPopulation())) / 5) - numConstructing - numPlanned;
else
additional = Math.ceil((20 - (gameState.getPopulationLimit() - gameState.getPopulation())) / 10) - numConstructing - numPlanned;
for ( var i = 0; i < additional; i++) {
queues.house.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_house"));
@ -1020,13 +1070,13 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
if (gameState.ai.playedTurn % 20 === 0){
this.setWorkersIdleByPriority(gameState);
} else {
Engine.ProfileStart("Assign builders");
this.assignToFoundations(gameState, false);
Engine.ProfileStop();
Engine.ProfileStart("Reassign Idle Workers");
this.reassignIdleWorkers(gameState);
Engine.ProfileStop();
Engine.ProfileStart("Assign builders");
this.assignToFoundations(gameState, false);
Engine.ProfileStop();
}
// TODO: do this incrementally a la defence.js
@ -1081,8 +1131,10 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
} else {
this.targetNumBuilders = Config.Economy.targetNumBuilders;
}
if (gameState.currentPhase() == 1)
this.femaleRatio = Config.Economy.femaleRatio * 1.5;
if (gameState.currentPhase() == 1 && !this.fastStart)
this.femaleRatio = Config.Economy.femaleRatio * 1.25;
else if (gameState.currentPhase() == 1 && this.fastStart)
this.femaleRatio = Config.Economy.femaleRatio * 0.9; // might expect a rush and build some CS to counter.
else
this.femaleRatio = Config.Economy.femaleRatio;
@ -1124,10 +1176,6 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
this.setWorkersIdleByPriority(gameState);
}
Engine.ProfileStart("Assign builders");
this.assignToFoundations(gameState);
Engine.ProfileStop();
Engine.ProfileStart("Reassign Idle Workers");
this.reassignIdleWorkers(gameState);
Engine.ProfileStop();
@ -1162,6 +1210,10 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
Engine.ProfileStop();
}
Engine.ProfileStart("Assign builders");
this.assignToFoundations(gameState);
Engine.ProfileStop();
// TODO: do this incrementally a la defence.js (Changed slightly to be faster already).
Engine.ProfileStart("Run Workers");
gameState.getOwnEntitiesByRole("worker").forEach(function(ent){

View File

@ -52,7 +52,7 @@ enemyWatcher.prototype.getEnemyBuildings = function(gameState, specialClass, One
else if (OneTime)
return this.enemyBuildings.filter(filter);
return gameState.updatingGlobalCollection("player-" +this.watched + "-structures-" +specialClass, filter, gameState.getGEC("player-" +this.watched + "-structure"));
return gameState.updatingGlobalCollection("player-" +this.watched + "-structures-" +specialClass, filter, gameState.getGEC("player-" +this.watched + "-structures"));
};
enemyWatcher.prototype.getDangerousArmies = function() {
var toreturn = {};

View File

@ -1,13 +1,16 @@
// returns some sort of DPS * health factor. If you specify a class, it'll use the modifiers against that class too.
function getMaxStrength(ent, againstClass)
{
var strength = 0.0;
var attackTypes = ent.attackTypes();
var armourStrength = ent.armourStrengths();
var hp = ent.maxHitpoints() / 100.0; // some normalization
for (var typeKey in attackTypes) {
var type = attackTypes[typeKey];
if (type == "Slaughter" || type == "Charged")
continue;
var attackStrength = ent.attackStrengths(type);
var attackRange = ent.attackRange(type);
var attackTimes = ent.attackTimes(type);

View File

@ -148,35 +148,42 @@ MilitaryAttackManager.prototype.findBestTrainableUnit = function(gameState, clas
units.sort(function(a, b) { //}) {
var aDivParam = 0, bDivParam = 0;
var aTopParam = 0, bTopParam = 0;
for (i in parameters) {
var param = parameters[i];
if (param[0] == "base") {
aTopParam = param[1];
bTopParam = param[1];
}
if (param[0] == "strength") {
aTopParam += getMaxStrength(a[1]) * param[1];
bTopParam += getMaxStrength(b[1]) * param[1];
}
if (param[0] == "siegeStrength") {
aTopParam += getMaxStrength(a[1], "Structure") * param[1];
bTopParam += getMaxStrength(b[1], "Structure") * param[1];
}
if (param[0] == "speed") {
aTopParam += a[1].walkSpeed() * param[1];
bTopParam += b[1].walkSpeed() * param[1];
}
if (param[0] == "cost") {
aDivParam += a[1].costSum() * param[1];
bDivParam += b[1].costSum() * param[1];
}
}
return -(aTopParam/(aDivParam+1)) + (bTopParam/(bDivParam+1));
});
var aDivParam = 0, bDivParam = 0;
var aTopParam = 0, bTopParam = 0;
for (i in parameters) {
var param = parameters[i];
if (param[0] == "base") {
aTopParam = param[1];
bTopParam = param[1];
}
if (param[0] == "strength") {
aTopParam += getMaxStrength(a[1]) * param[1];
bTopParam += getMaxStrength(b[1]) * param[1];
}
if (param[0] == "siegeStrength") {
aTopParam += getMaxStrength(a[1], "Structure") * param[1];
bTopParam += getMaxStrength(b[1], "Structure") * param[1];
}
if (param[0] == "speed") {
aTopParam += a[1].walkSpeed() * param[1];
bTopParam += b[1].walkSpeed() * param[1];
}
if (param[0] == "cost") {
aDivParam += a[1].costSum() * param[1];
bDivParam += b[1].costSum() * param[1];
}
if (param[0] == "canGather") {
// checking against wood, could be anything else really.
if (a[1].resourceGatherRates() && a[1].resourceGatherRates()["wood.tree"])
aTopParam *= param[1];
if (b[1].resourceGatherRates() && b[1].resourceGatherRates()["wood.tree"])
bTopParam *= param[1];
}
}
return -(aTopParam/(aDivParam+1)) + (bTopParam/(bDivParam+1));
});
return units[0][0];
};
@ -376,8 +383,8 @@ MilitaryAttackManager.prototype.measureEnemyStrength = function(gameState){
// Adds towers to the defenceBuilding queue
MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv('structures/{civ}_defense_tower'))
+ queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["DefenseTower"]
&& gameState.currentPhase() > 1) {
+ queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["DefenseTower"] && queues.defenceBuilding.totalLength() < 4
&& gameState.currentPhase() > 1 && queues.defenceBuilding.totalLength() < 3) {
gameState.getOwnEntities().forEach(function(dropsiteEnt) {
if (dropsiteEnt.resourceDropsiteTypes() && dropsiteEnt.getMetadata(PlayerID, "defenseTower") !== true && !dropsiteEnt.hasClass("CivCentre")){
var position = dropsiteEnt.position();
@ -394,7 +401,7 @@ MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
numFortresses += gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bFort[i]));
}
if (numFortresses + queues.defenceBuilding.totalLength() < 1 && gameState.currentPhase() > 2)
if (numFortresses + queues.defenceBuilding.totalLength() < 2 && gameState.currentPhase() > 2)
{
if (gameState.getTimeElapsed() > this.fortressStartTime + numFortresses * this.fortressLapseTime){
if (gameState.ai.pathsToMe && gameState.ai.pathsToMe.length > 0){
@ -412,12 +419,22 @@ MilitaryAttackManager.prototype.constructTrainingBuildings = function(gameState,
// Build more military buildings
// TODO: make military building better
Engine.ProfileStart("Build buildings");
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > 15 && gameState.currentPhase() > 1) {
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0]))
+ queues.militaryBuilding.totalLength() < 1) {
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > 25 && (gameState.currentPhase() > 1 || gameState.isResearching("phase_town"))) {
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0])) + queues.militaryBuilding.totalLength() < 1) {
debug ("Trying to build barracks");
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0]));
}
}
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0])) < 2 && gameState.getTimeElapsed() > 15*60*1000)
if (queues.militaryBuilding.totalLength() < 1)
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0]));
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0])) < 3 && gameState.getTimeElapsed() > 23*60*1000 &&
(gameState.civ() == "gaul" || gameState.civ() == "brit" || gameState.civ() == "iber"))
if (queues.militaryBuilding.totalLength() < 1)
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0]));
//build advanced military buildings
if (gameState.getTimeElapsed() > this.advancedMilitaryStartTime && gameState.currentPhase() > 2){
if (queues.militaryBuilding.totalLength() === 0){
@ -432,6 +449,20 @@ MilitaryAttackManager.prototype.constructTrainingBuildings = function(gameState,
}
}
}
if (gameState.civ() !== "gaul" && gameState.civ() !== "brit" && gameState.civ() !== "iber" &&
gameState.getTimeElapsed() > this.advancedMilitaryStartTime && gameState.currentPhase() > 2 && gameState.getTimeElapsed() > 25*60*1000)
{
var Const = 0;
for (var i in this.bAdvanced)
Const += gameState.countEntitiesByType(gameState.applyCiv(this.bAdvanced[i]));
if (inConst == 1) {
var i = Math.floor(Math.random() * this.bAdvanced.length);
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bAdvanced[i])) < 1){
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bAdvanced[i]));
}
}
}
Engine.ProfileStop();
};

View File

@ -125,12 +125,14 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
friendlyTiles.addInfluence(x, z, infl/3.0, infl + 1);
friendlyTiles.addInfluence(x, z, Math.ceil(infl/5.0), -(infl/2.0), 'linear');
}
//avoid building too close to each other if possible.
friendlyTiles.addInfluence(x, z, 5, -5, 'linear');
}
}
});
}
//friendlyTiles.dumpIm(template.buildCategory() + "_" +gameState.getTimeElapsed() + ".png", 200);
friendlyTiles.dumpIm(template.buildCategory() + "_" +gameState.getTimeElapsed() + ".png", 200);
// Find target building's approximate obstruction radius, and expand by a bit to make sure we're not too close, this
// allows room for units to walk between buildings.
@ -142,7 +144,9 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
else if (template.buildCategory() === "Dock")
radius = 1;//Math.floor(template.obstructionRadius() / cellSize);
else if (template.genericName() != "House" && !template.hasClass("DropsiteWood") && !template.hasClass("DropsiteStone") && !template.hasClass("DropsiteMetal"))
radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
radius = Math.ceil(template.obstructionRadius() / cellSize + 0.5);
else if (gameState.civ() === "iber" || gameState.civ() === "gaul" || gameState.civ() === "brit")
radius = Math.ceil(template.obstructionRadius() / cellSize);
else
radius = Math.ceil(template.obstructionRadius() / cellSize);

View File

@ -42,17 +42,13 @@ UnitTrainingPlan.prototype.execute = function(gameState) {
// plans that have already been executed this turn)
if (trainers.length > 0){
trainers.sort(function(a, b) {
if (self.metadata["plan"] !== undefined) {
var aa = a.trainingQueueTime();
var bb = b.trainingQueueTime();
if (a.hasClass("Civic"))
aa += 20;
if (b.hasClass("Civic"))
bb += 20;
return (a.trainingQueueTime() - b.trainingQueueTime());
}
return a.trainingQueueTime() - b.trainingQueueTime();
var aa = a.trainingQueueTime();
var bb = b.trainingQueueTime();
if (a.hasClass("Civic") && !self.template.hasClass("Support"))
aa += 0.9;
if (b.hasClass("Civic") && !self.template.hasClass("Support"))
bb += 0.9;
return (aa - bb);
});
trainers[0].train(this.type, this.number, this.metadata);

View File

@ -202,6 +202,7 @@ QBotAI.prototype.OnUpdate = function(sharedScript) {
this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_town",true)); // we rush the town phase.
debug ("Trying to reach town phase");
} else if (gameState.canResearch("phase_city_generic",true) && gameState.getTimeElapsed() > (Config.Economy.cityPhase*1000)
&& gameState.getOwnEntitiesByRole("worker").length > 90
&& gameState.findResearchers("phase_city_generic").length != 0 && this.queues.majorTech.length() === 0) {
debug ("Trying to reach city phase");
this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_city_generic"));
@ -223,6 +224,22 @@ QBotAI.prototype.OnUpdate = function(sharedScript) {
this.queueManager.update(gameState);
/*
if (this.playedTurn % 80 === 0)
{
// some debug informations about units.
var units = gameState.getOwnEntities().filter(Filters.byClass("Unit"));
for (var i in units._entities)
{
var ent = units._entities[i];
debug ("Unit " + ent.id() + " is a " + ent._templateName);
debug ("It is a " + uneval(ent.getMetadata(PlayerID, "role")) + ", "+ uneval(ent.getMetadata(PlayerID, "subrole")));
if (ent.getMetadata(PlayerID, "plan") != undefined)
debug ("it is part of the plan " + uneval(ent.getMetadata(PlayerID, "plan")));
}
}*/
//if (this.playedTurn % 15 === 0)
// this.queueManager.printQueues(gameState);

View File

@ -132,10 +132,10 @@ QueueManager.prototype.futureNeeds = function(gameState, EcoManager) {
} else {
// Return predicted values minus the current stockpiles along with a base rater for all resources
return {
"food" : Math.max(needs.food - current.food, 0) + EcoManager.baseNeed["food"]*2,
"wood" : Math.max(needs.wood - current.wood, 0) + EcoManager.baseNeed["wood"]*2,
"stone" : Math.max(needs.stone - current.stone, 0) + EcoManager.baseNeed["stone"]*2,
"metal" : Math.max(needs.metal - current.metal, 0) + EcoManager.baseNeed["metal"]*2
"food" : Math.max(needs.food - current.food, 0) + EcoManager.baseNeed["food"],
"wood" : Math.max(needs.wood - current.wood, 0) + EcoManager.baseNeed["wood"],
"stone" : Math.max(needs.stone - current.stone, 0) + EcoManager.baseNeed["stone"],
"metal" : Math.max(needs.metal - current.metal, 0) + EcoManager.baseNeed["metal"]
};
}
};

View File

@ -17,8 +17,8 @@ Worker.prototype.update = function(gameState) {
// If the worker has no position then no work can be done
return;
}
if (subrole === "gatherer"){
if (subrole === "gatherer") {
if (this.ent.unitAIState().split(".")[1] !== "GATHER" && this.ent.unitAIState().split(".")[1] !== "COMBAT" && this.ent.unitAIState().split(".")[1] !== "RETURNRESOURCE"){
// TODO: handle combat for hunting animals
if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0 ||
@ -400,6 +400,7 @@ Worker.prototype.startGathering = function(gameState){
if (!tried) {
this.maxApproachTime = Math.max(25000, VectorDistance(pos,this.ent.position()) * 1000);
ent.gather(nearestSupply);
ent.setMetadata(PlayerID, "target-foundation", undefined);
}
} else {
if (resource === "food" && this.buildAnyField(gameState))

View File

@ -580,6 +580,8 @@ ProductionQueue.prototype.ProgressTimeout = function(data)
if (item.timeRemaining > time)
{
item.timeRemaining -= time;
// send a message for the AIs.
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
break;
}

View File

@ -1196,6 +1196,27 @@ bool Autostart(const CmdLineArgs& args)
scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player);
}
}
// Set player data for Civs
if (args.Has("autostart-civ"))
{
std::vector<CStr> civArgs = args.GetMultiple("autostart-civ");
for (size_t i = 0; i < civArgs.size(); ++i)
{
// Instead of overwriting existing player data, modify the array
CScriptVal player;
if (!scriptInterface.GetPropertyInt(playerData.get(), i, player) || player.undefined())
{
scriptInterface.Eval("({})", player);
}
int playerID = civArgs[i].BeforeFirst(":").ToInt();
CStr name = civArgs[i].AfterFirst(":");
scriptInterface.SetProperty(player.get(), "Civ", std::string(name));
scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player);
}
}
// Add player data to map settings
scriptInterface.SetProperty(settings.get(), "PlayerData", playerData);

View File

@ -291,11 +291,11 @@ public:
};
CAIWorker() :
// TODO: Passing a 24 MB argument to CreateRuntime() is a temporary fix
// TODO: Passing a 32 MB argument to CreateRuntime() is a temporary fix
// to prevent frequent AI out-of-memory crashes. The argument should be
// removed as soon whenever the new pathfinder is committed
// And the AIs can stop relying on their own little hands.
m_ScriptRuntime(ScriptInterface::CreateRuntime(25165824)),
m_ScriptRuntime(ScriptInterface::CreateRuntime(33554432)),
m_ScriptInterface("Engine", "AI", m_ScriptRuntime),
m_TurnNum(0),
m_CommandsComputed(true),
@ -726,7 +726,7 @@ private:
}
// Run GC if we are about to overflow
if (JS_GetGCParameter(m_ScriptInterface.GetRuntime(), JSGC_BYTES) > 24000000)
if (JS_GetGCParameter(m_ScriptInterface.GetRuntime(), JSGC_BYTES) > 33000000)
{
PROFILE3("AI compute GC");