1
0
forked from 0ad/0ad

New version of Aegis. Huge rewrite for WIP naval support (still very, very buggy at this point.) Features a few bugfixes and might be more efficient than the former version.

NEEDS TESTING.

This was SVN commit r13907.
This commit is contained in:
wraitii 2013-09-29 13:32:52 +00:00
parent 994ebd9836
commit d663dae2d8
29 changed files with 4709 additions and 3206 deletions

View File

@ -10,4 +10,4 @@ Please report any error to the wildfire games forum ( http://www.wildfiregames.c
Requires common-api-v3.
(note: no saved game support as of yet).
(note: no saved game support as of yet).

View File

@ -1,4 +1,10 @@
function QBotAI(settings) {
// "local" global variables for stuffs that will need a unique ID
// Note that since order of loading is alphabetic, this means this file must go before any other file using them.
var uniqueIDBOPlans = 0; // training/building/research plans
var uniqueIDBases = 1; // base manager ID. Starts at one because "0" means "no base" on the map
var uniqueIDTPlans = 1; // transport plans. starts at 1 because 0 might be used as none.
function AegisBot(settings) {
BaseAI.call(this, settings);
Config.updateDifficulty(settings.difficulty);
@ -6,47 +12,33 @@ function QBotAI(settings) {
this.turn = 0;
this.playedTurn = 0;
this.modules = {
"economy": new EconomyManager(),
"military": new MilitaryAttackManager()
};
this.priorities = Config.priorities;
// this.queues can only be modified by the queue manager or things will go awry.
this.queues = {
house : new Queue(),
citizenSoldier : new Queue(),
villager : new Queue(),
economicBuilding : new Queue(),
dropsites : new Queue(),
field : new Queue(),
militaryBuilding : new Queue(),
defenceBuilding : new Queue(),
civilCentre: new Queue(),
majorTech: new Queue(),
minorTech: new Queue()
};
this.productionQueues = [];
this.priorities = Config.priorities;
this.queues = {};
for (i in this.priorities)
this.queues[i] = new Queue();
this.queueManager = new QueueManager(this.queues, this.priorities);
this.HQ = new HQ();
this.firstTime = true;
this.savedEvents = [];
this.waterMap = false;
this.defcon = 5;
this.defconChangeTime = -10000000;
}
QBotAI.prototype = new BaseAI();
AegisBot.prototype = new BaseAI();
AegisBot.prototype.InitShared = function(gameState, sharedScript) {
this.HQ.init(gameState,sharedScript.events,this.queues);
debug ("Initialized with the difficulty " + Config.difficulty);
// Bit of a hack: I run the pathfinder early, before the map apears, to avoid a sometimes substantial lag right at the start.
QBotAI.prototype.InitShared = function(gameState, sharedScript) {
var ents = gameState.getEntities().filter(Filters.byOwner(PlayerID));
var myKeyEntities = ents.filter(function(ent) {
return ent.hasClass("CivCentre");
@ -63,6 +55,8 @@ QBotAI.prototype.InitShared = function(gameState, sharedScript) {
enemyKeyEntities = gameState.getEntities().filter(Filters.not(Filters.byOwner(PlayerID)));
}
this.myIndex = this.accessibility.getAccessValue(myKeyEntities.toEntityArray()[0].position());
this.pathFinder = new aStarPath(gameState, false, true);
this.pathsToMe = [];
this.pathInfo = { "angle" : 0, "needboat" : true, "mkeyPos" : myKeyEntities.toEntityArray()[0].position(), "ekeyPos" : enemyKeyEntities.toEntityArray()[0].position() };
@ -82,92 +76,28 @@ QBotAI.prototype.InitShared = function(gameState, sharedScript) {
}
this.pathInfo.angle += Math.PI/3.0;
}
//Some modules need the gameState to fully initialise
QBotAI.prototype.runInit = function(gameState, events){
this.chooseRandomStrategy();
}
for (var i in this.modules){
if (this.modules[i].init){
this.modules[i].init(gameState, events);
}
}
debug ("Inited, diff is " + Config.difficulty);
this.timer = new Timer();
var ents = gameState.getOwnEntities();
var myKeyEntities = gameState.getOwnEntities().filter(function(ent) {
return ent.hasClass("CivCentre");
});
if (myKeyEntities.length == 0){
myKeyEntities = gameState.getOwnEntities();
}
// disband the walls themselves
if (gameState.playerData.civ == "iber") {
gameState.getOwnEntities().filter(function(ent) { //}){
if (ent.hasClass("StoneWall") && !ent.hasClass("Tower"))
ent.destroy();
});
}
var filter = Filters.byClass("CivCentre");
var enemyKeyEntities = gameState.getEnemyEntities().filter(filter);
if (enemyKeyEntities.length == 0){
enemyKeyEntities = gameState.getEnemyEntities();
}
//this.accessibility = new Accessibility(gameState, myKeyEntities.toEntityArray()[0].position());
this.myIndex = this.accessibility.getAccessValue(myKeyEntities.toEntityArray()[0].position());
if (enemyKeyEntities.length == 0)
return;
this.templateManager = new TemplateManager(gameState);
};
QBotAI.prototype.OnUpdate = function(sharedScript) {
AegisBot.prototype.OnUpdate = function(sharedScript) {
if (this.gameFinished){
return;
}
if (this.events.length > 0){
if (this.events.length > 0 && this.turn !== 0){
this.savedEvents = this.savedEvents.concat(this.events);
}
// Run the update every n turns, offset depending on player ID to balance the load
// this also means that init at turn 0 always happen and is never run in parallel to the first played turn so I use an else if.
if (this.turn == 0) {
if ((this.turn + this.player) % 8 == 5) {
//Engine.DumpImage("terrain.png", this.accessibility.map, this.accessibility.width,this.accessibility.height,255)
//Engine.DumpImage("Access.png", this.accessibility.passMap, this.accessibility.width,this.accessibility.height,this.accessibility.regionID+1)
var gameState = sharedScript.gameState[PlayerID];
gameState.ai = this;
this.runInit(gameState, this.savedEvents);
// Delete creation events
delete this.savedEvents;
this.savedEvents = [];
} else if ((this.turn + this.player) % 8 == 5) {
Engine.ProfileStart("Aegis bot");
Engine.ProfileStart("Aegis bot (player " + this.player +")");
this.playedTurn++;
var gameState = sharedScript.gameState[PlayerID];
gameState.ai = this;
if (gameState.getOwnEntities().length === 0){
if (this.gameState.getOwnEntities().length === 0){
Engine.ProfileStop();
return; // With no entities to control the AI cannot do anything
}
@ -175,7 +105,7 @@ QBotAI.prototype.OnUpdate = function(sharedScript) {
if (this.pathInfo !== undefined)
{
var pos = [this.pathInfo.mkeyPos[0] + 150*Math.cos(this.pathInfo.angle),this.pathInfo.mkeyPos[1] + 150*Math.sin(this.pathInfo.angle)];
var path = this.pathFinder.getPath(this.pathInfo.ekeyPos, pos, 6, 5);// uncomment for debug:*/, 300000, gameState);
var path = this.pathFinder.getPath(this.pathInfo.ekeyPos, pos, 6, 5);// uncomment for debug:*/, 300000, this.gameState);
if (path !== undefined && path[1] !== undefined && path[1] == false) {
// path is viable and doesn't require boating.
// blackzone the last two waypoints.
@ -191,55 +121,52 @@ QBotAI.prototype.OnUpdate = function(sharedScript) {
if (this.pathInfo.needboat)
{
debug ("Assuming this is a water map");
this.waterMap = true;
this.HQ.waterMap = true;
}
delete this.pathFinder;
delete this.pathInfo;
}
}
var sfx = "_generic";
if (gameState.civ() == "athen")
sfx = "_athen"
var townPhase = this.gameState.townPhase();
var cityPhase = this.gameState.cityPhase();
// try going up phases.
if (gameState.canResearch("phase_town" + sfx,true) && gameState.getTimeElapsed() > (Config.Economy.townPhase*1000)
&& gameState.findResearchers("phase_town" + sfx,true).length != 0 && this.queues.majorTech.totalLength() === 0) {
this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_town" + sfx,true)); // we rush the town phase.
// TODO: softcode this.
if (this.gameState.canResearch(townPhase,true) && this.gameState.getTimeElapsed() > (Config.Economy.townPhase*1000) && this.gameState.getPopulation() > 40
&& this.gameState.findResearchers(townPhase,true).length != 0 && this.queues.majorTech.length() === 0
&& this.gameState.getOwnEntities().filter(Filters.byClass("Village")).length > 5)
{
this.queueManager.pauseQueue("villager", true);
this.queueManager.pauseQueue("citizenSoldier", true);
this.queueManager.pauseQueue("house", true);
this.queues.majorTech.addItem(new ResearchPlan(this.gameState, townPhase,0,-1,true)); // we rush the town phase.
debug ("Trying to reach town phase");
var nb = gameState.getOwnEntities().filter(Filters.byClass("Village")).length-1;
if (nb < 5)
{
while (nb < 5 && ++nb)
this.queues.house.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_house"));
}
} else if (gameState.canResearch("phase_city_generic",true) && gameState.getTimeElapsed() > (Config.Economy.cityPhase*1000)
&& gameState.getOwnEntitiesByRole("worker").length > 85
&& gameState.findResearchers("phase_city_generic", true).length != 0 && this.queues.majorTech.totalLength() === 0) {
}
else if (this.gameState.canResearch(cityPhase,true) && this.gameState.getTimeElapsed() > (Config.Economy.cityPhase*1000)
&& this.gameState.getOwnEntitiesByRole("worker").length > 85
&& this.gameState.findResearchers(cityPhase, true).length != 0 && this.queues.majorTech.length() === 0) {
debug ("Trying to reach city phase");
this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_city_generic"));
this.queues.majorTech.addItem(new ResearchPlan(this.gameState, cityPhase));
}
// defcon cooldown
if (this.defcon < 5 && gameState.timeSinceDefconChange() > 20000)
if (this.defcon < 5 && this.gameState.timeSinceDefconChange() > 20000)
{
this.defcon++;
debug ("updefconing to " +this.defcon);
if (this.defcon >= 4 && this.modules.military.hasGarrisonedFemales)
this.modules.military.ungarrisonAll(gameState);
if (this.defcon >= 4 && this.HQ.hasGarrisonedFemales)
this.HQ.ungarrisonAll(this.gameState);
}
for (var i in this.modules){
this.modules[i].update(gameState, this.queues, this.savedEvents);
}
this.queueManager.update(gameState);
this.HQ.update(this.gameState, this.queues, this.savedEvents);
this.queueManager.update(this.gameState);
/*
// Use this to debug informations about the metadata.
if (this.playedTurn % 10 === 0)
{
// some debug informations about units.
var units = gameState.getOwnEntities();
var units = this.gameState.getOwnEntities();
for (var i in units._entities)
{
var ent = units._entities[i];
@ -265,7 +192,7 @@ QBotAI.prototype.OnUpdate = function(sharedScript) {
//if (this.playedTurn % 5 === 0)
// this.queueManager.printQueues(gameState);
// this.queueManager.printQueues(this.gameState);
// Generate some entropy in the random numbers (against humans) until the engine gets random initialised numbers
// TODO: remove this when the engine gives a random seed
@ -283,7 +210,7 @@ QBotAI.prototype.OnUpdate = function(sharedScript) {
this.turn++;
};
QBotAI.prototype.chooseRandomStrategy = function()
AegisBot.prototype.chooseRandomStrategy = function()
{
// deactivated for now.
this.strategy = "normal";
@ -292,7 +219,7 @@ QBotAI.prototype.chooseRandomStrategy = function()
{
this.strategy = "rush";
// going to rush.
this.modules.economy.targetNumWorkers = 0;
this.HQ.targetNumWorkers = 0;
Config.Economy.townPhase = 480;
Config.Economy.cityPhase = 900;
Config.Economy.farmsteadStartTime = 600;
@ -302,7 +229,7 @@ QBotAI.prototype.chooseRandomStrategy = function()
// TODO: Remove override when the whole AI state is serialised
// TODO: this currently is very much equivalent to "rungamestateinit" with a few hacks. Should deserialize/serialize properly someday.
QBotAI.prototype.Deserialize = function(data, sharedScript)
AegisBot.prototype.Deserialize = function(data, sharedScript)
{
BaseAI.prototype.Deserialize.call(this, data);
@ -345,7 +272,7 @@ QBotAI.prototype.Deserialize = function(data, sharedScript)
};
// Override the default serializer
QBotAI.prototype.Serialize = function()
AegisBot.prototype.Serialize = function()
{
//var ret = BaseAI.prototype.Serialize.call(this);
return {};

View File

@ -6,7 +6,7 @@
* There is a basic support for naval expeditions here.
*/
function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , targetFinder) {
function CityAttack(gameState, HQ, uniqueID, targetEnemy, type , targetFinder) {
//This is the list of IDs of the units in the plan
this.idList=[];
@ -330,16 +330,16 @@ CityAttack.prototype.addBuildOrder = function(gameState, name, unitStats, resetQ
// Three returns possible: 1 is "keep going", 0 is "failed plan", 2 is "start"
// 3 is a special case: no valid path returned. Right now I stop attacking alltogether.
CityAttack.prototype.updatePreparation = function(gameState, militaryManager,events) {
CityAttack.prototype.updatePreparation = function(gameState, HQ,events) {
var self = this;
if (this.path == undefined || this.target == undefined || this.path === "toBeContinued") {
// find our target
if (this.target == undefined)
{
var targets = this.targetFinder(gameState, militaryManager);
var targets = this.targetFinder(gameState, HQ);
if (targets.length === 0)
targets = this.defaultTargetFinder(gameState, militaryManager);
targets = this.defaultTargetFinder(gameState, HQ);
if (targets.length !== 0) {
debug ("Aiming for " + targets);
@ -365,7 +365,7 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
// Thus I will not do everything at once.
// It will probably carry over a few turns but that's no issue.
if (this.path === undefined)
this.path = this.pathFinder.getPath(this.rallyPoint,this.targetPos, this.pathSampling, this.pathWidth,250);//,gameState);
this.path = this.pathFinder.getPath(this.rallyPoint,this.targetPos, this.pathSampling, this.pathWidth,175);//,gameState);
else if (this.path === "toBeContinued")
this.path = this.pathFinder.continuePath();//gameState);
@ -383,19 +383,13 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
} else if (this.path[1] === true && this.pathWidth == 2) {
// okay so we need a ship.
// Basically we'll add it as a new class to train compulsorily, and we'll recompute our path.
if (!gameState.ai.waterMap)
if (!gameState.ai.HQ.waterMap)
{
debug ("This is actually a water map.");
gameState.ai.waterMap = true;
gameState.ai.HQ.waterMap = true;
return 0;
}
debug ("We need a ship.");
var stat = { "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;
}
this.addBuildOrder(gameState, "TransportShip", stat);
this.needsShip = true;
this.pathWidth = 3;
this.pathSampling = 3;
@ -414,6 +408,8 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
this.rallyPoint = this.path[i-1][0];
else
this.rallyPoint = this.path[0][0];
if (i >= 1)
this.path.splice(0,i-1);
break;
}
}
@ -473,25 +469,18 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
} else if (!this.mustStart(gameState)) {
// We still have time left to recruit units and do stuffs.
// TODO: check why this can happen instead of resorting to this "hack".
if (this.buildOrder.length === 0 || this.buildOrder[0] === undefined) {
debug ("Ending plan: no build orders");
return 0; // will abort the plan, should return something else
}
// let's sort by training advancement, ie 'current size / target size'
// count the number of queued units too.
// substract priority.
this.buildOrder.sort(function (a,b) { //}) {
var aQueued = gameState.countOwnQueuedEntitiesWithMetadata("special","Plan_"+self.name+"_"+a[4]);
aQueued += self.queue.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+a[4]);
aQueued += self.queueChamp.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+a[4]);
aQueued += self.queue.countQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+a[4]);
aQueued += self.queueChamp.countQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+a[4]);
a[0] = (a[2].length + aQueued)/a[3]["targetSize"];
var bQueued = gameState.countOwnQueuedEntitiesWithMetadata("special","Plan_"+self.name+"_"+b[4]);
bQueued += self.queue.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+b[4]);
bQueued += self.queueChamp.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+b[4]);
bQueued += self.queue.countQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+b[4]);
bQueued += self.queueChamp.countQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+b[4]);
b[0] = (b[2].length + bQueued)/b[3]["targetSize"];
a[0] -= a[3]["priority"];
@ -512,7 +501,7 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
var specialData = "Plan_"+this.name+"_"+this.buildOrder[0][4];
var inTraining = gameState.countOwnQueuedEntitiesWithMetadata("special",specialData);
var queued = this.queue.countTotalQueuedUnitsWithMetadata("special",specialData) + this.queueChamp.countTotalQueuedUnitsWithMetadata("special",specialData)
var queued = this.queue.countQueuedUnitsWithMetadata("special",specialData) + this.queueChamp.countQueuedUnitsWithMetadata("special",specialData)
if (queued + inTraining + this.buildOrder[0][2].length <= this.buildOrder[0][3]["targetSize"]) {
// find the actual queue we want
@ -521,16 +510,11 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
queue = this.queueChamp;
if (this.buildOrder[0][0] < 1 && queue.length() <= 5) {
var template = militaryManager.findBestTrainableUnit(gameState, this.buildOrder[0][1], this.buildOrder[0][3]["interests"] );
var template = HQ.findBestTrainableSoldier(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") {
Engine.ProfileStop();
Engine.ProfileStop();
return 0;
}
delete this.unitStat[this.buildOrder[0][4]]; // deleting the associated unitstat.
this.buildOrder.splice(0,1);
} else {
@ -538,10 +522,10 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
// TODO: this should be plan dependant.
if (gameState.getTimeElapsed() > 1800000)
max *= 2;
if (gameState.getTemplate(template).hasClasses(["CitizenSoldier", "Infantry"]))
queue.addItem( new UnitTrainingPlan(gameState,template, { "role" : "worker", "plan" : this.name, "special" : specialData }, this.buildOrder[0][3]["batchSize"],max ) );
if (gameState.getTemplate(template).hasClass("CitizenSoldier"))
queue.addItem( new TrainingPlan(gameState,template, { "role" : "worker", "plan" : this.name, "special" : specialData, "base" : 1 }, this.buildOrder[0][3]["batchSize"],max ) );
else
queue.addItem( new UnitTrainingPlan(gameState,template, { "role" : "attack", "plan" : this.name, "special" : specialData }, this.buildOrder[0][3]["batchSize"],max ) );
queue.addItem( new TrainingPlan(gameState,template, { "role" : "attack", "plan" : this.name, "special" : specialData, "base" : 1 }, this.buildOrder[0][3]["batchSize"],max ) );
}
}
}
@ -549,9 +533,9 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
if (!this.startedPathing && this.path === undefined) {
// find our target
var targets = this.targetFinder(gameState, militaryManager);
var targets = this.targetFinder(gameState, HQ);
if (targets.length === 0){
targets = this.defaultTargetFinder(gameState, militaryManager);
targets = this.defaultTargetFinder(gameState, HQ);
}
if (targets.length) {
this.targetPos = undefined;
@ -632,7 +616,7 @@ CityAttack.prototype.AllToRallyPoint = function(gameState, evenWorkers) {
if (evenWorkers) {
for (var unitCat in this.unit) {
this.unit[unitCat].forEach(function (ent) {
if (ent.getMetadata(PlayerID, "role") != "defence" && !ent.hasClass("Warship"))
if (ent.getMetadata(PlayerID, "role") != "defence")
{
ent.setMetadata(PlayerID,"role", "attack");
ent.move(self.rallyPoint[0],self.rallyPoint[1]);
@ -642,7 +626,7 @@ CityAttack.prototype.AllToRallyPoint = function(gameState, evenWorkers) {
} else {
for (var 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.move(self.rallyPoint[0],self.rallyPoint[1]);
});
}
@ -650,19 +634,19 @@ CityAttack.prototype.AllToRallyPoint = function(gameState, evenWorkers) {
}
// Default target finder aims for conquest critical targets
CityAttack.prototype.defaultTargetFinder = function(gameState, militaryManager){
CityAttack.prototype.defaultTargetFinder = function(gameState, HQ){
var targets = undefined;
targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "CivCentre",true);
targets = HQ.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "CivCentre",true);
if (targets.length == 0) {
targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "ConquestCritical");
targets = HQ.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "ConquestCritical");
}
// If there's nothing, attack anything else that's less critical
if (targets.length == 0) {
targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "Town",true);
targets = HQ.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "Town",true);
}
if (targets.length == 0) {
targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "Village",true);
targets = HQ.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "Village",true);
}
// no buildings, attack anything conquest critical, even units (it's assuming it won't move).
if (targets.length == 0) {
@ -672,7 +656,7 @@ CityAttack.prototype.defaultTargetFinder = function(gameState, militaryManager){
};
// tupdate
CityAttack.prototype.raidingTargetFinder = function(gameState, militaryManager, Target){
CityAttack.prototype.raidingTargetFinder = function(gameState, HQ, Target){
var targets = undefined;
if (Target == "villager")
{
@ -693,14 +677,14 @@ CityAttack.prototype.raidingTargetFinder = function(gameState, militaryManager,
}
return targets;
} else {
return this.defaultTargetFinder(gameState, militaryManager);
return this.defaultTargetFinder(gameState, HQ);
}
};
// Executes the attack plan, after this is executed the update function will be run every turn
// If we're here, it's because we have in our IDlist enough units.
// now the IDlist units are treated turn by turn
CityAttack.prototype.StartAttack = function(gameState, militaryManager){
CityAttack.prototype.StartAttack = function(gameState, HQ){
// check we have a target and a path.
if (this.targetPos && this.path !== undefined) {
@ -711,11 +695,11 @@ CityAttack.prototype.StartAttack = function(gameState, militaryManager){
var curPos = this.unitCollection.getCentrePosition();
this.unitCollection.forEach(function(ent) { ent.setMetadata(PlayerID, "subrole", "walking"); ent.setMetadata(PlayerID, "role", "attack") ;});
this.unitCollectionNoWarship = this.unitCollection.filter(Filters.not(Filters.byClass("Warship")));
this.unitCollectionNoWarship.registerUpdates();
// optimize our collection now.
this.unitCollection.freeze();
this.unitCollection.allowQuickIter();
this.unitCollection.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]);
this.unitCollection.setStance("aggressive");
this.unitCollection.filter(Filters.byClass("Siege")).setStance("defensive");
@ -729,7 +713,7 @@ CityAttack.prototype.StartAttack = function(gameState, militaryManager){
};
// Runs every turn after the attack is executed
CityAttack.prototype.update = function(gameState, militaryManager, events){
CityAttack.prototype.update = function(gameState, HQ, events){
var self = this;
Engine.ProfileStart("Update Attack");
@ -771,9 +755,9 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
{
attackedNB++;
}
//if (militaryManager.enemyWatchers[attacker.owner()]) {
//if (HQ.enemyWatchers[attacker.owner()]) {
//toProcess[attacker.id()] = attacker;
//var armyID = militaryManager.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id());
//var armyID = HQ.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id());
//armyToProcess[armyID[0]] = armyID[1];
//}
}
@ -851,7 +835,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}
if (this.state === "walking"){
this.position = this.unitCollectionNoWarship.getCentrePosition();
this.position = this.unitCollection.getCentrePosition();
// probably not too good.
if (!this.position) {
@ -880,7 +864,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
this.position5TurnsAgo = this.position;
if (this.lastPosition && SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) {
this.unitCollectionNoWarship.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
this.unitCollection.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")));
var nexttoWalls = false;
@ -902,162 +886,62 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}
// check if our land units are close enough from the next waypoint.
if (SquareVectorDistance(this.unitCollectionNoWarship.getCentrePosition(), this.targetPos) < 7500 ||
SquareVectorDistance(this.unitCollectionNoWarship.getCentrePosition(), this.path[0][0]) < 850) {
if (SquareVectorDistance(this.unitCollection.getCentrePosition(), this.targetPos) < 7500 ||
SquareVectorDistance(this.unitCollection.getCentrePosition(), this.path[0][0]) < 650) {
if (this.unitCollection.filter(Filters.byClass("Siege")).length !== 0
&& SquareVectorDistance(this.unitCollectionNoWarship.getCentrePosition(), this.targetPos) > 7500
&& SquareVectorDistance(this.unitCollection.filter(Filters.byClass("Siege")).getCentrePosition(), this.path[0][0]) > 850)
&& SquareVectorDistance(this.unitCollection.getCentrePosition(), this.targetPos) >= 7500
&& SquareVectorDistance(this.unitCollection.filter(Filters.byClass("Siege")).getCentrePosition(), this.path[0][0]) >= 650)
{
} else {
for (var i = 0; i < this.path.length; ++i)
{
debug ("path waypoint " + i + "," + this.path[i][1] + " at " + uneval(this.path[i][0]));
}
debug ("position is " + this.unitCollection.getCentrePosition());
// 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".
if (this.path[0][1] !== true)
{
this.path.shift();
if (this.path.length > 0){
this.unitCollectionNoWarship.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]);
} else {
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";
}
} else if (this.path[0][1] === true)
} else
{
// okay we must load our units.
// check if we have some kind of ships.
var ships = this.unitCollection.filter(Filters.byClass("Warship"));
if (ships.length === 0) {
// TODO: make this require an escort later on.
this.path.shift();
if (this.path.length === 0) {
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";
} else {
/*
var plan = new TransportPlan(gameState, this.unitCollection.toIdArray(), this.path[0][0], false);
this.tpPlanID = plan.ID;
HQ.navalManager.transportPlans.push(plan);
debug ("Transporting over sea");
this.state = "transporting";
*/
// TODO: fix this above
//right now we'll abort.
Engine.ProfileStop();
return 0; // abort
return 0;
}
debug ("switch to boarding");
this.state = "boarding";
}
}
}
} 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")).move(this.path[0][0][0], this.path[0][0][1]);
} else if (this.state === "transporting") {
// check that we haven't finished transporting, ie the plan
if (!HQ.navalManager.checkActivePlan(this.tpPlanID))
{
this.state = "walking";
}
if (SquareVectorDistance(this.position, this.path[0][0]) < 1600) {
if (this.path[0][1] !== true)
{
this.path.shift();
if (this.path.length > 0){
this.unitCollection.filter(Filters.byClass("Warship")).move(this.path[0][0][0], this.path[0][0][1]);
} else {
debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination, but it's still on the ship…");
Engine.ProfileStop();
return 0; // abort
}
} else if (this.path[0][1] === true)
{
debug ("switch to unboarding");
// we unload
this.state = "unboarding";
}
}
} else if (this.state === "boarding") {
this.position = this.unitCollectionNoWarship.getCentrePosition();
var ships = this.unitCollection.filter(Filters.byClass("Warship"));
if (ships.length === 0) {
Engine.ProfileStop();
return 0; // abort
}
var globalPos = this.unitCollectionNoWarship.getCentrePosition();
var shipPos = ships.getCentrePosition();
if (globalPos !== undefined && SquareVectorDistance(globalPos,shipPos) > 800)
{ // get them closer
ships.moveIndiv(globalPos[0],globalPos[1]);
this.unitCollectionNoWarship.moveIndiv(shipPos[0],shipPos[1]);
} else {
// okay try to garrison.
var shipsArray = ships.toEntityArray();
this.unitCollectionNoWarship.forEach(function (ent) { //}){
if (ent.position()) // if we're not garrisoned
for (var shipId = 0; shipId < shipsArray.length; shipId++) {
if (shipsArray[shipId].garrisoned().length < shipsArray[shipId].garrisonMax())
{
ent.garrison(shipsArray[shipId]);
break;
}
}
});
var garrLength = 0;
for (var shipId = 0; shipId < shipsArray.length; shipId++)
garrLength += shipsArray[shipId].garrisoned().length;
if (garrLength == this.unitCollectionNoWarship.length) {
// okay.
this.path.shift();
if (this.path.length > 0){
ships.move(this.path[0][0][0], this.path[0][0][1]);
debug ("switch to shipping");
this.state = "shipping";
} else {
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";
}
}
}
} else if (this.state === "unboarding") {
var ships = this.unitCollection.filter(Filters.byClass("Warship"));
if (ships.length === 0) {
Engine.ProfileStop();
return 0; // abort
}
this.position = ships.getCentrePosition();
// the procedure is pretty simple: we move the ships to the next point and try to unload until all units are over.
// TODO: make it better, like avoiding collisions, and so on.
if (this.path.length > 1)
ships.move(this.path[1][0][0], this.path[1][0][1]);
ships.forEach(function (ship) {
ship.unloadAll();
});
var shipsArray = ships.toEntityArray();
var garrLength = 0;
for (var shipId = 0; shipId < shipsArray.length; shipId++)
garrLength += shipsArray[shipId].garrisoned().length;
if (garrLength == 0) {
// release the ships
ships.forEach(function (ent) {
ent.setMetadata(PlayerID, "role",undefined);
ent.setMetadata(PlayerID, "subrole",undefined);
ent.setMetadata(PlayerID, "plan",undefined);
});
for (var shipId = 0; shipId < shipsArray.length; shipId++)
this.unitCollection.removeEnt(shipsArray[shipId]);
this.path.shift();
if (this.path.length > 0){
this.unitCollection.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
debug ("switch to walking");
this.state = "walking";
} else {
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";
}
}
}
@ -1094,7 +978,6 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}
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) {
var e = events[key];
@ -1106,14 +989,19 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
if (attacker && attacker.position() && attacker.hasClass("Unit") && attacker.owner() != 0 && attacker.owner() != PlayerID) {
if (ourUnit.hasClass("Siege"))
{
var help = this.unitCollection.filter(Filters.and(Filters.not(Filters.byClass("Siege")),Filters.isIdle()));
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());
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());
}
@ -1122,67 +1010,78 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
}
}
var enemyUnits = gameState.getEnemyEntities().filter(Filters.and(Filters.byOwner(this.targetPlayer), Filters.byClass("Unit")));
var enemyStructures = gameState.getEnemyEntities().filter(Filters.and(Filters.byOwner(this.targetPlayer), Filters.byClass("Structure")));
var enemyUnits = gameState.getGEC("player-" +this.targetPlayer + "-units");
var enemyStructures = gameState.getGEC("player-" +this.targetPlayer + "-structures");
if (this.unitCollUpdateArray === undefined || this.unitCollUpdateArray.length === 0)
{
this.unitCollUpdateArray = this.unitCollection.toEntityArray();
this.unitCollUpdateArray = this.unitCollection.toIdArray();
} else {
// some stuffs for locality and speed
var territoryMap = Map.createTerritoryMap(gameState);
var timeElapsed = gameState.getTimeElapsed();
// Let's check a few units each time we update. Currently 10
for (var check = 0; check < Math.min(this.unitCollUpdateArray.length,10); check++)
var lgth = Math.min(this.unitCollUpdateArray.length,10);
for (var check = 0; check < lgth; check++)
{
var ent = this.unitCollUpdateArray[0];
// if the unit is not in my territory, make it move.
var territoryMap = Map.createTerritoryMap(gameState);
var ent = gameState.getEntityById(this.unitCollUpdateArray[0]);
if (!ent)
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") && (!ent.unitAIOrderData() || !ent.unitAIOrderData()["target"] || !gameState.getEntityById(ent.unitAIOrderData()["target"]).hasClass("ConquestCritical")) )
if (ent.hasClass("Siege") && (!orderData || !orderData["target"] || !gameState.getEntityById(orderData["target"]) || !gameState.getEntityById(orderData["target"]).hasClass("ConquestCritical")) )
needsUpdate = true;
else if (ent.unitAIOrderData() && ent.unitAIOrderData()["target"] && gameState.getEntityById(ent.unitAIOrderData()["target"]).hasClass("Structure"))
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
if (gameState.getTimeElapsed() - ent.getMetadata(PlayerID, "lastAttackPlanUpdateTime") < 10000)
if (timeElapsed - ent.getMetadata(PlayerID, "lastAttackPlanUpdateTime") < 10000)
needsUpdate = false;
if (needsUpdate || arrivedthisTurn)
if (needsUpdate === true || arrivedthisTurn)
{
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", gameState.getTimeElapsed());
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", timeElapsed);
var mStruct = enemyStructures.filter(function (enemy) { //}){
if (!enemy.position() || (enemy.hasClass("StoneWall") && ent.canAttackClass("StoneWall"))) {
return false;
}
if (SquareVectorDistance(enemy.position(),ent.position()) > 2000) {
if (SquareVectorDistance(enemy.position(),ent.position()) > 3000) {
return false;
}
return true;
});
var mUnit;
if (ent.hasClass("Cavalry")) {
if (ent.hasClass("Cavalry") && ent.countersClasses(["Support"])) {
mUnit = enemyUnits.filter(function (enemy) { //}){
if (!enemy.position()) {
return false;
}
if (!enemy.hasClass("Support"))
return false;
if (SquareVectorDistance(enemy.position(),ent.position()) > 2000) {
if (SquareVectorDistance(enemy.position(),ent.position()) > 10000) {
return false;
}
return true;
});
}
if (!ent.hasClass("Cavalry") || mUnit.length === 0) {
if (!(ent.hasClass("Cavalry") && ent.countersClasses(["Support"])) || mUnit.length === 0) {
mUnit = enemyUnits.filter(function (enemy) { //}){
if (!enemy.position()) {
return false;
}
if (SquareVectorDistance(enemy.position(),ent.position()) > 2000) {
if (SquareVectorDistance(enemy.position(),ent.position()) > 10000) {
return false;
}
return true;
@ -1211,7 +1110,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
//warn ("Structure " +structb.genericName() + " is worth " +valb);
return (valb - vala);
});
// TODO: handle ballistas here
if (mStruct.length !== 0) {
if (isGate)
ent.attack(mStruct[0].id());
@ -1226,10 +1125,13 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
ent.move(self.targetPos[0],self.targetPos[1]);
}
} else {
if (mUnit.length !== 0 && !isGate) {
if (mUnit.length !== 0) {
var rand = Math.floor(Math.random() * mUnit.length*0.99);
ent.attack(mUnit[(+rand)].id());
//debug ("Units attacking a unit from " +mUnit[+rand].owner() + " , " +mUnit[+rand].templateName());
} else if (SquareVectorDistance(self.targetPos, ent.position()) > 900 ){
//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();
@ -1256,22 +1158,18 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
ent.attack(mStruct[+rand].id());
//debug ("Units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName());
}
} else if (SquareVectorDistance(self.targetPos, ent.position()) > 900 ){
//debug ("Units moving to " + uneval(self.targetPos));
ent.move(self.targetPos[0],self.targetPos[1]);
}
}
}
this.unitCollUpdateArray.splice(0,1);
}
this.unitCollUpdateArray.splice(0,10);
}
// updating targets.
if (!gameState.getEntityById(this.target.id()))
{
var targets = this.targetFinder(gameState, militaryManager);
var targets = this.targetFinder(gameState, HQ);
if (targets.length === 0){
targets = this.defaultTargetFinder(gameState, militaryManager);
targets = this.defaultTargetFinder(gameState, HQ);
}
if (targets.length) {
debug ("Seems like our target has been destroyed. Switching.");

View File

@ -0,0 +1,955 @@
/* Base Manager
* Handles lower level economic stuffs.
* Some tasks:
-tasking workers: gathering/hunting/building/repairing?/scouting/plans.
-giving feedback/estimates on GR
-achieving building stuff plans (scouting/getting ressource/building) or other long-staying plans, if I ever get any.
-getting good spots for dropsites
-managing dropsite use in the base
> warning HQ if we'll need more space
-updating whatever needs updating, keeping track of stuffs (rebuilding needs)
*/
var BaseManager = function() {
this.farmingFields = false;
this.ID = uniqueIDBases++;
// anchor building: seen as the main building of the base. Needs to have territorial influence
this.anchor = undefined;
// list of IDs of buildings in our base that have a "territory pusher" function.
this.territoryBuildings = [];
// will tell if we should be considered as a source of X.
this.willGather = { "food": 0, "wood": 0, "stone":0, "metal": 0 };
this.isFarming = false;
this.isHunting = true;
this.constructing = false;
// vector for iterating, to check one use the HQ map.
this.territoryIndices = [];
};
BaseManager.prototype.init = function(gameState, events, unconstructed){
this.constructing = unconstructed;
// entitycollections
this.units = gameState.getOwnEntities().filter(Filters.and(Filters.byClass("Unit"),Filters.byMetadata(PlayerID, "base", this.ID)));
this.buildings = gameState.getOwnEntities().filter(Filters.and(Filters.byClass("Structure"),Filters.byMetadata(PlayerID, "base", this.ID)));
this.workers = this.units.filter(Filters.byMetadata(PlayerID,"role","worker"));
this.workers.allowQuickIter();
this.buildings.allowQuickIter();
this.units.allowQuickIter();
this.units.registerUpdates();
this.buildings.registerUpdates();
this.workers.registerUpdates();
// array of entity IDs, with each being
// { "food" : [close entities, semi-close entities, faraway entities, closeAmount, medianAmount, assignedWorkers collection ] … } (one per resource)
// note that "median amount" also counts the closeAmount.
this.dropsites = { };
// 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 };
// 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 };
// 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 };
};
BaseManager.prototype.assignEntity = function(unit){
unit.setMetadata(PlayerID, "base", this.ID);
this.units.updateEnt(unit);
this.workers.updateEnt(unit);
this.buildings.updateEnt(unit);
// TODO: immediately assign it some task?
if (unit.hasClass("Structure") && unit.hasTerritoryInfluence() && this.territoryBuildings.indexOf(unit.id()) === -1)
this.territoryBuildings.push(unit.id());
};
BaseManager.prototype.setAnchor = function(anchorEntity) {
if (!anchorEntity.hasClass("Structure") || !anchorEntity.hasTerritoryInfluence())
{
warn("Error: Aegis' base " + this.ID + " has been assigned an anchor building that has no territorial influence. Please report this on the forum.")
return false;
}
this.anchor = anchorEntity;
this.anchor.setMetadata(PlayerID, "base", this.ID);
this.anchor.setMetadata(PlayerID, "baseAnchor", true);
this.buildings.updateEnt(this.anchor);
if (this.territoryBuildings.indexOf(this.anchor.id()) === -1)
this.territoryBuildings.push(this.anchor.id());
return true;
}
// affects the HQ map.
BaseManager.prototype.initTerritory = function(HQ, gameState) {
if (!this.anchor)
warn ("Error: Aegis tried to initialize the territory of base " + this.ID + " without assigning it an anchor building first");
var radius = Math.round((this.anchor.territoryInfluenceRadius() / 4.0) * 1.25);
var LandSize = gameState.sharedScript.accessibility.getRegionSize(this.anchor.position());
this.accessIndex = gameState.sharedScript.accessibility.getAccessValue(this.anchor.position());
if (LandSize < 6500)
{
// We're on a small land, we'll assign all territories in the vicinity.
// there's a slight chance we're on an elongated weird stuff, we'll just pump up a little the radius
radius = Math.round(radius*1.2);
}
var x = Math.round(this.anchor.position()[0]/gameState.cellSize);
var y = Math.round(this.anchor.position()[1]/gameState.cellSize);
this.territoryIndices = [];
var width = gameState.getMap().width;
for (var xi = -radius; xi <= radius; ++xi)
for (var yi = -radius; yi <= radius; ++yi)
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)])
{
this.territoryIndices.push((x+xi) + (y+yi)*width);
HQ.basesMap.map[(x+xi) + (y+yi)*width] = this.ID;
}
}
}
BaseManager.prototype.initGatheringFunctions = function(HQ, gameState, specTypes) {
// init our gathering functions.
var types = ["food","wood","stone","metal"];
if (specTypes !== undefined)
type = specTypes;
var self = this;
var count = 0;
for (i in types)
{
var type = types[i];
// TODO: set us as "X" gatherer
this.buildings.filter(Filters.isDropsite(type)).forEach(function(ent) { self.initializeDropsite(gameState, ent,type) });
if (this.getResourceLevel(gameState, type, "all") > 1000)
this.willGather[type] = 1;
}
if (this.willGather["food"] === 0)
{
var needFarm = true;
// Let's check again for food
for (base in HQ.baseManagers)
if (HQ.baseManagers[base].willGather["food"] === 1)
needFarm = false;
if (needFarm)
this.willGather["food"] = 1;
}
debug ("food" + this.willGather["food"]);
debug (this.willGather["wood"]);
debug (this.willGather["stone"]);
debug (this.willGather["metal"]);
}
BaseManager.prototype.checkEvents = function (gameState, events, queues) {
for (i in events)
{
if (events[i].type == "Destroy")
{
// let's check we haven't lost an important building here.
var evt = events[i];
if (evt.msg != undefined && !evt.msg.SuccessfulFoundation && evt.msg.entityObj != undefined && evt.msg.metadata !== undefined && evt.msg.metadata[PlayerID] &&
evt.msg.metadata[PlayerID]["base"] !== undefined && evt.msg.metadata[PlayerID]["base"] == this.ID)
{
var ent = evt.msg.entityObj;
if (ent.hasTerritoryInfluence())
this.territoryBuildings.splice(this.territoryBuildings.indexOf(ent.id()),1);
if (ent.resourceDropsiteTypes())
this.scrapDropsite(gameState, ent);
if (evt.msg.metadata[PlayerID]["baseAnchor"] && evt.msg.metadata[PlayerID]["baseAnchor"] == true)
{
// sounds like we lost our anchor. Let's try rebuilding it.
// TODO: currently the HQ manager sets us as initgathering, we probably ouht to do it
this.anchor = undefined;
this.constructing = true; // let's switch mode.
this.workers.forEach( function (worker) {
worker.stopMoving();
});
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 ConstructionPlan(gameState, "structures/{civ}_civil_centre", { "base" : this.ID, "baseAnchor" : true }, 0 , -1,ent.position()));
} else {
// TODO
queues.civilCentre.addItem(new ConstructionPlan(gameState, "structures/{civ}_civil_centre", { "base" : this.ID, "baseAnchor" : true },0,-1,ent.position()));
}
}
}
} else if (events[i].type == "ConstructionFinished")
{
// let's check we haven't lost an important building here.
var evt = events[i];
if (evt.msg && evt.msg.newentity)
{
// TODO: we ought to add new resources or do something.
var ent = gameState.getEntityById(evt.msg.newentity);
if (ent.getMetadata(PlayerID,"base") == this.ID)
{
if(ent.hasTerritoryInfluence())
this.territoryBuildings.push(ent.id());
if (ent.resourceDropsiteTypes())
for (ress in ent.resourceDropsiteTypes())
this.initializeDropsite(gameState, ent, ent.resourceDropsiteTypes()[ress]);
if (ent.resourceSupplyAmount() && ent.resourceSupplyType()["specific"] == "grain")
this.assignResourceToDP(gameState,ent);
}
}
} else if (events[i].type == "Create")
{
// Checking for resources.
var evt = events[i];
if (evt.msg && evt.msg.entity)
{
var ent = gameState.getEntityById(evt.msg.entity);
if (ent && ent.resourceSupplyAmount() && ent.owner() == 0)
this.assignResourceToDP(gameState,ent);
}
}
}
};
// If no specific dropsite, it'll assign to the closest
BaseManager.prototype.assignResourceToDP = function (gameState, supply, specificDP) {
var type = supply.resourceSupplyType()["generic"];
if (type == "treasure")
type = supply.resourceSupplyType()["specific"];
if (!specificDP)
{
var closest = -1;
var dist = Math.min();
for (i in this.dropsites)
{
var dp = gameState.getEntityById(i);
var distance = SquareVectorDistance(supply.position(), dp.position());
if (distance < dist && distance < this.bigRadius[type])
{
closest = dp.id();
dist = distance;
}
}
if (closest !== -1)
{
supply.setMetadata(PlayerID, "linked-dropsite-close", (dist < this.smallRadius[type]) );
supply.setMetadata(PlayerID, "linked-dropsite-nearby", (dist < this.medRadius[type]) );
supply.setMetadata(PlayerID, "linked-dropsite", closest );
supply.setMetadata(PlayerID, "linked-dropsite-dist", +dist);
}
}
// TODO: ought to recount immediatly.
}
BaseManager.prototype.initializeDropsite = function (gameState, ent, type) {
var count = 0, farCount = 0;
var self = this;
var resources = gameState.getResourceSupplies(type);
// TODO: if we're initing, we should probably remove them anyway.
if (self.dropsites[ent.id()] === undefined || self.dropsites[ent.id()][type] === undefined) {
resources.filter( function (supply) { //}){
if (!supply.position() || !ent.position())
return;
var distance = SquareVectorDistance(supply.position(), ent.position());
if (supply.getMetadata(PlayerID, "linked-dropsite") == undefined || supply.getMetadata(PlayerID, "linked-dropsite-dist") > distance) {
if (distance < self.bigRadius[type]) {
supply.setMetadata(PlayerID, "linked-dropsite-close", (distance < self.smallRadius[type]) );
supply.setMetadata(PlayerID, "linked-dropsite-nearby", (distance < self.medRadius[type]) );
supply.setMetadata(PlayerID, "linked-dropsite", ent.id() );
supply.setMetadata(PlayerID, "linked-dropsite-dist", +distance);
if(distance < self.smallRadius[type])
count += supply.resourceSupplyAmount();
if (distance < self.medRadius[type])
farCount += supply.resourceSupplyAmount();
}
}
});
// This one is both for the nearby and the linked
var filter = Filters.byMetadata(PlayerID, "linked-dropsite", ent.id());
var collection = resources.filter(filter);
collection.registerUpdates();
filter = Filters.byMetadata(PlayerID, "linked-dropsite-close",true);
var collection2 = collection.filter(filter);
collection2.registerUpdates();
filter = Filters.byMetadata(PlayerID, "linked-dropsite-nearby",true);
var collection3 = collection.filter(filter);
collection3.registerUpdates();
filter = Filters.byMetadata(PlayerID, "linked-to-dropsite", ent.id());
var WkCollection = this.workers.filter(filter);
WkCollection.registerUpdates();
if (!self.dropsites[ent.id()])
self.dropsites[ent.id()] = {};
self.dropsites[ent.id()][type] = [collection2,collection3, collection, count, farCount, WkCollection];
// TODO: flag us on the SharedScript "type" map.
// TODO: get workers on those resources and do something with them.
}
if (Config.debug)
{
// Make resources glow wildly
if (type == "food") {
self.dropsites[ent.id()][type][2].forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0.5,0,0]});
});
self.dropsites[ent.id()][type][1].forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [2,0,0]});
});
self.dropsites[ent.id()][type][0].forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [10,0,0]});
});
}
if (type == "wood") {
self.dropsites[ent.id()][type][2].forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,0.5,0]});
});
self.dropsites[ent.id()][type][1].forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,2,0]});
});
self.dropsites[ent.id()][type][0].forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,10,0]});
});
}
if (type == "stone") {
self.dropsites[ent.id()][type][2].forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0.5,0.5,0]});
});
self.dropsites[ent.id()][type][1].forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [2,2,0]});
});
self.dropsites[ent.id()][type][0].forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [10,10,0]});
});
}
if (type == "metal") {
self.dropsites[ent.id()][type][2].forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,0.5,0.5]});
});
self.dropsites[ent.id()][type][1].forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,2,2]});
});
self.dropsites[ent.id()][type][0].forEach(function(ent){
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,10,10]});
});
}
}
};
// completely and "safely" remove a dropsite from our list.
// this also removes any linked resource and so on.
// TODO: should re-add the resources to another dropsite.
BaseManager.prototype.scrapDropsite = function (gameState, ent) {
if (this.dropsites[ent.id()] === undefined)
return true;
for (i in this.dropsites[ent.id()])
{
var type = i;
var dp = this.dropsites[ent.id()][i];
dp[2].forEach(function (supply) { //}){
supply.deleteMetadata(PlayerID,"linked-dropsite-nearby");
supply.deleteMetadata(PlayerID,"linked-dropsite-close");
supply.deleteMetadata(PlayerID,"linked-dropsite");
supply.deleteMetadata(PlayerID,"linked-dropsite-dist");
});
dp[5].forEach(function (worker) {
worker.deleteMetadata(PlayerID,"linked-to-dropsite");
// TODO: should probably stop the worker or something.
});
dp = [undefined, undefined, undefined, 0, 0, undefined];
delete this.dropsites[ent.id()][i];
}
this.dropsites[ent.id()] = undefined;
delete this.dropsites[ent.id()];
return true;
};
// Returns the position of the best place to build a new dropsite for the specified resource
BaseManager.prototype.findBestDropsiteLocation = function(gameState, resource){
var storeHousePlate = gameState.getTemplate(gameState.applyCiv("structures/{civ}_storehouse"));
// This builds a map. The procedure is fairly simple. It adds the resource maps
// (which are dynamically updated and are made so that they will facilitate DP placement)
// Then checks for a good spot in the territory. If none, and town/city phase, checks outside
// The AI will currently not build a CC if it wouldn't connect with an existing CC.
var territory = Map.createTerritoryMap(gameState);
var obstructions = Map.createObstructionMap(gameState,this.accessIndex,storeHousePlate);
obstructions.expandInfluences();
// copy the resource map as initialization.
var friendlyTiles = new Map(gameState.sharedScript, gameState.sharedScript.resourceMaps[resource].map, true);
var DPFoundations = gameState.getOwnFoundations().filter(Filters.byType(gameState.applyCiv("foundation|structures/{civ}_storehouse")));
// TODO: might be better to check dropsites someplace else.
// loop over this in this.terrytoryindices. It's usually a little too much, but it's always enough.
for (var p = 0; p < this.territoryIndices.length; ++p)
{
var j = this.territoryIndices[p];
friendlyTiles.map[j] *= 1.5;
// only add where the map is currently not null, ie in our territory and some "Resource" would be close.
// This makes the placement go from "OK" to "human-like".
for (var i in gameState.sharedScript.resourceMaps)
if (friendlyTiles.map[j] !== 0 && i !== "food")
friendlyTiles.map[j] += gameState.sharedScript.resourceMaps[i].map[j];
for (var i in this.dropsites)
{
var pos = [j%friendlyTiles.width, Math.floor(j/friendlyTiles.width)];
var dpPos = gameState.getEntityById(i).position();
if (dpPos && SquareVectorDistance(friendlyTiles.gamePosToMapPos(dpPos), pos) < 250)
{
friendlyTiles.map[j] = 0;
continue;
} else if (dpPos && SquareVectorDistance(friendlyTiles.gamePosToMapPos(dpPos), pos) < 450)
friendlyTiles.map[j] /= 2;
}
for (var i in DPFoundations._entities)
{
var pos = [j%friendlyTiles.width, Math.floor(j/friendlyTiles.width)];
var dpPos = gameState.getEntityById(i).position();
if (dpPos && SquareVectorDistance(friendlyTiles.gamePosToMapPos(dpPos), pos) < 250)
friendlyTiles.map[j] = 0;
else if (dpPos && SquareVectorDistance(friendlyTiles.gamePosToMapPos(dpPos), pos) < 450)
friendlyTiles.map[j] /= 2;
}
}
if (Config.debug)
friendlyTiles.dumpIm("DP_" + resource + "_" + gameState.getTimeElapsed() + ".png");
var best = friendlyTiles.findBestTile(2, obstructions); // try to find a spot to place a DP.
var bestIdx = best[0];
// tell the dropsite builder we haven't found anything satisfactory.
if (best[1] < 60)
return false;
var x = ((bestIdx % friendlyTiles.width) + 0.5) * gameState.cellSize;
var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * gameState.cellSize;
return [x,z];
};
// update the resource level of a dropsite.
BaseManager.prototype.updateDropsite = function (gameState, ent, type) {
if (this.dropsites[ent.id()] === undefined || this.dropsites[ent.id()][type] === undefined)
return undefined; // should initialize it first.
var count = 0, farCount = 0;
var resources = gameState.getResourceSupplies(type);
this.dropsites[ent.id()][type][1].forEach( function (supply) { //}){
farCount += supply.resourceSupplyAmount();
});
this.dropsites[ent.id()][type][0].forEach( function (supply) { //}){
count += supply.resourceSupplyAmount();
});
this.dropsites[ent.id()][type][3] = count;
this.dropsites[ent.id()][type][4] = farCount;
return true;
};
// Updates dropsites.
BaseManager.prototype.updateDropsites = function (gameState) {
// for each dropsite, recalculate
for (i in this.dropsites)
{
for (type in this.dropsites[i])
{
this.updateDropsite(gameState,gameState.getEntityById(i),type);
}
}
};
// TODO: ought to be cached or something probably
// Returns the number of slots available for workers here.
// we're assuming Max - 3 for metal/stone mines, and 20 for any dropsite that has wood.
// TODO: for wood might want to count the trees too.
// TODO: this returns "future" worker capacity, might want to have a current one.
BaseManager.prototype.getWorkerCapacity = function (gameState, type) {
var count = 0;
if (type == "food")
return 1000000; // TODO: perhaps return something sensible here.
if (type === "stone" || type === "metal")
{
for (id in this.dropsites)
if (this.dropsites[id][type])
this.dropsites[id][type][1].forEach(function (ent) {// }){
if (ent.resourceSupplyAmount() > 500)
count += ent.maxGatherers() - 3;
});
} else if (type === "wood")
{
for (id in this.dropsites)
if (this.dropsites[id][type] && (this.dropsites[id][type][4]) > 1000)
count += Math.min(15, this.dropsites[id][type][4] / 200);
}
return count;
};
// TODO: ought to be cached or something probably
// Returns the amount of resource left
BaseManager.prototype.getResourceLevel = function (gameState, type, searchType, threshold) {
var count = 0;
if (searchType == "all")
{
// return all resources in the base area.
gameState.getResourceSupplies(type).filter(Filters.byTerritory(gameState.ai.HQ.basesMap, this.ID)).forEach( function (ent) { //}){
count += ent.resourceSupplyAmount();
});
return count;
}
if (searchType == "dropsites")
{
// for each dropsite, recalculate
for (i in this.dropsites)
if (this.dropsites[i][type] !== undefined)
count += this.dropsites[i][type][4];
return count;
}
if (searchType == "dropsitesClose")
{
// for each dropsite, recalculate
for (i in this.dropsites)
if (this.dropsites[i][type] !== undefined)
count += this.dropsites[i][type][3];
return count;
}
if (searchType == "dropsites-dpcount")
{
var seuil = 800;
if (threshold)
seuil = threshold;
// for each dropsite, recalculate
for (i in this.dropsites)
if (this.dropsites[i][type] !== undefined)
{
if (this.dropsites[i][type][4] > seuil)
count++;
}
return count;
}
return 0;
};
// check our resource levels and react accordingly
BaseManager.prototype.checkResourceLevels = function (gameState,queues) {
for (type in this.willGather)
{
if (this.willGather[type] === 0)
continue;
if (type !== "food" && gameState.playedTurn % 10 === 4 && this.getResourceLevel(gameState,type, "all") < 200)
this.willGather[type] = 0; // won't gather at all
if (this.willGather[type] === 2)
continue;
var count = this.getResourceLevel(gameState,type, "dropsites");
if (type == "food")
{
if (!this.isFarming && count < 1600 && queues.field.length === 0)
{
// tell the queue manager we'll be trying to build fields shortly.
for (var i = 0; i < Config.Economy.initialFields;++i)
{
var plan = new ConstructionPlan(gameState, "structures/{civ}_field", { "base" : this.ID });
plan.isGo = function() { return false; }; // don't start right away.
queues.field.addItem(plan);
}
} else if (!this.isFarming && count < 650)
{
for (i in queues.field.queue)
queues.field.queue[i].isGo = function() { return true; }; // start them
this.isFarming = true;
}
if (this.isFarming)
{
var numFarms = 0;
this.buildings.filter(Filters.byClass("Field")).forEach(function (field) {
if (field.resourceSupplyAmount() > 400)
numFarms++;
});
var numFd = gameState.countEntitiesByType(gameState.applyCiv("foundation|structures/{civ}_field"), true);
numFarms += numFd;
numFarms += queues.field.countQueuedUnits();
// let's see if we need to push new farms.
if (numFd < 2)
if (numFarms < Math.round(this.gatherersByType(gameState, "food").length / 4.6) || numFarms < Math.round(this.workers.length / 15.0))
queues.field.addItem(new ConstructionPlan(gameState, "structures/{civ}_field", { "base" : this.ID }));
// TODO: refine count to only count my base.
}
} else if (queues.dropsites.length() === 0 && gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_storehouse")) === 0) {
var wantedDPs = Math.ceil(this.gatherersByType(gameState, type).length / 12.0);
var need = wantedDPs - this.getResourceLevel(gameState,type, "dropsites-dpcount",2000);
if (need > 0)
{
var pos = this.findBestDropsiteLocation(gameState, type);
if (!pos)
{
debug ("Found no right position for a " + type + " dropsite, going into \"noSpot\" mode");
this.willGather[type] = 2; // won't build
// 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 {
debug ("planning new dropsite for " + type);
queues.dropsites.addItem(new ConstructionPlan(gameState, "structures/{civ}_storehouse",{ "base" : this.ID }, 0, -1, pos));
}
}
}
}
};
// let's return the estimated gather rates.
BaseManager.prototype.getGatherRates = function(gameState, currentRates) {
};
BaseManager.prototype.assignRolelessUnits = function(gameState) {
// TODO: make this cleverer.
var roleless = this.units.filter(Filters.not(Filters.byHasMetadata(PlayerID, "role")));
var self = this;
roleless.forEach(function(ent) {
if (ent.hasClass("Worker") || ent.hasClass("CitizenSoldier")) {
if (ent.hasClass("Cavalry") && !self.isHunting)
return;
ent.setMetadata(PlayerID, "role", "worker");
}
});
};
// If the numbers of workers on the resources is unbalanced then set some of workers to idle so
// they can be reassigned by reassignIdleWorkers.
// TODO: actually this probably should be in the HQ.
BaseManager.prototype.setWorkersIdleByPriority = function(gameState){
var self = this;
if (gameState.currentPhase() < 2 && gameState.getTimeElapsed() < 360000)
return; // not in the first phase or the first 6 minutes.
var types = gameState.ai.queueManager.getAvailableResources(gameState);
var bestType = "";
var avgOverdraft = 0;
for (i in types.types)
avgOverdraft += types[types.types[i]];
avgOverdraft /= 4;
for (i in types.types)
if (types[types.types[i]] > avgOverdraft + 200 || (types[types.types[i]] > avgOverdraft && avgOverdraft > 200))
if (this.gatherersByType(gameState,types.types[i]).length > 0)
{
// TODO: perhaps change this?
var nb = 2;
this.gatherersByType(gameState,types.types[i]).forEach( function (ent) { //}){
if (nb > 0)
{
//debug ("Moving " +ent.id() + " from " + types.types[i]);
nb--;
// TODO: might want to direct assign.
ent.stopMoving();
ent.setMetadata(PlayerID, "subrole","idle");
}
});
}
//debug (currentRates);
};
// TODO: work on this.
BaseManager.prototype.reassignIdleWorkers = function(gameState) {
var self = this;
// Search for idle workers, and tell them to gather resources based on demand
var filter = Filters.or(Filters.byMetadata(PlayerID,"subrole","idle"), Filters.not(Filters.byHasMetadata(PlayerID,"subrole")));
var idleWorkers = gameState.updatingCollection("idle-workers-base-" + this.ID, filter, this.workers);
if (idleWorkers.length) {
idleWorkers.forEach(function(ent) {
// Check that the worker isn't garrisoned
if (ent.position() === undefined){
return;
}
if (ent.hasClass("Worker")) {
var types = gameState.ai.HQ.pickMostNeededResources(gameState);
//debug ("assigning " +ent.id() + " to " + types[0]);
ent.setMetadata(PlayerID, "subrole", "gatherer");
ent.setMetadata(PlayerID, "gather-type", types[0]);
if (gameState.turnCache["gathererAssignementCache-" + types[0]])
gameState.turnCache["gathererAssignementCache-" + types[0]]++;
else
gameState.turnCache["gathererAssignementCache-" + types[0]] = 1;
// Okay let's now check we can actually remain here for that
if (self.willGather[types[0]] !== 1)
{
// TODO: if fail, we should probably pick the second most needed resource.
gameState.ai.HQ.switchWorkerBase(gameState, ent, types[0]);
}
} else {
ent.setMetadata(PlayerID, "subrole", "hunter");
}
});
}
};
BaseManager.prototype.workersBySubrole = function(gameState, subrole) {
return gameState.updatingCollection("subrole-" + subrole +"-base-" + this.ID, Filters.byMetadata(PlayerID, "subrole", subrole), this.workers, true);
};
BaseManager.prototype.gatherersByType = function(gameState, type) {
return gameState.updatingCollection("workers-gathering-" + type +"-base-" + this.ID, Filters.byMetadata(PlayerID, "gather-type", type), this.workersBySubrole(gameState, "gatherer"));
};
// returns an entity collection of workers.
// They are idled immediatly and their subrole set to idle.
BaseManager.prototype.pickBuilders = function(gameState, number) {
var collec = new EntityCollection(gameState.sharedScript);
// TODO: choose better.
var workers = this.workers.filter(Filters.not(Filters.byClass("Cavalry"))).toEntityArray();
workers.sort(function (a,b) {
var vala = 0, valb = 0;
if (a.getMetadata(PlayerID,"subrole") == "builder")
vala = 100;
if (b.getMetadata(PlayerID,"subrole") == "builder")
valb = 100;
if (a.getMetadata(PlayerID,"plan") != undefined)
vala = -100;
if (b.getMetadata(PlayerID,"plan") != undefined)
valb = -100;
return a < b
});
for (var i = 0; i < number; ++i)
{
workers[i].stopMoving();
workers[i].setMetadata(PlayerID, "subrole","idle");
collec.addEnt(workers[i]);
}
return collec;
}
BaseManager.prototype.assignToFoundations = function(gameState, noRepair) {
// If we have some foundations, and we don't have enough builder-workers,
// try reassigning some other workers who are nearby
// AI tries to use builders sensibly, not completely stopping its econ.
var self = this;
var foundations = this.buildings.filter(Filters.and(Filters.isFoundation(),Filters.not(Filters.byClass("Field")))).toEntityArray();
var damagedBuildings = this.buildings.filter(function (ent) { if (ent.needsRepair() && ent.getMetadata(PlayerID, "plan") == undefined) { return true; } return false; }).toEntityArray();
// Check if nothing to build
if (!foundations.length && !damagedBuildings.length){
return;
}
var workers = this.workers.filter(Filters.not(Filters.byClass("Cavalry")));
var builderWorkers = this.workersBySubrole(gameState, "builder");
var idleBuilderWorkers = this.workersBySubrole(gameState, "builder").filter(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(Filters.and(Filters.isFoundation(), Filters.byMetadata(PlayerID, "baseAnchor", true))).length !== 0)
{
foundations = this.buildings.filter(Filters.byMetadata(PlayerID, "baseAnchor", true)).toEntityArray();
var tID = foundations[0].id();
workers.forEach(function (ent) { //}){
var target = ent.getMetadata(PlayerID, "target-foundation");
if (target && target != tID)
{
ent.stopMoving();
ent.setMetadata(PlayerID, "target-foundation", tID);
}
});
}
if (workers.length < 2)
{
var noobs = gameState.ai.HQ.bulkPickWorkers(gameState, this.ID, 2);
if(noobs)
{
noobs.forEach(function (worker) { //}){
worker.setMetadata(PlayerID,"base", self.ID);
worker.setMetadata(PlayerID,"subrole", "builder");
workers.updateEnt(worker);
builderWorkers.updateEnt(worker);
idleBuilderWorkers.updateEnt(worker);
});
}
}
var addedWorkers = 0;
var maxTotalBuilders = Math.ceil(workers.length * 0.15);
if (this.constructing == true && maxTotalBuilders < 15)
maxTotalBuilders = 15;
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
var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target.id()).length;
var targetNB = Config.Economy.targetNumBuilders; // TODO: dynamic that.
if (target.hasClass("CivCentre") || target.buildTime() > 150 || target.hasClass("House"))
targetNB *= 2;
if (target.getMetadata(PlayerID, "baseAnchor") == true)
targetNB = 15;
if (assigned < targetNB) {
if (builderWorkers.length - idleBuilderWorkers.length + addedWorkers < maxTotalBuilders) {
var addedToThis = 0;
idleBuilderWorkers.forEach(function(ent) {
if (ent.position() && 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) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.position() !== undefined); });
var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), targetNB - assigned - addedToThis);
nearestNonBuilders.forEach(function(ent) {
addedWorkers++;
addedToThis++;
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 i in damagedBuildings) {
var target = damagedBuildings[i];
if (gameState.defcon() < 5) {
if (target.healthLevel() > 0.5 || !target.hasClass("CivCentre") || !target.hasClass("StoneWall")) {
continue;
}
} else if (noRepair && !target.hasClass("CivCentre"))
continue;
var territory = Map.createTerritoryMap(gameState);
if (territory.getOwner(target.position()) !== PlayerID || territory.getOwner([target.position()[0] + 5, target.position()[1]]) !== PlayerID)
continue;
var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target.id()).length;
if (assigned < this.targetNumBuilders/3) {
if (builderWorkers.length + addedWorkers < this.targetNumBuilders*2) {
var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.position() !== undefined); });
if (gameState.defcon() < 5)
nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.hasClass("Female") && ent.position() !== undefined); });
var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders/3 - assigned);
nearestNonBuilders.forEach(function(ent) {
ent.stopMoving();
addedWorkers++;
ent.setMetadata(PlayerID, "subrole", "builder");
ent.setMetadata(PlayerID, "target-foundation", target.id());
});
}
}
}
};
BaseManager.prototype.update = function(gameState, queues, events) {
Engine.ProfileStart("Base update - base " + this.ID);
var self = this;
this.updateDropsites(gameState);
this.checkResourceLevels(gameState, queues);
Engine.ProfileStart("Assign builders");
this.assignToFoundations(gameState);
Engine.ProfileStop()
// if (!this.constructing)
// {
if (gameState.ai.playedTurn % 2 === 0)
this.setWorkersIdleByPriority(gameState);
this.assignRolelessUnits(gameState);
/*Engine.ProfileStart("Swap Workers");
var gathererGroups = {};
gameState.getOwnEntitiesByRole("worker").forEach(function(ent){ }){
if (ent.hasClass("Cavalry"))
return;
var key = uneval(ent.resourceGatherRates());
if (!gathererGroups[key]){
gathererGroups[key] = {"food": [], "wood": [], "metal": [], "stone": []};
}
if (ent.getMetadata(PlayerID, "gather-type") in gathererGroups[key]){
gathererGroups[key][ent.getMetadata(PlayerID, "gather-type")].push(ent);
}
});
for (var i in gathererGroups){
for (var j in gathererGroups){
var a = eval(i);
var b = eval(j);
if (a !== undefined && b !== undefined)
if (a["food.grain"]/b["food.grain"] > a["wood.tree"]/b["wood.tree"] && gathererGroups[i]["wood"].length > 0
&& gathererGroups[j]["food"].length > 0){
for (var k = 0; k < Math.min(gathererGroups[i]["wood"].length, gathererGroups[j]["food"].length); k++){
gathererGroups[i]["wood"][k].setMetadata(PlayerID, "gather-type", "food");
gathererGroups[j]["food"][k].setMetadata(PlayerID, "gather-type", "wood");
}
}
}
}
Engine.ProfileStop();*/
// should probably be last to avoid reallocations of units that would have done stuffs otherwise.
Engine.ProfileStart("Assigning Workers");
this.reassignIdleWorkers(gameState);
Engine.ProfileStop();
// }
// TODO: do this incrementally a la defence.js
Engine.ProfileStart("Run Workers");
this.workers.forEach(function(ent) {
if (!ent.getMetadata(PlayerID, "worker-object"))
ent.setMetadata(PlayerID, "worker-object", new Worker(ent));
ent.getMetadata(PlayerID, "worker-object").update(self, gameState);
});
Engine.ProfileStop();
Engine.ProfileStop();
};

