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:
parent
994ebd9836
commit
d663dae2d8
@ -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).
|
||||
|
@ -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 {};
|
||||
|
@ -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.");
|
||||
|
955
binaries/data/mods/public/simulation/ai/aegis/base-manager.js
Normal file
955
binaries/data/mods/public/simulation/ai/aegis/base-manager.js
Normal 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();
|
||||
};
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
1236
binaries/data/mods/public/simulation/ai/aegis/headquarters.js
Normal file
1236
binaries/data/mods/public/simulation/ai/aegis/headquarters.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
};
|
||||
|
@ -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();
|
||||
};
|
288
binaries/data/mods/public/simulation/ai/aegis/naval-manager.js
Normal file
288
binaries/data/mods/public/simulation/ai/aegis/naval-manager.js
Normal 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();
|
||||
};
|
472
binaries/data/mods/public/simulation/ai/aegis/plan-transport.js
Normal file
472
binaries/data/mods/public/simulation/ai/aegis/plan-transport.js
Normal 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;
|
||||
}
|
@ -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();
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
@ -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
|
||||
};
|
||||
};
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
@ -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;
|
||||
};
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
},
|
||||
|
@ -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 = [];
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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] );
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user