View File

@ -3,16 +3,22 @@ var baseConfig = {
"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 (first barracks)
"techStartTime" : 120, // time to wait before teching. Will only start after town phase so it's irrelevant.
"popForBarracks1" : 15,
"popForBarracks2" : 95,
"timeForBlacksmith" : 900,
},
"Economy" : {
"townPhase" : 180, // time to start trying to reach town phase (might be a while after. Still need the requirements + ress )
"cityPhase" : 540, // time to start trying to reach city phase
"farmsteadStartTime" : 400, // Time to wait before building a farmstead.
"cityPhase" : 840000, // time to start trying to reach city phase
"popForMarket" : 80,
"popForFarmstead" : 45,
"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.6 // percent of females among the workforce.
"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.
"initialFields" : 2
},
// Note: attack settings are set directly in attack_plan.js
@ -49,19 +55,20 @@ var baseConfig = {
// qbot
"priorities" : { // Note these are dynamic, you are only setting the initial values
"house" : 200,
"citizenSoldier" : 70,
"villager" : 55,
"economicBuilding" : 70,
"house" : 350,
"villager" : 40,
"citizenSoldier" : 60,
"ships" : 70,
"economicBuilding" : 90,
"dropsites" : 120,
"field" : 1000,
"militaryBuilding" : 90,
"field" : 500,
"militaryBuilding" : 110,
"defenceBuilding" : 70,
"majorTech" : 400,
"minorTech" : 40,
"civilCentre" : 10000 // will hog all resources
"majorTech" : 700,
"minorTech" : 50,
"civilCentre" : 400
},
"difficulty" : 2, // for now 2 is "hard", ie default. 1 is normal, 0 is easy. 3 is very hard
"difficulty" : 2, // 0 is sandbox, 1 is easy, 2 is medium, 3 is hard, 4 is very hard.
"debug" : false
};
@ -74,50 +81,35 @@ var Config = {
// changing settings based on difficulty.
if (Config.difficulty === 1)
{
Config["Military"] = {
"fortressLapseTime" : 900,
"defenceBuildingTime" : 720,
"attackPlansStartTime" : 1200
};
Config["Economy"] = {
"townPhase" : 360,
"cityPhase" : 900,
"farmsteadStartTime" : 600,
"dockStartTime" : 240,
"techStartTime" : 1320,
"targetNumBuilders" : 2,
"femaleRatio" : 0.5,
"targetNumWorkers" : 110 // should not make more than 2 barracks.
};
Config["Defence"] = {
"defenceRatio" : 4.0,
"armyCompactSize" : 700,
"armyBreakawaySize" : 900
};
} else if (Config.difficulty === 0)
Config.Military.defenceBuildingTime = 1200;
Config.Military.attackPlansStartTime = 960;
Config.Military.popForBarracks1 = 35;
Config.Military.popForBarracks2 = 150; // shouldn't reach it
Config.Military.popForBlacksmith = 150; // shouldn't reach it
Config.Economy.cityPhase = 1800;
Config.Economy.popForMarket = 80;
Config.Economy.techStartTime = 600;
Config.Economy.femaleRatio = 0.6;
Config.Economy.initialFields = 1;
// Config.Economy.targetNumWorkers will be set by AI scripts.
}
else if (Config.difficulty === 0)
{
Config["Military"] = {
"fortressLapseTime" : 1000000, // never
"defenceBuildingTime" : 900,
"attackPlansStartTime" : 120000 // never
};
Config["Economy"] = {
"townPhase" : 480,
"cityPhase" : 1200,
"farmsteadStartTime" : 1200,
"dockStartTime" : 240,
"techStartTime" : 600000, // never
"targetNumBuilders" : 1,
"femaleRatio" : 0.0, // makes us slower, but also less sucky at defending so it's still fun to attack it.
"targetNumWorkers" : 70
};
Config["Defence"] = {
"defenceRatio" : 2.0,
"armyCompactSize" : 700,
"armyBreakawaySize" : 900
};
Config.Military.defenceBuildingTime = 450;
Config.Military.attackPlansStartTime = 9600000; // never
Config.Military.popForBarracks1 = 60;
Config.Military.popForBarracks2 = 150; // shouldn't reach it
Config.Military.popForBlacksmith = 150; // shouldn't reach it
Config.Economy.cityPhase = 240000;
Config.Economy.popForMarket = 200;
Config.Economy.techStartTime = 1800;
Config.Economy.femaleRatio = 0.2;
Config.Economy.initialFields = 1;
// Config.Economy.targetNumWorkers will be set by AI scripts.
}
}
};
Config.__proto__ = baseConfig;
Config.__proto__ = baseConfig;

View File

@ -1,6 +1,6 @@
{
"name": "Aegis Bot",
"description": "Wraitii's improvement of qBot. It is more reliable and generally a better player. Note that it doesn't support saved games yet, and there may be other bugs. Please report issues to Wildfire Games (see the link in the main menu).",
"constructor": "QBotAI",
"constructor": "AegisBot",
"useShared": true
}

View File

@ -48,7 +48,7 @@ function Defence(){
// 1: Huge army in the base, outnumbering us.
Defence.prototype.update = function(gameState, events, militaryManager){
Defence.prototype.update = function(gameState, events, HQ){
Engine.ProfileStart("Defence Manager");
@ -80,13 +80,13 @@ Defence.prototype.update = function(gameState, events, militaryManager){
this.territoryMap = Map.createTerritoryMap(gameState); // used by many func
// First step: we deal with enemy armies, those are the highest priority.
this.defendFromEnemies(gameState, events, militaryManager);
this.defendFromEnemies(gameState, events, HQ);
// second step: we loop through messages, and sort things as needed (dangerous buildings, attack by animals, ships, lone units, whatever).
// TODO : a lot.
this.MessageProcess(gameState,events,militaryManager);
this.MessageProcess(gameState,events,HQ);
this.DealWithWantedUnits(gameState,events,militaryManager);
this.DealWithWantedUnits(gameState,events,HQ);
/*
var self = this;
@ -138,7 +138,7 @@ Defence.prototype.evaluateArmies = function(gameState, armies) {
}*/
// Incorporates an entity in an army. If no army fits, it creates a new one around this one.
// an army is basically an entity collection.
Defence.prototype.armify = function(gameState, entity, militaryManager, minNBForArmy) {
Defence.prototype.armify = function(gameState, entity, HQ, minNBForArmy) {
if (entity.position() === undefined)
return;
if (this.enemyArmy[entity.owner()] === undefined)
@ -160,10 +160,10 @@ Defence.prototype.armify = function(gameState, entity, militaryManager, minNBFor
}
}
}
if (militaryManager)
if (HQ)
{
var self = this;
var close = militaryManager.enemyWatchers[entity.owner()].enemySoldiers.filter(Filters.byDistance(entity.position(), self.armyCompactSize));
var close = HQ.enemyWatchers[entity.owner()].enemySoldiers.filter(Filters.byDistance(entity.position(), self.armyCompactSize));
if (!minNBForArmy || close.length >= minNBForArmy)
{
// if we're here, we need to create an army for it, and freeze it to make sure no unit will be added automatically
@ -223,7 +223,7 @@ Defence.prototype.reevaluateEntity = function(gameState, entity) {
}
// This deals with incoming enemy armies, setting the defcon if needed. It will take new soldiers, and assign them to attack
// TODO: still is still pretty dumb, it could use improvements.
Defence.prototype.defendFromEnemies = function(gameState, events, militaryManager) {
Defence.prototype.defendFromEnemies = function(gameState, events, HQ) {
var self = this;
// New, faster system will loop for enemy soldiers, and also females on occasions ( TODO )
@ -264,10 +264,10 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
// When it's finished it'll start over.
for (var enemyID in this.enemyArmy)
{
//this.enemyUnits[enemyID] = militaryManager.enemyWatchers[enemyID].getAllEnemySoldiers();
//this.enemyUnits[enemyID] = HQ.enemyWatchers[enemyID].getAllEnemySoldiers();
if (this.enemyUnits[enemyID] === undefined || this.enemyUnits[enemyID].length === 0)
{
this.enemyUnits[enemyID] = militaryManager.enemyWatchers[enemyID].enemySoldiers.toEntityArray();
this.enemyUnits[enemyID] = HQ.enemyWatchers[enemyID].enemySoldiers.toEntityArray();
} else {
// we have some units still to check in this array. Check 15 (TODO: DIFFLEVEL)
// Note: given the way memory works, if the entity has been recently deleted, its reference may still exist.
@ -283,7 +283,7 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
} else {
var dangerous = this.evaluateEntity(gameState, this.enemyUnits[enemyID][0]);
if (dangerous)
this.armify(gameState, this.enemyUnits[enemyID][0], militaryManager,2);
this.armify(gameState, this.enemyUnits[enemyID][0], HQ,2);
this.enemyUnits[enemyID].splice(0,1);
}
} else if (this.enemyUnits[enemyID].length > 0 && gameState.getEntityById(this.enemyUnits[enemyID][0].id()) === undefined)
@ -379,8 +379,8 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
defender.setMetadata(PlayerID, "subrole", undefined);
self.nbDefenders--;
});
militaryManager.ungarrisonAll(gameState);
militaryManager.unpauseAllPlans(gameState);
HQ.ungarrisonAll(gameState);
HQ.unpauseAllPlans(gameState);
return;
} else if (this.nbAttackers === 0 && this.nbDefenders !== 0) {
// Release all our units
@ -393,12 +393,12 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
defender.setMetadata(PlayerID, "subrole", undefined);
self.nbDefenders--;
});
militaryManager.ungarrisonAll(gameState);
militaryManager.unpauseAllPlans(gameState);
HQ.ungarrisonAll(gameState);
HQ.unpauseAllPlans(gameState);
return;
}
if ( (this.nbDefenders < 4 && this.nbAttackers >= 5) || this.nbDefenders === 0) {
militaryManager.ungarrisonAll(gameState);
HQ.ungarrisonAll(gameState);
}
//debug ("total number of attackers:"+ this.nbAttackers);
@ -449,7 +449,7 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
//debug ("nonDefenders.length "+ nonDefenders.length);
if (gameState.defcon() > 3)
militaryManager.unpauseAllPlans(gameState);
HQ.unpauseAllPlans(gameState);
if ( (nonDefenders.length + this.nbDefenders > newEnemies.length + this.nbAttackers)
|| this.nbDefenders + nonDefenders.length < 4)
@ -467,9 +467,9 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
/*
if (gameState.defcon() < 2 && (this.nbAttackers-this.nbDefenders) > 15)
{
militaryManager.pauseAllPlans(gameState);
HQ.pauseAllPlans(gameState);
} else if (gameState.defcon() < 3 && this.nbDefenders === 0 && newEnemies.length === 0) {
militaryManager.ungarrisonAll(gameState);
HQ.ungarrisonAll(gameState);
}*/
// A little sorting to target sieges/champions first.
@ -549,7 +549,7 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
// successfully sorted
defs.forEach(function (defender) { //}){
if (defender.getMetadata(PlayerID, "plan") != undefined && (gameState.defcon() < 4 || defender.getMetadata(PlayerID,"subrole") == "walking"))
militaryManager.pausePlan(gameState, defender.getMetadata(PlayerID, "plan"));
HQ.pausePlan(gameState, defender.getMetadata(PlayerID, "plan"));
//debug ("Against " +enemy.id() + " Assigning " + defender.id());
if (defender.getMetadata(PlayerID, "role") == "worker" || defender.getMetadata(PlayerID, "role") == "attack")
defender.setMetadata(PlayerID, "formerrole", defender.getMetadata(PlayerID, "role"));
@ -605,7 +605,7 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
// this processes the attackmessages
// So that a unit that gets attacked will not be completely dumb.
// warning: huge levels of indentation coming.
Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
Defence.prototype.MessageProcess = function(gameState,events, HQ) {
var self = this;
for (var key in events){
@ -677,7 +677,7 @@ Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
if (this.reevaluateEntity(gameState, attacker))
{
var position = attacker.position();
var close = militaryManager.enemyWatchers[attacker.owner()].enemySoldiers.filter(Filters.byDistance(position, self.armyCompactSize));
var close = HQ.enemyWatchers[attacker.owner()].enemySoldiers.filter(Filters.byDistance(position, self.armyCompactSize));
if (close.length > 2 || ourUnit.hasClass("Support") || attacker.hasClass("Siege"))
{
@ -736,7 +736,7 @@ Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
}; // nice sets of closing brackets, isn't it?
// At most, this will put defcon to 4
Defence.prototype.DealWithWantedUnits = function(gameState, events, militaryManager) {
Defence.prototype.DealWithWantedUnits = function(gameState, events, HQ) {
//if (gameState.defcon() < 3)
// return;

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,10 @@ var enemyWatcher = function(gameState, playerToWatch) {
var filter = Filters.and(Filters.byClass("Structure"), Filters.byOwner(this.watched));
this.enemyBuildings = gameState.updatingGlobalCollection("player-" +this.watched + "-structures", filter);
filter = Filters.and(Filters.byClass("Unit"), Filters.byOwner(this.watched));
this.enemyUnits = gameState.updatingGlobalCollection("player-" +this.watched + "-units", filter);
filter = Filters.and(Filters.byClass("Worker"), Filters.byOwner(this.watched));
this.enemyCivilians = gameState.updatingGlobalCollection("player-" +this.watched + "-civilians", filter);

View File

@ -6,5 +6,5 @@ function EntityCollectionFromIds(gameState, idList){
ents[id] = gameState.entities._entities[id];
}
}
return new EntityCollection(gameState.ai, ents);
return new EntityCollection(gameState.sharedScript, ents);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,44 +1,8 @@
const TERRITORY_PLAYER_MASK = 0x3F;
// other map functions
//TODO: Make this cope with negative cell values
// This is by default a 16-bit map but can be adapted into 8-bit.
function Map(gameState, originalMap, actualCopy){
// get the map to find out the correct dimensions
var gameMap = gameState.getMap();
this.width = gameMap.width;
this.height = gameMap.height;
this.length = gameMap.data.length;
this.maxVal = 65535;
if (originalMap && actualCopy){
this.map = new Uint16Array(this.length);
for (var i = 0; i < originalMap.length; ++i)
this.map[i] = originalMap[i];
} else if (originalMap) {
this.map = originalMap;
} else {
this.map = new Uint16Array(this.length);
}
this.cellSize = gameState.cellSize;
}
Map.prototype.setMaxVal = function(val){
this.maxVal = val;
};
Map.prototype.gamePosToMapPos = function(p){
return [Math.floor(p[0]/this.cellSize), Math.floor(p[1]/this.cellSize)];
};
Map.prototype.point = function(p){
var q = this.gamePosToMapPos(p);
return this.map[q[0] + this.width * q[1]];
};
// returns an 8-bit map.
Map.createObstructionMap = function(gameState, template){
Map.createObstructionMap = function(gameState, accessIndex, template){
var passabilityMap = gameState.getMap();
var territoryMap = gameState.ai.territoryMap;
var territoryMap = gameState.ai.territoryMap;
// default values
var placementType = "land";
@ -49,12 +13,12 @@ Map.createObstructionMap = function(gameState, template){
// If there is a template then replace the defaults
if (template){
placementType = template.buildPlacementType();
buildOwn = template.hasBuildTerritory("own");
buildAlly = template.hasBuildTerritory("ally");
buildNeutral = template.hasBuildTerritory("neutral");
buildOwn = template.hasBuildTerritory("own");
buildAlly = template.hasBuildTerritory("ally");
buildNeutral = template.hasBuildTerritory("neutral");
buildEnemy = template.hasBuildTerritory("enemy");
}
var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction") | gameState.getPassabilityClassMask("building-land");
if (placementType == "shore")
@ -66,10 +30,26 @@ Map.createObstructionMap = function(gameState, template){
{
for (var y = 0; y < passabilityMap.height; ++y)
{
okay = false;
var i = x + y*passabilityMap.width;
var tilePlayer = (territoryMap.data[i] & TERRITORY_PLAYER_MASK);
if (gameState.ai.myIndex !== gameState.ai.accessibility.landPassMap[i])
{
obstructionTiles[i] = 0;
continue;
}
if (gameState.isPlayerEnemy(tilePlayer) && tilePlayer !== 0)
{
obstructionTiles[i] = 0;
continue;
}
if ((passabilityMap.data[i] & (gameState.getPassabilityClassMask("building-shore") | gameState.getPassabilityClassMask("default"))))
{
obstructionTiles[i] = 0;
continue;
}
okay = false;
var positions = [[0,1], [1,1], [1,0], [1,-1], [0,-1], [-1,-1], [-1,0], [-1,1]];
var available = 0;
for each (var stuff in positions)
@ -78,10 +58,11 @@ Map.createObstructionMap = function(gameState, template){
var index2 = x + stuff[0]*2 + (y+stuff[1]*2)*passabilityMap.width;
var index3 = x + stuff[0]*3 + (y+stuff[1]*3)*passabilityMap.width;
var index4 = x + stuff[0]*4 + (y+stuff[1]*4)*passabilityMap.width;
if ((passabilityMap.data[index] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index) > 500)
if ((passabilityMap.data[index2] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index2) > 500)
if ((passabilityMap.data[index3] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index3) > 500)
if ((passabilityMap.data[index4] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index4) > 500) {
if ((passabilityMap.data[index] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index,true) > 500)
if ((passabilityMap.data[index2] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index2,true) > 500)
if ((passabilityMap.data[index3] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index3,true) > 500)
if ((passabilityMap.data[index4] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index4,true) > 500) {
if (available < 2)
available++;
else
@ -98,12 +79,6 @@ Map.createObstructionMap = function(gameState, template){
if (gameState.ai.terrainAnalyzer.map[id] === 0 || gameState.ai.terrainAnalyzer.map[id] == 30 || gameState.ai.terrainAnalyzer.map[id] == 40)
okay = false;
}
if (gameState.ai.myIndex !== gameState.ai.accessibility.passMap[i])
okay = false;
if (gameState.isPlayerEnemy(tilePlayer) && tilePlayer !== 0)
okay = false;
if ((passabilityMap.data[i] & (gameState.getPassabilityClassMask("building-shore") | gameState.getPassabilityClassMask("default"))))
okay = false;
obstructionTiles[i] = okay ? 255 : 0;
}
}
@ -120,14 +95,17 @@ Map.createObstructionMap = function(gameState, template){
(!buildNeutral && tilePlayer == 0) ||
(!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer != 0)
);
var tileAccessible = (gameState.ai.myIndex === gameState.ai.accessibility.passMap[i]);
if (accessIndex)
var tileAccessible = (accessIndex === gameState.ai.accessibility.landPassMap[i]);
else
var tileAccessible = true;
if (placementType === "shore")
tileAccessible = true;
obstructionTiles[i] = (!tileAccessible || invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 255;
}
}
var map = new Map(gameState, obstructionTiles);
var map = new Map(gameState.sharedScript, obstructionTiles);
map.setMaxVal(255);
if (template && template.buildDistance()){
@ -135,23 +113,24 @@ Map.createObstructionMap = function(gameState, template){
var category = template.buildDistance().FromCategory;
if (minDist !== undefined && category !== undefined){
gameState.getOwnEntities().forEach(function(ent) {
if (ent.buildCategory() === category && ent.position()){
var pos = ent.position();
var x = Math.round(pos[0] / gameState.cellSize);
var z = Math.round(pos[1] / gameState.cellSize);
map.addInfluence(x, z, minDist/gameState.cellSize, -255, 'constant');
}
});
if (ent.buildCategory() === category && ent.position()){
var pos = ent.position();
var x = Math.round(pos[0] / gameState.cellSize);
var z = Math.round(pos[1] / gameState.cellSize);
map.addInfluence(x, z, minDist/gameState.cellSize, -255, 'constant');
}
});
}
}
return map;
};
Map.createTerritoryMap = function(gameState) {
var map = gameState.ai.territoryMap;
var ret = new Map(gameState, map.data);
var ret = new Map(gameState.sharedScript, map.data);
ret.getOwner = function(p) {
return this.point(p) & TERRITORY_PLAYER_MASK;
@ -161,218 +140,3 @@ Map.createTerritoryMap = function(gameState) {
}
return ret;
};
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, cx + maxDist);
var y1 = Math.min(this.height, cy + maxDist);
var maxDist2 = maxDist * maxDist;
var str = 0.0;
switch (type){
case 'linear':
str = +strength / +maxDist;
break;
case 'quadratic':
str = +strength / +maxDist2;
break;
case 'constant':
str = +strength;
break;
}
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
var quant = 0;
switch (type){
case 'linear':
var r = Math.sqrt(r2);
quant = str * (maxDist - r);
break;
case 'quadratic':
quant = str * (maxDist2 - r2);
break;
case 'constant':
quant = str;
break;
}
if (this.map[x + y * this.width] + quant < 0)
this.map[x + y * this.width] = 0;
else if (this.map[x + y * this.width] + quant > this.maxVal)
this.map[x + y * this.width] = this.maxVal; // avoids overflow.
else
this.map[x + y * this.width] += quant;
}
}
}
};
Map.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type) {
strength = strength ? +strength : +maxDist;
type = type ? type : 'constant';
var x0 = Math.max(0, cx - maxDist);
var y0 = Math.max(0, cy - maxDist);
var x1 = Math.min(this.width, cx + maxDist);
var y1 = Math.min(this.height, cy + maxDist);
var maxDist2 = maxDist * maxDist;
var str = 0.0;
switch (type){
case 'linear':
str = strength / maxDist;
break;
case 'quadratic':
str = strength / maxDist2;
break;
case 'constant':
str = strength;
break;
}
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
var quant = 0;
switch (type){
case 'linear':
var r = Math.sqrt(r2);
quant = str * (maxDist - r);
break;
case 'quadratic':
quant = str * (maxDist2 - r2);
break;
case 'constant':
quant = str;
break;
}
var machin = this.map[x + y * this.width] * quant;
if (machin < 0)
this.map[x + y * this.width] = 0;
else if (machin > this.maxVal)
this.map[x + y * this.width] = this.maxVal;
else
this.map[x + y * this.width] = machin;
}
}
}
};
// doesn't check for overflow.
Map.prototype.setInfluence = function(cx, cy, maxDist, value) {
value = value ? value : 0;
var x0 = Math.max(0, cx - maxDist);
var y0 = Math.max(0, cy - maxDist);
var x1 = Math.min(this.width, cx + maxDist);
var y1 = Math.min(this.height, cy + maxDist);
var maxDist2 = maxDist * maxDist;
for ( var y = y0; y < y1; ++y) {
for ( var x = x0; x < x1; ++x) {
var dx = x - cx;
var dy = y - cy;
var r2 = dx*dx + dy*dy;
if (r2 < maxDist2){
this.map[x + y * this.width] = value;
}
}
}
};
/**
* Make each cell's 16-bit/8-bit value at least one greater than each of its
* neighbours' values. (If the grid is initialised with 0s and 65535s or 255s, the
* result of each cell is its Manhattan distance to the nearest 0.)
*/
Map.prototype.expandInfluences = function() {
var w = this.width;
var h = this.height;
var grid = this.map;
for ( var y = 0; y < h; ++y) {
var min = this.maxVal;
for ( var x = 0; x < w; ++x) {
var g = grid[x + y * w];
if (g > min)
grid[x + y * w] = min;
else if (g < min)
min = g;
++min;
}
for ( var x = w - 2; x >= 0; --x) {
var g = grid[x + y * w];
if (g > min)
grid[x + y * w] = min;
else if (g < min)
min = g;
++min;
}
}
for ( var x = 0; x < w; ++x) {
var min = this.maxVal;
for ( var y = 0; y < h; ++y) {
var g = grid[x + y * w];
if (g > min)
grid[x + y * w] = min;
else if (g < min)
min = g;
++min;
}
for ( var y = h - 2; y >= 0; --y) {
var g = grid[x + y * w];
if (g > min)
grid[x + y * w] = min;
else if (g < min)
min = g;
++min;
}
}
};
Map.prototype.findBestTile = function(radius, obstructionTiles){
// Find the best non-obstructed tile
var bestIdx = 0;
var bestVal = -1;
for ( var i = 0; i < this.length; ++i) {
if (obstructionTiles.map[i] > radius) {
var v = this.map[i];
if (v > bestVal) {
bestVal = v;
bestIdx = i;
}
}
}
return [bestIdx, bestVal];
};
// add to current map by the parameter map pixelwise
Map.prototype.add = function(map){
for (var i = 0; i < this.length; ++i) {
if (this.map[i] + map.map[i] < 0)
this.map[i] = 0;
else if (this.map[i] + map.map[i] > this.maxVal)
this.map[i] = this.maxVal;
else
this.map[i] += map.map[i];
}
};
Map.prototype.dumpIm = function(name, threshold){
name = name ? name : "default.png";
threshold = threshold ? threshold : this.maxVal;
Engine.DumpImage(name, this.map, this.width, this.height, threshold);
};

View File

@ -1,490 +0,0 @@
/*
* Military Manager.
* Basically this deals with constructing defense and attack buildings, but it's not very developped yet.
* There's a lot of work still to do here.
* It also handles the attack plans (see attack_plan.js)
* Not completely cleaned up from the original version in qBot.
*/
var MilitaryAttackManager = function() {
this.fortressStartTime = 0;
this.fortressLapseTime = Config.Military.fortressLapseTime * 1000;
this.defenceBuildingTime = Config.Military.defenceBuildingTime * 1000;
this.attackPlansStartTime = Config.Military.attackPlansStartTime * 1000;
this.defenceManager = new Defence();
this.TotalAttackNumber = 0;
this.upcomingAttacks = { "CityAttack" : [] };
this.startedAttacks = { "CityAttack" : [] };
};
MilitaryAttackManager.prototype.init = function(gameState) {
var civ = gameState.playerData.civ;
// load units and buildings from the config files
if (civ in Config.buildings.moderate){
this.bModerate = Config.buildings.moderate[civ];
}else{
this.bModerate = Config.buildings.moderate['default'];
}
if (civ in Config.buildings.advanced){
this.bAdvanced = Config.buildings.advanced[civ];
}else{
this.bAdvanced = Config.buildings.advanced['default'];
}
if (civ in Config.buildings.fort){
this.bFort = Config.buildings.fort[civ];
}else{
this.bFort = Config.buildings.fort['default'];
}
for (var i in this.bAdvanced){
this.bAdvanced[i] = gameState.applyCiv(this.bAdvanced[i]);
}
for (var i in this.bFort){
this.bFort[i] = gameState.applyCiv(this.bFort[i]);
}
// TODO: figure out how to make this generic
for (var i in this.attackManagers){
this.availableAttacks[i] = new this.attackManagers[i](gameState, this);
}
var enemies = gameState.getEnemyEntities();
var filter = Filters.byClassesOr(["CitizenSoldier", "Champion", "Hero", "Siege"]);
this.enemySoldiers = enemies.filter(filter); // TODO: cope with diplomacy changes
this.enemySoldiers.registerUpdates();
// each enemy watchers keeps a list of entity collections about the enemy it watches
// It also keeps track of enemy armies, merging/splitting as needed
this.enemyWatchers = {};
this.ennWatcherIndex = [];
for (var i = 1; i <= 8; i++)
if (PlayerID != i && gameState.isPlayerEnemy(i)) {
this.enemyWatchers[i] = new enemyWatcher(gameState, i);
this.ennWatcherIndex.push(i);
this.defenceManager.enemyArmy[i] = [];
}
};
// picks the best template based on parameters and classes
MilitaryAttackManager.prototype.findBestTrainableUnit = 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) {
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];
};
// Deals with building fortresses and towers.
// Currently build towers next to every useful dropsites.
// TODO: Fortresses are placed randomly atm.
MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
var workersNumber = gameState.getOwnEntitiesByRole("worker").filter(Filters.not(Filters.byHasMetadata(PlayerID,"plan"))).length;
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv('structures/{civ}_defense_tower'))
+ 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.getMetadata(PlayerID, "resource-quantity-wood") > 400 || dropsiteEnt.getMetadata(PlayerID, "resource-quantity-stone") > 500
|| dropsiteEnt.getMetadata(PlayerID, "resource-quantity-metal") > 500) ){
var position = dropsiteEnt.position();
if (position){
queues.defenceBuilding.addItem(new BuildingConstructionPlan(gameState, 'structures/{civ}_defense_tower', position));
}
dropsiteEnt.setMetadata(PlayerID, "defenseTower", true);
}
});
}
var numFortresses = 0;
for (var i in this.bFort){
numFortresses += gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bFort[i]));
}
if (queues.defenceBuilding.totalLength() < 1 && (gameState.currentPhase() > 2 || gameState.isResearching("phase_city_generic")))
{
if (workersNumber >= 80 && gameState.getTimeElapsed() > numFortresses * this.fortressLapseTime + this.fortressStartTime)
{
if (!this.fortressStartTime)
this.fortressStartTime = gameState.getTimeElapsed();
queues.defenceBuilding.addItem(new BuildingConstructionPlan(gameState, this.bFort[0]));
debug ("Building a fortress");
}
}
if (gameState.countEntitiesByType(gameState.applyCiv(this.bFort[i])) >= 1) {
// let's add a siege building plan to the current attack plan if there is none currently.
if (this.upcomingAttacks["CityAttack"].length !== 0)
{
var attack = this.upcomingAttacks["CityAttack"][0];
if (!attack.unitStat["Siege"])
{
// no minsize as we don't want the plan to fail at the last minute though.
var stat = { "priority" : 1.1, "minSize" : 0, "targetSize" : 4, "batchSize" : 2, "classes" : ["Siege"],
"interests" : [ ["siegeStrength", 3], ["cost",1] ] ,"templates" : [] };
if (gameState.civ() == "cart" || gameState.civ() == "maur")
stat["classes"] = ["Elephant"];
attack.addBuildOrder(gameState, "Siege", stat, true);
}
}
}
};
// Deals with constructing military buildings (barracks, stables…)
// They are mostly defined by Config.js. This is unreliable since changes could be done easily.
// TODO: We need to determine these dynamically. Also doesn't build fortresses since the above function does that.
// TODO: building placement is bad. Choice of buildings is also fairly dumb.
MilitaryAttackManager.prototype.constructTrainingBuildings = function(gameState, queues) {
Engine.ProfileStart("Build buildings");
var workersNumber = gameState.getOwnEntitiesByRole("worker").filter(Filters.not(Filters.byHasMetadata(PlayerID, "plan"))).length;
if (workersNumber > 30 && (gameState.currentPhase() > 1 || gameState.isResearching("phase_town_generic")
|| gameState.isResearching("phase_town_athens"))) {
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 && workersNumber > 85)
if (queues.militaryBuilding.totalLength() < 1)
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0]));
if (gameState.countEntitiesByType(gameState.applyCiv(this.bModerate[0])) === 2 && gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0])) < 3 && workersNumber > 125)
if (queues.militaryBuilding.totalLength() < 1)
{
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0]));
if (gameState.civ() == "gaul" || gameState.civ() == "brit" || gameState.civ() == "iber") {
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0]));
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0]));
}
}
//build advanced military buildings
if (workersNumber >= 75 && gameState.currentPhase() > 2){
if (queues.militaryBuilding.totalLength() === 0){
var inConst = 0;
for (var i in this.bAdvanced)
inConst += gameState.countFoundationsWithType(gameState.applyCiv(this.bAdvanced[i]));
if (inConst == 0 && this.bAdvanced && this.bAdvanced.length !== 0) {
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]));
}
}
}
}
if (gameState.civ() !== "gaul" && gameState.civ() !== "brit" && gameState.civ() !== "iber" &&
workersNumber > 130 && gameState.currentPhase() > 2)
{
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]));
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bAdvanced[i]));
}
}
}
Engine.ProfileStop();
};
// TODO: use pop(). Currently unused as this is too gameable.
MilitaryAttackManager.prototype.garrisonAllFemales = function(gameState) {
var buildings = gameState.getOwnEntities().filter(Filters.byCanGarrison()).toEntityArray();
var females = gameState.getOwnEntities().filter(Filters.byClass("Support"));
var cache = {};
females.forEach( function (ent) {
for (var i in buildings)
{
if (ent.position())
{
var struct = buildings[i];
if (!cache[struct.id()])
cache[struct.id()] = 0;
if (struct.garrisoned() && struct.garrisonMax() - struct.garrisoned().length - cache[struct.id()] > 0)
{
ent.garrison(struct);
cache[struct.id()]++;
break;
}
}
}
});
this.hasGarrisonedFemales = true;
};
MilitaryAttackManager.prototype.ungarrisonAll = function(gameState) {
this.hasGarrisonedFemales = false;
var buildings = gameState.getOwnEntities().filter(Filters.byCanGarrison()).toEntityArray();
buildings.forEach( function (struct) {
if (struct.garrisoned() && struct.garrisoned().length)
struct.unloadAll();
});
};
MilitaryAttackManager.prototype.pausePlan = function(gameState, planName) {
for (var attackType in this.upcomingAttacks) {
for (var i in this.upcomingAttacks[attackType]) {
var attack = this.upcomingAttacks[attackType][i];
if (attack.getName() == planName)
attack.setPaused(gameState, true);
}
}
for (var attackType in this.startedAttacks) {
for (var i in this.startedAttacks[attackType]) {
var attack = this.startedAttacks[attackType][i];
if (attack.getName() == planName)
attack.setPaused(gameState, true);
}
}
}
MilitaryAttackManager.prototype.unpausePlan = function(gameState, planName) {
for (var attackType in this.upcomingAttacks) {
for (var i in this.upcomingAttacks[attackType]) {
var attack = this.upcomingAttacks[attackType][i];
if (attack.getName() == planName)
attack.setPaused(gameState, false);
}
}
for (var attackType in this.startedAttacks) {
for (var i in this.startedAttacks[attackType]) {
var attack = this.startedAttacks[attackType][i];
if (attack.getName() == planName)
attack.setPaused(gameState, false);
}
}
}
MilitaryAttackManager.prototype.pauseAllPlans = function(gameState) {
for (var attackType in this.upcomingAttacks) {
for (var i in this.upcomingAttacks[attackType]) {
var attack = this.upcomingAttacks[attackType][i];
attack.setPaused(gameState, true);
}
}
for (var attackType in this.startedAttacks) {
for (var i in this.startedAttacks[attackType]) {
var attack = this.startedAttacks[attackType][i];
attack.setPaused(gameState, true);
}
}
}
MilitaryAttackManager.prototype.unpauseAllPlans = function(gameState) {
for (var attackType in this.upcomingAttacks) {
for (var i in this.upcomingAttacks[attackType]) {
var attack = this.upcomingAttacks[attackType][i];
attack.setPaused(gameState, false);
}
}
for (var attackType in this.startedAttacks) {
for (var i in this.startedAttacks[attackType]) {
var attack = this.startedAttacks[attackType][i];
attack.setPaused(gameState, false);
}
}
}
MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
var self = this;
Engine.ProfileStart("military update");
this.gameState = gameState;
Engine.ProfileStart("Constructing military buildings and building defences");
this.constructTrainingBuildings(gameState, queues);
if(gameState.getTimeElapsed() > this.defenceBuildingTime)
this.buildDefences(gameState, queues);
Engine.ProfileStop();
this.defenceManager.update(gameState, events, this);
Engine.ProfileStart("Looping through attack plans");
// TODO: implement some form of check before starting a new attack plans. Sometimes it is not the priority.
if (1) {
for (var attackType in this.upcomingAttacks) {
for (var i = 0;i < this.upcomingAttacks[attackType].length; ++i) {
var attack = this.upcomingAttacks[attackType][i];
// okay so we'll get the support plan
if (!attack.isStarted()) {
var updateStep = attack.updatePreparation(gameState, this,events);
// now we're gonna check if the preparation time is over
if (updateStep === 1 || attack.isPaused() ) {
// just chillin'
} else if (updateStep === 0 || updateStep === 3) {
debug ("Military Manager: " +attack.getType() +" plan " +attack.getName() +" aborted.");
if (updateStep === 3) {
this.attackPlansEncounteredWater = true;
debug("No attack path found. Aborting.");
}
attack.Abort(gameState, this);
this.upcomingAttacks[attackType].splice(i--,1);
} else if (updateStep === 2) {
var chatText = "I am launching an attack against " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
if (Math.random() < 0.2)
chatText = "Attacking " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
else if (Math.random() < 0.3)
chatText = "I have sent an army against " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
else if (Math.random() < 0.3)
chatText = "I'm starting an attack against " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
gameState.ai.chatTeam(chatText);
debug ("Military Manager: Starting " +attack.getType() +" plan " +attack.getName());
attack.StartAttack(gameState,this);
this.startedAttacks[attackType].push(attack);
this.upcomingAttacks[attackType].splice(i--,1);
}
} else {
var chatText = "I am launching an attack against " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
if (Math.random() < 0.2)
chatText = "Attacking " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
else if (Math.random() < 0.3)
chatText = "I have sent an army against " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
else if (Math.random() < 0.3)
chatText = "I'm starting an attack against " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
gameState.ai.chatTeam(chatText);
debug ("Military Manager: Starting " +attack.getType() +" plan " +attack.getName());
this.startedAttacks[attackType].push(attack);
this.upcomingAttacks[attackType].splice(i--,1);
}
}
}
}
for (var attackType in this.startedAttacks) {
for (var i = 0; i < this.startedAttacks[attackType].length; ++i) {
var attack = this.startedAttacks[attackType][i];
// okay so then we'll update the attack.
if (!attack.isPaused())
{
var remaining = attack.update(gameState,this,events);
if (remaining == 0 || remaining == undefined) {
debug ("Military Manager: " +attack.getType() +" plan " +attack.getName() +" is now finished.");
attack.Abort(gameState);
this.startedAttacks[attackType].splice(i--,1);
}
}
}
}
// 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";
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)
{
Lalala = new CityAttack(gameState, this,this.TotalAttackNumber, -1, "rush")
this.TotalAttackNumber++;
this.upcomingAttacks["CityAttack"].push(Lalala);
debug ("Starting a little something");
} else if (gameState.ai.strategy !== "rush")
{
// creating plans after updating because an aborted plan might be reused in that case.
if (gameState.countEntitiesByType(gameState.applyCiv(this.bModerate[0])) >= 1 && !this.attackPlansEncounteredWater
&& gameState.getTimeElapsed() > this.attackPlansStartTime) {
if (gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_dock")) === 0 && gameState.ai.waterMap)
{
// wait till we get a dock.
} else {
// basically only the first plan, really.
if (this.upcomingAttacks["CityAttack"].length == 0 && (gameState.getTimeElapsed() < 12*60000 || Config.difficulty < 1)) {
var Lalala = new CityAttack(gameState, this,this.TotalAttackNumber, -1);
if (Lalala.failed)
{
this.attackPlansEncounteredWater = true; // hack
} else {
debug ("Military Manager: Creating the plan " +this.TotalAttackNumber);
this.TotalAttackNumber++;
this.upcomingAttacks["CityAttack"].push(Lalala);
}
} else if (this.upcomingAttacks["CityAttack"].length == 0 && Config.difficulty !== 0) {
var Lalala = new CityAttack(gameState, this,this.TotalAttackNumber, -1, "superSized");
if (Lalala.failed)
{
this.attackPlansEncounteredWater = true; // hack
} else {
debug ("Military Manager: Creating the super sized plan " +this.TotalAttackNumber);
this.TotalAttackNumber++;
this.upcomingAttacks["CityAttack"].push(Lalala);
}
}
}
}
}
/*
// very old relic. This should be reimplemented someday so the code stays here.
if (this.HarassRaiding && this.preparingRaidNumber + this.startedRaidNumber < 1 && gameState.getTimeElapsed() < 780000) {
var Lalala = new CityAttack(gameState, this,this.totalStartedAttackNumber, -1, "harass_raid");
if (!Lalala.createSupportPlans(gameState, this, )) {
debug ("Military Manager: harrassing plan not a valid option");
this.HarassRaiding = false;
} else {
debug ("Military Manager: Creating the harass raid plan " +this.totalStartedAttackNumber);
this.totalStartedAttackNumber++;
this.preparingRaidNumber++;
this.currentAttacks.push(Lalala);
}
}
*/
Engine.ProfileStop();
Engine.ProfileStop();
};

View File

@ -0,0 +1,288 @@
/* Naval Manager
Will deal with anything ships.
-Basically trade over water (with fleets and goals commissioned by the economy manager)
-Defence over water (commissioned by the defense manager)
-subtask being patrols, escort, naval superiority.
-Transport of units over water (a few units).
-Scouting, ultimately.
Also deals with handling docks, making sure we have access and stuffs like that.
Does not build them though, that's for the base manager to handle.
*/
var NavalManager = function() {
// accessibility zones for which we have a dock.
// Connexion is described as [landindex] = [seaIndexes];
// technically they also exist for sea zones but I don't care.
this.landZoneDocked = [];
// list of seas I have a dock on.
this.accessibleSeas = [];
// ship subCollections. Also exist for land zones, idem, not caring.
this.seaShips = [];
this.seaTpShips = [];
this.seaWarships = [];
// wanted NB per zone.
this.wantedTpShips = [];
this.wantedWarships = [];
this.transportPlans = [];
this.askedPlans = [];
};
// More initialisation for stuff that needs the gameState
NavalManager.prototype.init = function(gameState, events, queues) {
// finished docks
this.docks = gameState.getOwnEntities().filter(Filters.and(Filters.byClass("Dock"), Filters.not(Filters.isFoundation())));
this.docks.allowQuickIter();
this.docks.registerUpdates();
this.ships = gameState.getOwnEntities().filter(Filters.byClass("Ship"));
// note: those two can overlap (some transport ships are warships too and vice-versa).
this.tpShips = this.ships.filter(Filters.byCanGarrison());
this.warships = this.ships.filter(Filters.byClass("Warship"));
this.ships.registerUpdates();
this.tpShips.registerUpdates();
this.warships.registerUpdates();
for (var i = 0; i < gameState.ai.accessibility.regionSize.length; ++i)
{
if (gameState.ai.accessibility.regionType[i] !== "water")
{
// push dummies
this.seaShips.push(new EntityCollection(gameState.sharedScript));
this.seaTpShips.push(new EntityCollection(gameState.sharedScript));
this.seaWarships.push(new EntityCollection(gameState.sharedScript));
this.wantedTpShips.push(0);
this.wantedWarships.push(0);
} else {
var collec = this.ships.filter(Filters.byStaticMetadata(PlayerID, "sea", i));
collec.registerUpdates();
this.seaShips.push(collec);
collec = this.tpShips.filter(Filters.byStaticMetadata(PlayerID, "sea", i));
collec.registerUpdates();
this.seaTpShips.push(collec);
var collec = this.warships.filter(Filters.byStaticMetadata(PlayerID, "sea", i));
collec.registerUpdates();
this.seaWarships.push(collec);
this.wantedTpShips.push(1);
this.wantedWarships.push(1);
}
this.landZoneDocked.push([]);
}
};
NavalManager.prototype.getUnconnectedSeas = function (gameState, region) {
var seas = gameState.ai.accessibility.regionLinks[region]
if (seas.length === 0)
return [];
for (var i = 0; i < seas.length; ++i)
{
if (this.landZoneDocked[region].indexOf(seas[i]) !== -1)
seas.splice(i--,1);
}
return seas;
};
// returns true if there is a path from A to B and we have docks.
NavalManager.prototype.canReach = function (gameState, regionA, regionB) {
var path = gameState.ai.accessibility.getTrajectToIndex(regionA, regionB);
if (!path)
{
return false;
}
for (var i = 0; i < path.length - 1; ++i)
{
if (gameState.ai.accessibility.regionType[path[i]] == "land")
if (this.accessibleSeas.indexOf(path[i+1]) === -1)
{
debug ("cannot reach because of " + path[i+1]);
return false; // we wn't be able to board on that sea
}
}
return true;
};
NavalManager.prototype.checkEvents = function (gameState, queues, events) {
for (i in events)
{
if (events[i].type == "Destroy")
{
// TODO: probably check stuffs like a base destruction.
} else if (events[i].type == "ConstructionFinished")
{
var evt = events[i];
if (evt.msg && evt.msg.newentity)
{
var entity = gameState.getEntityById(evt.msg.newentity);
if (entity && entity.hasClass("Dock") && entity.isOwn(PlayerID))
{
// okay we have a dock whose construction is finished.
// let's assign it to us.
var pos = entity.position();
var li = gameState.ai.accessibility.getAccessValue(pos);
var ni = entity.getMetadata(PlayerID, "sea");
if (this.landZoneDocked[li].indexOf(ni) === -1)
this.landZoneDocked[li].push(ni);
if (this.accessibleSeas.indexOf(ni) === -1)
this.accessibleSeas.push(ni);
}
}
}
}
};
NavalManager.prototype.addPlan = function(plan) {
this.transportPlans.push(plan);
};
// will create a plan at the end of the turn.
// many units can call this separately and end up in the same plan
// which can be useful.
NavalManager.prototype.askForTransport = function(entity, startPos, endPos) {
this.askedPlans.push([entity, startPos, endPos]);
};
// creates aforementionned plans
NavalManager.prototype.createPlans = function(gameState) {
var startID = {};
for (i in this.askedPlans)
{
var plan = this.askedPlans[i];
var startIndex = gameState.ai.accessibility.getAccessValue(plan[1]);
var endIndex = gameState.ai.accessibility.getAccessValue(plan[2]);
if (startIndex === 1 || endIndex === -1)
continue;
if (!startID[startIndex])
{
startID[startIndex] = {};
startID[startIndex][endIndex] = { "dest" : plan[2], "units": [plan[0]]};
}
else if (!startID[startIndex][endIndex])
startID[startIndex][endIndex] = { "dest" : plan[2], "units": [plan[0]]};
else
startID[startIndex][endIndex].units.push(plan[0]);
}
for (var i in startID)
for (var k in startID[i])
{
var tpPlan = new TransportPlan(gameState, startID[i][k].units, startID[i][k].dest, false)
this.transportPlans.push (tpPlan);
}
};
// TODO: work on this.
NavalManager.prototype.maintainFleet = function(gameState, queues, events) {
// check if we have enough transport ships.
// check per region.
for (var i = 0; i < this.seaShips.length; ++i)
{
var tpNb = gameState.countOwnQueuedEntitiesWithMetadata("sea", i);
if (this.accessibleSeas.indexOf(i) !== -1 && this.seaTpShips[i].length < this.wantedTpShips[i]
&& 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 TrainingPlan(gameState, "units/{civ}_ship_bireme", { "sea" : i }, 1, 0, -1, 1 ));
}
}
};
// bumps up the number of ships we want if we need more.
NavalManager.prototype.checkLevels = function(gameState, queues) {
if (queues.ships.length() !== 0)
return;
for (var i = 0; i < this.transportPlans.length; ++i)
{
var plan = this.transportPlans[i];
if (plan.needTpShips())
{
var zone = plan.neededShipsZone();
if (zone && gameState.countOwnQueuedEntitiesWithMetadata("sea", zone) > 0)
continue;
if (zone && this.wantedTpShips[i] === 0)
this.wantedTpShips[i]++;
else if (zone && plan.allAtOnce)
this.wantedTpShips[i]++;
}
}
};
// assigns free ships to plans that need some
NavalManager.prototype.assignToPlans = function(gameState, queues, events) {
for (var i = 0; i < this.transportPlans.length; ++i)
{
var plan = this.transportPlans[i];
if (plan.needTpShips())
{
// assign one per go.
var zone = plan.neededShipsZone();
if (zone)
{
for each (ship in this.seaTpShips[zone]._entities)
{
if (!ship.getMetadata(PlayerID, "tpplan"))
{
debug ("Assigning ship " + ship.id() + " to plan" + plan.ID);
plan.assignShip(gameState, ship);
return true;
}
}
}
}
}
return false;
};
NavalManager.prototype.checkActivePlan = function(ID) {
for (var i = 0; i < this.transportPlans.length; ++i)
if (this.transportPlans[i].ID === ID)
return true;
return false;
};
// Some functions are run every turn
// Others once in a while
NavalManager.prototype.update = function(gameState, queues, events) {
Engine.ProfileStart("Naval Manager update");
this.checkEvents(gameState, queues, events);
if (gameState.ai.playedTurn % 10 === 0)
{
this.maintainFleet(gameState, queues, events);
this.checkLevels(gameState, queues);
}
for (var i = 0; i < this.transportPlans.length; ++i)
if (!this.transportPlans[i].carryOn(gameState, this))
{
// whatever the reason, this plan needs to be ended
// it could be that it's finished though.
var seaZone = this.transportPlans[i].neededShipsZone();
var rallyPos = [];
this.docks.forEach(function (dock) {
if (dock.getMetadata(PlayerID,"sea") == seaZone)
rallyPos = dock.position();
});
this.transportPlans[i].ships.move(rallyPos);
this.transportPlans[i].releaseAll(gameState);
this.transportPlans.splice(i,1);
--i;
}
this.assignToPlans(gameState, queues, events);
if (gameState.ai.playedTurn % 10 === 2)
{
this.createPlans(gameState);
this.askedPlans = [];
}
Engine.ProfileStop();
};

View File

@ -0,0 +1,472 @@
/*
Describes a transport plan
Constructor assign units (units is an ID array, or an ID), a destionation (position, ingame), and a wanted escort size.
If "onlyIfOk" is true, then the plan will only start if the wanted escort size is met.
The naval manager will try to deal with it accordingly.
By this I mean that the naval manager will find how to go from access point 1 to access point 2 (relying on in-game pathfinder for mvt)
And then carry units from there.
If units are over multiple accessibility indexes (ie different islands) it will first group them
Note: only assign it units currently over land, or it won't work.
Also: destination should probably be land, otherwise the units will be lost at sea.
*/
// TODO: finish the support of multiple accessibility indexes.
// TODO: this doesn't check we can actually reach in the init, which we might want?
var TransportPlan = function(gameState, units, destination, allAtOnce, escortSize, onlyIfOK) {
var self = this;
this.ID = uniqueIDTPlans++;
var unitsID = [];
if (units.length !== undefined)
unitsID = units;
else
unitsID = [units];
this.units = EntityCollectionFromIds(gameState, unitsID);
this.units.forEach(function (ent) { //}){
ent.setMetadata(PlayerID, "tpplan", self.ID);
ent.setMetadata(PlayerID, "formerRole", ent.getMetadata(PlayerID, "role"));
ent.setMetadata(PlayerID, "role", "transport");
});
this.units.freeze();
this.units.registerUpdates();
debug ("Starting a new plan with ID " + this.ID + " to " + destination);
debug ("units are " + uneval (units));
this.destination = destination;
this.destinationIndex = gameState.ai.accessibility.getAccessValue(destination);
if (allAtOnce)
this.allAtOnce = allAtOnce;
else
this.allAtOnce = false;
if (escortSize)
this.escortSize = escortSize;
else
this.escortSize = 0;
if (onlyIfOK)
this.onlyIfOK = onlyIfOK;
else
this.onlyIfOK = false;
this.state = "unstarted";
this.ships = gameState.ai.HQ.navalManager.ships.filter(Filters.byMetadata(PlayerID, "tpplan", this.ID));
// note: those two can overlap (some transport ships are warships too and vice-versa).
this.transportShips = gameState.ai.HQ.navalManager.tpShips.filter(Filters.byMetadata(PlayerID, "tpplan", this.ID));
this.escortShips = gameState.ai.HQ.navalManager.warships.filter(Filters.byMetadata(PlayerID, "tpplan", this.ID));
this.ships.registerUpdates();
this.transportShips.registerUpdates();
this.escortShips.registerUpdates();
};
// count available slots
TransportPlan.prototype.countFreeSlots = function(onlyTrulyFree)
{
var slots = 0;
this.transportShips.forEach(function (ent) { //}){
slots += ent.garrisonMax();
if (onlyTrulyFree)
slots -= ent.garrisoned().length;
});
}
TransportPlan.prototype.assignShip = function(gameState, ship)
{
ship.setMetadata(PlayerID,"tpplan", this.ID);
}
TransportPlan.prototype.releaseAll = function(gameState)
{
this.ships.forEach(function (ent) { ent.setMetadata(PlayerID,"tpplan", undefined) });
this.units.forEach(function (ent) {
var fRole = ent.getMetadata(PlayerID, "formerRole");
if (fRole)
ent.setMetadata(PlayerID,"role", fRole);
ent.setMetadata(PlayerID,"tpplan", undefined)
});
}
TransportPlan.prototype.releaseAllShips = function(gameState)
{
this.ships.forEach(function (ent) { ent.setMetadata(PlayerID,"tpplan", undefined) });
}
TransportPlan.prototype.needTpShips = function()
{
if ((this.allAtOnce && this.countFreeSlots() >= this.units.length) || this.transportShips.length > 0)
return false;
return true;
}
TransportPlan.prototype.needEscortShips = function()
{
return !((this.onlyIfOK && this.escortShips.length < this.escortSize) || !this.onlyIfOK);
}
// returns the zone for which we are needing our ships
TransportPlan.prototype.neededShipsZone = function()
{
if (!this.seaZone)
return false;
return this.seaZone;
}
// try to move on.
/* several states:
"unstarted" is the initial state, and will determine wether we follow basic or grouping path
Basic path:
- "waitingForBoarding" means we wait 'till we have enough transport ships and escort ships to move stuffs.
- "Boarding" means we're trying to board units onto our ships
- "Moving" means we're moving ships
- "Unboarding" means we're unbording
- Once we're unboarded, we either return to boarding point (if we still have units to board) or we clear.
> there is the possibility that we'll be moving units on land, but that's basically a restart too, with more clearing.
Grouping Path is basically the same with "grouping" and we never unboard (unless there is a need to)
*/
TransportPlan.prototype.carryOn = function(gameState, navalManager)
{
if (this.state === "unstarted")
{
// Okay so we can start the plan.
// So what we'll do is check what accessibility indexes our units are.
var unitIndexes = [];
this.units.forEach( function (ent) { //}){
var idx = gameState.ai.accessibility.getAccessValue(ent.position());
if (unitIndexes.indexOf(idx) === -1 && idx !== 1)
unitIndexes.push(idx);
});
// we have indexes. If there is more than 1, we'll try and regroup them.
if (unitIndexes.length > 1)
{
warn("Transport Plan path is too complicated, aborting");
return false;
/*
this.state = "waitingForGrouping";
// get the best index for grouping, ie start by the one farthest away in terms of movement.
var idxLength = {};
for (var i = 0; i < unitIndexes.length; ++i)
idxLength[unitIndexes[i]] = gameState.ai.accessibility.getTrajectToIndex(unitIndexes[i], this.destinationIndex).length;
var sortedArray = unitIndexes.sort(function (a,b) { //}){
return idxLength[b] - idxLength[a];
});
this.startIndex = sortedArray[0];
// okay so we'll board units from this index and we'll try to join them with units of the next index.
// this might not be terribly efficient but it won't be efficient anyhow.
return true;*/
}
this.state = "waitingForBoarding";
// let's get our index this turn.
this.startIndex = unitIndexes[0];
debug ("plan " + this.ID + " from " + this.startIndex);
return true;
}
if (this.state === "waitingForBoarding")
{
if (!this.path)
{
this.path = gameState.ai.accessibility.getTrajectToIndex(this.startIndex, this.destinationIndex);
if (!this.path || this.path.length === 0 || this.path.length % 2 === 0)
return false; // TODO: improve error handling
if (this.path[0] !== this.startIndex)
{
warn ("Start point of the path is not the start index, aborting transport plan");
return false;
}
// we have a path, register the first sea zone.
this.seaZone = this.path[1];
debug ("Plan " + this.ID + " over seazone " + this.seaZone);
}
// if we currently have no baoarding spot, try and find one.
if (!this.boardingSpot)
{
// TODO: improve on this whenever we have danger maps.
// okay so we have units over an accessibility index.
// we'll get a map going on.
var Xibility = gameState.ai.accessibility;
// custom obstruction map that uses the shore as the obstruction map
// but doesn't really check like for a building.
// created realtime with the other map.
var passabilityMap = gameState.getMap();
var territoryMap = gameState.ai.territoryMap;
var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction") | gameState.getPassabilityClassMask("building-shore");
var obstructions = new Map(gameState.sharedScript);
// wanted map.
var friendlyTiles = new Map(gameState.sharedScript);
for (var j = 0; j < friendlyTiles.length; ++j)
{
// only on the wanted island
if (Xibility.landPassMap[j] !== this.startIndex)
continue;
// setting obstructions
var tilePlayer = (territoryMap.data[j] & TERRITORY_PLAYER_MASK);
// invalid is enemy-controlled or not on the right sea/land (we need a shore for this, we might want to check neighbhs instead).
var invalidTerritory = (gameState.isPlayerEnemy(tilePlayer) && tilePlayer != 0)
|| (Xibility.navalPassMap[j] !== this.path[1]);
obstructions.map[j] = (invalidTerritory || (passabilityMap.data[j] & obstructionMask)) ? 0 : 255;
// currently we'll just like better on our territory
if (tilePlayer == PlayerID)
friendlyTiles.map[j] = 100;
}
obstructions.expandInfluences();
var best = friendlyTiles.findBestTile(4, obstructions);
var bestIdx = best[0];
// not good enough.
if (best[1] <= 0)
{
best = friendlyTiles.findBestTile(1, obstructions);
bestIdx = best[0];
if (best[1] <= 0)
return false; // apparently we won't be able to board.
}
var x = ((bestIdx % friendlyTiles.width) + 0.5) * gameState.cellSize;
var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * gameState.cellSize;
// we have the spot we want to board at.
this.boardingSpot = [x,z];
debug ("Plan " + this.ID + " new boarding spot is " + this.boardingSpot);
}
// if all at once we need to be full, else we just need enough escort ships.
if (!this.needTpShips() && !this.needEscortShips())
{
// preparing variables
// TODO: destroy former entity collection.
this.garrisoningUnits = this.units.filter(Filters.not(Filters.isGarrisoned()));
this.garrisoningUnits.registerUpdates();
this.garrisoningUnits.freeze();
this.garrisonShipID = -1;
debug ("Boarding");
this.state = "boarding";
}
return true;
} else if (this.state === "waitingForGrouping")
{
// TODO: this.
return true;
}
if (this.state === "boarding" && gameState.ai.playedTurn % 5 === 0)
{
// TODO: improve error recognition.
if (this.units.length === 0)
return false;
if (!this.boardingSpot)
return false;
if (this.needTpShips())
{
this.state = "waitingForBoarding";
return true;
}
if (this.needEscortShips())
{
this.state = "waitingForBoarding";
return true;
}
// check if we aren't actually finished.
if (this.units.getCentrePosition() == undefined || this.countFreeSlots(true) === 0)
{
delete this.boardingSpot;
this.garrisoningUnits.unregister();
this.state = "moving";
return true;
}
// check if we need to move our units and ships closer together
var stillMoving = false;
if (SquareVectorDistance(this.ships.getCentrePosition(),this.boardingSpot) > 1600)
{
this.ships.move(this.boardingSpot[0],this.boardingSpot[1]);
stillMoving = true; // wait till ships are in position
}
if (SquareVectorDistance(this.units.getCentrePosition(),this.boardingSpot) > 1600)
{
this.units.move(this.boardingSpot[0],this.boardingSpot[1]);
stillMoving = true; // wait till units are in position
}
if (stillMoving)
{
return true; // wait.
}
// check if we need to try and board units.
var garrisonShip = gameState.getEntityById(this.garrisonShipID);
var self = this;
// check if ship we're currently garrisoning in is full
if (garrisonShip && garrisonShip.canGarrisonInside())
{
// okay garrison units
var nbStill = garrisonShip.garrisonMax() - garrisonShip.garrisoned().length;
if (this.garrisoningUnits.length < nbStill)
{
Engine.PostCommand({"type": "garrison", "entities": this.garrisoningUnits.toIdArray(), "target": garrisonShip.id(),"queued": false});
}
return true;
} else if (garrisonShip)
{
// full ship, abort
this.garrisonShipID = -1;
garrisonShip = false; // will enter next if.
}
if (!garrisonShip)
{
// could have died or could have be full
// we'll pick a new one, one that isn't full
for (i in this.transportShips._entities)
{
if (this.transportShips._entities[i].canGarrisonInside())
{
this.garrisonShipID = this.transportShips._entities[i].id();
break;
}
}
return true; // wait.
}
// could I actually get here?
return true;
}
if (this.state === "moving")
{
if (!this.unboardingSpot)
{
// TODO: improve on this whenever we have danger maps.
// okay so we have units over an accessibility index.
// we'll get a map going on.
var Xibility = gameState.ai.accessibility;
// custom obstruction map that uses the shore as the obstruction map
// but doesn't really check like for a building.
// created realtime with the other map.
var passabilityMap = gameState.getMap();
var territoryMap = gameState.ai.territoryMap;
var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction") | gameState.getPassabilityClassMask("building-shore");
var obstructions = new Map(gameState.sharedScript);
// wanted map.
var friendlyTiles = new Map(gameState.sharedScript);
var wantedIndex = -1;
if (this.path.length >= 3)
{
this.path.splice(0,2);
wantedIndex = this.path[0];
} else {
debug ("too short at " +uneval(this.path));
return false; // Incomputable
}
for (var j = 0; j < friendlyTiles.length; ++j)
{
// only on the wanted island
if (Xibility.landPassMap[j] !== wantedIndex)
continue;
// setting obstructions
var tilePlayer = (territoryMap.data[j] & TERRITORY_PLAYER_MASK);
// invalid is not on the right land (we need a shore for this, we might want to check neighbhs instead).
var invalidTerritory = (Xibility.landPassMap[j] !== wantedIndex);
obstructions.map[j] = (invalidTerritory || (passabilityMap.data[j] & obstructionMask)) ? 0 : 255;
// currently we'll just like better on our territory
if (tilePlayer == PlayerID)
friendlyTiles.map[j] = 100;
else if (gameState.isPlayerEnemy(tilePlayer) && tilePlayer != 0)
friendlyTiles.map[j] = 4;
else
friendlyTiles.map[j] = 50;
}
obstructions.expandInfluences();
var best = friendlyTiles.findBestTile(4, obstructions);
var bestIdx = best[0];
// not good enough.
if (best[1] <= 0)
{
best = friendlyTiles.findBestTile(1, obstructions);
bestIdx = best[0];
if (best[1] <= 0)
return false; // apparently we won't be able to unboard.
}
var x = ((bestIdx % friendlyTiles.width) + 0.5) * gameState.cellSize;
var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * gameState.cellSize;
// we have the spot we want to board at.
this.unboardingSpot = [x,z];
return true;
}
// TODO: improve error recognition.
if (this.units.length === 0)
return false;
if (!this.unboardingSpot)
return false;
// check if we need to move ships
if (SquareVectorDistance(this.ships.getCentrePosition(),this.unboardingSpot) > 400)
{
this.ships.move(this.unboardingSpot[0],this.unboardingSpot[1]);
} else {
this.state = "unboarding";
return true;
}
return true;
}
if (this.state === "unboarding")
{
// TODO: improve error recognition.
if (this.units.length === 0)
return false;
// check if we need to move ships
if (SquareVectorDistance(this.ships.getCentrePosition(),this.unboardingSpot) > 400)
{
this.ships.move(this.unboardingSpot[0],this.unboardingSpot[1]);
} else {
this.transportShips.forEach( function (ent) { ent.unloadAll() });
// TODO: improve on this.
if (this.path.length > 1)
{
debug ("plan " + this.ID + " going back for more");
// basically reset.
delete this.boardingSpot;
delete this.unboardingSpot;
this.state = "unstarted";
this.releaseAllShips();
return true;
}
debug ("plan " + this.ID + " is finished");
return false;
}
}
return true;
}

View File

@ -15,8 +15,6 @@
// get some part of the total, and if all queues have 70% of their needs, nothing gets done
// Particularly noticeable when phasing: the AI often overshoots by a good 200/300 resources before starting.
//
// The fact that there is an outqueue is mostly a relic of qBot.
//
// This system should be improved. It's probably not flexible enough.
var QueueManager = function(queues, priorities) {
@ -36,7 +34,6 @@ var QueueManager = function(queues, priorities) {
this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) });
this.curItemQueue = [];
};
QueueManager.prototype.getAvailableResources = function(gameState, noAccounts) {
@ -49,54 +46,217 @@ QueueManager.prototype.getAvailableResources = function(gameState, noAccounts) {
return resources;
};
QueueManager.prototype.futureNeeds = function(gameState, EcoManager) {
QueueManager.prototype.getTotalAccountedResources = function(gameState) {
var resources = new Resources();
for (var key in this.queues) {
resources.add(this.accounts[key]);
}
return resources;
};
QueueManager.prototype.currentNeeds = function(gameState) {
var needs = new Resources();
// get ouy current resources, not removing accounts.
// get out current resources, not removing accounts.
var current = this.getAvailableResources(gameState, true);
//queueArrays because it's faster.
for (var i in this.queueArrays)
{
var name = this.queueArrays[i][0];
var queue = this.queueArrays[i][1];
for (var j = 0; j < Math.min(2,queue.length()); ++j)
if (queue.length() > 0 && queue.getNext().isGo(gameState))
needs.add(queue.getNext().getCost());
else if (queue.length() > 0 && !queue.getNext().isGo(gameState))
{
needs.add(queue.queue[j].getCost());
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 (EcoManager === false) {
return {
"food" : Math.max(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)
};
} 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
};
}
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)
};
};
QueueManager.prototype.printQueues = function(gameState){
debug("OUTQUEUES");
for (var i in this.queues){
var qStr = "";
var q = this.queues[i];
if (q.outQueue.length > 0)
debug((i + ":"));
for (var j in q.outQueue){
qStr = " " + q.outQueue[j].type + " ";
if (q.outQueue[j].number)
qStr += "x" + q.outQueue[j].number;
debug (qStr);
QueueManager.prototype.futureNeeds = function(gameState) {
var needs = new Resources();
// get out current resources, not removing accounts.
var current = this.getAvailableResources(gameState, true);
//queueArrays because it's faster.
for (var i in this.queueArrays)
{
var name = this.queueArrays[i][0];
var queue = this.queueArrays[i][1];
for (var j = 0; j < queue.length(); ++j)
{
var costs = queue.queue[j].getCost();
if (!queue.queue[j].isGo(gameState))
costs.multiply(0.5);
needs.add(costs);
}
}
return {
"food" : Math.max(25 + needs.food - current.food, 10),
"wood" : Math.max(needs.wood - current.wood, 10),
"stone" : Math.max(needs.stone - current.stone, 0),
"metal" : Math.max(needs.metal - current.metal, 0)
};
};
// calculate the gather rates we'd want to be able to use all elements in our queues
QueueManager.prototype.wantedGatherRates = function(gameState) {
var rates = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 };
var qTime = gameState.getTimeElapsed();
var qCosts = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 };
var currentRess = this.getAvailableResources(gameState);
debug("INQUEUES");
//queueArrays because it's faster.
for (var i in this.queueArrays)
{
qCosts = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 };
qTime = gameState.getTimeElapsed();
var name = this.queueArrays[i][0];
var queue = this.queueArrays[i][1];
for (var j = 0; j < queue.length(); ++j)
{
var elem = queue.queue[j];
var cost = elem.getCost();
if (qTime < elem.startTime)
qTime = elem.startTime;
if (!elem.isGo(gameState))
{
// assume 2 minutes.
// TODO work on this.
for (type in qCosts)
qCosts[type] += cost[type];
qTime += 120000;
break; // disregard other stuffs.
}
if (!elem.endTime)
{
// estimate time based on priority + cost + nb
// TODO: work on this.
for (type in qCosts)
qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name]));
qTime += 30000;
} else {
// TODO: work on this.
for (type in qCosts)
qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name]));
// TODO: refine based on % completed.
qTime += (elem.endTime-elem.startTime);
}
}
for (j in qCosts)
{
qCosts[j] -= this.accounts[name][j];
var diff = Math.min(qCosts[j], currentRess[j]);
qCosts[j] -= diff;
currentRess[j] -= diff;
rates[j] += qCosts[j]/(qTime/1000);
}
}
return rates;
};
/*QueueManager.prototype.logNeeds = function(gameState) {
if (!this.totor)
{
this.totor = [];
this.currentGathR = [];
this.currentGathRWanted = [];
this.ressLev = [];
}
if (gameState.ai.playedTurn % 10 !== 0)
return;
var array = this.wantedGatherRates(gameState);
this.totor.push( array );
var currentRates = {};
for (var type in array)
currentRates[type] = 0;
for (i in gameState.ai.HQ.baseManagers)
{
var base = gameState.ai.HQ.baseManagers[i];
for (var type in array)
{
base.gatherersByType(gameState,type).forEach (function (ent) { //}){
var worker = ent.getMetadata(PlayerID, "worker-object");
if (worker)
currentRates[type] += worker.getGatherRate(gameState);
});
}
}
this.currentGathR.push( currentRates );
var types = Object.keys(array);
types.sort(function(a, b) {
var va = (Math.max(0,array[a] - currentRates[a]))/ (currentRates[a]+1);
var vb = (Math.max(0,array[b] - currentRates[b]))/ (currentRates[b]+1);
if (va === vb)
return (array[b]/(currentRates[b]+1)) - (array[a]/(currentRates[a]+1));
return vb-va;
});
this.currentGathRWanted.push( types );
var rss = gameState.getResources();
this.ressLev.push( {"food" : rss["food"],"stone" : rss["stone"],"wood" : rss["wood"],"metal" : rss["metal"]} );
if (gameState.getTimeElapsed() > 20*60*1000 && !this.once)
{
this.once = true;
for (j in array)
{
log (j + ";");
for (var i = 0; i < this.totor.length; ++i)
{
log (this.totor[i][j] + ";");
}
}
log();
for (j in array)
{
log (j + ";");
for (var i = 0; i < this.totor.length; ++i)
{
log (this.currentGathR[i][j] + ";");
}
}
log();
for (j in array)
{
log (j + ";");
for (var i = 0; i < this.totor.length; ++i)
{
log (this.currentGathRWanted[i].indexOf(j) + ";");
}
}
log();
for (j in array)
{
log (j + ";");
for (var i = 0; i < this.totor.length; ++i)
{
log (this.ressLev[i][j] + ";");
}
}
}
};
*/
QueueManager.prototype.printQueues = function(gameState){
debug("QUEUES");
for (var i in this.queues){
var qStr = "";
var q = this.queues[i];
@ -115,10 +275,56 @@ QueueManager.prototype.printQueues = function(gameState){
debug(p + ": " + uneval(this.accounts[p]));
}
debug("Needed Resources:" + uneval(this.futureNeeds(gameState,false)));
debug ("Wanted Gather Rates:" + uneval(this.wantedGatherRates(gameState)));
debug ("Current Resources:" + uneval(gameState.getResources()));
debug ("Available Resources:" + uneval(this.getAvailableResources(gameState)));
};
// nice readable HTML version.
QueueManager.prototype.HTMLprintQueues = function(gameState){
if (!Config.debug)
return;
log("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\"> <html> <head> <title>Aegis Queue Manager</title> <link rel=\"stylesheet\" href=\"table.css\"> </head> <body> <table> <caption>Aegis Build Order</caption> ");
for (var i in this.queues){
log ("<tr>");
var q = this.queues[i];
var str = "<th>" + i +"<br>";
for each (k in this.accounts[i].types)
if(k != "population")
{
str += this.accounts[i][k] + k.substr(0,1).toUpperCase() ;
if (k != "metal") str += " / ";
}
log(str + "</th>");
for (var j in q.queue) {
if (q.queue[j].isGo(gameState))
log ("<td>");
else
log ("<td class=\"NotGo\">");
var qStr = "";
qStr += q.queue[j].type;
if (q.queue[j].number)
qStr += "x" + q.queue[j].number;
log (qStr);
log ("</td>");
}
log ("</tr>");
}
log ("</table>");
/*log ("<h3>Accounts</h3>");
for (var p in this.accounts)
{
log("<p>" + p + ": " + uneval(this.accounts[p]) + " </p>");
}*/
log ("<p>Needed Resources:" + uneval(this.futureNeeds(gameState,false)) + "</p>");
log ("<p>Wanted Gather Rate:" + uneval(this.wantedGatherRates(gameState)) + "</p>");
log ("<p>Current Resources:" + uneval(gameState.getResources()) + "</p>");
log ("<p>Available Resources:" + uneval(this.getAvailableResources(gameState)) + "</p>");
log("</body></html>");
};
QueueManager.prototype.clear = function(){
this.curItemQueue = [];
for (var i in this.queues)
@ -136,35 +342,9 @@ QueueManager.prototype.update = function(gameState) {
}
Engine.ProfileStart("Queue Manager");
//if (gameState.ai.playedTurn % 10 === 0)
// this.printQueues(gameState);
Engine.ProfileStart("Pick items from queues");
// TODO: this only pushes the first object. SHould probably try to push any possible object to maximize productivity. Perhaps a settinh?
// looking at queues in decreasing priorities and pushing to the current item queues.
for (var i in this.queueArrays)
{
var name = this.queueArrays[i][0];
var queue = this.queueArrays[i][1];
if (queue.length() > 0)
{
var item = queue.getNext();
var total = new Resources();
total.add(this.accounts[name]);
total.subtract(queue.outQueueCost());
if (total.canAfford(item.getCost()))
{
queue.nextToOutQueue();
}
} else if (queue.totalLength() === 0) {
this.accounts[name].reset();
}
}
// Let's assign resources to plans that need'em
var availableRes = this.getAvailableResources(gameState);
// assign some accounts to queues. This is done by priority, and by need.
for (var ress in availableRes)
{
if (availableRes[ress] > 0 && ress != "population")
@ -173,7 +353,7 @@ QueueManager.prototype.update = function(gameState) {
var tempPrio = {};
var maxNeed = {};
// Okay so this is where it gets complicated.
// If a queue requires "ress" for the next elements (in the queue or the outqueue)
// If a queue requires "ress" for the next elements (in the queue)
// And the account is not high enough for it.
// Then we add it to the total priority.
// To try and be clever, we don't want a long queue to hog all resources. So two things:
@ -182,76 +362,156 @@ QueueManager.prototype.update = function(gameState) {
// This avoids getting a high priority queue with many elements hogging all of one resource
// uselessly while it awaits for other resources.
for (var j in this.queues) {
var outQueueCost = this.queues[j].outQueueCost();
var queueCost = this.queues[j].queueCost();
if (this.accounts[j][ress] < queueCost[ress] + outQueueCost[ress])
// returns exactly the correct amount, ie 0 if we're not go.
var queueCost = this.queues[j].maxAccountWanted(gameState);
if (this.queues[j].length() > 0 && this.accounts[j][ress] < queueCost[ress] && !this.queues[j].paused)
{
// check that we're not too forward in this resource compared to others.
/*var maxp = this.accounts[j][ress] / (queueCost[ress]+1);
var tooFull = false;
for (tempRess in availableRes)
if (tempRess !== ress && queueCost[tempRess] > 0 && (this.accounts[j][tempRess] / (queueCost[tempRess]+1)) - maxp < -0.2)
tooFull = true;
if (tooFull)
continue;*/
// adding us to the list of queues that need an update.
tempPrio[j] = this.priorities[j];
maxNeed[j] = outQueueCost[ress] + this.queues[j].getNext().getCost()[ress];
// if we have enough of that resource for the outqueue and our first resource in the queue, diminish our priority.
if (this.accounts[j][ress] >= outQueueCost[ress] + this.queues[j].getNext().getCost()[ress])
{
maxNeed[j] = queueCost[ress] - this.accounts[j][ress];
// if we have enough of that resource for our first item in the queue, diminish our priority.
if (this.accounts[j][ress] >= this.queues[j].getNext().getCost()[ress])
tempPrio[j] /= 2;
if (this.queues[j].length() !== 1)
{
var halfcost = this.queues[j].queue[1].getCost()[ress]*0.8;
maxNeed[j] += halfcost;
if (this.accounts[j][ress] >= outQueueCost[ress] + this.queues[j].getNext().getCost()[ress] + halfcost)
delete tempPrio[j];
}
}
if (tempPrio[j])
totalPriority += tempPrio[j];
}
else if (this.accounts[j][ress] > queueCost[ress])
{
this.accounts[j][ress] = queueCost[ress];
}
}
// Now we allow resources to the accounts. We can at most allow "TempPriority/totalpriority*available"
// But we'll sometimes allow less if that would overflow.
for (var j in tempPrio) {
// we'll add at much what can be allowed to this queue.
var toAdd = Math.floor(tempPrio[j]/totalPriority * availableRes[ress]);
// let's check we're not adding too much.
var maxAdd = Math.min(maxNeed[j] - this.accounts[j][ress], toAdd);
var toAdd = tempPrio[j]/totalPriority * availableRes[ress];
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)
{
// 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 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.
continue;
}
// need some discrepancy in priorities
if (this.priorities[queues[under]] < this.priorities[queues[over]] - 20)
{
if (this.accounts[queues[under]] + this.accounts[queues[over]] >= cost)
{
var amnt = cost - this.accounts[queues[over]];
this.accounts[queues[under]] -= amnt;
this.accounts[queues[over]] += amnt;
--over;
debug ("Shifting " + amnt + " from " + queues[under] + " to " +queues[over]);
continue;
} else {
++under;
continue;
}
} else {
break;
}
}
// okaaaay.
}
}*/
}
Engine.ProfileStop();
Engine.ProfileStart("Execute items");
var units_Techs_passed = 0;
// Handle output queues by executing items where possible
for (var p in this.queueArrays) {
var name = this.queueArrays[p][0];
var queue = this.queueArrays[p][1];
var next = queue.outQueueNext();
if (!next)
continue;
if (next.category === "building") {
if (gameState.buildingsBuilt == 0) {
if (next.canExecute(gameState)) {
this.accounts[name].subtract(next.getCost())
//debug ("Starting " + next.type + " substracted " + uneval(next.getCost()))
queue.executeNext(gameState);
gameState.buildingsBuilt += 1;
Engine.ProfileStart("Pick items from queues");
//debug ("start");
//debug (uneval(this.accounts));
// Start the next item in the queue if we can afford it.
for (var i in this.queueArrays)
{
var name = this.queueArrays[i][0];
var queue = this.queueArrays[i][1];
if (queue.length() > 0 && !queue.paused)
{
var item = queue.getNext();
var total = new Resources();
total.add(this.accounts[name]);
if (total.canAfford(item.getCost()))
{
if (item.canStart(gameState))
{
this.accounts[name].subtract(item.getCost());
queue.startNext(gameState);
}
}
} else {
if (units_Techs_passed < 2 && queue.outQueueNext().canExecute(gameState)){
//debug ("Starting " + next.type + " substracted " + uneval(next.getCost()))
this.accounts[name].subtract(next.getCost())
queue.executeNext(gameState);
units_Techs_passed++;
}
} else if (queue.length() === 0) {
this.accounts[name].reset();
}
if (units_Techs_passed >= 2)
continue;
}
//debug (uneval(this.accounts));
Engine.ProfileStop();
if (gameState.ai.playedTurn % 30 === 0)
this.HTMLprintQueues(gameState);
Engine.ProfileStop();
};
QueueManager.prototype.pauseQueue = function(queue, scrapAccounts) {
if (this.queues[queue])
{
this.queues[queue].paused = true;
if (scrapAccounts)
this.accounts[queue].reset();
}
}
QueueManager.prototype.unpauseQueue = function(queue) {
if (this.queues[queue])
this.queues[queue].paused = false;
}
QueueManager.prototype.pauseAll = function(scrapAccounts, but) {
for (var p in this.queues)
if (p != but)
{
if (scrapAccounts)
this.accounts[p].reset();
this.queues[p].paused = true;
}
}
QueueManager.prototype.unpauseAll = function(but) {
for (var p in this.queues)
if (p != but)
this.queues[p].paused = false;
}
QueueManager.prototype.addQueue = function(queueName, priority) {
if (this.queues[queueName] == undefined) {
this.queues[queueName] = new Queue();

View File

@ -4,15 +4,13 @@
var Queue = function() {
this.queue = [];
this.outQueue = [];
this.paused = false;
};
Queue.prototype.empty = function() {
this.queue = [];
this.outQueue = [];
};
Queue.prototype.addItem = function(plan) {
for (var i in this.queue)
{
@ -33,18 +31,26 @@ Queue.prototype.getNext = function() {
}
};
Queue.prototype.outQueueNext = function(){
if (this.outQueue.length > 0) {
return this.outQueue[0];
Queue.prototype.startNext = function(gameState) {
if (this.queue.length > 0) {
this.queue.shift().start(gameState);
return true;
} else {
return null;
return false;
}
};
Queue.prototype.outQueueCost = function(){
// returns the maximal account we'll accept for this queue.
// Currently 100% of the cost of the first element and 80% of that of the second
Queue.prototype.maxAccountWanted = function(gameState) {
var cost = new Resources();
for (var key in this.outQueue){
cost.add(this.outQueue[key].getCost());
if (this.queue.length > 0 && this.queue[0].isGo(gameState))
cost.add(this.queue[0].getCost());
if (this.queue.length > 1 && this.queue[1].isGo(gameState))
{
var costs = this.queue[1].getCost();
costs.multiply(0.8);
cost.add(costs);
}
return cost;
};
@ -57,21 +63,6 @@ Queue.prototype.queueCost = function(){
return cost;
};
Queue.prototype.nextToOutQueue = function(){
if (this.queue.length > 0){
this.outQueue.push(this.queue.shift());
}
};
Queue.prototype.executeNext = function(gameState) {
if (this.outQueue.length > 0) {
this.outQueue.shift().execute(gameState);
return true;
} else {
return false;
}
};
Queue.prototype.length = function() {
return this.queue.length;
};
@ -84,57 +75,23 @@ Queue.prototype.countQueuedUnits = function(){
return count;
};
Queue.prototype.countOutQueuedUnits = function(){
var count = 0;
for (var i in this.outQueue){
count += this.outQueue[i].number;
}
return count;
};
Queue.prototype.countTotalQueuedUnits = function(){
var count = 0;
for (var i in this.queue){
count += this.queue[i].number;
}
for (var i in this.outQueue){
count += this.outQueue[i].number;
}
return count;
};
Queue.prototype.countTotalQueuedUnitsWithClass = function(classe){
Queue.prototype.countQueuedUnitsWithClass = function(classe){
var count = 0;
for (var i in this.queue){
if (this.queue[i].template && this.queue[i].template.hasClass(classe))
count += this.queue[i].number;
}
for (var i in this.outQueue){
if (this.outQueue[i].template && this.outQueue[i].template.hasClass(classe))
count += this.outQueue[i].number;
}
return count;
};
Queue.prototype.countTotalQueuedUnitsWithMetadata = function(data,value){
Queue.prototype.countQueuedUnitsWithMetadata = function(data,value){
var count = 0;
for (var i in this.queue){
if (this.queue[i].metadata[data] && this.queue[i].metadata[data] == value)
count += this.queue[i].number;
}
for (var i in this.outQueue){
if (this.outQueue[i].metadata[data] && this.outQueue[i].metadata[data] == value)
count += this.outQueue[i].number;
}
return count;
};
Queue.prototype.totalLength = function(){
return this.queue.length + this.outQueue.length;
};
Queue.prototype.outQueueLength = function(){
return this.outQueue.length;
};
Queue.prototype.countAllByType = function(t){
var count = 0;
@ -143,10 +100,5 @@ Queue.prototype.countAllByType = function(t){
count += this.queue[i].number;
}
}
for (var i = 0; i < this.outQueue.length; i++){
if (this.outQueue[i].type === t){
count += this.outQueue[i].number;
}
}
return count;
};
};

View File

@ -1,35 +1,55 @@
var BuildingConstructionPlan = function(gameState, type, position) {
var ConstructionPlan = function(gameState, type, metadata, startTime, expectedTime, position) {
this.type = gameState.applyCiv(type);
this.position = position;
this.metadata = metadata;
this.ID = uniqueIDBOPlans++;
this.template = gameState.getTemplate(this.type);
if (!this.template) {
this.invalidTemplate = true;
this.template = undefined;
debug("Cannot build " + this.type);
return;
return false;
}
this.category = "building";
this.cost = new 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;
return true;
};
BuildingConstructionPlan.prototype.canExecute = function(gameState) {
if (this.invalidTemplate){
return false;
}
// return true if we willstart amassing resource for this plan
ConstructionPlan.prototype.isGo = function(gameState) {
return (gameState.getTimeElapsed() > this.startTime);
};
// TODO: verify numeric limits etc
if (this.template.requiredTech() && !gameState.isResearched(this.template.requiredTech()))
// checks other than resource ones.
// TODO: change this.
ConstructionPlan.prototype.canStart = function(gameState) {
if (gameState.buildingsBuilt > 0)
return false;
// TODO: verify numeric limits etc
if (this.template.requiredTech() && !gameState.isResearched(this.template.requiredTech()))
{
return false;
}
var builders = gameState.findBuilders(this.type);
return (builders.length != 0);
};
BuildingConstructionPlan.prototype.execute = function(gameState) {
ConstructionPlan.prototype.start = function(gameState) {
var builders = gameState.findBuilders(this.type).toEntityArray();
// We don't care which builder we assign, since they won't actually
@ -39,45 +59,52 @@ BuildingConstructionPlan.prototype.execute = function(gameState) {
var pos = this.findGoodPosition(gameState);
if (!pos){
if (this.template.hasClass("Naval"))
gameState.ai.modules.economy.dockFailed = true;
gameState.ai.HQ.dockFailed = true;
debug("No room to place " + this.type);
return;
}
if (this.template.hasClass("Naval"))
debug (pos);
gameState.buildingsBuilt++;
if (gameState.getTemplate(this.type).buildCategory() === "Dock")
{
for (var angle = 0; angle < Math.PI * 2; angle += Math.PI/4)
{
builders[0].construct(this.type, pos.x, pos.z, angle);
builders[0].construct(this.type, pos.x, pos.z, angle, this.metadata);
}
} else
builders[0].construct(this.type, pos.x, pos.z, pos.angle);
builders[0].construct(this.type, pos.x, pos.z, pos.angle, this.metadata);
};
BuildingConstructionPlan.prototype.getCost = function() {
return this.cost;
ConstructionPlan.prototype.getCost = function() {
var costs = new Resources();
costs.add(this.cost);
return costs;
};
BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
ConstructionPlan.prototype.findGoodPosition = function(gameState) {
var template = gameState.getTemplate(this.type);
var cellSize = gameState.cellSize; // size of each tile
// First, find all tiles that are far enough away from obstructions:
var obstructionMap = Map.createObstructionMap(gameState,template);
var obstructionMap = Map.createObstructionMap(gameState,0, template);
//obstructionMap.dumpIm(template.buildCategory() + "_obstructions.png");
//obstructionMap.dumpIm(template.buildCategory() + "_obstructions_pre.png");
if (template.buildCategory() !== "Dock")
obstructionMap.expandInfluences();
//obstructionMap.dumpIm(template.buildCategory() + "_obstructions.png");
// Compute each tile's closeness to friendly structures:
var friendlyTiles = new Map(gameState);
var friendlyTiles = new Map(gameState.sharedScript);
var alreadyHasHouses = false;
// If a position was specified then place the building as close to it as possible
if (this.position){
var x = Math.round(this.position[0] / cellSize);
@ -116,7 +143,6 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
{
friendlyTiles.addInfluence(x, z, 30, -50);
} else if (template.genericName() == "House") {
friendlyTiles.addInfluence(x, z, Math.ceil(infl/2.0),infl); // houses are farther away from other buildings but houses
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"))
{
@ -139,6 +165,11 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
}
}
});
if (this.metadata && this.metadata.base !== undefined)
for (var base in gameState.ai.HQ.baseManagers)
if (base != this.metadata.base)
for (var j in gameState.ai.HQ.baseManagers[base].territoryIndices)
friendlyTiles.map[gameState.ai.HQ.baseManagers[base].territoryIndices[j]] = 0;
}
//friendlyTiles.dumpIm(template.buildCategory() + "_" +gameState.getTimeElapsed() + ".png", 200);
@ -149,7 +180,7 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
// also not for fields who can be stacked quite a bit
var radius = 0;
if (template.genericName() == "Field")
radius = Math.ceil(template.obstructionRadius() / cellSize) - 0.4;
radius = Math.ceil(template.obstructionRadius() / cellSize);
else if (template.hasClass("GarrisonFortress"))
radius = Math.ceil(template.obstructionRadius() / cellSize) + 2;
else if (template.buildCategory() === "Dock")
@ -191,8 +222,8 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
var angle = 3*Math.PI/4;
return {
"x" : x,
"z" : z,
"x" : x+2,
"z" : z+2,
"angle" : angle
};
};

View File

@ -1,33 +1,51 @@
var ResearchPlan = function(gameState, type, rush) {
var ResearchPlan = function(gameState, type, startTime, expectedTime, rush) {
this.type = type;
this.ID = uniqueIDBOPlans++;
this.template = gameState.getTemplate(this.type);
if (!this.template || this.template.researchTime === undefined) {
this.invalidTemplate = true;
this.template = undefined;
debug ("Invalid research");
return false;
}
this.category = "technology";
this.cost = new 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;
return true;
};
ResearchPlan.prototype.canExecute = function(gameState) {
if (this.invalidTemplate)
return false;
// return true if we willstart amassing resource for this plan
ResearchPlan.prototype.isGo = function(gameState) {
return (gameState.getTimeElapsed() > this.startTime);
};
ResearchPlan.prototype.canStart = function(gameState) {
// also checks canResearch
return (gameState.findResearchers(this.type).length !== 0);
};
ResearchPlan.prototype.execute = function(gameState) {
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();
//debug ("Starting the research plan for " + this.type);
var trainers = gameState.findResearchers(this.type).toEntityArray();
@ -49,6 +67,8 @@ ResearchPlan.prototype.execute = function(gameState) {
};
ResearchPlan.prototype.getCost = function(){
return this.cost;
var costs = new Resources();
costs.add(this.cost);
return costs;
};

View File

@ -1,27 +1,43 @@
var UnitTrainingPlan = function(gameState, type, metadata, number, maxMerge) {
var TrainingPlan = function(gameState, type, metadata, number, startTime, expectedTime, maxMerge) {
this.type = gameState.applyCiv(type);
this.metadata = metadata;
this.ID = uniqueIDBOPlans++;
this.template = gameState.getTemplate(this.type);
if (!this.template) {
this.invalidTemplate = true;
this.template = undefined;
return;
}
this.category= "unit";
if (!this.template)
return false;
this.category = "unit";
this.cost = new Resources(this.template.cost(), this.template._template.Cost.Population);
if (!number){
if (!number)
this.number = 1;
}else{
else
this.number = number;
}
if (!maxMerge)
this.maxMerge = 5;
else
this.maxMerge = maxMerge;
if (!startTime)
this.startTime = 0;
else
this.startTime = startTime;
if (!expectedTime)
this.expectedTime = -1;
else
this.expectedTime = expectedTime;
return true;
};
UnitTrainingPlan.prototype.canExecute = function(gameState) {
// return true if we willstart amassing resource for this plan
TrainingPlan.prototype.isGo = function(gameState) {
return (gameState.getTimeElapsed() > this.startTime);
};
TrainingPlan.prototype.canStart = function(gameState) {
if (this.invalidTemplate)
return false;
@ -32,8 +48,8 @@ UnitTrainingPlan.prototype.canExecute = function(gameState) {
return (trainers.length != 0);
};
UnitTrainingPlan.prototype.execute = function(gameState) {
//warn("Executing UnitTrainingPlan " + uneval(this));
TrainingPlan.prototype.start = function(gameState) {
//warn("Executing TrainingPlan " + uneval(this));
var self = this;
var trainers = gameState.findTrainers(this.type).toEntityArray();
@ -50,20 +66,21 @@ UnitTrainingPlan.prototype.execute = function(gameState) {
bb += 0.9;
return (aa - bb);
});
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);
}
};
UnitTrainingPlan.prototype.getCost = function(){
TrainingPlan.prototype.getCost = function(){
var multCost = new Resources();
multCost.add(this.cost);
multCost.multiply(this.number);
return multCost;
};
UnitTrainingPlan.prototype.addItem = function(amount){
TrainingPlan.prototype.addItem = function(amount){
if (amount === undefined)
amount = 1;
this.number += amount;
};
};

View File

@ -6,10 +6,11 @@ var Worker = function(ent) {
this.ent = ent;
this.maxApproachTime = 45000;
this.unsatisfactoryResource = false; // if true we'll reguarly check if we can't have better now.
this.baseID = 0;
};
Worker.prototype.update = function(gameState) {
Worker.prototype.update = function(baseManager, gameState) {
this.baseID = baseManager.ID;
var subrole = this.ent.getMetadata(PlayerID, "subrole");
if (!this.ent.position() || (this.ent.getMetadata(PlayerID,"fleeing") && gameState.getTimeElapsed() - this.ent.getMetadata(PlayerID,"fleeing") < 8000)){
@ -19,14 +20,30 @@ Worker.prototype.update = function(gameState) {
if (this.ent.getMetadata(PlayerID,"fleeing"))
this.ent.setMetadata(PlayerID,"fleeing", undefined);
// Okay so we have a few tasks.
// If we're gathering, we'll check that we haven't run idle.
// ANd we'll also check that we're gathering a resource we want to gather.
// If we're fighting, let's not start gathering, heh?
// TODO: remove this when we're hunting?
if (this.ent.unitAIState().split(".")[1] === "COMBAT" || this.ent.getMetadata(PlayerID, "role") === "transport")
{
return;
}
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 ||
if (this.ent.isIdle()) {
// if we aren't storing resources or it's the same type as what we're about to gather,
// let's just pick a new resource.
if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0 ||
this.ent.resourceCarrying()[0].type === this.ent.getMetadata(PlayerID, "gather-type")){
Engine.ProfileStart("Start Gathering");
this.startGathering(gameState);
this.unsatisfactoryResource = false;
this.startGathering(baseManager,gameState);
Engine.ProfileStop();
this.startApproachingResourceTime = gameState.getTimeElapsed();
} else {
// Should deposit resources
Engine.ProfileStart("Return Resources");
@ -34,10 +51,12 @@ Worker.prototype.update = function(gameState) {
{
// no dropsite, abandon cargo.
// if we have a new order
// if we were ordered to gather something else, try that.
if (this.ent.resourceCarrying()[0].type !== this.ent.getMetadata(PlayerID, "gather-type"))
this.startGathering(gameState);
this.startGathering(baseManager,gameState);
else {
// okay so we haven't found a proper dropsite for the resource we're supposed to gather
// so let's get idle and the base manager will reassign us, hopefully well.
this.ent.setMetadata(PlayerID, "gather-type",undefined);
this.ent.setMetadata(PlayerID, "subrole", "idle");
this.ent.stopMoving();
@ -45,16 +64,53 @@ Worker.prototype.update = function(gameState) {
}
Engine.ProfileStop();
}
this.startApproachingResourceTime = gameState.getTimeElapsed();
// debug: show the resource we're gathering from
//Engine.PostCommand({"type": "set-shading-color", "entities": [this.ent.id()], "rgb": [10,0,0]});
} else if (this.ent.unitAIState().split(".")[1] === "GATHER") {
if (this.unsatisfactoryResource && (this.ent.id() + gameState.ai.playedTurn) % 20 === 0)
// check for transport.
if (gameState.ai.playedTurn % 5 === 0)
{
if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[2] === "APPROACHING" && this.ent.unitAIOrderData()[0]["target"])
{
var ress = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]);
if (ress !== undefined)
{
var index = gameState.ai.accessibility.getAccessValue(ress.position());
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());
}
}
}
}
/*
if (gameState.getTimeElapsed() - this.startApproachingResourceTime > this.maxApproachTime)
{
if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[1] === "GATHER"
&& this.ent.unitAIOrderData()[0]["target"])
{
var ent = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]);
debug ("here " + this.startApproachingResourceAmount + "," + ent.resourceSupplyAmount());
if (ent && this.startApproachingResourceAmount == ent.resourceSupplyAmount() && this.startEnt == ent.id()) {
debug (ent.toString() + " is inaccessible");
ent.setMetadata(PlayerID, "inaccessible", true);
this.ent.flee(ent);
this.ent.setMetadata(PlayerID, "subrole", "idle");
}
}
}*/
// we're gathering. Let's check that it's not a resource we'd rather not gather from.
if ((this.ent.id() + gameState.ai.playedTurn) % 6 === 0 && this.checkUnsatisfactoryResource(gameState))
{
Engine.ProfileStart("Start Gathering");
this.startGathering(gameState);
this.startGathering(baseManager,gameState);
Engine.ProfileStop();
}
// TODO: reimplement the "reaching time" check.
/*if (gameState.getTimeElapsed() - this.startApproachingResourceTime > this.maxApproachTime) {
if (this.gatheringFrom) {
var ent = gameState.getEntityById(this.gatheringFrom);
@ -67,7 +123,7 @@ Worker.prototype.update = function(gameState) {
this.gatheringFrom = undefined;
}
}
}*/
}*/
} else if (this.ent.unitAIState().split(".")[1] === "COMBAT") {
/*if (gameState.getTimeElapsed() - this.startApproachingResourceTime > this.maxApproachTime) {
var ent = gameState.getEntityById(this.ent.unitAIOrderData()[0].target);
@ -80,41 +136,78 @@ Worker.prototype.update = function(gameState) {
this.gatheringFrom = undefined;
}
}*/
} else {
this.startApproachingResourceTime = gameState.getTimeElapsed();
}
} else if(subrole === "builder") {
if (this.ent.unitAIState().split(".")[1] !== "REPAIR"){
var target = this.ent.getMetadata(PlayerID, "target-foundation");
if (target.foundationProgress() === undefined && target.needsRepair() == false)
this.ent.setMetadata(PlayerID, "subrole", "idle");
// check for transport.
if (gameState.ai.playedTurn % 5 === 0)
{
if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[2] === "APPROACHING" && this.ent.unitAIOrderData()[0]["target"])
{
var ress = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]);
if (ress !== undefined)
{
var index = gameState.ai.accessibility.getAccessValue(ress.position());
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());
}
}
}
}
if (this.ent.unitAIState().split(".")[1] !== "REPAIR") {
var target = gameState.getEntityById(this.ent.getMetadata(PlayerID, "target-foundation"));
// okay so apparently we aren't working.
// Unless we've been explicitely told to keep our role, make us idle.
if (!target || target.foundationProgress() === undefined && target.needsRepair() == false)
{
if (!this.ent.getMetadata(PlayerID, "keepSubrole"))
this.ent.setMetadata(PlayerID, "subrole", "idle");
}
else
this.ent.repair(target);
}
this.startApproachingResourceTime = gameState.getTimeElapsed();
//Engine.PostCommand({"type": "set-shading-color", "entities": [this.ent.id()], "rgb": [0,10,0]});
// TODO: we should maybe decide on our own to build other buildings, not rely on the assigntofoundation stuff.
} else if(subrole === "hunter") {
if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0){
if (this.ent.isIdle()){
Engine.ProfileStart("Start Hunting");
this.startHunting(gameState);
this.startHunting(gameState, baseManager);
Engine.ProfileStop();
}
} else {
this.startApproachingResourceTime = gameState.getTimeElapsed();
}
};
Worker.prototype.startGathering = function(gameState){
// check if our current resource is unsatisfactory
// this can happen in two ways:
// -either we were on an unsatisfactory resource last time we started gathering (this.unsatisfactoryResource)
// -Or we auto-moved to a bad resource thanks to the great UnitAI.
Worker.prototype.checkUnsatisfactoryResource = function(gameState) {
if (this.unsatisfactoryResource)
return true;
if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[1] === "GATHER" && this.ent.unitAIState().split(".")[2] === "GATHERING" && this.ent.unitAIOrderData()[0]["target"])
{
var ress = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]);
if (!ress || !ress.getMetadata(PlayerID,"linked-dropsite") || !ress.getMetadata(PlayerID,"linked-dropsite-nearby") || gameState.ai.accessibility.getAccessValue(ress.position()) === -1)
return true;
}
return false;
};
Worker.prototype.startGathering = function(baseManager, gameState) {
var resource = this.ent.getMetadata(PlayerID, "gather-type");
var ent = this.ent;
var self = this;
if (!ent.position()){
// TODO: work out what to do when entity has no position
return;
}
this.unsatisfactoryResource = false;
// TODO: this is not necessarily optimal.
// find closest dropsite which has nearby resources of the correct type
@ -125,67 +218,77 @@ Worker.prototype.startGathering = function(gameState){
// first step: count how many dropsites we have that have enough resources "close" to them.
// TODO: this is a huge part of multi-base support. Count only those in the same base as the worker.
var number = 0;
var ourDropsites = gameState.getOwnDropsites(resource);
var ourDropsites = EntityCollectionFromIds(gameState,Object.keys(baseManager.dropsites));
if (ourDropsites.length === 0)
{
debug ("We do not have a dropsite for " + resource + ", aborting");
return;
}
ourDropsites.forEach(function (dropsite) {
if (dropsite.getMetadata(PlayerID, "linked-resources-" +resource) !== undefined
&& dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) !== undefined && dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) > 200) {
number++;
}
});
var maxPerDP = 20;
if (resource === "food")
maxPerDP = 200;
//debug ("Available " +resource + " dropsites: " +ourDropsites.length);
ourDropsites.forEach(function (dropsite) {
if (baseManager.dropsites[dropsite.id()][resource] && baseManager.dropsites[dropsite.id()][resource][4] > 1000
&& baseManager.dropsites[dropsite.id()][resource][5].length < maxPerDP)
number++;
});
// Allright second step, if there are any such dropsites, we pick the closest.
// we pick one with a lot of resource, or we pick the only one available (if it's high enough, otherwise we'll see with "far" below).
if (number > 0)
{
ourDropsites.forEach(function (dropsite) { //}){
if (dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) == undefined)
if (baseManager.dropsites[dropsite.id()][resource] === undefined)
return;
if (dropsite.position() && (dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) > 700 || (number === 1 && dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) > 200) ) ) {
if (dropsite.position() && (baseManager.dropsites[dropsite.id()][resource][4] > 1000 || (number === 1 && baseManager.dropsites[dropsite.id()][resource][4] > 200) )
&& baseManager.dropsites[dropsite.id()][resource][5].length < maxPerDP) {
var dist = SquareVectorDistance(ent.position(), dropsite.position());
if (dist < minDropsiteDist){
minDropsiteDist = dist;
nearestResources = dropsite.getMetadata(PlayerID, "linked-resources-" + resource);
nearestResources = baseManager.dropsites[dropsite.id()][resource][1];
nearestDropsite = dropsite;
}
}
});
}
//debug ("Nearest dropsite: " +nearestDropsite);
// Now if we have no dropsites, we repeat the process with resources "far" from dropsites but still linked with them.
// I add the "close" value for code sanity.
// Again, we choose a dropsite with a lot of resources left, or we pick the only one available (in this case whatever happens).
// we've found no fitting dropsites close enough from us.
// So'll try with far away.
if (!nearestResources || nearestResources.length === 0) {
//debug ("here(1)");
gameState.getOwnDropsites(resource).forEach(function (dropsite){ //}){
var quantity = dropsite.getMetadata(PlayerID, "resource-quantity-" +resource)+dropsite.getMetadata(PlayerID, "resource-quantity-far-" +resource);
if (dropsite.position() && (quantity) > 700 || number === 1) {
ourDropsites.forEach(function (dropsite) { //}){
if (baseManager.dropsites[dropsite.id()][resource] === undefined)
return;
if (dropsite.position() && baseManager.dropsites[dropsite.id()][resource][4] > 400
&& baseManager.dropsites[dropsite.id()][resource][5].length < maxPerDP) {
var dist = SquareVectorDistance(ent.position(), dropsite.position());
if (dist < minDropsiteDist){
minDropsiteDist = dist;
nearestResources = dropsite.getMetadata(PlayerID, "linked-resources-" + resource);
nearestResources = baseManager.dropsites[dropsite.id()][resource][1];
nearestDropsite = dropsite;
}
}
});
this.unsatisfactoryResource = true;
//debug ("Nearest dropsite: " +nearestDropsite);
}
// If we still haven't found any fitting dropsite...
// Then we'll just pick any resource, and we'll check for the closest dropsite to that one
if (!nearestResources || nearestResources.length === 0){
if (resource === "food")
if (this.buildAnyField(gameState))
return;
if (this.unsatisfactoryResource == true)
return; // we were already not satisfied, we're still not, change not.
if (gameState.ai.HQ.switchWorkerBase(gameState, this.ent, resource))
return;
//debug ("No fitting dropsite for " + resource + " found, iterating the map.");
nearestResources = gameState.getResourceSupplies(resource);
this.unsatisfactoryResource = true;
// TODO: should try setting up dropsites.
} else {
this.unsatisfactoryResource = false;
}
if (nearestResources.length === 0){
@ -193,10 +296,16 @@ Worker.prototype.startGathering = function(gameState){
{
if (this.buildAnyField(gameState))
return;
if (gameState.ai.HQ.switchWorkerBase(gameState, this.ent, resource))
return;
debug("No " + resource + " found! (1)");
}
else
{
if (gameState.ai.HQ.switchWorkerBase(gameState, this.ent, resource))
return;
debug("No " + resource + " found! (1)");
}
return;
}
//debug("Found " + nearestResources.length + "spots for " + resource);
@ -213,7 +322,6 @@ Worker.prototype.startGathering = function(gameState){
// filter resources
// TODo: add a bonus for resources with a lot of resources left, perhaps, to spread gathering?
nearestResources.forEach(function(supply) { //}){
// sanity check, perhaps sheep could be garrisoned?
if (!supply.position()) {
//debug ("noposition");
@ -224,28 +332,28 @@ Worker.prototype.startGathering = function(gameState){
//debug ("inaccessible");
return;
}
if (supply.isFull() === true || (supply.maxGatherers() - supply.resourceSupplyGatherers().length == 0) ||
(gameState.turnCache["ressGathererNB"] && gameState.turnCache["ressGathererNB"][supply.id()]
&& gameState.turnCache["ressGathererNB"][supply.id()] + supply.resourceSupplyGatherers().length >= supply.maxGatherers())) {
if (supply.isFull() === true
|| (gameState.turnCache["ressGathererNB"] && gameState.turnCache["ressGathererNB"][supply.id()]
&& gameState.turnCache["ressGathererNB"][supply.id()] + supply.resourceSupplyGatherers().length >= supply.maxGatherers))
return;
}
// Don't gather enemy farms
if (!supply.isOwn(PlayerID) && supply.owner() !== 0) {
// Don't gather enemy farms or farms from another base
if ((!supply.isOwn(PlayerID) && supply.owner() !== 0) || (supply.isOwn(PlayerID) && supply.getMetadata(PlayerID,"base") !== self.baseID)) {
//debug ("enemy");
return;
}
// quickscope accessbility check.
if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position(), true)) {
if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position())) {
//debug ("nopath");
return;
}
// some simple check for chickens: if they're in a square that's inaccessible, we won't gather from them.
if (supply.footprintRadius() < 1)
{
var fakeMap = new Map(gameState,gameState.getMap().data);
var fakeMap = new Map(gameState.sharedScript,gameState.getMap().data);
var id = fakeMap.gamePosToMapPos(supply.position())[0] + fakeMap.width*fakeMap.gamePosToMapPos(supply.position())[1];
if ( (gameState.sharedScript.passabilityClasses["pathfinderObstruction"] & gameState.getMap().data[id]) )
{
@ -268,12 +376,10 @@ Worker.prototype.startGathering = function(gameState){
var territoryOwner = Map.createTerritoryMap(gameState).getOwner(supply.position());
if (territoryOwner != PlayerID && territoryOwner != 0) {
dist *= 3.0;
dist *= 5.0;
//return;
}
// Go for treasure as a priority
if (dist < 40000 && supply.resourceSupplyType().generic == "treasure"){
} else if (dist < 40000 && supply.resourceSupplyType().generic == "treasure"){
// go for treasures if they're not in enemy territory
dist /= 1000;
}
@ -282,7 +388,6 @@ Worker.prototype.startGathering = function(gameState){
nearestSupply = supply;
}
});
if (nearestSupply) {
var pos = nearestSupply.position();
@ -310,11 +415,11 @@ Worker.prototype.startGathering = function(gameState){
{
tried = this.buildAnyField(gameState);
if (!tried && SquareVectorDistance(pos,this.ent.position()) > 62500) {
// TODO: ought to change behavior here.
return; // wait. a farm should appear.
}
}
if (!tried) {
if (!gameState.turnCache["ressGathererNB"])
{
gameState.turnCache["ressGathererNB"] = {};
@ -324,41 +429,28 @@ Worker.prototype.startGathering = function(gameState){
else
gameState.turnCache["ressGathererNB"][nearestSupply.id()]++;
this.maxApproachTime = Math.max(25000, VectorDistance(pos,this.ent.position()) * 1000);
this.maxApproachTime = Math.max(30000, VectorDistance(pos,this.ent.position()) * 5000);
this.startApproachingResourceAmount = ent.resourceSupplyAmount();
this.startEnt = ent.id();
ent.gather(nearestSupply);
ent.setMetadata(PlayerID, "target-foundation", undefined);
// check if the resource we've started gathering from is now full, in which case inform the dropsite.
if (gameState.turnCache["ressGathererNB"][nearestSupply.id()] + nearestSupply.resourceSupplyGatherers().length >= nearestSupply.maxGatherers()
&& nearestSupply.getMetadata(PlayerID, "linked-dropsite") != undefined)
{
var dropsite = gameState.getEntityById(nearestSupply.getMetadata(PlayerID, "linked-dropsite"));
if (dropsite == undefined || dropsite.getMetadata(PlayerID, "linked-resources-" + resource) === undefined)
return;
if (nearestSupply.getMetadata(PlayerID, "linked-dropsite-nearby") == true) {
dropsite.setMetadata(PlayerID, "resource-quantity-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) - (+nearestSupply.getMetadata(PlayerID, "dp-update-value")));
dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(nearestSupply);
dropsite.getMetadata(PlayerID, "nearby-resources-" + resource).updateEnt(nearestSupply);
} else {
dropsite.setMetadata(PlayerID, "resource-quantity-far-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) - (+nearestSupply.getMetadata(PlayerID, "dp-update-value")));
dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(nearestSupply);
}
}
}
} else {
if (resource === "food" && this.buildAnyField(gameState))
return;
debug("No " + resource + " found! (2)");
if (gameState.ai.HQ.switchWorkerBase(gameState, this.ent, resource))
return;
if (resource !== "food")
debug("No " + resource + " found! (2)");
// If we had a fitting closest dropsite with a lot of resources, mark it as not good. It means it's probably full. Then retry.
// it'll be resetted next time it's counted anyway.
if (nearestDropsite && nearestDropsite.getMetadata(PlayerID, "resource-quantity-" +resource)+nearestDropsite.getMetadata(PlayerID, "resource-quantity-far-" +resource) > 400)
{
nearestDropsite.setMetadata(PlayerID, "resource-quantity-" +resource, 0);
nearestDropsite.setMetadata(PlayerID, "resource-quantity-far-" +resource, 0);
this.startGathering(gameState);
this.startGathering(baseManager, gameState);
}
}
};
@ -397,10 +489,10 @@ Worker.prototype.returnResources = function(gameState){
return true;
};
Worker.prototype.startHunting = function(gameState){
Worker.prototype.startHunting = function(gameState, baseManager){
var ent = this.ent;
if (!ent.position() || ent.getMetadata(PlayerID, "stoppedHunting"))
if (!ent.position() || !baseManager.isHunting)
return;
// So here we're doing it basic. We check what we can hunt, we hunt it. No fancies.
@ -438,7 +530,7 @@ Worker.prototype.startHunting = function(gameState){
}
// quickscope accessbility check
if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position(), true))
if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position(),false, true))
return;
if (dist < nearestSupplyDist) {
@ -464,20 +556,20 @@ Worker.prototype.startHunting = function(gameState){
});
if (!nearestDropsite)
{
ent.setMetadata(PlayerID, "stoppedHunting", true);
baseManager.isHunting = false;
ent.setMetadata(PlayerID, "role", undefined);
debug ("No dropsite for hunting food");
return;
}
if (minDropsiteDist > 45000) {
ent.setMetadata(PlayerID, "stoppedHunting", true);
if (minDropsiteDist > 35000) {
baseManager.isHunting = false;
ent.setMetadata(PlayerID, "role", undefined);
} else {
ent.gather(nearestSupply);
ent.setMetadata(PlayerID, "target-foundation", undefined);
}
} else {
ent.setMetadata(PlayerID, "stoppedHunting", true);
baseManager.isHunting = false;
ent.setMetadata(PlayerID, "role", undefined);
debug("No food found for hunting! (2)");
}
@ -495,10 +587,32 @@ Worker.prototype.getResourceType = function(type){
}
};
Worker.prototype.getGatherRate = function(gameState) {
if (this.ent.getMetadata(PlayerID,"subrole") !== "gatherer")
return 0;
var rates = this.ent.resourceGatherRates();
if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[1] === "GATHER" && this.ent.unitAIOrderData()[0]["target"])
{
var ress = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]);
if (!ress)
return 0;
var type = ress.resourceSupplyType();
if (type.generic == "treasure")
return 1000;
var tstring = type.generic + "." + type.specific;
//debug (+rates[tstring] + " for " + tstring + " for " + this.ent._templateName);
if (rates[tstring])
return rates[tstring];
return 0;
}
return 0;
};
Worker.prototype.buildAnyField = function(gameState){
var self = this;
var okay = false;
var foundations = gameState.getOwnFoundations();
var foundations = gameState.getOwnFoundations().filter(Filters.byMetadata(PlayerID,"base",this.baseID));
foundations.filterNearest(this.ent.position(), foundations.length);
foundations.forEach(function (found) {
if (found._template.BuildRestrictions.Category === "Field" && !okay) {
@ -507,5 +621,18 @@ Worker.prototype.buildAnyField = function(gameState){
return;
}
});
if (!okay)
{
var foundations = gameState.getOwnFoundations();
foundations.filterNearest(this.ent.position(), foundations.length);
foundations.forEach(function (found) {
if (found._template.BuildRestrictions.Category === "Field" && !okay) {
self.ent.repair(found);
self.ent.setMetadata(PlayerID,"base", found.getMetadata(PlayerID,"base"));
okay = true;
return;
}
});
}
return okay;
};

View File

@ -12,6 +12,7 @@ function BaseAI(settings)
PlayerID = this._player;
this.turn = 0;
this.timeElapsed = 0;
}
//Return a simple object (using no classes etc) that will be serialized
@ -35,13 +36,14 @@ BaseAI.prototype.InitWithSharedScript = function(state, sharedAI)
this.terrainAnalyzer = sharedAI.terrainAnalyzer;
this.passabilityClasses = sharedAI.passabilityClasses;
this.passabilityMap = sharedAI.passabilityMap;
this.territoryMap = sharedAI.territoryMap;
this.timeElapsed = state.timeElapsed;
var gameState = sharedAI.gameState[PlayerID];
gameState.ai = this;
this.gameState = sharedAI.gameState[PlayerID];
this.gameState.ai = this;
this.sharedScript = sharedAI;
this.InitShared(gameState, sharedAI);
delete gameState.ai;
this.InitShared(this.gameState, this.sharedScript);
}
BaseAI.prototype.HandleMessage = function(state, sharedAI)
@ -59,7 +61,7 @@ BaseAI.prototype.HandleMessage = function(state, sharedAI)
this.timeElapsed = sharedAI.timeElapsed;
this.accessibility = sharedAI.accessibility;
this.terrainAnalyzer = sharedAI.terrainAnalyzer;
this.techModifications = sharedAI.techModifications[this._player];
this.techModifications = sharedAI._techModifications[this._player];
Engine.ProfileStop();

View File

@ -32,7 +32,14 @@ var EntityTemplate = Class({
return undefined;
return this._template.Identity.RequiredTechnology;
},
available: function(gameState) {
if (!this._template.Identity || !this._template.Identity.RequiredTechnology)
return true;
return gameState.isResearched(this._template.Identity.RequiredTechnology);
},
// specifically
phase: function() {
if (!this._template.Identity || !this._template.Identity.RequiredTechnology)
return 0;
@ -412,7 +419,25 @@ var EntityTemplate = Class({
var territories = this.buildTerritories();
return (territories && territories.indexOf(territory) != -1);
},
hasTerritoryInfluence: function() {
return (this._template.TerritoryInfluence !== undefined);
},
territoryInfluenceRadius: function() {
if (this._template.TerritoryInfluence !== undefined)
return (this._template.TerritoryInfluence.Radius);
else
return -1;
},
territoryInfluenceWeight: function() {
if (this._template.TerritoryInfluence !== undefined)
return (this._template.TerritoryInfluence.Weight);
else
return -1;
},
visionRange: function() {
if (!this._template.Vision)
return undefined;
@ -427,7 +452,7 @@ var Entity = Class({
_init: function(sharedAI, entity)
{
this._super.call(this, sharedAI.GetTemplate(entity.template), sharedAI.techModifications[entity.owner]);
this._super.call(this, sharedAI.GetTemplate(entity.template), sharedAI._techModifications[entity.owner]);
this._ai = sharedAI;
this._templateName = entity.template;
@ -462,9 +487,13 @@ var Entity = Class({
this._ai.setMetadata(player, this, key, value);
},
deleteMetadata: function(player) {
deleteAllMetadata: function(player) {
delete this._ai._entityMetadata[player][this.id()];
},
deleteMetadata: function(player, key) {
this._ai.deleteMetadata(player, this, key);
},
position: function() { return this._entity.position; },
@ -500,7 +529,7 @@ var Entity = Class({
},
foundationProgress: function() {
if (typeof this._entity.foundationProgress === "undefined")
if (this._entity.foundationProgress == undefined)
return undefined;
return this._entity.foundationProgress;
},
@ -537,6 +566,7 @@ var Entity = Class({
{
if (this._entity.resourceSupplyGatherers !== undefined)
return (this.maxGatherers() === this._entity.resourceSupplyGatherers.length);
return undefined;
},
@ -547,6 +577,8 @@ var Entity = Class({
},
garrisoned: function() { return new EntityCollection(this._ai, this._entity.garrisoned); },
canGarrisonInside: function() { return this._entity.garrisoned.length < this.garrisonMax(); },
// TODO: visibility
@ -658,7 +690,7 @@ var Entity = Class({
return this;
},
construct: function(template, x, z, angle) {
construct: function(template, x, z, angle, metadata) {
// TODO: verify this unit can construct this, just for internal
// sanity-checking and error reporting
@ -671,7 +703,8 @@ var Entity = Class({
"angle": angle,
"autorepair": false,
"autocontinue": false,
"queued": false
"queued": false,
"metadata" : metadata // can be undefined
});
return this;
},

View File

@ -3,6 +3,9 @@ function EntityCollection(sharedAI, entities, filters)
this._ai = sharedAI;
this._entities = entities || {};
this._filters = filters || [];
this._quickIter = false; // will make the entity collection store an array (not associative) of entities used when calling "foreach".
// probably should not be usde for very dynamic entity collections.
// Compute length lazily on demand, since it can be
// expensive for large collections
@ -23,6 +26,8 @@ function EntityCollection(sharedAI, entities, filters)
// If an entitycollection is frozen, it will never automatically add a unit.
// But can remove one.
// this makes it easy to create entity collection that will auto-remove dead units
// but never add new ones.
EntityCollection.prototype.freeze = function()
{
this.frozen = true;
@ -32,6 +37,14 @@ EntityCollection.prototype.defreeze = function()
this.frozen = false;
};
EntityCollection.prototype.allowQuickIter = function()
{
this._quickIter = true;
this._entitiesArray = [];
for each (var ent in this._entities)
this._entitiesArray.push(ent);
};
EntityCollection.prototype.toIdArray = function()
{
var ret = [];
@ -42,6 +55,8 @@ EntityCollection.prototype.toIdArray = function()
EntityCollection.prototype.toEntityArray = function()
{
if (this._quickIter === true)
return this._entitiesArray;
var ret = [];
for each (var ent in this._entities)
ret.push(ent);
@ -59,12 +74,23 @@ EntityCollection.prototype.toString = function()
EntityCollection.prototype.filterNearest = function(targetPos, n)
{
// Compute the distance of each entity
var data = []; // [ [id, ent, distance], ... ]
for (var id in this._entities)
var data = []; // [id, ent, distance]
if (this._quickIter === true)
{
var ent = this._entities[id];
if (ent.position())
data.push([id, ent, VectorDistance(targetPos, ent.position())]);
for (var i in this._entitiesArray)
{
var ent = this._entitiesArray[i];
if (ent.position() !== -1)
data.push([ent.id(), ent, SquareVectorDistance(targetPos, ent.position())]);
}
} else {
for (var id in this._entities)
{
var ent = this._entities[id];
if (ent.position() !== -1)
data.push([id, ent, SquareVectorDistance(targetPos, ent.position())]);
}
}
// Sort by increasing distance
@ -72,8 +98,9 @@ EntityCollection.prototype.filterNearest = function(targetPos, n)
// Extract the first n
var ret = {};
for each (var val in data.slice(0, n))
ret[val[0]] = val[1];
var length = Math.min(n, entData.length);
for (var i = 0; i < length; ++i)
ret[data[i][0]] = data[i][1];
return new EntityCollection(this._ai, ret);
};
@ -84,11 +111,22 @@ EntityCollection.prototype.filter = function(filter, thisp)
filter = {"func": filter, "dynamicProperties": []};
var ret = {};
for (var id in this._entities)
if (this._quickIter === true)
{
var ent = this._entities[id];
if (filter.func.call(thisp, ent, id, this))
ret[id] = ent;
for (var i in this._entitiesArray)
{
var ent = this._entitiesArray[i];
var id = ent.id();
if (filter.func.call(thisp, ent, id, this))
ret[id] = ent;
}
} else {
for (var id in this._entities)
{
var ent = this._entities[id];
if (filter.func.call(thisp, ent, id, this))
ret[id] = ent;
}
}
return new EntityCollection(this._ai, ret, this._filters.concat([filter]));
@ -107,6 +145,23 @@ EntityCollection.prototype.filter_raw = function(callback, thisp)
return new EntityCollection(this._ai, ret);
};
EntityCollection.prototype.forEach = function(callback)
{
if (this._quickIter === true)
{
for (var id in this._entitiesArray)
{
callback(this._entitiesArray[id]);
}
return this;
}
for (var id in this._entities)
{
callback(this._entities[id]);
}
return this;
};
EntityCollection.prototype.filterNearest = function(targetPos, n)
{
// Compute the distance of each entity
@ -130,16 +185,6 @@ EntityCollection.prototype.filterNearest = function(targetPos, n)
return new EntityCollection(this._ai, ret);
};
EntityCollection.prototype.forEach = function(callback, thisp)
{
for (var id in this._entities)
{
var ent = this._entities[id];
callback.call(thisp, ent, id, this);
}
return this;
};
EntityCollection.prototype.move = function(x, z, queued)
{
queued = queued || false;
@ -241,6 +286,8 @@ EntityCollection.prototype.removeEnt = function(ent)
// Checking length may initialize it, so do it before deleting.
if (this.length !== undefined)
this._length--;
if (this._quickIter === true)
this._entitiesArray.splice(this._entitiesArray.indexOf(ent),1);
delete this._entities[ent.id()];
return true;
}
@ -263,6 +310,8 @@ EntityCollection.prototype.addEnt = function(ent)
if (this.length !== undefined)
this._length++;
this._entities[ent.id()] = ent;
if (this._quickIter === true)
this._entitiesArray.push(ent);
return true;
}
};
@ -297,6 +346,11 @@ EntityCollection.prototype.registerUpdates = function(noPush)
this._ai.registerUpdatingEntityCollection(this,noPush);
};
EntityCollection.prototype.unregister = function()
{
this._ai.removeUpdatingEntityCollection(this);
};
EntityCollection.prototype.dynamicProperties = function()
{
var ret = [];

View File

@ -41,6 +41,15 @@ var Filters = {
},
"dynamicProperties": ['metadata.' + key]};
},
// can be used for stuffs which won't change once entities are created.
byStaticMetadata: function(player, key, value){
return {"func" : function(ent){
return (ent.getMetadata(player, key) == value);
},
"dynamicProperties": []};
},
byHasMetadata: function(player, key){
return {"func" : function(ent){
return (ent.getMetadata(player, key) != undefined);
@ -97,7 +106,7 @@ var Filters = {
byCanGarrison: function(){
return {"func" : function(ent){
return ent.garrisonMax();
return ent.garrisonMax() > 0;
},
"dynamicProperties": []};
},
@ -126,6 +135,13 @@ var Filters = {
},
"dynamicProperties": []};
},
isGarrisoned: function(){
return {"func" : function(ent){
return ent.position() == -1; // assumes garrisoned
},
"dynamicProperties": []};
},
isSoldier: function(){
return {"func" : function(ent){
@ -184,7 +200,7 @@ var Filters = {
isDropsite: function(resourceType){
return {"func": function(ent){
return (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resourceType) !== -1);
return (ent.resourceDropsiteTypes() && (resourceType === undefined || ent.resourceDropsiteTypes().indexOf(resourceType) !== -1));
},
"dynamicProperties": []};
},
@ -203,7 +219,7 @@ var Filters = {
return false;
// And don't go for the bloody fish! TODO: better accessibility checks
if (ent.hasClass("SeaCreature")){
if (ent.hasClass("SeaCreature")) {
return false;
}

View File

@ -2,51 +2,56 @@
* Provides an API for the rest of the AI scripts to query the world state at a
* higher level than the raw data.
*/
var GameState = function(SharedScript, state, player) {
var GameState = function() {
this.ai = null; // must be updated by the AIs.
this.cellSize = 4; // Size of each map tile
this.buildingsBuilt = 0;
this.turnCache = {};
};
GameState.prototype.init = function(SharedScript, state, player) {
this.sharedScript = SharedScript;
this.EntCollecNames = SharedScript._entityCollectionsName;
this.EntCollec = SharedScript._entityCollections;
this.timeElapsed = state.timeElapsed;
this.timeElapsed = SharedScript.timeElapsed;
this.templates = SharedScript._templates;
this.techTemplates = SharedScript._techTemplates;
this.entities = SharedScript.entities;
this.player = player;
this.playerData = state.players[player];
this.techModifications = SharedScript.techModifications[player];
this.buildingsBuilt = 0;
this.ai = null; // must be updated by the AIs.
this.cellSize = 4; // Size of each map tile
this.turnCache = {};
this.playerData = this.sharedScript.playersData[this.player];
this.techModifications = SharedScript._techModifications[this.player];
};
GameState.prototype.update = function(SharedScript, state) {
this.sharedScript = SharedScript;
this.EntCollecNames = SharedScript._entityCollectionsName;
this.EntCollec = SharedScript._entityCollections;
this.timeElapsed = state.timeElapsed;
this.timeElapsed = SharedScript.timeElapsed;
this.templates = SharedScript._templates;
this.techTemplates = SharedScript._techTemplates;
this._entities = SharedScript._entities;
this.entities = SharedScript.entities;
this.playerData = state.players[this.player];
this.techModifications = SharedScript.techModifications[this.player];
this.playerData = SharedScript.playersData[this.player];
this.techModifications = SharedScript._techModifications[this.player];
this.buildingsBuilt = 0;
this.turnCache = {};
};
GameState.prototype.updatingCollection = function(id, filter, collection){
GameState.prototype.updatingCollection = function(id, filter, collection, allowQuick){
// automatically add the player ID
id = this.player + "-" + id;
if (!this.EntCollecNames[id]){
if (collection !== undefined)
this.EntCollecNames[id] = collection.filter(filter);
else {
this.EntCollecNames[id] = this.entities.filter(filter);
}
if (allowQuick)
this.EntCollecNames[id].allowQuickIter();
this.EntCollecNames[id].registerUpdates();
// warn ("New Collection named " +id);
}
return this.EntCollecNames[id];
@ -69,13 +74,16 @@ GameState.prototype.getEC = function(id){
return undefined;
};
GameState.prototype.updatingGlobalCollection = function(id, filter, collection) {
GameState.prototype.updatingGlobalCollection = function(id, filter, collection, allowQuick) {
if (!this.EntCollecNames[id]){
if (collection !== undefined)
this.EntCollecNames[id] = collection.filter(filter);
else
this.EntCollecNames[id] = this.entities.filter(filter);
if (allowQuick)
this.EntCollecNames[id].allowQuickIter();
this.EntCollecNames[id].registerUpdates();
//warn ("New Global Collection named " +id);
}
return this.EntCollecNames[id];
@ -105,6 +113,18 @@ GameState.prototype.currentPhase = function()
return 0;
};
GameState.prototype.townPhase = function()
{
if (this.playerData.civ == "athen")
return "phase_town_athen";
return "phase_town_generic";
};
GameState.prototype.cityPhase = function()
{
return "phase_city_generic";
};
GameState.prototype.isResearched = function(template)
{
return this.playerData.researchedTechs[template] !== undefined;
@ -228,6 +248,7 @@ GameState.prototype.getTemplate = function(type)
GameState.prototype.applyCiv = function(str) {
return str.replace(/\{civ\}/g, this.playerData.civ);
};
GameState.prototype.civ = function() {
return this.playerData.civ;
};
@ -349,48 +370,56 @@ GameState.prototype.getEntityById = function(id){
return undefined;
};
GameState.prototype.getOwnEntitiesByMetadata = function(key, value){
return this.updatingCollection(key + "-" + value, Filters.byMetadata(this.player, key, value),this.getOwnEntities());
GameState.prototype.getOwnEntitiesByMetadata = function(key, value, maintain){
if (maintain === true)
return this.updatingCollection(key + "-" + value, Filters.byMetadata(this.player, key, value),this.getOwnEntities());
return this.getOwnEntities().filter(Filters.byMetadata(this.player, key, value));
};
GameState.prototype.getOwnEntitiesByRole = function(role){
return this.getOwnEntitiesByMetadata("role", role);
return this.getOwnEntitiesByMetadata("role", role, true);
};
GameState.prototype.getOwnTrainingFacilities = function(){
return this.updatingCollection("own-training-facilities", Filters.byTrainingQueue(), this.getOwnEntities());
return this.updatingCollection("own-training-facilities", Filters.byTrainingQueue(), this.getOwnEntities(), true);
};
GameState.prototype.getOwnResearchFacilities = function(){
return this.updatingCollection("own-research-facilities", Filters.byResearchAvailable(), this.getOwnEntities());
return this.updatingCollection("own-research-facilities", Filters.byResearchAvailable(), this.getOwnEntities(), true);
};
GameState.prototype.getOwnEntitiesByType = function(type){
GameState.prototype.getOwnEntitiesByType = function(type, maintain){
var filter = Filters.byType(type);
return this.updatingCollection("own-by-type-" + type, filter, this.getOwnEntities());
if (maintain === true)
return this.updatingCollection("own-by-type-" + type, filter, this.getOwnEntities());
return this.getOwnEntities().filter(filter);
};
GameState.prototype.countEntitiesByType = function(type) {
return this.getOwnEntitiesByType(type).length;
GameState.prototype.countEntitiesByType = function(type, maintain) {
return this.getOwnEntitiesByType(type, maintain).length;
};
GameState.prototype.countEntitiesAndQueuedByType = function(type) {
var count = this.countEntitiesByType(type);
var count = this.countEntitiesByType(type, true);
// Count building foundations
count += this.countEntitiesByType("foundation|" + type);
// Count animal resources
count += this.countEntitiesByType("resource|" + type);
// Count entities in building production queues
this.getOwnTrainingFacilities().forEach(function(ent){
ent.trainingQueue().forEach(function(item) {
if (item.unitTemplate == type){
count += item.count;
}
if (this.getTemplate(type).hasClass("Structure") === true)
count += this.countEntitiesByType("foundation|" + type, true);
else if (this.getTemplate(type).resourceSupplyType() !== undefined) // animal resources
count += this.countEntitiesByType("resource|" + type, true);
else
{
// Count entities in building production queues
// TODO: maybe this fails for corrals.
this.getOwnTrainingFacilities().forEach(function(ent){
ent.trainingQueue().forEach(function(item) {
if (item.unitTemplate == type){
count += item.count;
}
});
});
});
}
return count;
};
@ -508,11 +537,13 @@ GameState.prototype.getOwnFoundations = function() {
};
GameState.prototype.getOwnDropsites = function(resource){
return this.updatingCollection("dropsite-own-" + resource, Filters.isDropsite(resource), this.getOwnEntities());
if (resource !== undefined)
return this.updatingCollection("dropsite-own-" + resource, Filters.isDropsite(resource), this.getOwnEntities(), true);
return this.updatingCollection("dropsite-own", Filters.isDropsite(), this.getOwnEntities(), true);
};
GameState.prototype.getResourceSupplies = function(resource){
return this.updatingGlobalCollection("resource-" + resource, Filters.byResource(resource), this.getEntities());
return this.updatingGlobalCollection("resource-" + resource, Filters.byResource(resource), this.getEntities(), true);
};
GameState.prototype.getEntityLimits = function() {
@ -589,8 +620,7 @@ GameState.prototype.findAvailableTech = function() {
if (this.canResearch(techs[1]._templateName))
ret.push([techs[1]._templateName, techs[1]] );
} else {
if (this.canResearch(allResearchable[i]) && template._templateName != "phase_town_generic"
&& template._templateName != "phase_town_athens" && template._templateName != "phase_city_generic")
if (this.canResearch(allResearchable[i]) && template._templateName != this.townPhase() && template._templateName != this.cityPhase())
ret.push( [allResearchable[i], template] );
}
}

View File

@ -2,6 +2,8 @@
* Copied with changes from QuantumState's original for qBot, it's a component for storing 8 bit values.
*/
const TERRITORY_PLAYER_MASK = 0x3F;
function Map(sharedScript, originalMap, actualCopy){
// get the map to find out the correct dimensions
var gameMap = sharedScript.passabilityMap;
@ -9,6 +11,8 @@ function Map(sharedScript, originalMap, actualCopy){
this.height = gameMap.height;
this.length = gameMap.data.length;
this.maxVal = 255;
if (originalMap && actualCopy){
this.map = new Uint8Array(this.length);
for (var i = 0; i < originalMap.length; ++i)
@ -21,8 +25,12 @@ function Map(sharedScript, originalMap, actualCopy){
this.cellSize = 4;
}
Map.prototype.setMaxVal = function(val){
this.maxVal = val;
};
Map.prototype.gamePosToMapPos = function(p){
return [Math.round(p[0]/this.cellSize), Math.round(p[1]/this.cellSize)];
return [Math.floor(p[0]/this.cellSize), Math.floor(p[1]/this.cellSize)];
};
Map.prototype.point = function(p){
@ -43,13 +51,13 @@ Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) {
switch (type){
case 'linear':
str = +strength / +maxDist;
break;
break;
case 'quadratic':
str = +strength / +maxDist2;
break;
break;
case 'constant':
str = +strength;
break;
break;
}
for ( var y = y0; y < y1; ++y) {
@ -71,13 +79,12 @@ Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) {
quant = str;
break;
}
if (this.map[x + y * this.width] + quant > 255){
this.map[x + y * this.width] = 255;
} else if (this.map[x + y * this.width] + quant < 0){
if (this.map[x + y * this.width] + quant < 0)
this.map[x + y * this.width] = 0;
} else {
else if (this.map[x + y * this.width] + quant > this.maxVal)
this.map[x + y * this.width] = this.maxVal; // avoids overflow.
else
this.map[x + y * this.width] += quant;
}
}
}
}
@ -126,15 +133,18 @@ Map.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type) {
break;
}
var machin = this.map[x + y * this.width] * quant;
if (machin <= 0){
this.map[x + y * this.width] = 0; //set anything which would have gone negative to 0
}else{
if (machin < 0)
this.map[x + y * this.width] = 0;
else if (machin > this.maxVal)
this.map[x + y * this.width] = this.maxVal;
else
this.map[x + y * this.width] = machin;
}
}
}
}
};
// doesn't check for overflow.
Map.prototype.setInfluence = function(cx, cy, maxDist, value) {
value = value ? value : 0;
@ -178,8 +188,9 @@ Map.prototype.sumInfluence = function(cx, cy, radius){
return sum;
};
/**
* Make each cell's 8-bit value at least one greater than each of its
* neighbours' values. Possible assignment of a cap (maximum).
* Make each cell's 16-bit/8-bit value at least one greater than each of its
* neighbours' values. (If the grid is initialised with 0s and 65535s or 255s, the
* result of each cell is its Manhattan distance to the nearest 0.)
*/
Map.prototype.expandInfluences = function(maximum, map) {
var grid = this.map;
@ -187,7 +198,7 @@ Map.prototype.expandInfluences = function(maximum, map) {
grid = map;
if (maximum == undefined)
maximum = 255;
maximum = this.maxVal;
var w = this.width;
var h = this.height;
for ( var y = 0; y < h; ++y) {
@ -266,7 +277,18 @@ Map.prototype.multiply = function(map, onlyBetter, divider, maxMultiplier){
};
// add to current map by the parameter map pixelwise
Map.prototype.add = function(map){
for (var i = 0; i < this.length; ++i){
this.map[i] += +map.map[i];
for (var i = 0; i < this.length; ++i) {
if (this.map[i] + map.map[i] < 0)
this.map[i] = 0;
else if (this.map[i] + map.map[i] > this.maxVal)
this.map[i] = this.maxVal;
else
this.map[i] += map.map[i];
}
};
Map.prototype.dumpIm = function(name, threshold){
name = name ? name : "default.png";
threshold = threshold ? threshold : this.maxVal;
Engine.DumpImage(name, this.map, this.width, this.height, threshold);
};

View File

@ -22,7 +22,15 @@ function SharedScript(settings)
this._entityCollectionsName = {};
this._entityCollectionsByDynProp = {};
this._entityCollectionsUID = 0;
// A few notes about these maps. They're updated by checking for "create" and "destroy" events for all resources
// TODO: change the map when the resource amounts change for at least stone and metal mines.
this.resourceMaps = {}; // Contains maps showing the density of wood, stone and metal
this.CCResourceMaps = {}; // Contains maps showing the density of wood, stone and metal, optimized for CC placement.
// Resource maps data.
// By how much to divide the resource amount for plotting (ie a tree having 200 wood is "4").
this.decreaseFactor = {'wood': 50.0, 'stone': 90.0, 'metal': 90.0, 'food': 40.0};
this.turn = 0;
}
@ -160,31 +168,44 @@ SharedScript.prototype.GetTemplate = function(name)
// initialize the shared component using a given gamestate (the initial gamestate after map creation, usually)
// this is called right at the end of map generation, before you actually reach the map.
SharedScript.prototype.initWithState = function(state) {
this.events = state.events;
this.passabilityClasses = state.passabilityClasses;
this.passabilityMap = state.passabilityMap;
this.players = this._players;
this.playersData = state.players;
this.territoryMap = state.territoryMap;
this.timeElapsed = state.timeElapsed;
for (var o in state.players)
this._techModifications[o] = state.players[o].techModifications;
this.techModifications = this._techModifications;
this._entities = {};
for (var id in state.entities)
this.entities = new EntityCollection(this);
/* (var id in state.entities)
{
this._entities[id] = new Entity(this, state.entities[id]);
}
// entity collection updated on create/destroy event.
this.entities = new EntityCollection(this, this._entities);
*/
this.ApplyEntitiesDelta(state);
// create the terrain analyzer
this.terrainAnalyzer = new TerrainAnalysis(this, state);
this.accessibility = new Accessibility(state, this.terrainAnalyzer);
this.terrainAnalyzer = new TerrainAnalysis();
this.terrainAnalyzer.init(this, state);
this.accessibility = new Accessibility();
this.accessibility.init(state, this.terrainAnalyzer);
// defined in TerrainAnalysis.js
this.updateResourceMaps(this, this.events);
this.gameState = {};
for (var i in this._players)
{
this.gameState[this._players[i]] = new GameState(this,state,this._players[i]);
this.gameState[this._players[i]] = new GameState();
this.gameState[this._players[i]].init(this,state,this._players[i]);
}
};
@ -193,8 +214,9 @@ SharedScript.prototype.initWithState = function(state) {
// applies entity deltas, and each gamestate.
SharedScript.prototype.onUpdate = function(state)
{
this.ApplyEntitiesDelta(state);
if (this.turn !== 0)
this.ApplyEntitiesDelta(state);
Engine.ProfileStart("onUpdate");
this.events = state.events;
@ -211,6 +233,8 @@ SharedScript.prototype.onUpdate = function(state)
for (var i in this.gameState)
this.gameState[i].update(this,state);
if (this.turn !== 0)
this.updateResourceMaps(this, this.events);
this.terrainAnalyzer.updateMapWithEvents(this);
//this.OnUpdate();
@ -223,6 +247,8 @@ SharedScript.prototype.onUpdate = function(state)
SharedScript.prototype.ApplyEntitiesDelta = function(state)
{
Engine.ProfileStart("Shared ApplyEntitiesDelta");
var foundationFinished = {};
for each (var evt in state.events)
{
@ -236,11 +262,10 @@ SharedScript.prototype.ApplyEntitiesDelta = function(state)
this.entities.addEnt(this._entities[evt.msg.entity]);
// Update all the entity collections since the create operation affects static properties as well as dynamic
for each (var entCollection in this._entityCollections)
for (var entCollection in this._entityCollections)
{
entCollection.updateEnt(this._entities[evt.msg.entity]);
this._entityCollections[entCollection].updateEnt(this._entities[evt.msg.entity]);
}
}
else if (evt.type == "Destroy")
{
@ -252,6 +277,9 @@ SharedScript.prototype.ApplyEntitiesDelta = function(state)
if (!this._entities[evt.msg.entity])
continue;
if (foundationFinished[evt.msg.entity])
evt.msg["SuccessfulFoundation"] = true;
// The entity was destroyed but its data may still be useful, so
// remember the entity and this AI's metadata concerning it
evt.msg.metadata = {};
@ -280,15 +308,41 @@ SharedScript.prototype.ApplyEntitiesDelta = function(state)
}
}
}
else if (evt.type == "ConstructionFinished")
{
// we can rely on this being before the "Destroy" command as this is the order defined by FOundation.js
// we'll move metadata.
if (!this._entities[evt.msg.entity])
continue;
var ent = this._entities[evt.msg.entity];
var newEnt = this._entities[evt.msg.newentity];
if (this._entityMetadata[ent.owner()] && this._entityMetadata[ent.owner()][evt.msg.entity] !== undefined)
for (var key in this._entityMetadata[ent.owner()][evt.msg.entity])
{
this.setMetadata(ent.owner(), newEnt, key, this._entityMetadata[ent.owner()][evt.msg.entity][key])
}
foundationFinished[evt.msg.entity] = true;
}
else if (evt.type == "AIMetadata")
{
if (!this._entities[evt.msg.id])
continue; // might happen in some rare cases of foundations getting destroyed, perhaps.
// Apply metadata (here for buildings for example)
for (var key in evt.msg.metadata)
{
this.setMetadata(evt.msg.owner, this._entities[evt.msg.id], key, evt.msg.metadata[key])
}
}
}
for (var id in state.entities)
{
var changes = state.entities[id];
for (var prop in changes)
{
this._entities[id]._entity[prop] = changes[prop];
this.updateEntityCollections(prop, this._entities[id]);
}
}
@ -335,9 +389,9 @@ SharedScript.prototype.updateEntityCollections = function(property, ent)
{
if (this._entityCollectionsByDynProp[property] !== undefined)
{
for each (var entCollection in this._entityCollectionsByDynProp[property])
for (var entCollectionid in this._entityCollectionsByDynProp[property])
{
entCollection.updateEnt(ent);
this._entityCollectionsByDynProp[property][entCollectionid].updateEnt(ent);
}
}
}
@ -360,6 +414,16 @@ SharedScript.prototype.getMetadata = function(player, ent, key)
return undefined;
return metadata[key];
};
SharedScript.prototype.deleteMetadata = function(player, ent, key)
{
var metadata = this._entityMetadata[player][ent.id()];
if (!metadata || !(key in metadata))
return true;
metadata[key] = undefined;
delete metadata[key];
return true;
};
function copyPrototype(descendant, parent) {
var sConstructor = parent.toString();

View File

@ -12,12 +12,16 @@
* But truly separating optimizes.
*/
function TerrainAnalysis(sharedScript,rawState){
var self = this;
function TerrainAnalysis() {
this.cellSize = 4;
}
copyPrototype(TerrainAnalysis, Map);
TerrainAnalysis.prototype.init = function(sharedScript,rawState) {
var self = this;
var passabilityMap = rawState.passabilityMap;
this.width = passabilityMap.width;
this.height = passabilityMap.height;
@ -98,10 +102,11 @@ function TerrainAnalysis(sharedScript,rawState){
this.obstructionMaskLand = null;
this.obstructionMaskWater = null;
this.obstructionMask = null;
delete this.obstructionMaskLand;
delete this.obstructionMaskWater;
delete this.obstructionMask;
};
copyPrototype(TerrainAnalysis, Map);
// Returns the (approximately) closest point which is passable by searching in a spiral pattern
TerrainAnalysis.prototype.findClosestPassablePoint = function(startPoint, onLand, limitDistance, quickscope){
var w = this.width;
@ -238,34 +243,102 @@ TerrainAnalysis.prototype.updateMapWithEvents = function(sharedAI) {
* so this can use the land regions already.
*/
function Accessibility(rawState, terrainAnalyser){
function Accessibility() {
}
copyPrototype(Accessibility, TerrainAnalysis);
Accessibility.prototype.init = function(rawState, terrainAnalyser){
var self = this;
this.Map(rawState, terrainAnalyser.map);
this.passMap = new Uint8Array(terrainAnalyser.length);
this.landPassMap = new Uint8Array(terrainAnalyser.length);
this.navalPassMap = new Uint8Array(terrainAnalyser.length);
this.regionSize = [];
this.regionSize.push(0);
this.regionType = []; // "inaccessible", "land" or "water";
// ID of the region associated with an array of region IDs.
this.regionLinks = [];
// initialized to 0, it's more optimized to start at 1 (I'm checking that if it's not 0, then it's already aprt of a region, don't touch);
// However I actually store all unpassable as region 1 (because if I don't, on some maps the toal nb of region is over 256, and it crashes as the mapis 8bit.)
// So start at 2.
this.regionID = 2;
for (var i = 0; i < this.passMap.length; ++i) {
if (this.passMap[i] === 0 && this.map[i] !== 0) { // any non-painted, non-inacessible area.
this.regionSize.push(0); // updated
this.floodFill(i,this.regionID,false);
this.regionID++;
} else if (this.passMap[i] === 0) { // any non-painted, inacessible area.
for (var i = 0; i < this.landPassMap.length; ++i) {
if (this.map[i] !== 0) { // any non-painted, non-inacessible area.
if (this.landPassMap[i] === 0 && this.floodFill(i,this.regionID,false))
this.regionType[this.regionID++] = "land";
if (this.navalPassMap[i] === 0 && this.floodFill(i,this.regionID,true))
this.regionType[this.regionID++] = "water";
} else if (this.landPassMap[i] === 0) { // any non-painted, inacessible area.
this.floodFill(i,1,false);
this.floodFill(i,1,true);
}
}
}
copyPrototype(Accessibility, TerrainAnalysis);
// calculating region links. Regions only touching diagonaly are not linked.
// since we're checking all of them, we'll check from the top left to the bottom right
var w = this.width;
for (var x = 0; x < this.width-1; ++x)
{
for (var y = 0; y < this.height-1; ++y)
{
// checking right.
var thisLID = this.landPassMap[x+y*w];
var thisNID = this.navalPassMap[x+y*w];
var rightLID = this.landPassMap[x+1+y*w];
var rightNID = this.navalPassMap[x+1+y*w];
var bottomLID = this.landPassMap[x+y*w+w];
var bottomNID = this.navalPassMap[x+y*w+w];
if (thisLID > 1)
{
if (rightNID > 1)
if (this.regionLinks[thisLID].indexOf(rightNID) === -1)
this.regionLinks[thisLID].push(rightNID);
if (bottomNID > 1)
if (this.regionLinks[thisLID].indexOf(bottomNID) === -1)
this.regionLinks[thisLID].push(bottomNID);
}
if (thisNID > 1)
{
if (rightLID > 1)
if (this.regionLinks[thisNID].indexOf(rightLID) === -1)
this.regionLinks[thisNID].push(rightLID);
if (bottomLID > 1)
if (this.regionLinks[thisNID].indexOf(bottomLID) === -1)
this.regionLinks[thisNID].push(bottomLID);
if (thisLID > 1)
if (this.regionLinks[thisNID].indexOf(thisLID) === -1)
this.regionLinks[thisNID].push(thisLID);
}
}
}
//warn(uneval(this.regionLinks));
Accessibility.prototype.getAccessValue = function(position){
//Engine.DumpImage("LandPassMap.png", this.landPassMap, this.width, this.height, 255);
//Engine.DumpImage("NavalPassMap.png", this.navalPassMap, this.width, this.height, 255);
}
Accessibility.prototype.getAccessValue = function(position, onWater) {
var gamePos = this.gamePosToMapPos(position);
return this.passMap[gamePos[0] + this.width*gamePos[1]];
if (onWater === true)
return this.navalPassMap[gamePos[0] + this.width*gamePos[1]];
var ret = this.landPassMap[gamePos[0] + this.width*gamePos[1]];
if (ret === 1)
{
// quick spiral search.
var indx = [ [-1,-1],[-1,0],[-1,1],[0,1],[1,1],[1,0],[1,-1],[0,-1]]
for (i in indx)
{
ret = this.landPassMap[gamePos[0]+indx[0] + this.width*(gamePos[1]+indx[0])]
if (ret !== undefined && ret !== 1)
return ret;
}
}
return ret;
};
// Returns true if a point is deemed currently accessible (is not blocked by surrounding trees...)
@ -283,49 +356,233 @@ Accessibility.prototype.isAccessible = function(gameState, position, onLand){
// Return true if you can go from a point to a point without switching means of transport
// Hardcore means is also checks for isAccessible at the end (it checks for either water or land though, beware).
// This is a blind check and not a pathfinder: for all it knows there is a huge block of trees in the middle.
Accessibility.prototype.pathAvailable = function(gameState, start,end, hardcore){
Accessibility.prototype.pathAvailable = function(gameState, start, end, onWater, hardcore){
var pstart = this.gamePosToMapPos(start);
var istart = pstart[0] + pstart[1]*this.width;
var pend = this.gamePosToMapPos(end);
var iend = pend[0] + pend[1]*this.width;
if (this.passMap[istart] === this.passMap[iend]) {
if (hardcore && (this.isAccessible(gameState, end,true) || this.isAccessible(gameState, end,false)))
if (onWater)
{
if (this.navalPassMap[istart] === this.navalPassMap[iend]) {
if (hardcore && this.isAccessible(gameState, end,false))
return true;
else if (hardcore)
return false;
return true;
else if (hardcore)
return false;
return true;
}
} else {
if (this.landPassMap[istart] === this.landPassMap[iend]) {
if (hardcore && this.isAccessible(gameState, end,true))
return true;
else if (hardcore)
return false;
return true;
}
}
return false;
};
Accessibility.prototype.getRegionSize = function(position){
var pos = this.gamePosToMapPos(position);
var index = pos[0] + pos[1]*this.width;
if (this.regionSize[this.passMap[index]] === undefined)
return 0;
return this.regionSize[this.passMap[index]];
};
Accessibility.prototype.getRegionSizei = function(index) {
if (this.regionSize[this.passMap[index]] === undefined)
return 0;
return this.regionSize[this.passMap[index]];
Accessibility.prototype.getTrajectTo = function(start, end, noBound) {
var pstart = this.gamePosToMapPos(start);
var istart = pstart[0] + pstart[1]*this.width;
var pend = this.gamePosToMapPos(end);
var iend = pend[0] + pend[1]*this.width;
var onLand = true;
if (this.landPassMap[istart] <= 1 && this.navalPassMap[istart] > 1)
onLand = false;
if (this.landPassMap[istart] <= 1 && this.navalPassMap[istart] <= 1)
return false;
var endRegion = this.landPassMap[iend];
if (endRegion <= 1 && this.navalPassMap[iend] > 1)
endRegion = this.navalPassMap[iend];
else if (endRegion <= 1)
return false;
if (onLand)
var startRegion = this.landPassMap[istart];
else
var startRegion = this.navalPassMap[istart];
return this.getTrajectToIndex(startRegion, endRegion, noBound);
}
// Return a "path" of accessibility indexes from one point to another, including the start and the end indexes (unless specified otherwise)
// this can tell you what sea zone you need to have a dock on, for example.
// assumes a land unit unless start point is over deep water.
// if the path is more complicated than "land->sea->land" (or "sea->land->sea"), it will run A* to try and figure it out
// Thus it can handle arbitrarily complicated paths (theoretically).
Accessibility.prototype.getTrajectToIndex = function(istart, iend, noBound){
var startRegion = istart;
var currentRegion = istart;
var endRegion = iend;
// optimizations to avoid needless memory usage
// if it's the same, return the path
if (startRegion === endRegion)
return [startRegion];
else if (this.regionLinks[startRegion].indexOf(endRegion) !== -1)
return [startRegion, endRegion];
else
{
var rgs = this.regionLinks[startRegion];
for (p in rgs)
{
if (this.regionLinks[rgs[p]].indexOf(endRegion) !== -1)
return [startRegion, rgs[p], endRegion];
}
}
// it appears to be difficult.
// computing A* over a graph with all nodes equally good (might want to change this sometimes), currently it returns the shortest path switch-wise.
this.openList = [];
this.parentSquare = new Uint8Array(this.regionSize.length);
this.isOpened = new Boolean(this.regionSize.length);
this.gCostArray = new Uint8Array(this.regionSize.length);
this.isOpened[currentRegion] = true;
this.openList.push(currentRegion);
this.gCostArray[currentRegion] = 0;
this.parentSquare[currentRegion] = currentRegion;
var w = this.width;
var h = this.height;
//creation of variables used in the loop
var found = false;
// on to A*
while (found === false && this.openList.length !== 0) {
var currentDist = 300;
var ti = 0;
for (var i in this.openList)
{
var sum = this.gCostArray[this.openList[i]];
if (sum < currentDist)
{
ti = i;
currentRegion = this.openList[i];
currentDist = sum;
}
}
this.openList.splice(ti,1);
this.isOpened[currentRegion] = false;
// special case, might make it faster (usually oceans connect multiple land masses, sometimes all of them)
if (this.regionType[currentRegion] == "water" && endLand)
{
var idx = this.regionLinks[currentRegion].indexOf(endRegion);
if (idx !== -1)
{
this.parentSquare[endRegion] = currentRegion;
this.gCostArray[endRegion] = this.gCostArray[currentRegion] + 1;
found = true;
break;
}
}
for (var i in this.regionLinks[currentRegion])
{
var region = this.regionLinks[currentRegion][i];
if(this.isOpened[region] === undefined)
{
this.parentSquare[region] = currentRegion;
this.gCostArray[region] = this.gCostArray[currentRegion] + 1;
this.openList.push(region);
this.isOpened[region] = true;
if (region === endRegion)
{
found = true;
break;
}
} else {
if (this.gCostArray[region] > 1 + this.gCostArray[currentRegion])
{
this.parentSquare[region] = currentRegion;
this.gCostArray[region] = 1 + this.gCostArray[currentRegion];
}
}
}
}
var path = [];
if (found) {
currentRegion = endRegion;
if (!noBound)
path.push(currentRegion);
while (this.parentSquare[currentRegion] !== startRegion)
{
currentRegion = this.parentSquare[currentRegion];
path.push(currentRegion);
}
if (!noBound)
path.push(startRegion);
} else {
delete this.parentSquare;
delete this.isOpened;
delete this.gCostArray;
return false;
}
delete this.parentSquare;
delete this.isOpened;
delete this.gCostArray;
return path;
};
// Implementation of a fast flood fill. Reasonably good performances. Runs once at startup.
Accessibility.prototype.getRegionSize = function(position, onWater){
var pos = this.gamePosToMapPos(position);
var index = pos[0] + pos[1]*this.width;
var ID = (onWater === true) ? this.navalPassMap[index] : this.landPassMap[index];
if (this.regionSize[ID] === undefined)
return 0;
return this.regionSize[ID];
};
Accessibility.prototype.getRegionSizei = function(index, onWater) {
if (this.regionSize[this.landPassMap[index]] === undefined && (!onWater || this.regionSize[this.navalPassMap[index]] === undefined))
return 0;
if (onWater && this.regionSize[this.navalPassMap[index]] > this.regionSize[this.landPassMap[index]])
return this.regionSize[this.navalPassMap[index]];
return this.regionSize[this.landPassMap[index]];
};
// Implementation of a fast flood fill. Reasonably good performances for JS.
// TODO: take big zones of impassable trees into account?
Accessibility.prototype.floodFill = function(startIndex, value, onWater)
{
this.s = startIndex;
if (this.passMap[this.s] !== 0) {
if ((!onWater && this.landPassMap[this.s] !== 0) || (onWater && this.navalPassMap[this.s] !== 0) ) {
return false; // already painted.
}
this.floodFor = "land";
if (this.map[this.s] === 200 || (this.map[this.s] === 201 && onWater === true))
if (this.map[this.s] === 0)
{
this.landPassMap[this.s] = 1;
this.navalPassMap[this.s] = 1;
return false;
}
if (onWater === true)
{
if (this.map[this.s] !== 200 && this.map[this.s] !== 201)
{
this.navalPassMap[this.s] = 1; // impassable for naval
return false; // do nothing
}
this.floodFor = "water";
else if (this.map[this.s] === 0)
this.floodFor = "impassable";
} else if (this.map[this.s] === 200) {
this.landPassMap[this.s] = 1; // impassable for land
return false;
}
// here we'll be able to start.
for (var i = this.regionSize.length; i <= value; ++i)
{
this.regionLinks.push([]);
this.regionSize.push(0);
this.regionType.push("inaccessible");
}
var w = this.width;
var h = this.height;
@ -347,11 +604,9 @@ Accessibility.prototype.floodFill = function(startIndex, value, onWater)
var index = +newIndex + w*y;
if (index < 0)
break;
if (this.floodFor === "impassable" && this.map[index] === 0 && this.passMap[index] === 0) {
if (this.floodFor === "land" && this.landPassMap[index] === 0 && this.map[index] !== 0 && this.map[index] !== 200) {
loop = true;
} else if (this.floodFor === "land" && this.passMap[index] === 0 && this.map[index] !== 0 && this.map[index] !== 200) {
loop = true;
} else if (this.floodFor === "water" && this.passMap[index] === 0 && (this.map[index] === 200 || (this.map[index] === 201 && this.onWater)) ) {
} else if (this.floodFor === "water" && this.navalPassMap[index] === 0 && (this.map[index] === 200 || this.map[index] === 201)) {
loop = true;
} else {
break;
@ -363,14 +618,12 @@ Accessibility.prototype.floodFill = function(startIndex, value, onWater)
loop = true;
do {
var index = +newIndex + w*y;
if (this.floodFor === "impassable" && this.map[index] === 0 && this.passMap[index] === 0) {
this.passMap[index] = value;
if (this.floodFor === "land" && this.landPassMap[index] === 0 && this.map[index] !== 0 && this.map[index] !== 200) {
this.landPassMap[index] = value;
this.regionSize[value]++;
} else if (this.floodFor === "land" && this.passMap[index] === 0 && this.map[index] !== 0 && this.map[index] !== 200) {
this.passMap[index] = value;
this.regionSize[value]++;
} else if (this.floodFor === "water" && this.passMap[index] === 0 && (this.map[index] === 200 || (this.map[index] === 201 && this.onWater)) ) {
this.passMap[index] = value;
} else if (this.floodFor === "water" && this.navalPassMap[index] === 0 && (this.map[index] === 200 || this.map[index] === 201)) {
this.navalPassMap[index] = value;
this.regionSize[value]++;
} else {
break;
@ -378,17 +631,12 @@ Accessibility.prototype.floodFill = function(startIndex, value, onWater)
if (index%w > 0)
{
if (this.floodFor === "impassable" && this.map[index -1] === 0 && this.passMap[index -1] === 0) {
if (this.floodFor === "land" && this.landPassMap[index -1] === 0 && this.map[index -1] !== 0 && this.map[index -1] !== 200) {
if(!reachLeft) {
IndexArray.push(index -1);
reachLeft = true;
}
} else if (this.floodFor === "land" && this.passMap[index -1] === 0 && this.map[index -1] !== 0 && this.map[index -1] !== 200) {
if(!reachLeft) {
IndexArray.push(index -1);
reachLeft = true;
}
} else if (this.floodFor === "water" && this.passMap[index -1] === 0 && (this.map[index -1] === 200 || (this.map[index -1] === 201 && this.onWater)) ) {
} else if (this.floodFor === "water" && this.navalPassMap[index -1] === 0 && (this.map[index -1] === 200 || this.map[index -1] === 201)) {
if(!reachLeft) {
IndexArray.push(index -1);
reachLeft = true;
@ -399,17 +647,12 @@ Accessibility.prototype.floodFill = function(startIndex, value, onWater)
}
if (index%w < w - 1)
{
if (this.floodFor === "impassable" && this.map[index +1] === 0 && this.passMap[index +1] === 0) {
if (this.floodFor === "land" && this.landPassMap[index +1] === 0 && this.map[index +1] !== 0 && this.map[index +1] !== 200) {
if(!reachRight) {
IndexArray.push(index +1);
reachRight = true;
}
} else if (this.floodFor === "land" && this.passMap[index +1] === 0 && this.map[index +1] !== 0 && this.map[index +1] !== 200) {
if(!reachRight) {
IndexArray.push(index +1);
reachRight = true;
}
} else if (this.floodFor === "water" && this.passMap[index +1] === 0 && (this.map[index +1] === 200 || (this.map[index +1] === 201 && this.onWater)) ) {
} else if (this.floodFor === "water" && this.navalPassMap[index +1] === 0 && (this.map[index +1] === 200 || this.map[index +1] === 201)) {
if(!reachRight) {
IndexArray.push(index +1);
reachRight = true;
@ -419,157 +662,81 @@ Accessibility.prototype.floodFill = function(startIndex, value, onWater)
}
}
++y;
} while (index/w < w) // should actually break
} while (index/w < w-1) // should actually break
}
return true;
}
function landSizeCounter(rawState,terrainAnalyzer) {
var self = this;
// TODO: make it regularly update stone+metal mines and their resource levels.
// creates and maintains a map of unused resource density
// this also takes dropsites into account.
// resources that are "part" of a dropsite are not counted.
SharedScript.prototype.updateResourceMaps = function(sharedScript, events) {
this.passMap = terrainAnalyzer.map;
var map = new Uint8Array(this.passMap.length);
this.Map(rawState,map);
for (var i = 0; i < this.passMap.length; ++i) {
if (this.passMap[i] !== 0)
this.map[i] = 255;
else
this.map[i] = 0;
}
this.expandInfluences();
}
copyPrototype(landSizeCounter, TerrainAnalysis);
// Implementation of A* as a flood fill. Possibility of (clever) oversampling
// for efficiency or for disregarding too small passages.
// can operate over several turns, though default is only one turn.
landSizeCounter.prototype.getAccessibleLandSize = function(position, sampling, mode, OnlyBuildable, sizeLimit, iterationLimit)
{
if (sampling === undefined)
this.Sampling = 1;
else
this.Sampling = sampling < 1 ? 1 : sampling;
// this checks from the actual starting point. If that is inaccessible (0), it returns undefined;
if (position.length !== undefined) {
// this is an array
if (position[0] < 0 || this.gamePosToMapPos(position)[0] >= this.width || position[1] < 0 || this.gamePosToMapPos(position)[1] >= this.height)
return undefined;
var s = this.gamePosToMapPos(position);
this.s = s[0] + w*s[1];
if (this.map[this.s] === 0 || this.map[this.s] === 200 || (OnlyBuildable === true && this.map[this.s] === 201) ) {
return undefined;
}
} else {
this.s = position;
if (this.map[this.s] === 0 || this.map[this.s] === 200 || (OnlyBuildable === true && this.map[this.s] === 201) ) {
return undefined;
for (var resource in this.decreaseFactor){
// if there is no resourceMap create one with an influence for everything with that resource
if (! this.resourceMaps[resource]){
// We're creting them 8-bit. Things could go above 255 if there are really tons of resources
// But at that point the precision is not really important anyway. And it saves memory.
this.resourceMaps[resource] = new Map(sharedScript, new Uint8Array(sharedScript.passabilityMap.data.length));
this.resourceMaps[resource].setMaxVal(255);
this.CCResourceMaps[resource] = new Map(sharedScript, new Uint8Array(sharedScript.passabilityMap.data.length));
this.CCResourceMaps[resource].setMaxVal(255);
}
}
if (mode === undefined)
this.mode = "default";
else
this.mode = mode;
if (sizeLimit === undefined)
this.sizeLimit = 300000;
else
this.sizeLimit = sizeLimit;
var w = this.width;
var h = this.height;
// max map size is 512*512, this is higher.
this.iterationLimit = 300000;
if (iterationLimit !== undefined)
this.iterationLimit = iterationLimit;
this.openList = [];
this.isOpened = new Boolean(this.map.length);
this.gCostArray = new Uint16Array(this.map.length);
this.currentSquare = this.s;
this.isOpened[this.s] = true;
this.openList.push(this.s);
this.gCostArray[this.s] = 0;
this.countedValue = 1;
this.countedArray = [this.s];
if (OnlyBuildable !== undefined)
this.onlyBuildable = OnlyBuildable;
else
this.onlyBuildable = true;
return this.continueLandSizeCalculation();
}
landSizeCounter.prototype.continueLandSizeCalculation = function()
{
var w = this.width;
var h = this.height;
var positions = [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1]];
var cost = [10,10,10,10,15,15,15,15];
//creation of variables used in the loop
var nouveau = false;
var shortcut = false;
var Sampling = this.Sampling;
var infinity = Math.min();
var currentDist = infinity;
var iteration = 0;
while (this.openList.length !== 0 && iteration < this.iterationLimit && this.countedValue < this.sizeLimit && this.countedArray.length < this.sizeLimit){
currentDist = infinity;
for (var i in this.openList)
{
var sum = this.gCostArray[this.openList[i]];
if (sum < currentDist)
{
this.currentSquare = this.openList[i];
currentDist = sum;
// Look for destroy events and subtract the entities original influence from the resourceMap
// TODO: perhaps do something when dropsites appear/disappear.
for (var key in events) {
var e = events[key];
if (e.type === "Destroy") {
if (e.msg.entityObj){
var ent = e.msg.entityObj;
if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic !== "treasure") {
var resource = ent.resourceSupplyType().generic;
var x = Math.floor(ent.position()[0] / 4);
var z = Math.floor(ent.position()[1] / 4);
var strength = Math.floor(ent.resourceSupplyMax()/this.decreaseFactor[resource]);
if (resource === "wood" || resource === "food")
{
this.resourceMaps[resource].addInfluence(x, z, 2, 5,'constant');
this.resourceMaps[resource].addInfluence(x, z, 9.0, -strength,'constant');
this.CCResourceMaps[resource].addInfluence(x, z, 15, -strength/2.0,'constant');
}
else if (resource === "stone" || resource === "metal")
{
this.resourceMaps[resource].addInfluence(x, z, 8, 50);
this.resourceMaps[resource].addInfluence(x, z, 12.0, -strength/1.5);
this.resourceMaps[resource].addInfluence(x, z, 12.0, -strength/2.0,'constant');
this.CCResourceMaps[resource].addInfluence(x, z, 30, -strength,'constant');
}
}
}
}
this.openList.splice(this.openList.indexOf(this.currentSquare),1);
shortcut = false;
this.isOpened[this.currentSquare] = false;
for (var i in positions) {
var index = 0 + this.currentSquare + positions[i][0]*Sampling + w*Sampling*positions[i][1];
if (this.passMap[index] !== 0 && this.passMap[index] !== 200 && this.map[index] >= Sampling && (!this.onlyBuildable || this.passMap[index] !== 201)) {
if(this.isOpened[index] === undefined) {
if (this.mode === "default")
this.countedValue++;
else if (this.mode === "array")
this.countedArray.push(index);
this.gCostArray[index] = this.gCostArray[this.currentSquare] + cost[i] * Sampling;
this.openList.push(index);
this.isOpened[index] = true;
} else if (e.type === "Create") {
if (e.msg.entity){
var ent = sharedScript._entities[e.msg.entity];
if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic !== "treasure"){
var resource = ent.resourceSupplyType().generic;
var x = Math.floor(ent.position()[0] / 4);
var z = Math.floor(ent.position()[1] / 4);
var strength = Math.floor(ent.resourceSupplyMax()/this.decreaseFactor[resource]);
if (resource === "wood" || resource === "food")
{
this.CCResourceMaps[resource].addInfluence(x, z, 15, strength/2.0,'constant');
this.resourceMaps[resource].addInfluence(x, z, 9.0, strength,'constant');
this.resourceMaps[resource].addInfluence(x, z, 2, -5,'constant');
}
else if (resource === "stone" || resource === "metal")
{
this.CCResourceMaps[resource].addInfluence(x, z, 30, strength,'constant');
this.resourceMaps[resource].addInfluence(x, z, 12.0, strength/1.5);
this.resourceMaps[resource].addInfluence(x, z, 12.0, strength/2.0,'constant');
this.resourceMaps[resource].addInfluence(x, z, 8, -50);
}
}
}
}
iteration++;
}
if (iteration === this.iterationLimit && this.openList.length !== 0 && this.countedValue !== this.sizeLimit && this.countedArray.length !== this.sizeLimit)
{
// we've got to assume that we stopped because we reached the upper limit of iterations
return "toBeContinued";
}
delete this.parentSquare;
delete this.isOpened;
delete this.fCostArray;
delete this.gCostArray;
if (this.mode === "default")
return this.countedValue;
else if (this.mode === "array")
return this.countedArray;
return undefined;
}
};