From 3f1db1ef01f7980a25943b6308890c142b39f7e8 Mon Sep 17 00:00:00 2001 From: mimo Date: Mon, 15 Sep 2014 20:19:00 +0000 Subject: [PATCH] remove aegis bot This was SVN commit r15756. --- .../public/simulation/ai/aegis/_Read Me.txt | 13 - .../mods/public/simulation/ai/aegis/_init.js | 1 - .../mods/public/simulation/ai/aegis/aegis.js | 300 ---- .../mods/public/simulation/ai/aegis/army--.js | 382 ----- .../simulation/ai/aegis/army-defense.js | 156 -- .../public/simulation/ai/aegis/attack_plan.js | 1217 --------------- .../simulation/ai/aegis/base-manager.js | 1124 -------------- .../mods/public/simulation/ai/aegis/config.js | 121 -- .../mods/public/simulation/ai/aegis/data.json | 7 - .../public/simulation/ai/aegis/defence.js | 348 ----- .../simulation/ai/aegis/entity-extend.js | 69 - .../ai/aegis/entitycollection-extend.js | 16 - .../simulation/ai/aegis/gamestate-extend.js | 57 - .../simulation/ai/aegis/headquarters.js | 1345 ----------------- .../simulation/ai/aegis/license_gpl-2.0.txt | 339 ----- .../public/simulation/ai/aegis/map-module.js | 149 -- .../simulation/ai/aegis/naval-manager.js | 290 ---- .../simulation/ai/aegis/plan-transport.js | 479 ------ .../simulation/ai/aegis/queue-manager.js | 560 ------- .../mods/public/simulation/ai/aegis/queue.js | 111 -- .../public/simulation/ai/aegis/queueplan--.js | 71 - .../simulation/ai/aegis/queueplan-building.js | 223 --- .../simulation/ai/aegis/queueplan-research.js | 50 - .../simulation/ai/aegis/queueplan-training.js | 62 - .../simulation/ai/aegis/template-manager.js | 122 -- .../mods/public/simulation/ai/aegis/timer.js | 112 -- .../simulation/ai/aegis/utils-extend.js | 32 - .../mods/public/simulation/ai/aegis/worker.js | 643 -------- 28 files changed, 8399 deletions(-) delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/_Read Me.txt delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/_init.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/aegis.js delete mode 100755 binaries/data/mods/public/simulation/ai/aegis/army--.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/army-defense.js delete mode 100755 binaries/data/mods/public/simulation/ai/aegis/attack_plan.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/base-manager.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/config.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/data.json delete mode 100755 binaries/data/mods/public/simulation/ai/aegis/defence.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/entity-extend.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/entitycollection-extend.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/gamestate-extend.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/headquarters.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/license_gpl-2.0.txt delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/map-module.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/naval-manager.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/plan-transport.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/queue-manager.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/queue.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/queueplan--.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/queueplan-building.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/queueplan-research.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/queueplan-training.js delete mode 100755 binaries/data/mods/public/simulation/ai/aegis/template-manager.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/timer.js delete mode 100755 binaries/data/mods/public/simulation/ai/aegis/utils-extend.js delete mode 100644 binaries/data/mods/public/simulation/ai/aegis/worker.js diff --git a/binaries/data/mods/public/simulation/ai/aegis/_Read Me.txt b/binaries/data/mods/public/simulation/ai/aegis/_Read Me.txt deleted file mode 100644 index e484be41f8..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/_Read Me.txt +++ /dev/null @@ -1,13 +0,0 @@ -Aegis. AI for 0 A.D. ( http://play0ad.com/ ). An effort to improve over two bots: qBot (by Quantumstate, based on TestBot) and Marilyn (by Wraitii, itself based on qBot). - -Install by placing files into the data/mods/public/simulation/ai/aegis folder. - -You may want to set "debug : true" in config.js if you are developping, you will get a better understanding of what the AI does. There are also many commented debug outputs, and many commented map outputs that you may want to uncomment. - -This bot has been made default as of Alpha 13. It features some technological support, early naval support, better economic management, better defense and better attack management (over qBot). It is generally much stronger than the former, and should hopefully be able to handle more situations properly. It is, however, not faultless. - -Please report any error to the wildfire games forum ( http://www.wildfiregames.com/forum/index.php?act=idx ), and thanks for playing! - -Requires common-api; - -(note: no saved game support as of yet). diff --git a/binaries/data/mods/public/simulation/ai/aegis/_init.js b/binaries/data/mods/public/simulation/ai/aegis/_init.js deleted file mode 100644 index ef146454ab..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/_init.js +++ /dev/null @@ -1 +0,0 @@ -Engine.IncludeModule("common-api"); diff --git a/binaries/data/mods/public/simulation/ai/aegis/aegis.js b/binaries/data/mods/public/simulation/ai/aegis/aegis.js deleted file mode 100644 index 1a456c97a7..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/aegis.js +++ /dev/null @@ -1,300 +0,0 @@ -var AEGIS = (function() { -var m = {}; - -// "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. -m.playerGlobals = []; - -m.AegisBot = function AegisBot(settings) { - API3.BaseAI.call(this, settings); - - this.turn = 0; - this.playedTurn = 0; - - this.Config = new m.Config(); - this.Config.updateDifficulty(settings.difficulty); - this.Config.personality = settings.personality; - - this.firstTime = true; - - this.savedEvents = {}; - - this.defcon = 5; - this.defconChangeTime = -10000000; -}; - -m.AegisBot.prototype = new API3.BaseAI(); - -m.AegisBot.prototype.CustomInit = function(gameState, sharedScript) { - - this.initPersonality(this.gameState); - - this.priorities = this.Config.priorities; - // this.queues can only be modified by the queue manager or things will go awry. - this.queues = {}; - for (var i in this.priorities) - this.queues[i] = new m.Queue(); - - this.queueManager = new m.QueueManager(this.Config, this.queues, this.priorities); - - this.HQ = new m.HQ(this.Config); - gameState.Config = this.Config; - - m.playerGlobals[PlayerID] = {}; - m.playerGlobals[PlayerID].uniqueIDBOPlans = 0; // training/building/research plans - m.playerGlobals[PlayerID].uniqueIDBases = 1; // base manager ID. Starts at one because "0" means "no base" on the map - m.playerGlobals[PlayerID].uniqueIDTPlans = 1; // transport plans. starts at 1 because 0 might be used as none. - m.playerGlobals[PlayerID].uniqueIDArmy = 0; - - this.HQ.init(gameState,this.queues); - m.debug ("Initialized with the difficulty " + this.Config.difficulty); - - var ents = gameState.getEntities().filter(API3.Filters.byOwner(this.player)); - var myKeyEntities = ents.filter(function(ent) { - return ent.hasClass("CivCentre"); - }); - - if (myKeyEntities.length == 0){ - myKeyEntities = gameState.getEntities().filter(API3.Filters.byOwner(this.player)); - } - - var filter = API3.Filters.byClass("CivCentre"); - var enemyKeyEntities = gameState.getEntities().filter(API3.Filters.not(API3.Filters.byOwner(this.player))).filter(filter); - - if (enemyKeyEntities.length == 0){ - enemyKeyEntities = gameState.getEntities().filter(API3.Filters.not(API3.Filters.byOwner(this.player))); - } - - this.myIndex = this.accessibility.getAccessValue(myKeyEntities.toEntityArray()[0].position()); - - this.pathFinder = new API3.aStarPath(gameState, false, true); - this.pathsToMe = []; - this.pathInfo = { "angle" : 0, "needboat" : true, "mkeyPos" : myKeyEntities.toEntityArray()[0].position(), "ekeyPos" : enemyKeyEntities.toEntityArray()[0].position() }; - - // First path has a sampling of 3, which ensures we'll get at least one path even on Acropolis. The others are 6 so might fail. - 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, 2, 2);// uncomment for debug:*/, 300000, gameState); - - //Engine.DumpImage("initialPath" + this.player + ".png", this.pathFinder.TotorMap.map, this.pathFinder.TotorMap.width,this.pathFinder.TotorMap.height,255); - - if (path !== undefined && path[1] !== undefined && path[1] == false) { - // path is viable and doesn't require boating. - // blackzone the last two waypoints. - this.pathFinder.markImpassableArea(path[0][0][0],path[0][0][1],20); - this.pathsToMe.push(path[0][0][0]); - this.pathInfo.needboat = false; - } - - this.pathInfo.angle += Math.PI/3.0; -} - -m.AegisBot.prototype.OnUpdate = function(sharedScript) { - if (this.gameFinished){ - return; - } - - for (var i in this.events) - { - if(this.savedEvents[i] !== undefined) - this.savedEvents[i] = this.savedEvents[i].concat(this.events[i]); - else - this.savedEvents[i] = this.events[i]; - } - - // Run the update every n turns, offset depending on player ID to balance the load - if ((this.turn + this.player) % 8 == 5) { - - Engine.ProfileStart("Aegis bot (player " + this.player +")"); - - this.playedTurn++; - - if (this.gameState.getOwnEntities().length === 0){ - Engine.ProfileStop(); - return; // With no entities to control the AI cannot do anything - } - - 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, this.gameState); - if (path !== undefined && path[1] !== undefined && path[1] == false) { - // path is viable and doesn't require boating. - // blackzone the last two waypoints. - this.pathFinder.markImpassableArea(path[0][0][0],path[0][0][1],20); - this.pathsToMe.push(path[0][0][0]); - this.pathInfo.needboat = false; - } - - this.pathInfo.angle += Math.PI/3.0; - - if (this.pathInfo.angle > Math.PI*2.0) - { - if (this.pathInfo.needboat) - { - m.debug ("Assuming this is a water map"); - this.HQ.waterMap = true; - } - delete this.pathFinder; - delete this.pathInfo; - } - } - - var townPhase = this.gameState.townPhase(); - var cityPhase = this.gameState.cityPhase(); - - // try going up phases. - // TODO: softcode this more - if (this.gameState.canResearch(townPhase,true) && this.gameState.getPopulation() >= this.Config.Economy.villagePopCap - 10 - && this.gameState.findResearchers(townPhase,true).length != 0 && this.queues.majorTech.length() === 0) - { - var plan = new m.ResearchPlan(this.gameState, townPhase, true); - plan.lastIsGo = false; - plan.onStart = function (gameState) { gameState.ai.HQ.econState = "growth"; gameState.ai.HQ.OnTownPhase(gameState) }; - plan.isGo = function (gameState) { - var ret = gameState.getPopulation() >= gameState.Config.Economy.villagePopCap - if (ret && !this.lastIsGo) - this.onGo(gameState); - else if (!ret && this.lastIsGo) - this.onNotGo(gameState); - this.lastIsGo = ret; - return ret; - }; - plan.onGo = function (gameState) { gameState.ai.HQ.econState = "townPhasing"; m.debug ("Trying to reach TownPhase"); }; - plan.onNotGo = function (gameState) { gameState.ai.HQ.econState = "growth"; }; - - this.queues.majorTech.addItem(plan); - m.debug ("Planning Town Phase"); - } else if (this.gameState.canResearch(cityPhase,true) && this.gameState.getTimeElapsed() > (this.Config.Economy.cityPhase*1000) - && this.gameState.getOwnEntitiesByRole("worker", true).length > 85 - && this.gameState.findResearchers(cityPhase, true).length != 0 && this.queues.majorTech.length() === 0 - && this.queues.civilCentre.length() === 0) { - m.debug ("Trying to reach city phase"); - this.queues.majorTech.addItem(new m.ResearchPlan(this.gameState, cityPhase)); - } - // defcon cooldown - if (this.defcon < 5 && this.gameState.timeSinceDefconChange() > 20000) - { - this.defcon++; - m.debug ("updefconing to " +this.defcon); - if (this.defcon >= 4 && this.HQ.hasGarrisonedFemales) - this.HQ.ungarrisonAll(this.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 = this.gameState.getOwnEntities(); - for (var i in units._entities) - { - var ent = units._entities[i]; - if (!ent.isIdle()) - continue; - warn ("Unit " + ent.id() + " is a " + ent._templateName); - if (sharedScript._entityMetadata[PlayerID][ent.id()]) - { - var metadata = sharedScript._entityMetadata[PlayerID][ent.id()]; - for (var j in metadata) - { - warn ("Metadata " + j); - if (typeof(metadata[j]) == "object") - warn ("Object"); - else if (typeof(metadata[j]) == undefined) - warn ("Undefined"); - else - warn(uneval(metadata[j])); - } - } - } - }*/ - - - //if (this.playedTurn % 5 === 0) - // 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 - var n = this.savedEvents["Create"].length % 29; - for (var i = 0; i < n; i++){ - Math.random(); - } - - for (var i in this.savedEvents) - this.savedEvents[i] = []; - - Engine.ProfileStop(); - } - - this.turn++; -}; - -// defines our core components strategy-wise. -// TODO: the sky's the limit here. -m.AegisBot.prototype.initPersonality = function(gameState) -{ - this.aggressiveness = 0.5; // I'll try to keep this as a percent but it's basically arbitrary. - if (this.Config.difficulty >= 2) - this.aggressiveness = Math.random(); - - var agrThrsh = 0.8; // treshold for aggressiveness. - if (gameState.civ() == "athen") - agrThrsh = 0.6; // works very well with athens - - if (this.aggressiveness > agrThrsh) - { - m.debug("Going the Rush route"); - this.aggressiveness = 1.0; - // we'll try to pull in an attack at village phase. - this.Config.Military.defenceBuildingTime = 900; - this.Config.Military.popForBarracks1 = 0; - this.Config.Economy.villagePopCap = 75; - this.Config.Economy.cityPhase = 900; - this.Config.Economy.popForMarket = 80; - this.Config.Economy.popForFarmstead = 50; - this.Config.Economy.targetNumBuilders = 2; - this.Config.Economy.femaleRatio = 0.6; - this.Config.Defence.prudence = 0.5; - } else if (this.aggressiveness < 0.15) { - m.debug("Going the Boom route"); - // Now and then Superboom - this.Config.Military.defenceBuildingTime = 600; - this.Config.Economy.cityPhase = 1000; - this.Config.Military.attackPlansStartTime = 1000; - this.Config.Military.popForBarracks1 = 39; - this.Config.Economy.villagePopCap = 50; - this.Config.Economy.femaleRatio = 1.0; - this.Config.Economy.popForMarket = 55; - this.Config.Economy.popForFarmstead = 55; - } -}; - -/*m.AegisBot.prototype.Deserialize = function(data, sharedScript) -{ -}; - -// Override the default serializer -AegisBot.prototype.Serialize = function() -{ - return {}; -};*/ - -// For the moment we just use the debugging flag and the debugging function from the API. -// Maybe it will make sense in the future to separate them. -m.DebugEnabled = function() -{ - return API3.DebugEnabled; -} - -m.debug = function(output) -{ - API3.debug(output); -} - - -return m; -}()); diff --git a/binaries/data/mods/public/simulation/ai/aegis/army--.js b/binaries/data/mods/public/simulation/ai/aegis/army--.js deleted file mode 100755 index 60d0d02585..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/army--.js +++ /dev/null @@ -1,382 +0,0 @@ -var AEGIS = function(m) -{ - -/* Defines an army - * An army is a collection of own entities and enemy entities. - * This doesn't use entity collections are they aren't really useful - * and it would probably slow the rest of the system down too much. - * All entities are therefore lists of ID - * Inherited by the defense manager and several of the attack manager's attack plan. - */ - -m.Army = function(gameState, owner, ownEntities, foeEntities) -{ - this.ID = m.playerGlobals[PlayerID].uniqueIDArmy++; - - this.Config = owner.Config; - this.defenceRatio = this.Config.Defence.defenceRatio; - this.compactSize = this.Config.Defence.armyCompactSize; - this.breakawaySize = this.Config.Defence.armyBreakawaySize; - - // average - this.foePosition = [0,0]; - this.ownPosition = [0,0]; - this.positionLastUpdate = gameState.getTimeElapsed(); - - // Some caching - // A list of our defenders that were tasked with attacking a particular unit - // This doesn't mean that they actually are since they could move on to something else on their own. - this.assignedAgainst = {}; - // who we assigned against, for quick removal. - this.assignedTo = {}; - - // For substrengths, format is "name": [classes] - - this.foeEntities = []; - this.foeStrength = 0; - this.foeSubStrength = {}; - - this.ownEntities = []; - this.ownStrength = 0; - this.ownSubStrength = {}; - - // actually add units - for (var i in foeEntities) - this.addFoe(gameState,foeEntities[i], true); - for (var i in ownEntities) - this.addOwn(gameState,ownEntities[i]); - - this.recalculatePosition(gameState, true); - - return true; -} - -// if not forced, will only recalculate if on a different turn. -m.Army.prototype.recalculatePosition = function(gameState, force) -{ - if (!force && this.positionLastUpdate === gameState.getTimeElapsed()) - return; - var pos = [0,0]; - if (this.foeEntities.length !== 0) - { - for each (var id in this.foeEntities) - { - var ent = gameState.getEntityById(id); - var epos = ent.position(); - pos[0] += epos[0]; - pos[1] += epos[1]; - } - this.foePosition[0] = pos[0]/this.foeEntities.length; - this.foePosition[1] = pos[1]/this.foeEntities.length; - } else - this.foePosition = [0,0]; - - pos = [0,0]; - if (this.ownEntities.length !== 0) - { - for each (var id in this.ownEntities) - { - var ent = gameState.getEntityById(id); - var epos = ent.position(); - pos[0] += epos[0]; - pos[1] += epos[1]; - } - this.ownPosition[0] = pos[0]/this.ownEntities.length; - this.ownPosition[1] = pos[1]/this.ownEntities.length; - } else - this.ownPosition = [0,0]; - - this.positionLastUpdate = gameState.getTimeElapsed(); -} - -// helper -m.Army.prototype.recalculateStrengths = function (gameState) -{ - this.ownStrength = 0; - this.foeStrength = 0; - - // todo: deal with specifics. - - for each (var id in this.foeEntities) - this.evaluateStrength(gameState.getEntityById(id)); - for each (var id in this.ownEntities) - this.evaluateStrength(gameState.getEntityById(id), true); -} - -// adds or remove the strength of the entity either to the enemy or to our units. -m.Army.prototype.evaluateStrength = function (ent, isOwn, remove) -{ - var entStrength = m.getMaxStrength(ent); - if (remove) - entStrength *= -1; - - if (isOwn) - this.ownStrength += entStrength; - else - this.foeStrength += entStrength; - - // todo: deal with specifics. -} - -// add an entity to the enemy army -// Will return true if the entity was added and false otherwise. -// won't recalculate our position but will dirty it. -m.Army.prototype.addFoe = function (gameState, enemyID, force) -{ - if (this.foeEntities.indexOf(enemyID) !== -1) - return false; - var ent = gameState.getEntityById(enemyID); - if (ent === undefined || ent.position() === undefined) - return false; - - // check distance - if (!force && API3.SquareVectorDistance(ent.position(), this.foePosition) > this.compactSize) - return false; - - this.foeEntities.push(enemyID); - this.assignedAgainst[enemyID] = []; - this.positionLastUpdate = 0; - this.evaluateStrength(ent); - ent.setMetadata(PlayerID, "PartOfArmy", this.ID); - - return true; -} - -// returns true if the entity was removed and false otherwise. -// TODO: when there is a technology update, we should probably recompute the strengths, or weird stuffs will happen. -m.Army.prototype.removeFoe = function (gameState, enemyID, enemyEntity) -{ - var idx = this.foeEntities.indexOf(enemyID); - if (idx === -1) - return false; - var ent = enemyEntity === undefined ? gameState.getEntityById(enemyID) : enemyEntity; - if (ent === undefined) - { - warn("Trying to remove a non-existing enemy entity, crashing for stacktrace"); - xgzrg(); - } - this.foeEntities.splice(idx, 1); - this.evaluateStrength(ent, false, true); - ent.setMetadata(PlayerID, "PartOfArmy", undefined); - - this.assignedAgainst[enemyID] = undefined; - - return true; -} - -// adds a defender but doesn't assign him yet. -m.Army.prototype.addOwn = function (gameState, ID) -{ - if (this.ownEntities.indexOf(ID) !== -1) - return false; - var ent = gameState.getEntityById(ID); - if (ent === undefined || ent.position() === undefined) - return false; - - this.ownEntities.push(ID); - this.evaluateStrength(ent, true); - ent.setMetadata(PlayerID, "PartOfArmy", this.ID); - this.assignedTo[ID] = 0; - - var formerRole = ent.getMetadata(PlayerID, "role"); - var formerSubRole = ent.getMetadata(PlayerID, "subrole"); - if (formerRole !== undefined) - ent.setMetadata(PlayerID,"formerRole", formerRole); - if (formerSubRole !== undefined) - ent.setMetadata(PlayerID,"formerSubRole", formerSubRole); - - ent.setMetadata(PlayerID, "role", "defense"); - ent.setMetadata(PlayerID, "subrole", "defending"); - - return true; -} - -m.Army.prototype.removeOwn = function (gameState, ID, Entity) -{ - var idx = this.ownEntities.indexOf(ID); - if (idx === -1) - return false; - var ent = Entity === undefined ? gameState.getEntityById(ID) : Entity; - if (ent === undefined) - { - warn( ID); - warn("Trying to remove a non-existing entity, crashing for stacktrace"); - xgzrg(); - } - - this.ownEntities.splice(idx, 1); - this.evaluateStrength(ent, true, true); - ent.setMetadata(PlayerID, "PartOfArmy", undefined); - - if (this.assignedTo[ID] !== 0) - { - var temp = this.assignedAgainst[this.assignedTo[ID]]; - if (temp) - temp.splice(temp.indexOf(ID), 1); - } - this.assignedTo[ID] = undefined; - - var formerRole = ent.getMetadata(PlayerID, "formerRole"); - var formerSubRole = ent.getMetadata(PlayerID, "formerSubRole"); - if (formerRole !== undefined) - ent.setMetadata(PlayerID,"role", formerRole); - else - ent.setMetadata(PlayerID,"role", undefined); - if (formerSubRole !== undefined) - ent.setMetadata(PlayerID,"subrole", formerSubRole); - else - ent.setMetadata(PlayerID,"subrole", undefined); - - return true; -} - -// this one is "undefined entity" proof because it's called at odd times. -// Orders a unit to attack an enemy. -// overridden by specific army classes. -m.Army.prototype.assignUnit = function (gameState, entID) -{ -} - -// resets the army properly. -// assumes we already cleared dead units. -m.Army.prototype.clear = function (gameState, events) -{ - while(this.foeEntities.length > 0) - this.removeFoe(gameState,this.foeEntities[0]); - while(this.ownEntities.length > 0) - this.removeOwn(gameState,this.ownEntities[0]); - - this.assignedAgainst = {}; - this.assignedTo = {}; - - this.recalculateStrengths(gameState); - this.recalculatePosition(gameState); -} - -// merge this army with another properly. -// assumes units are in only one army. -// also assumes that all have been properly cleaned up (no dead units). -m.Army.prototype.merge = function (gameState, otherArmy) -{ - // copy over all parameters. - for (var i in otherArmy.assignedAgainst) - { - if (this.assignedAgainst[i] === undefined) - this.assignedAgainst[i] = otherArmy.assignedAgainst[i]; - else - this.assignedAgainst[i] = this.assignedAgainst[i].concat(otherArmy.assignedAgainst[i]); - } - for (var i in otherArmy.assignedTo) - this.assignedTo[i] = otherArmy.assignedTo[i]; - - for each (var id in otherArmy.foeEntities) - this.addFoe(gameState, id); - // TODO: reassign those ? - for each (var id in otherArmy.ownEntities) - this.addOwn(gameState, id); - - this.recalculatePosition(gameState, true); - this.recalculateStrengths(gameState); - - return true; -} - -// TODO: when there is a technology update, we should probably recompute the strengths, or weird stuffs might happen. -m.Army.prototype.checkEvents = function (gameState, events) -{ - var renameEvents = events["EntityRenamed"]; // take care of promoted and packed units - var destroyEvents = events["Destroy"]; - var convEvents = events["OwnershipChanged"]; - var garriEvents = events["Garrison"]; - - // Warning the metadata is already cloned in shared.js. Futhermore, changes should be done before destroyEvents - // otherwise it would remove the old entity from this army list - // TODO we should may-be reevaluate the strength - for each (var msg in renameEvents) - { - if (this.foeEntities.indexOf(msg.entity) !== -1) - { - var idx = this.foeEntities.indexOf(msg.entity); - this.foeEntities[idx] = msg.newentity; - this.assignedAgainst[msg.newentity] = this.assignedAgainst[msg.entity]; - this.assignedAgainst[msg.entity] = undefined; - for (var to in this.assignedTo) - if (this.assignedTo[to] == msg.entity) - this.assignedTo[to] = msg.newentity; - } - else if (this.ownEntities.indexOf(msg.entity) !== -1) - { - var idx = this.ownEntities.indexOf(msg.entity); - this.ownEntities[idx] = msg.newentity; - this.assignedTo[msg.newentity] = this.assignedTo[msg.entity]; - this.assignedTo[msg.entity] = undefined; - for (var against in this.assignedAgainst) - { - if (!this.assignedAgainst[against]) - continue; - if (this.assignedAgainst[against].indexOf(msg.entity) !== -1) - this.assignedAgainst[against][this.assignedAgainst[against].indexOf(msg.entity)] = msg.newentity; - } - } - } - - for each (var msg in destroyEvents) - { - if (msg.entityObj === undefined) - continue; - if (msg.entityObj._entity.owner === PlayerID) - this.removeOwn(gameState, msg.entity, msg.entityObj); - else - this.removeFoe(gameState, msg.entity, msg.entityObj); - } - - for each (var msg in garriEvents) - this.removeFoe(gameState, msg.entity); - - for each (var msg in convEvents) - { - if (msg.to === PlayerID) - { - // we have converted an enemy, let's assign it as a defender - if (this.removeFoe(gameState, msg.entity)) - this.addOwn(gameState, msg.entity); - } else if (msg.from === PlayerID) - this.removeOwn(gameState, msg.entity); // TODO: add allies - } -} - -// assumes cleaned army. -// this only checks for breakaways. -m.Army.prototype.onUpdate = function (gameState) -{ - var breakaways = []; - // TODO: assign unassigned defenders, cleanup of a few things. - // perhaps occasional strength recomputation - - // occasional update or breakaways, positions… - if (gameState.getTimeElapsed() - this.positionLastUpdate > 5000) - { - this.recalculatePosition(gameState); - this.positionLastUpdate = gameState.getTimeElapsed(); - - // Check for breakaways. - for (var i = 0; i < this.foeEntities.length; ++i) - { - var id = this.foeEntities[i]; - var ent = gameState.getEntityById(id); - if (API3.SquareVectorDistance(ent.position(), this.foePosition) > this.breakawaySize) - { - breakaways.push(id); - if(this.removeFoe(gameState, id)) - i--; - } - } - - this.recalculatePosition(gameState); - } - - return breakaways; -} - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/army-defense.js b/binaries/data/mods/public/simulation/ai/aegis/army-defense.js deleted file mode 100644 index 4cfa74cd95..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/army-defense.js +++ /dev/null @@ -1,156 +0,0 @@ -var AEGIS = function(m) -{ - -// Specialization of Armies used by the defense manager. -m.DefenseArmy = function(gameState, defManager, ownEntities, foeEntities) -{ - if (!m.Army.call(this, gameState, defManager, ownEntities, foeEntities)) - return false; - - this.watchTSMultiplicator = this.Config.Defence.armyStrengthWariness; - this.watchDecrement = this.Config.Defence.prudence; - - this.foeSubStrength = { - "spear" : ["Infantry", "Spear"], //also pikemen - "sword" : ["Infantry", "Sword"], - "ranged" : ["Infantry", "Ranged"], - "meleeCav" : ["Cavalry", "Melee"], - "rangedCav" : ["Cavalry", "Ranged"], - "Elephant" : ["Elephant"], - "meleeSiege" : ["Siege", "Melee"], - "rangedSiege" : ["Siege", "Ranged"] - }; - this.ownSubStrength = { - "spear" : ["Infantry", "Spear"], //also pikemen - "sword" : ["Infantry", "Sword"], - "ranged" : ["Infantry", "Ranged"], - "meleeCav" : ["Cavalry", "Melee"], - "rangedCav" : ["Cavalry", "Ranged"], - "Elephant" : ["Elephant"], - "meleeSiege" : ["Siege", "Melee"], - "rangedSiege" : ["Siege", "Ranged"] - }; - - this.checkDangerosity(gameState); // might push us to 1. - this.watchLevel = this.foeStrength * this.watchTSMultiplicator; - - return true; -} - -m.DefenseArmy.prototype = Object.create(m.Army.prototype); - -m.DefenseArmy.prototype.assignUnit = function (gameState, entID) -{ - // we'll assume this defender is ours already. - // we'll also override any previous assignment - - var ent = gameState.getEntityById(entID); - if (!ent) - return false; - - // TODO: improve the logic in there. - var maxVal = 1000000; - var maxEnt = -1; - - for each (var id in this.foeEntities) - { - var eEnt = gameState.getEntityById(id); - if (!eEnt) // probably can't happen. - continue; - - if (maxVal > this.assignedAgainst[id].length) - { - maxVal = this.assignedAgainst[id].length; - maxEnt = id; - } - } - if (maxEnt === -1) - return false; - - // let's attack id - this.assignedAgainst[maxEnt].push(entID); - this.assignedTo[entID] = maxEnt; - - ent.attack(maxEnt); - - return true; -} - -// TODO: this should return cleverer results ("needs anti-elephant"…) -m.DefenseArmy.prototype.needsDefenders = function (gameState, events) -{ - // some preliminary checks because we don't update for tech - if (this.foeStrength < 0 || this.ownStrength < 0) - this.recalculateStrengths(gameState); - - if (this.foeStrength * this.defenceRatio < this.ownStrength) - return false; - return this.foeStrength * this.defenceRatio - this.ownStrength; -} - -m.DefenseArmy.prototype.getState = function (gameState) -{ - if (this.foeEntities.length === 0) - return 0; - if (this.state === 2) - return 2; - if (this.watchLevel > 0) - return 1; - return 0; -} - -// check if we should remain at state 2 or drift away -m.DefenseArmy.prototype.checkDangerosity = function (gameState) -{ - this.territoryMap = m.createTerritoryMap(gameState); - // right now we'll check if our position is "enemy" or not. - if (this.territoryMap.getOwner(this.ownPosition) !== PlayerID) - this.state = 1; - else if (this.state === 1) - this.state = 2; -} - -m.DefenseArmy.prototype.update = function (gameState) -{ - var breakaways = this.onUpdate(gameState); - - this.checkDangerosity(gameState); - - var normalWatch = this.foeStrength * this.watchTSMultiplicator; - if (this.state === 2) - this.watchLevel = normalWatch; - else if (this.watchLevel > normalWatch) - this.watchLevel = normalWatch; - else - this.watchLevel -= this.watchDecrement; - - // TODO: deal with watchLevel? - - return breakaways; -} - -m.DefenseArmy.prototype.debug = function (gameState) -{ - m.debug(" "); - m.debug ("Army " + this.ID) -// m.debug ("state " + this.state); -// m.debug ("WatchLevel " + this.watchLevel); -// m.debug ("Entities " + this.foeEntities.length); -// m.debug ("Strength " + this.foeStrength); - // debug (gameState.getEntityById(ent)._templateName + ", ID " + ent); - //debug ("Defenders " + this.ownEntities.length); - for each (ent in this.foeEntities) - { - if (gameState.getEntityById(ent) !== undefined) - { - m.debug (gameState.getEntityById(ent)._templateName + ", ID " + ent); - Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent], "rgb": [0.5,0,0]}); - } else - m.debug("ent " + ent); - } - //debug ("Strength " + this.ownStrength); - m.debug (""); - -} -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/attack_plan.js b/binaries/data/mods/public/simulation/ai/aegis/attack_plan.js deleted file mode 100755 index fbd14bab48..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/attack_plan.js +++ /dev/null @@ -1,1217 +0,0 @@ -var AEGIS = function(m) -{ - -/* This is an attack plan (despite the name, it's a relic of older times). - * It deals with everything in an attack, from picking a target to picking a path to it - * To making sure units rae built, and pushing elements to the queue manager otherwise - * It also handles the actual attack, though much work is needed on that. - * These should be extremely flexible with only minimal work. - * There is a basic support for naval expeditions here. - */ - -m.CityAttack = function CityAttack(gameState, HQ, Config, uniqueID, targetEnemy, type , targetFinder) { - - this.Config = Config; - //This is the list of IDs of the units in the plan - this.idList=[]; - - this.state = "unexecuted"; - this.targetPlayer = targetEnemy; - if (this.targetPlayer === -1 || this.targetPlayer === undefined) { - // let's find our prefered target, basically counting our enemies units. - // TODO: improve this. - var enemyCount = {}; - for (var i = 1; i <=8; i++) - enemyCount[i] = 0; - gameState.getEntities().forEach(function(ent) { if (gameState.isEntityEnemy(ent) && ent.owner() !== 0) { enemyCount[ent.owner()]++; } }); - var max = 0; - for (var i in enemyCount) - if (enemyCount[i] > max && +i !== PlayerID) - { - this.targetPlayer = +i; - max = enemyCount[i]; - } - } - if (this.targetPlayer === undefined || this.targetPlayer === -1) - { - this.failed = true; - return false; - } - - var CCs = gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")); - if (CCs.length === 0) - { - this.failed = true; - return false; - } - - m.debug ("Target (" + PlayerID +") = " +this.targetPlayer); - this.targetFinder = targetFinder || this.defaultTargetFinder; - this.type = type || "normal"; - this.name = uniqueID; - this.healthRecord = []; - - this.timeOfPlanStart = gameState.getTimeElapsed(); // we get the time at which we decided to start the attack - - this.maxPreparationTime = 210*1000; - - this.pausingStart = 0; - this.totalPausingTime = 0; - this.paused = false; - - this.onArrivalReaction = "proceedOnTargets"; - - // priority of the queues we'll create. - var priority = 70; - - // priority is relative. If all are 0, the only relevant criteria is "currentsize/targetsize". - // if not, this is a "bonus". The higher the priority, the faster this unit will get built. - // Should really be clamped to [0.1-1.5] (assuming 1 is default/the norm) - // Eg: if all are priority 1, and the siege is 0.5, the siege units will get built - // only once every other category is at least 50% of its target size. - // note: siege build order is currently added by the military manager if a fortress is there. - this.unitStat = {}; - this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 6, "targetSize" : 18, "batchSize" : 3, "classes" : ["Infantry","Ranged"], "interests" : [ ["canGather", 1], ["strength",1.6], ["cost",1.5], ["costsResource", 0.3, "stone"], ["costsResource", 0.3, "metal"] ], "templates" : [] }; - this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 6, "targetSize" : 18, "batchSize" : 3, "classes" : ["Infantry","Melee"], "interests" : [ ["canGather", 1], ["strength",1.6], ["cost",1.5], ["costsResource", 0.3, "stone"], ["costsResource", 0.3, "metal"] ], "templates" : [] }; - - var ats = Math.random() * 15000 - 15000; // attack time shuffle: move the exact attack time around a bit. - - // in this case we want to have the attack ready by the 14th minute. Countdown. Minimum 2 minutes. - if (this.Config.difficulty >= 1) - this.maxPreparationTime = (800000+ats) - gameState.getTimeElapsed() < 120000 ? 120000 : 800000 + ats - gameState.getTimeElapsed(); - - if (type === "Rush") { - // we have 3 minutes to train infantry. - delete this.unitStat["RangedInfantry"]; - delete this.unitStat["MeleeInfantry"]; - this.unitStat["Infantry"] = { "priority" : 1, "minSize" : 10, "targetSize" : 30, "batchSize" : 2, "classes" : ["Infantry"], "interests" : [ ["strength",1], ["cost",1], ["costsResource", 0.5, "stone"], ["costsResource", 0.6, "metal"] ], "templates" : [] }; - this.maxPreparationTime = (540000+ats) - gameState.getTimeElapsed() < 120000 ? 120000 : 540000 + ats - gameState.getTimeElapsed(); - priority = 250; - } else if (type === "superSized") { - // our first attack has started worst case at the 14th minute, we want to attack another time by the 21th minute, so we rock 6.5 minutes - this.maxPreparationTime = 480000; // 8 minutes - // basically we want a mix of citizen soldiers so our barracks have a purpose, and champion units. - this.unitStat["RangedInfantry"] = { "priority" : 0.7, "minSize" : 5, "targetSize" : 15, "batchSize" : 5, "classes" : ["Infantry","Ranged", "CitizenSoldier"], "interests" : [["strength",3], ["cost",1] ], "templates" : [] }; - this.unitStat["MeleeInfantry"] = { "priority" : 0.7, "minSize" : 5, "targetSize" : 15, "batchSize" : 5, "classes" : ["Infantry","Melee", "CitizenSoldier" ], "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] }; - this.unitStat["ChampRangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 25, "batchSize" : 5, "classes" : ["Infantry","Ranged", "Champion"], "interests" : [["strength",3], ["cost",1] ], "templates" : [] }; - this.unitStat["ChampMeleeInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 20, "batchSize" : 5, "classes" : ["Infantry","Melee", "Champion" ], "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] }; - this.unitStat["MeleeCavalry"] = { "priority" : 0.7, "minSize" : 3, "targetSize" : 15, "batchSize" : 3, "classes" : ["Cavalry","Melee", "CitizenSoldier" ], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; - this.unitStat["RangedCavalry"] = { "priority" : 0.7, "minSize" : 3, "targetSize" : 15, "batchSize" : 3, "classes" : ["Cavalry","Ranged", "CitizenSoldier"], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; - this.unitStat["ChampMeleeInfantry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 18, "batchSize" : 3, "classes" : ["Infantry","Melee", "Champion" ], "interests" : [ ["strength",3], ["cost",1] ], "templates" : [] }; - this.unitStat["ChampMeleeCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 18, "batchSize" : 3, "classes" : ["Cavalry","Melee", "Champion" ], "interests" : [ ["strength",2], ["cost",1] ], "templates" : [] }; - - priority = 90; - } - - // TODO: there should probably be one queue per type of training building - gameState.ai.queueManager.addQueue("plan_" + this.name, priority); - this.queue = gameState.ai.queues["plan_" + this.name]; - gameState.ai.queueManager.addQueue("plan_" + this.name +"_champ", priority+1); - this.queueChamp = gameState.ai.queues["plan_" + this.name +"_champ"]; - /* - this.unitStat["Siege"]["filter"] = function (ent) { - var strength = [ent.attackStrengths("Melee")["crush"],ent.attackStrengths("Ranged")["crush"]]; - return (strength[0] > 15 || strength[1] > 15); - };*/ - - var filter = API3.Filters.and(API3.Filters.byMetadata(PlayerID, "plan",this.name),API3.Filters.byOwner(PlayerID)); - this.unitCollection = gameState.getOwnUnits().filter(filter); - this.unitCollection.registerUpdates(); - this.unitCollection.length; - - this.unit = {}; - - // each array is [ratio, [associated classes], associated EntityColl, associated unitStat, name ] - this.buildOrder = []; - - // defining the entity collections. Will look for units I own, that are part of this plan. - // Also defining the buildOrders. - for (var unitCat in this.unitStat) { - var cat = unitCat; - var Unit = this.unitStat[cat]; - - filter = API3.Filters.and(API3.Filters.byClassesAnd(Unit["classes"]),API3.Filters.and(API3.Filters.byMetadata(PlayerID, "plan",this.name),API3.Filters.byOwner(PlayerID))); - this.unit[cat] = gameState.getOwnUnits().filter(filter); - this.unit[cat].registerUpdates(); - this.unit[cat].length; - this.buildOrder.push([0, Unit["classes"], this.unit[cat], Unit, cat]); - } - /*if (gameState.getTimeElapsed() > 900000) // 15 minutes - { - - this.unitStat.Cavalry.Ranged["minSize"] = 5; - this.unitStat.Cavalry.Melee["minSize"] = 5; - this.unitStat.Infantry.Ranged["minSize"] = 10; - this.unitStat.Infantry.Melee["minSize"] = 10; - this.unitStat.Cavalry.Ranged["targetSize"] = 10; - this.unitStat.Cavalry.Melee["targetSize"] = 10; - this.unitStat.Infantry.Ranged["targetSize"] = 20; - this.unitStat.Infantry.Melee["targetSize"] = 20; - this.unitStat.Siege["targetSize"] = 5; - this.unitStat.Siege["minSize"] = 2; - - } else { - this.maxPreparationTime = 180000; - }*/ - // todo: REACTIVATE (in all caps) - if (type === "harass_raid" && 0 == 1) - { - this.targetFinder = this.raidingTargetFinder; - this.onArrivalReaction = "huntVillagers"; - - this.type = "harass_raid"; - // This is a Cavalry raid against villagers. A Cavalry Swordsman has a bonus against these. Only build these - this.maxPreparationTime = 180000; // 3 minutes. - if (gameState.playerData.civ === "hele") // hellenes have an ealry Cavalry Swordsman - { - this.unitCount.Cavalry.Melee = { "subCat" : ["Swordsman"] , "usesSubcategories" : true, "Swordsman" : undefined, "priority" : 1, "currentAmount" : 0, "minimalAmount" : 0, "preferedAmount" : 0 }; - this.unitCount.Cavalry.Melee.Swordsman = { "priority" : 1, "currentAmount" : 0, "minimalAmount" : 4, "preferedAmount" : 7, "fallback" : "abort" }; - } else { - this.unitCount.Cavalry.Melee = { "subCat" : undefined , "usesSubcategories" : false, "priority" : 1, "currentAmount" : 0, "minimalAmount" : 4, "preferedAmount" : 7 }; - } - this.unitCount.Cavalry.Ranged["minimalAmount"] = 0; - this.unitCount.Cavalry.Ranged["preferedAmount"] = 0; - this.unitCount.Infantry.Ranged["minimalAmount"] = 0; - this.unitCount.Infantry.Ranged["preferedAmount"] = 0; - this.unitCount.Infantry.Melee["minimalAmount"] = 0; - this.unitCount.Infantry.Melee["preferedAmount"] = 0; - this.unitCount.Siege["preferedAmount"] = 0; - } - this.anyNotMinimal = true; // used for support plans - - - var myFortresses = gameState.getOwnTrainingFacilities().filter(API3.Filters.byClass("GarrisonFortress")); - if (myFortresses.length !== 0) - { - // make this our rallypoint - for (var i in myFortresses._entities) - { - if (myFortresses._entities[i].position()) - { - this.rallyPoint = myFortresses._entities[i].position(); - break; - } - } - } else { - - if(gameState.ai.pathsToMe.length > 1) - var position = [(gameState.ai.pathsToMe[0][0]+gameState.ai.pathsToMe[1][0])/2.0,(gameState.ai.pathsToMe[0][1]+gameState.ai.pathsToMe[1][1])/2.0]; - else if (gameState.ai.pathsToMe.length !== 0) - var position = [gameState.ai.pathsToMe[0][0],gameState.ai.pathsToMe[0][1]]; - else - var position = [-1,-1]; - - if (gameState.ai.accessibility.getAccessValue(position) !== gameState.ai.myIndex) - var position = [-1,-1]; - - var nearestCCArray = CCs.filterNearest(position, 1).toEntityArray(); - var CCpos = nearestCCArray[0].position(); - this.rallyPoint = [0,0]; - if (position[0] !== -1) { - this.rallyPoint[0] = position[0]; - this.rallyPoint[1] = position[1]; - } else { - this.rallyPoint[0] = CCpos[0]; - this.rallyPoint[1] = CCpos[1]; - } - if (type == 'harass_raid') - { - this.rallyPoint[0] = (position[0]*3.9 + 0.1 * CCpos[0]) / 4.0; - this.rallyPoint[1] = (position[1]*3.9 + 0.1 * CCpos[1]) / 4.0; - } - } - - // some variables for during the attack - this.position5TurnsAgo = [0,0]; - this.lastPosition = [0,0]; - this.position = [0,0]; - - this.threatList = []; // sounds so FBI - this.tactics = undefined; - - this.assignUnits(gameState); - - //m.debug ("Before"); - //Engine.DumpHeap(); - - // get a good path to an estimated target. - this.pathFinder = new API3.aStarPath(gameState,false,false, this.targetPlayer); - //Engine.DumpImage("widthmap.png", this.pathFinder.widthMap, this.pathFinder.width,this.pathFinder.height,255); - - this.pathWidth = 6; // prefer a path far from entities. This will avoid units getting stuck in trees and also results in less straight paths. - this.pathSampling = 2; - this.onBoat = false; // tells us if our units are loaded on boats. - this.needsShip = false; - - //m.debug ("after"); - //Engine.DumpHeap(); - return true; -}; - -m.CityAttack.prototype.getName = function(){ - return this.name; -}; -m.CityAttack.prototype.getType = function(){ - return this.type; -}; -// Returns true if the attack can be executed at the current time -// Basically his checks we have enough units. -// We run a count of our units. -m.CityAttack.prototype.canStart = function(gameState){ - for (var unitCat in this.unitStat) { - var Unit = this.unitStat[unitCat]; - if (this.unit[unitCat].length < Unit["minSize"]) - { - m.debug(unitCat + " doesn't have enough units : " + this.unit[unitCat].length); - return false; - } - } - return true; - - // TODO: check if our target is valid and a few other stuffs (good moment to attack?) -}; -m.CityAttack.prototype.isStarted = function(){ - if ((this.state !== "unexecuted")) - m.debug ("Attack plan already started"); - return !(this.state == "unexecuted"); -}; - -m.CityAttack.prototype.isPaused = function(){ - return this.paused; -}; -m.CityAttack.prototype.setPaused = function(gameState, boolValue){ - if (!this.paused && boolValue === true) { - this.pausingStart = gameState.getTimeElapsed(); - this.paused = true; - m.debug ("Pausing attack plan " +this.name); - } else if (this.paused && boolValue === false) { - this.totalPausingTime += gameState.getTimeElapsed() - this.pausingStart; - this.paused = false; - m.debug ("Unpausing attack plan " +this.name); - } -}; -m.CityAttack.prototype.mustStart = function(gameState){ - if (this.isPaused() || this.path === undefined) - return false; - var MaxReachedEverywhere = true; - for (var unitCat in this.unitStat) { - var Unit = this.unitStat[unitCat]; - if (this.unit[unitCat].length < Unit["targetSize"]) { - MaxReachedEverywhere = false; - } - } - if (MaxReachedEverywhere || (gameState.getPopulationMax() - gameState.getPopulation() < 10 && this.canStart(gameState))) - return true; - return (this.maxPreparationTime + this.timeOfPlanStart + this.totalPausingTime < gameState.getTimeElapsed()); -}; - -// Adds a build order. If resetQueue is true, this will reset the queue. -m.CityAttack.prototype.addBuildOrder = function(gameState, name, unitStats, resetQueue) { - if (!this.isStarted()) - { - m.debug ("Adding a build order for " + name); - // no minsize as we don't want the plan to fail at the last minute though. - this.unitStat[name] = unitStats; - var Unit = this.unitStat[name]; - var filter = API3.Filters.and(API3.Filters.byClassesAnd(Unit["classes"]),API3.Filters.and(API3.Filters.byMetadata(PlayerID, "plan",this.name),API3.Filters.byOwner(PlayerID))); - this.unit[name] = gameState.getOwnUnits().filter(filter); - this.unit[name].registerUpdates(); - this.buildOrder.push([0, Unit["classes"], this.unit[name], Unit, name]); - if (resetQueue) - { - this.queue.empty(); - this.queueChamp.empty(); - } - } -}; - -// 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. -m.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, HQ); - if (targets.length === 0) - targets = this.defaultTargetFinder(gameState, HQ); - - if (targets.length !== 0) { - m.debug ("Aiming for " + targets); - // picking a target - var maxDist = -1; - var index = 0; - for (var i in targets._entities) - { - // we're sure it has a position has TargetFinder already checks that. - var dist = API3.SquareVectorDistance(targets._entities[i].position(), this.rallyPoint); - if (dist < maxDist || maxDist === -1) - { - maxDist = dist; - index = i; - } - } - this.target = targets._entities[index]; - this.targetPos = this.target.position(); - } - } - // when we have a target, we path to it. - // I'd like a good high width sampling first. - // 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,175);//,gameState); - else if (this.path === "toBeContinued") - this.path = this.pathFinder.continuePath();//gameState); - - if (this.path === undefined) { - if (this.pathWidth == 6) - { - this.pathWidth = 2; - delete this.path; - } else { - delete this.pathFinder; - return 3; // no path. - } - } else if (this.path === "toBeContinued") { - // carry on. - } 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.HQ.waterMap) - { - m.debug ("This is actually a water map."); - gameState.ai.HQ.waterMap = true; - return 0; - } - m.debug ("We need a ship."); - this.needsShip = true; - this.pathWidth = 3; - this.pathSampling = 3; - this.path = this.path[0].reverse(); - delete this.pathFinder; - // Change the rally point to something useful (should avoid rams getting stuck in houses in my territory, which is dumb.) - for (var i = 0; i < this.path.length; ++i) - { - // my pathfinder returns arrays in arrays in arrays. - var waypointPos = this.path[i][0]; - var territory = m.createTerritoryMap(gameState); - if (territory.getOwner(waypointPos) !== PlayerID || this.path[i][1] === true) - { - // if we're suddenly out of our territory or this is the point where we change transportation method. - if (i !== 0) - this.rallyPoint = this.path[i-1][0]; - else - this.rallyPoint = this.path[0][0]; - if (i >= 1) - this.path.splice(0,i-1); - break; - } - } - } else if (this.path[1] === true && this.pathWidth == 6) { - // retry with a smaller pathwidth: - this.pathWidth = 2; - delete this.path; - } else { - this.path = this.path[0].reverse(); - delete this.pathFinder; - - // Change the rally point to something useful (should avoid rams getting stuck in houses in my territory, which is dumb.) - for (var i = 0; i < this.path.length; ++i) - { - // my pathfinder returns arrays in arrays in arrays. - var waypointPos = this.path[i][0]; - var territory = m.createTerritoryMap(gameState); - if (territory.getOwner(waypointPos) !== PlayerID || this.path[i][1] === true) - { - // if we're suddenly out of our territory or this is the point where we change transportation method. - if (i !== 0) - { - this.rallyPoint = this.path[i-1][0]; - } else - this.rallyPoint = this.path[0][0]; - if (i >= 1) - this.path.splice(0,i-1); - break; - } - } - } - } - - Engine.ProfileStart("Update Preparation"); - - // special case: if we're reached max pop, and we can start the plan, start it. - if ((gameState.getPopulationMax() - gameState.getPopulation() < 10) && this.canStart()) - { - this.assignUnits(gameState); - this.queue.empty(); - this.queueChamp.empty(); - if ( gameState.ai.playedTurn % 5 == 0) - this.AllToRallyPoint(gameState, true); - } else if (this.mustStart(gameState) && (gameState.countOwnQueuedEntitiesWithMetadata("plan", +this.name) > 0)) { - // keep on while the units finish being trained, then we'll start - this.assignUnits(gameState); - - this.queue.empty(); - this.queueChamp.empty(); - - if (gameState.ai.playedTurn % 5 == 0) { - this.AllToRallyPoint(gameState, true); - // TODO: should use this time to let gatherers deposit resources. - } - Engine.ProfileStop(); - return 1; - } else if (!this.mustStart(gameState)) { - // We still have time left to recruit units and do stuffs. - - // 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.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.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"]; - b[0] -= b[3]["priority"]; - return (a[0]) - (b[0]); - }); - - this.assignUnits(gameState); - - if (gameState.ai.playedTurn % 5 == 0) { - this.AllToRallyPoint(gameState, false); - this.unitCollection.setStance("standground"); // make sure units won't disperse out of control - } - - Engine.ProfileStart("Creating units."); - - // gets the number in training of the same kind as the first one. - var specialData = "Plan_"+this.name+"_"+this.buildOrder[0][4]; - var inTraining = gameState.countOwnQueuedEntitiesWithMetadata("special",specialData); - - 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 - var queue = this.queue; - if (this.buildOrder[0][3]["classes"].indexOf("Champion") !== -1) - queue = this.queueChamp; - - if (this.buildOrder[0][0] < 1 && queue.length() <= 5) { - var template = HQ.findBestTrainableUnit(gameState, this.buildOrder[0][1], this.buildOrder[0][3]["interests"] ); - //m.debug ("tried " + uneval(this.buildOrder[0][1]) +", and " + template); - // HACK (TODO replace) : if we have no trainable template... Then we'll simply remove the buildOrder, effectively removing the unit from the plan. - if (template === undefined) { - // TODO: this is a complete hack. - delete this.unitStat[this.buildOrder[0][4]]; // deleting the associated unitstat. - this.buildOrder.splice(0,1); - } else { - var max = this.buildOrder[0][3]["batchSize"]; - // TODO: this should be plan dependant. - if (gameState.getTimeElapsed() > 1800000) - max *= 2; - if (gameState.getTemplate(template).hasClass("CitizenSoldier")) - queue.addItem( new m.TrainingPlan(gameState,template, { "role" : "worker", "plan" : this.name, "special" : specialData, "base" : 1 }, this.buildOrder[0][3]["batchSize"],max ) ); - else - queue.addItem( new m.TrainingPlan(gameState,template, { "role" : "attack", "plan" : this.name, "special" : specialData, "base" : 1 }, this.buildOrder[0][3]["batchSize"],max ) ); - } - } - } - /* - if (!this.startedPathing && this.path === undefined) { - - // find our target - var targets = this.targetFinder(gameState, HQ); - if (targets.length === 0){ - targets = this.defaultTargetFinder(gameState, HQ); - } - if (targets.length) { - this.targetPos = undefined; - var count = 0; - while (!this.targetPos){ - var rand = Math.floor((Math.random()*targets.length)); - var target = targets.toEntityArray()[rand]; - this.targetPos = target.position(); - count++; - if (count > 1000){ - m.debug("No target with a valid position found"); - Engine.ProfileStop(); - Engine.ProfileStop(); - return false; - } - } - this.startedPathing = true; - // Start pathfinding using the optimized version, with a minimal sampling of 2 - this.pathFinder.getPath(this.rallyPoint,this.targetPos, false, 2, gameState); - } - } else if (this.startedPathing) { - var path = this.pathFinder.continuePath(gameState); - if (path !== "toBeContinued") { - this.startedPathing = false; - this.path = path; - m.debug("Pathing ended"); - } - } - */ - Engine.ProfileStop(); - Engine.ProfileStop(); - // can happen for now - if (this.buildOrder.length === 0) { - m.debug ("Ending plan: no build orders"); - return 0; // will abort the plan, should return something else - } - return 1; - } - this.unitCollection.forEach(function (entity) { entity.setMetadata(PlayerID, "role","attack"); }); - - Engine.ProfileStop(); - // if we're here, it means we must start (and have no units in training left). - // if we can, do, else, abort. - if (this.canStart(gameState)) - return 2; - else - return 0; - return 0; -}; -m.CityAttack.prototype.assignUnits = function(gameState){ - var self = this; - - // TODO: assign myself units that fit only, right now I'm getting anything. - // Assign all no-roles that fit (after a plan aborts, for example). - var NoRole = gameState.getOwnEntitiesByRole(undefined, false); - if (this.type === "rush") - NoRole = gameState.getOwnEntitiesByRole("worker", true); - NoRole.forEach(function(ent) { - if (ent.hasClass("Unit") && ent.attackTypes() !== undefined) - { - if (ent.hasClasses(["CitizenSoldier", "Infantry"])) - ent.setMetadata(PlayerID, "role", "worker"); - else - ent.setMetadata(PlayerID, "role", "attack"); - ent.setMetadata(PlayerID, "plan", self.name); - } - }); - -}; -// this sends a unit by ID back to the "rally point" -m.CityAttack.prototype.ToRallyPoint = function(gameState,id) -{ - // Move back to nearest rallypoint - gameState.getEntityById(id).move(this.rallyPoint[0],this.rallyPoint[1]); -} -// this sends all units back to the "rally point" by entity collections. -// It doesn't disturb ones that could be currently defending, even if the plan is not (yet) paused. -m.CityAttack.prototype.AllToRallyPoint = function(gameState, evenWorkers) { - var self = this; - if (evenWorkers) { - for (var unitCat in this.unit) { - this.unit[unitCat].forEach(function (ent) { - if (ent.getMetadata(PlayerID, "role") != "defence") - { - ent.setMetadata(PlayerID,"role", "attack"); - ent.move(self.rallyPoint[0],self.rallyPoint[1]); - } - }); - } - } 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.move(self.rallyPoint[0],self.rallyPoint[1]); - }); - } - } -} - -// Default target finder aims for conquest critical targets -m.CityAttack.prototype.defaultTargetFinder = function(gameState, HQ){ - var targets = undefined; - - targets = gameState.getEnemyStructures(this.targetPlayer).filter(API3.Filters.byClass("CivCentre")); - if (targets.length == 0) { - targets = gameState.getEnemyStructures(this.targetPlayer).filter(API3.Filters.byClass("ConquestCritical")); - } - // If there's nothing, attack anything else that's less critical - if (targets.length == 0) { - targets = gameState.getEnemyStructures(this.targetPlayer).filter(API3.Filters.byClass("Town")); - } - if (targets.length == 0) { - targets = gameState.getEnemyStructures(this.targetPlayer).filter(API3.Filters.byClass("Village")); - } - // no buildings, attack anything conquest critical, even units (it's assuming it won't move). - if (targets.length == 0) { - targets = gameState.getEnemyEntities(this.targetPlayer).filter(API3.Filters.byClass("ConquestCritical")); - } - return targets; -}; - -// tupdate -m.CityAttack.prototype.raidingTargetFinder = function(gameState, HQ, Target){ - var targets = undefined; - if (Target == "villager") - { - // let's aim for any resource dropsite. We assume villagers are in the neighborhood (note: the human player could certainly troll us... small (scouting) TODO here.) - targets = gameState.entities.filter(function(ent) { - return (ent.hasClass("Structure") && ent.resourceDropsiteTypes() !== undefined && !ent.hasClass("CivCentre") && ent.owner() === this.targetPlayer && ent.position()); - }); - if (targets.length == 0) { - targets = gameState.entities.filter(function(ent) { - return (ent.hasClass("CivCentre") && ent.resourceDropsiteTypes() !== undefined && ent.owner() === this.targetPlayer && ent.position()); - }); - } - if (targets.length == 0) { - // if we're here, it means they also don't have no CC... So I'll just take any building at this point. - targets = gameState.entities.filter(function(ent) { - return (ent.hasClass("Structure") && ent.owner() === this.targetPlayer && ent.position()); - }); - } - return targets; - } else { - 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 -m.CityAttack.prototype.StartAttack = function(gameState, HQ){ - - // check we have a target and a path. - if (this.targetPos && this.path !== undefined) { - // erase our queue. This will stop any leftover unit from being trained. - gameState.ai.queueManager.removeQueue("plan_" + this.name); - gameState.ai.queueManager.removeQueue("plan_" + this.name + "_champ"); - - var curPos = this.unitCollection.getCentrePosition(); - - this.unitCollection.forEach(function(ent) { ent.setMetadata(PlayerID, "subrole", "walking"); ent.setMetadata(PlayerID, "role", "attack") ;}); - // optimize our collection now. - this.unitCollection.allowQuickIter(); - - this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]); - this.unitCollection.setStance("aggressive"); - this.unitCollection.filter(API3.Filters.byClass("Siege")).setStance("defensive"); - - this.state = "walking"; - } else { - gameState.ai.gameFinished = true; - m.debug ("I do not have any target. So I'll just assume I won the game."); - return false; - } - return true; -}; - -// Runs every turn after the attack is executed -m.CityAttack.prototype.update = function(gameState, HQ, events){ - var self = this; - - Engine.ProfileStart("Update Attack"); - - // we're marching towards the target - // Check for attacked units in our band. - var bool_attacked = false; - // raids don't care about attacks much - - if (this.unitCollection.length === 0) { - Engine.ProfileStop(); - return 0; - } - - this.position = this.unitCollection.getCentrePosition(); - - var IDs = this.unitCollection.toIdArray(); - - // this actually doesn't do anything right now. - if (this.state === "walking") { - var attackedNB = 0; - - var toProcess = {}; - var armyToProcess = {}; - // Let's check if any of our unit has been attacked. In case yes, we'll determine if we're simply off against an enemy army, a lone unit/builing - // or if we reached the enemy base. Different plans may react differently. - - var attackedEvents = events["Attacked"]; - for (var key in attackedEvents) { - var e = attackedEvents[key]; - if (IDs.indexOf(e.target) !== -1) { - var attacker = gameState.getEntityById(e.attacker); - var ourUnit = gameState.getEntityById(e.target); - - if (attacker && attacker.position() && attacker.hasClass("Unit") && attacker.owner() != 0 && attacker.owner() != PlayerID) { - attackedNB++; - //if (HQ.enemyWatchers[attacker.owner()]) { - //toProcess[attacker.id()] = attacker; - //var armyID = HQ.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id()); - //armyToProcess[armyID[0]] = armyID[1]; - //} - } - // if we're being attacked by a building, flee. - if (attacker && ourUnit && attacker.hasClass("Structure")) { - ourUnit.flee(attacker); - } - - } - } - var territoryMap = m.createTerritoryMap(gameState); - if ((territoryMap.getOwner(this.position) === this.targetPlayer && attackedNB > 1) || attackedNB > 4) { - m.debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination."); - // we must assume we've arrived at the end of the trail. - this.state = "arrived"; - } - - /* - - }&& this.type !== "harass_raid"){ // walking toward the target - var sumAttackerPos = [0,0]; - var numAttackers = 0; - // let's check if one of our unit is not under attack, by any chance. - for (var key in events){ - var e = events[key]; - if (e.type === "Attacked" && e.msg){ - if (this.unitCollection.toIdArray().indexOf(e.msg.target) !== -1){ - var attacker = HeadQuarters.entity(e.msg.attacker); - if (attacker && attacker.position()){ - sumAttackerPos[0] += attacker.position()[0]; - sumAttackerPos[1] += attacker.position()[1]; - numAttackers += 1; - bool_attacked = true; - // todo: differentiate depending on attacker type... If it's a ship, let's not do anythin, a building, depends on the attack type/ - if (this.threatList.indexOf(e.msg.attacker) === -1) - { - var enemySoldiers = HeadQuarters.getEnemySoldiers().toEntityArray(); - for (var j in enemySoldiers) - { - var enemy = enemySoldiers[j]; - if (enemy.position() === undefined) // likely garrisoned - continue; - if (m.inRange(enemy.position(), attacker.position(), 1000) && this.threatList.indexOf(enemy.id()) === -1) - this.threatList.push(enemy.id()); - } - this.threatList.push(e.msg.attacker); - } - } - } - } - } - if (bool_attacked > 0){ - var avgAttackerPos = [sumAttackerPos[0]/numAttackers, sumAttackerPos[1]/numAttackers]; - units.move(avgAttackerPos[0], avgAttackerPos[1]); // let's run towards it. - this.tactics = new Tactics(gameState,HeadQuarters, this.idList,this.threatList,true); - this.state = "attacking_threat"; - } - }else if (this.state === "attacking_threat"){ - this.tactics.eventMetadataCleanup(events,HeadQuarters); - var removeList = this.tactics.removeTheirDeads(HeadQuarters); - this.tactics.removeMyDeads(HeadQuarters); - for (var i in removeList){ - this.threatList.splice(this.threatList.indexOf(removeList[i]),1); - } - if (this.threatList.length <= 0) - { - this.tactics.disband(HeadQuarters,events); - this.tactics = undefined; - this.state = "walking"; - units.move(this.path[0][0], this.path[0][1]); - }else - { - this.tactics.reassignAttacks(HeadQuarters); - } - }*/ - } - if (this.state === "walking"){ - - this.position = this.unitCollection.getCentrePosition(); - - // probably not too good. - if (!this.position) { - Engine.ProfileStop(); - return undefined; // should spawn an error. - } - - // basically haven't moved an inch: very likely stuck) - if (API3.SquareVectorDistance(this.position, this.position5TurnsAgo) < 10 && this.path.length > 0 && gameState.ai.playedTurn % 5 === 0) { - // check for stuck siege units - var sieges = this.unitCollection.filter(API3.Filters.byClass("Siege")); - var farthest = 0; - var farthestEnt = -1; - sieges.forEach (function (ent) { - if (API3.SquareVectorDistance(ent.position(),self.position) > farthest) - { - farthest = API3.SquareVectorDistance(ent.position(),self.position); - farthestEnt = ent; - } - }); - if (farthestEnt !== -1) - farthestEnt.destroy(); - } - if (gameState.ai.playedTurn % 5 === 0) - this.position5TurnsAgo = this.position; - - if (this.lastPosition && API3.SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) { - 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(API3.Filters.and(API3.Filters.byOwner(this.targetPlayer), API3.Filters.byClass("StoneWall"))); - var nexttoWalls = false; - walls.forEach( function (ent) { - if (!nexttoWalls && API3.SquareVectorDistance(self.position, ent.position()) < 800) - nexttoWalls = true; - }); - // there are walls but we can attack - if (nexttoWalls && this.unitCollection.filter(API3.Filters.byCanAttack("StoneWall")).length !== 0) - { - m.debug ("Attack Plan " +this.type +" " +this.name +" has met walls and is not happy."); - this.state = "arrived"; - } else if (nexttoWalls) { - // abort plan. - m.debug ("Attack Plan " +this.type +" " +this.name +" has met walls and gives up."); - Engine.ProfileStop(); - return 0; - } - } - - // check if our land units are close enough from the next waypoint. - if (API3.SquareVectorDistance(this.position, this.targetPos) < 9000 || - API3.SquareVectorDistance(this.position, this.path[0][0]) < 650) { - if (this.unitCollection.filter(API3.Filters.byClass("Siege")).length !== 0 - && API3.SquareVectorDistance(this.position, this.targetPos) >= 9000 - && API3.SquareVectorDistance(this.unitCollection.filter(API3.Filters.byClass("Siege")).getCentrePosition(), this.path[0][0]) >= 650) - { - } else { - // okay so here basically two cases. First case is "we've arrived" - // Second case is "either we need a boat, or we need to unload" - if (this.path[0][1] !== true) - { - this.path.shift(); - if (this.path.length > 0){ - this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]); - } else { - m.debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination."); - // we must assume we've arrived at the end of the trail. - this.state = "arrived"; - } - } else - { - // TODO: make this require an escort later on. - this.path.shift(); - if (this.path.length === 0) { - m.debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination."); - // we must assume we've arrived at the end of the trail. - this.state = "arrived"; - } else { - /* - var plan = new m.TransportPlan(gameState, this.unitCollection.toIdArray(), this.path[0][0], false); - this.tpPlanID = plan.ID; - HQ.navalManager.transportPlans.push(plan); - m.debug ("Transporting over sea"); - this.state = "transporting"; - */ - // TODO: fix this above - //right now we'll abort. - Engine.ProfileStop(); - return 0; - } - } - } - } - } else if (this.state === "transporting") { - // check that we haven't finished transporting, ie the plan - if (!HQ.navalManager.checkActivePlan(this.tpPlanID)) - { - this.state = "walking"; - } - } - - - // todo: re-implement raiding - if (this.state === "arrived"){ - // let's proceed on with whatever happens now. - // There's a ton of TODOs on this part. - if (this.onArrivalReaction == "proceedOnTargets") { - this.state = ""; - this.unitCollection.forEach( function (ent) { //}) { - ent.stopMoving(); - ent.setMetadata(PlayerID, "subrole", "attacking"); - }); - } else if (this.onArrivalReaction == "huntVillagers") { - // let's get any villager and target them with a tactics manager - var enemyCitizens = gameState.entities.filter(function(ent) { - return (gameState.isEntityEnemy(ent) && ent.hasClass("Support") && ent.owner() !== 0 && ent.position()); - }); - var targetList = []; - enemyCitizens.forEach( function (enemy) { - if (m.inRange(enemy.position(), units.getCentrePosition(), 2500) && targetList.indexOf(enemy.id()) === -1) - targetList.push(enemy.id()); - }); - if (targetList.length > 0) - { - this.tactics = new Tactics(gameState,HeadQuarters, this.idList,targetList); - this.state = "huntVillagers"; - var arrivedthisTurn = true; - } else { - this.state = ""; - var arrivedthisTurn = true; - } - } - } - - // basic state of attacking. - if (this.state === "") { - - // events watch: if siege units are attacked, we'll send some units to deal with enemies. - var attackedEvents = events["Attacked"]; - for (var key in attackedEvents) { - var e = attackedEvents[key]; - if (IDs.indexOf(e.target) !== -1) { - var attacker = gameState.getEntityById(e.attacker); - var ourUnit = gameState.getEntityById(e.target); - - if (!attacker || !attacker.position() || !attacker.hasClass("Unit") || attacker.owner() === 0 || attacker.owner() === PlayerID) - continue; - - if (!ourUnit.hasClass("Siege")) - continue; - var collec = this.unitCollection.filter(API3.Filters.not(API3.Filters.byClass("Siege"))).filterNearest(ourUnit.position(), 5); - if (collec.length === 0) - continue; - collec.attack(attacker.id()) - } - } - - var enemyUnits = gameState.getEnemyUnits(this.targetPlayer); - var enemyStructures = gameState.getEnemyStructures(this.targetPlayer); - - if (this.unitCollUpdateArray === undefined || this.unitCollUpdateArray.length === 0) - { - this.unitCollUpdateArray = this.unitCollection.toIdArray(); - } else { - // some stuffs for locality and speed - var territoryMap = m.createTerritoryMap(gameState); - var timeElapsed = gameState.getTimeElapsed(); - - // Let's check a few units each time we update. Currently 10 - var lgth = Math.min(this.unitCollUpdateArray.length,10); - for (var check = 0; check < lgth; check++) - { - var ent = gameState.getEntityById(this.unitCollUpdateArray[0]); - if (!ent) - continue; - - // if the unit is in my territory, make it move towards the target. - if (territoryMap.point(ent.position()) - 64 === PlayerID) { - ent.move(this.targetPos[0],this.targetPos[1]); - continue; - } - - var orderData = ent.unitAIOrderData(); - if (orderData.length !== 0) - orderData = orderData[0]; - else - orderData = undefined; - - // update it. - var needsUpdate = false; - if (ent.isIdle()) - needsUpdate = true; - else if (ent.hasClass("Siege") && (!orderData || !orderData["target"] || !gameState.getEntityById(orderData["target"]) || !gameState.getEntityById(orderData["target"]).hasClass("ConquestCritical")) ) - needsUpdate = true; - else if (!ent.hasClass("Siege") && orderData && orderData["target"] && gameState.getEntityById(orderData["target"]) && gameState.getEntityById(orderData["target"]).hasClass("Structure")) - needsUpdate = true; // try to make it attack a unit instead - - // don't update too soon. - if (timeElapsed - ent.getMetadata(PlayerID, "lastAttackPlanUpdateTime") < 10000) - continue; - - if (needsUpdate === false && !arrivedthisTurn) - continue; - - ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", timeElapsed); - - // let's filter targets further based on this unit. - var mStruct = enemyStructures.filter(function (enemy) { //}){ - if (!enemy.position() || (enemy.hasClass("StoneWall") && ent.canAttackClass("StoneWall"))) { - return false; - } - if (API3.SquareVectorDistance(enemy.position(),ent.position()) > 3000) { - return false; - } - return true; - }); - var mUnit = enemyUnits.filter(function (enemy) { - if (!enemy.position()) - return false; - if (API3.SquareVectorDistance(enemy.position(),ent.position()) > 10000) - return false; - return true; - }); - // Checking for gates if we're a siege unit. - var isGate = false; - mUnit = mUnit.toEntityArray(); - mStruct = mStruct.toEntityArray(); - if (ent.hasClass("Siege")) { - mStruct.sort(function (structa,structb) { - var vala = structa.costSum(); - if (structa.hasClass("Gates") && ent.canAttackClass("StoneWall")) { // we hate gates - isGate = true; - vala += 10000; - } else if (structa.hasClass("ConquestCritical")) - vala += 200; - var valb = structb.costSum(); - if (structb.hasClass("Gates") && ent.canAttackClass("StoneWall")) { // we hate gates - isGate = true; - valb += 10000; - } else if (structb.hasClass("ConquestCritical")) - valb += 200; - //warn ("Structure " +structa.genericName() + " is worth " +vala); - //warn ("Structure " +structb.genericName() + " is worth " +valb); - return (valb - vala); - }); - // TODO: handle ballistas here - if (mStruct.length !== 0) { - if (isGate) - ent.attack(mStruct[0].id()); - else - { - var rand = Math.floor(Math.random() * mStruct.length*0.2); - ent.attack(mStruct[+rand].id()); - //m.debug ("Siege units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName()); - } - } else if (API3.SquareVectorDistance(self.targetPos, ent.position()) > 900 ) { - //m.debug ("Siege units moving to " + uneval(self.targetPos)); - ent.move(self.targetPos[0],self.targetPos[1]); - } - } else { - if (mUnit.length !== 0) { - mUnit.sort(function (unitA,unitB) { - var vala = unitA.hasClass("Support") ? 50 : 0; - if (ent.countersClasses(unitA.classes())) - vala += 100; - var valb = unitB.hasClass("Support") ? 50 : 0; - if (ent.countersClasses(unitB.classes())) - valb += 100; - return valb - vala; - }); - var rand = Math.floor(Math.random() * mUnit.length*0.1); - ent.attack(mUnit[(+rand)].id()); - //m.debug ("Units attacking a unit from " +mUnit[+rand].owner() + " , " +mUnit[+rand].templateName()); - } else if (API3.SquareVectorDistance(self.targetPos, ent.position()) > 900 ){ - //m.debug ("Units moving to " + uneval(self.targetPos)); - ent.move(self.targetPos[0],self.targetPos[1]); - } else if (mStruct.length !== 0) { - mStruct.sort(function (structa,structb) { //}){ - var vala = structa.costSum(); - if (structa.hasClass("Gates") && ent.canAttackClass("StoneWall")) // we hate gates - { - isGate = true; - vala += 10000; - } else if (structa.hasClass("ConquestCritical")) - vala += 100; - var valb = structb.costSum(); - if (structb.hasClass("Gates") && ent.canAttackClass("StoneWall")) // we hate gates - { - isGate = true; - valb += 10000; - } else if (structb.hasClass("ConquestCritical")) - valb += 100; - return (valb - vala); - }); - if (isGate) - ent.attack(mStruct[0].id()); - else - { - var rand = Math.floor(Math.random() * mStruct.length*0.1); - ent.attack(mStruct[+rand].id()); - //m.debug ("Units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName()); - } - } - } - } - this.unitCollUpdateArray.splice(0,10); - } - // updating targets. - if (!gameState.getEntityById(this.target.id())) - { - var targets = this.targetFinder(gameState, HQ); - if (targets.length === 0){ - targets = this.defaultTargetFinder(gameState, HQ); - } - if (targets.length) { - m.debug ("Seems like our target has been destroyed. Switching."); - m.debug ("Aiming for " + targets); - // picking a target - this.targetPos = undefined; - var count = 0; - while (!this.targetPos){ - var rand = Math.floor((Math.random()*targets.length)); - this.target = targets.toEntityArray()[rand]; - this.targetPos = this.target.position(); - count++; - if (count > 1000){ - m.debug("No target with a valid position found"); - Engine.ProfileStop(); - return false; - } - } - } - } - - // regularly update the target position in case it's a unit. - if (this.target.hasClass("Unit")) - this.targetPos = this.target.position(); - } - /* - if (this.state === "huntVillagers") - { - this.tactics.eventMetadataCleanup(events,HeadQuarters); - this.tactics.removeTheirDeads(HeadQuarters); - this.tactics.removeMyDeads(HeadQuarters); - if (this.tactics.isBattleOver()) - { - this.tactics.disband(HeadQuarters,events); - this.tactics = undefined; - this.state = ""; - return 0; // assume over - } else - this.tactics.reassignAttacks(HeadQuarters); - }*/ - this.lastPosition = this.position; - Engine.ProfileStop(); - - return this.unitCollection.length; -}; -m.CityAttack.prototype.totalCountUnits = function(gameState){ - var totalcount = 0; - for (var i in this.idList) - { - totalcount++; - } - return totalcount; -}; -// reset any units -m.CityAttack.prototype.Abort = function(gameState){ - // Do not use QuickIter with forEach when forEach removes elements - this.unitCollection.preventQuickIter(); - this.unitCollection.forEach(function(ent) { - ent.setMetadata(PlayerID, "role",undefined); - ent.setMetadata(PlayerID, "subrole",undefined); - ent.setMetadata(PlayerID, "plan",undefined); - }); - for (var unitCat in this.unitStat) { - delete this.unitStat[unitCat]; - delete this.unit[unitCat]; - } - delete this.unitCollection; - gameState.ai.queueManager.removeQueue("plan_" + this.name); - gameState.ai.queueManager.removeQueue("plan_" + this.name + "_champ"); -}; - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/base-manager.js b/binaries/data/mods/public/simulation/ai/aegis/base-manager.js deleted file mode 100644 index e1730b34ce..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/base-manager.js +++ /dev/null @@ -1,1124 +0,0 @@ -var AEGIS = function(m) -{ -/* 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…) - */ - -m.BaseManager = function(Config) { - this.Config = Config; - this.farmingFields = false; - this.ID = m.playerGlobals[PlayerID].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 = []; -}; - -m.BaseManager.prototype.init = function(gameState, unconstructed){ - this.constructing = unconstructed; - // entitycollections - this.units = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "base", this.ID)); - this.buildings = gameState.getOwnStructures().filter(API3.Filters.byMetadata(PlayerID, "base", this.ID)); - - this.workers = this.units.filter(API3.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, Rebuilt ] … } (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':50*50,'wood':50*50,'stone':40*40,'metal':40*40 }; - // medRadius is the maximal distance for a link, albeit one that would still make us want to build a new dropsite. - this.medRadius = { 'food':90*90,'wood':55*55,'stone':80*80,'metal':80*80 }; - // bigRadius is the distance for a weak link, mainly for optimizing search for resources when a DP is depleted. - this.bigRadius = { 'food':200*200,'wood':200*200,'stone':200*200,'metal':200*200 }; -}; - -m.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()); -}; - -m.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. -m.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 (x+xi >= width || y+yi >= width) - continue; - if (xi*xi+yi*yi < radius*radius && HQ.basesMap.map[(x+xi) + (y+yi)*width] === 0) - { - if (this.accessIndex == gameState.sharedScript.accessibility.landPassMap[x+xi + width*(y+yi)]) - { - this.territoryIndices.push((x+xi) + (y+yi)*width); - HQ.basesMap.map[(x+xi) + (y+yi)*width] = this.ID; - } - } - } -} - -m.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 (var i in types) - { - var type = types[i]; - // TODO: set us as "X" gatherer - - this.buildings.filter(API3.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 (var base in HQ.baseManagers) - if (HQ.baseManagers[base].willGather["food"] === 1) - needFarm = false; - if (needFarm) - this.willGather["food"] = 1; - } - m.debug ("food" + this.willGather["food"]); - m.debug (this.willGather["wood"]); - m.debug (this.willGather["stone"]); - m.debug (this.willGather["metal"]); -} - -m.BaseManager.prototype.checkEvents = function (gameState, events, queues) { - var renameEvents = events["EntityRenamed"]; - var destEvents = events["Destroy"]; - var createEvents = events["Create"]; - var cFinishedEvents = events["ConstructionFinished"]; - - for (var i in renameEvents) - { - var ent = gameState.getEntityById(renameEvents[i].newentity); - if (!ent) - continue; - var workerObject = ent.getMetadata(PlayerID, "worker-object"); - if (workerObject) - workerObject.ent = ent; - } - - for (var i in destEvents) - { - var evt = destEvents[i]; - // let's check we haven't lost an important building here. - if (evt != undefined && !evt.SuccessfulFoundation && evt.entityObj != undefined && evt.metadata !== undefined && evt.metadata[PlayerID] && - evt.metadata[PlayerID]["base"] !== undefined && evt.metadata[PlayerID]["base"] == this.ID) - { - var ent = evt.entityObj; - if (ent.hasTerritoryInfluence()) - this.territoryBuildings.splice(this.territoryBuildings.indexOf(ent.id()),1); - if (ent.resourceDropsiteTypes()) - this.scrapDropsite(gameState, ent); - if (evt.metadata[PlayerID]["baseAnchor"] && evt.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 m.ConstructionPlan(gameState, "structures/{civ}_civil_centre", { "base" : this.ID, "baseAnchor" : true }, ent.position())); - } else { - // TODO - queues.civilCentre.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre", { "base" : this.ID, "baseAnchor" : true }, ent.position())); - } - } - - } - } - for (var i in cFinishedEvents) - { - var evt = cFinishedEvents[i]; - if (evt && evt.newentity) - { - // TODO: we ought to add new resources or do something. - var ent = gameState.getEntityById(evt.newentity); - - if (ent === undefined) - continue; - - if (ent.getMetadata(PlayerID,"base") == this.ID) - { - if(ent.hasTerritoryInfluence()) - this.territoryBuildings.push(ent.id()); - if (ent.resourceDropsiteTypes()) - for (var ress in ent.resourceDropsiteTypes()) - this.initializeDropsite(gameState, ent, ent.resourceDropsiteTypes()[ress]); - if (ent.resourceSupplyAmount() && ent.resourceSupplyType()["specific"] == "grain") - this.assignResourceToDP(gameState,ent); - } - } - } - for (var i in createEvents) - { - var evt = createEvents[i]; - // Checking for resources. - var evt = events[i]; - if (evt && evt.entity) - { - var ent = gameState.getEntityById(evt.entity); - - if (ent === undefined) - continue; - - if (ent.resourceSupplyAmount() && ent.owner() == 0) - this.assignResourceToDP(gameState,ent); - - } - } -}; - -// If no specific dropsite, it'll assign to the closest -m.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 (var i in this.dropsites) - { - var dp = gameState.getEntityById(i); - var distance = API3.SquareVectorDistance(supply.position(), dp.position()); - if (supply.resourceSupplyType()["specific"] === "grain") - distance /= 100; - 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); - - if (m.DebugEnabled()) - { - if (type == "food" && dist < this.smallRadius[type]) - Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [supply.id()], "rgb": [10,0,0]}); - else if (type == "food" && dist < this.medRadius[type]) - Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [supply.id()], "rgb": [2,0,0]}); - else - Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [supply.id()], "rgb": [0.5,0,0]}); - } - } - } - // TODO: ought to recount immediatly. -} - -m.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 = API3.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 = API3.Filters.byMetadata(PlayerID, "linked-dropsite", ent.id()); - var collection = resources.filter(filter); - collection.registerUpdates(); - - filter = API3.Filters.byMetadata(PlayerID, "linked-dropsite-close",true); - var collection2 = collection.filter(filter); - collection2.registerUpdates(); - - filter = API3.Filters.byMetadata(PlayerID, "linked-dropsite-nearby",true); - var collection3 = collection.filter(filter); - collection3.registerUpdates(); - - filter = API3.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, false]; - - // TODO: flag us on the SharedScript "type" map. - // TODO: get workers on those resources and do something with them. - } - - if (m.DebugEnabled()) - { - // Make resources glow wildly - if (type == "food") { - self.dropsites[ent.id()][type][2].forEach(function(ent){ - Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [0.5,0,0]}); - }); - self.dropsites[ent.id()][type][1].forEach(function(ent){ - Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [2,0,0]}); - }); - self.dropsites[ent.id()][type][0].forEach(function(ent){ - Engine.PostCommand(PlayerID,{"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(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,0.5,0]}); - }); - self.dropsites[ent.id()][type][1].forEach(function(ent){ - Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,2,0]}); - }); - self.dropsites[ent.id()][type][0].forEach(function(ent){ - Engine.PostCommand(PlayerID,{"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(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [0.5,0.5,0]}); - }); - self.dropsites[ent.id()][type][1].forEach(function(ent){ - Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [2,2,0]}); - }); - self.dropsites[ent.id()][type][0].forEach(function(ent){ - Engine.PostCommand(PlayerID,{"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(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,0.5,0.5]}); - }); - self.dropsites[ent.id()][type][1].forEach(function(ent){ - Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,2,2]}); - }); - self.dropsites[ent.id()][type][0].forEach(function(ent){ - Engine.PostCommand(PlayerID,{"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. -m.BaseManager.prototype.scrapDropsite = function (gameState, ent) { - if (this.dropsites[ent.id()] === undefined) - return true; - - for (var 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 -m.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 = m.createTerritoryMap(gameState); - - var obstructions = m.createObstructionMap(gameState,this.accessIndex,storeHousePlate); - obstructions.expandInfluences(); - - // copy the resource map as initialization. - var friendlyTiles = new API3.Map(gameState.sharedScript, gameState.sharedScript.resourceMaps[resource].map, true); - - var DPFoundations = gameState.getOwnFoundations().filter(API3.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 && API3.SquareVectorDistance(friendlyTiles.gamePosToMapPos(dpPos), pos) < 250) - { - friendlyTiles.map[j] = 0; - continue; - } else if (dpPos && API3.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 && API3.SquareVectorDistance(friendlyTiles.gamePosToMapPos(dpPos), pos) < 250) - friendlyTiles.map[j] = 0; - else if (dpPos && API3.SquareVectorDistance(friendlyTiles.gamePosToMapPos(dpPos), pos) < 450) - friendlyTiles.map[j] /= 2; - } - } - - if (m.DebugEnabled()) - 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]; - - m.debug ("for dropsite best is " +best[1] + " at " + gameState.getTimeElapsed()); - - // 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. -m.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); - - var dropsite = this.dropsites[ent.id()][type]; - - var medianPositionX = 0; - var medianPositionY = 0; - var divider = 0; - dropsite[1].forEach( function (supply) { //}){ - farCount += supply.resourceSupplyAmount(); - medianPositionX += supply.position()[0]; - medianPositionY += supply.position()[1]; - ++divider; - }); - dropsite[0].forEach( function (supply) { //}){ - count += supply.resourceSupplyAmount(); - medianPositionX += supply.position()[0]; - medianPositionY += supply.position()[1]; - ++divider; - }); - - // once per dropsite, if the average wood resource is too far away, try to build a closer one. - if (type === "wood" && divider !== 0 && !dropsite[6] && count < 300 && farCount > 700 - && gameState.ai.queues.dropsites.length() === 0 && gameState.countFoundationsByType(gameState.applyCiv("structures/{civ}_storehouse"), true) === 0) - { - medianPositionX /= divider; - medianPositionY /= divider; - if (API3.SquareVectorDistance([medianPositionX,medianPositionY], ent.position()) > 600) - { - dropsite[6] = true; - gameState.ai.queues.dropsites.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_storehouse", { "base" : this.ID }, [medianPositionX,medianPositionY])); - } - } - - dropsite[3] = count; - dropsite[4] = farCount; - return true; -}; - -// Updates dropsites. -m.BaseManager.prototype.updateDropsites = function (gameState) { - // for each dropsite, recalculate - for (var i in this.dropsites) - { - for (var 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 on this dropsite. -m.BaseManager.prototype.getDpWorkerCapacity = function (gameState, entID, type, faraway) { - if (this.dropsites[entID] === undefined || this.dropsites[entID][type] === undefined) - return undefined; // should initialize it first, or it just doesn't exist. - var dropsite = this.dropsites[entID]; - - var count = 0; - if (type == "food") - { - var slot = dropsite[type][0]; - if (faraway === true) - slot = dropsite[type][1]; - slot.forEach(function (ent) { - if (ent.resourceSupplyAmount() > 50) - count += ent.maxGatherers(); - }); - } - else if ((type === "stone" && dropsite["stone"])|| (type === "metal" && dropsite["metal"])) - { - var slot = dropsite[type][0]; - if (faraway === true) - slot = dropsite[type][1]; - slot.forEach(function (ent) { - if (ent.resourceSupplyAmount() > 500) - count += ent.maxGatherers(); - }); - } else if (type === "wood") - { - count = (faraway === true) ? dropsite[type][4] / 250 : dropsite[type][3] / 200; - } - return count; -}; - -// TODO: ought to be cached or something probably -// Returns the number of slots available for workers here. -m.BaseManager.prototype.getWorkerCapacity = function (gameState, type, faraway) { - var count = 0; - for (var i in this.dropsites) - count += this.getDpWorkerCapacity(gameState,i,type, faraway); - return count; -}; - -// TODO: ought to be cached or something probably -// Returns the amount of resource left -m.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(API3.Filters.byTerritory(gameState.ai.HQ.basesMap, this.ID)).forEach( function (ent) { //}){ - count += ent.resourceSupplyAmount(); - }); - return count; - } - if (searchType == "dropsites") - { - // for each dropsite, recalculate - for (var 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 (var 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 (var 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 -m.BaseManager.prototype.checkResourceLevels = function (gameState,queues) { - for (var type in this.willGather) - { - if (this.willGather[type] === 0) - continue; - // not enough resources on the map, tell us we've stopped. - if (type !== "food" && gameState.ai.playedTurn % 10 === 4 && this.getResourceLevel(gameState,type, "all") < 200) - this.willGather[type] = 0; // won't gather at all - // we're waiting for a new dropsite. - 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 < this.Config.Economy.initialFields;++i) - { - var plan = new m.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 < 400) - { - for (var 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(API3.Filters.byClass("Field")).forEach(function (field) { - if (field.resourceSupplyAmount() > 400) - numFarms++; - }); - var numFd = gameState.countEntitiesByType(gameState.applyCiv("foundation|structures/{civ}_field"), true); - if (numFarms+numFd > 15) - this.willGather["food"] = 2; - var numQueued = queues.field.countQueuedUnits(); - numFarms += numFd + numQueued; - - - // let's see if we need to push new farms. - var maxGatherers = gameState.getTemplate(gameState.applyCiv("structures/{civ}_field")).maxGatherers(); - if (numQueued < 3) - if (numFarms < Math.round(this.gatherersByType(gameState, "food").length / (maxGatherers*0.9))) - queues.field.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_field", { "base" : this.ID })); - // TODO: refine count to only count my base. - } - } else if (queues.dropsites.length() === 0 && gameState.countFoundationsByType(gameState.applyCiv("structures/{civ}_storehouse"), true) === 0) { - var workerCapacity = this.getWorkerCapacity(gameState, type); // only close; - // check how often we'll want new dropsites. - // if we're booming we'll aggressively grab terrain - if (gameState.currentPhase() >= 1 && gameState.ai.aggressiveness < 0.15) - var wantDropsite = (this.gatherersByType(gameState, type).length / workerCapacity) > 0.45; - else if (gameState.currentPhase() >= 1) - var wantDropsite = (this.gatherersByType(gameState, type).length / workerCapacity) > 0.6; - else - var wantDropsite = (this.gatherersByType(gameState, type).length / workerCapacity) > 0.9; - if (wantDropsite) - { - var pos = this.findBestDropsiteLocation(gameState, type); - if (!pos) - { - m.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 { - m.debug ("planning new dropsite for " + type); - queues.dropsites.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_storehouse",{ "base" : this.ID }, pos)); - } - } - } - } - -}; - -// let's return the estimated gather rates. -m.BaseManager.prototype.getGatherRates = function(gameState, currentRates) { - for (var i in currentRates) - { - // I calculate the exact gathering rate for each unit. - // I must then lower that to account for travel time. - // Given that the faster you gather, the more travel time matters, - // I use some logarithms. - // TODO: this should take into account for unit speed and/or distance to target - - var units = this.gatherersByType(gameState, i); - units.forEach(function (ent) { - var gRate = ent.currentGatherRate(); - if (gRate !== undefined) - currentRates[i] += Math.log(1+gRate)/1.1; - }); - if (i === "food") - { - units = this.workers.filter(API3.Filters.byMetadata(PlayerID, "subrole", "hunter")); - units.forEach(function (ent) { - var gRate = ent.currentGatherRate() - if (gRate !== undefined) - currentRates[i] += Math.log(1+gRate)/1.1; - }); - } - currentRates[i] += 0.5*m.GetTCRessGatherer(gameState,i); - } -}; - -m.BaseManager.prototype.assignRolelessUnits = function(gameState) { - // TODO: make this cleverer. - var roleless = this.units.filter(API3.Filters.not(API3.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. -m.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 (var i in types.types) - avgOverdraft += types[types.types[i]]; - - avgOverdraft /= 4; - - for (var 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) - { - //m.debug ("Moving " +ent.id() + " from " + types.types[i]); - nb--; - // TODO: might want to direct assign. - ent.stopMoving(); - ent.setMetadata(PlayerID, "subrole","idle"); - } - }); - } - //m.debug (currentRates); -}; - -// TODO: work on this. -m.BaseManager.prototype.reassignIdleWorkers = function(gameState) { - - var self = this; - - // Search for idle workers, and tell them to gather resources based on demand - var filter = API3.Filters.or(API3.Filters.byMetadata(PlayerID,"subrole","idle"), API3.Filters.not(API3.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); - - for (var i = 0; i < 4; ++i) - { - // Okay let's now check we can actually remain here for that - if (self.willGather[types[i]] !== 1) - { - if (!gameState.ai.HQ.switchWorkerBase(gameState, ent, types[i])) - continue; - else - break; - } - //m.debug ("assigning " +ent.id() + " to " + types[0]); - ent.setMetadata(PlayerID, "subrole", "gatherer"); - ent.setMetadata(PlayerID, "gather-type", types[i]); - m.AddTCRessGatherer(gameState,types[i]); - break; - } - } else { - ent.setMetadata(PlayerID, "subrole", "hunter"); - } - }); - } -}; - -m.BaseManager.prototype.workersBySubrole = function(gameState, subrole) { - return gameState.updatingCollection("subrole-" + subrole +"-base-" + this.ID, API3.Filters.byMetadata(PlayerID, "subrole", subrole), this.workers, true); -}; - -m.BaseManager.prototype.gatherersByType = function(gameState, type) { - return gameState.updatingCollection("workers-gathering-" + type +"-base-" + this.ID, API3.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. -m.BaseManager.prototype.pickBuilders = function(gameState, workers, number) { - // TODO: choose better. - var availableWorkers = this.workers.filter(API3.Filters.not(API3.Filters.byClass("Cavalry"))).toEntityArray(); - availableWorkers.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 vala < valb - }); - var needed = Math.min(number, availableWorkers.length); - for (var i = 0; i < needed; ++i) - { - availableWorkers[i].stopMoving(); - availableWorkers[i].setMetadata(PlayerID, "subrole", "idle"); - workers.addEnt(availableWorkers[i]); - } - return; -}; - -m.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; - - // TODO: this is not perfect performance-wise. - var foundations = this.buildings.filter(API3.Filters.and(API3.Filters.isFoundation(),API3.Filters.not(API3.Filters.byClass("Field")))).toEntityArray(); - - var damagedBuildings = this.buildings.filter(function (ent) { - if (ent.foundationProgress() === undefined && ent.needsRepair()) - return true; - return false; - }).toEntityArray(); - - // Check if nothing to build - if (!foundations.length && !damagedBuildings.length){ - return; - } - var workers = this.workers.filter(API3.Filters.not(API3.Filters.byClass("Cavalry"))); - var builderWorkers = this.workersBySubrole(gameState, "builder"); - var idleBuilderWorkers = this.workersBySubrole(gameState, "builder").filter(API3.Filters.isIdle()); - - // if we're constructing and we have the foundations to our base anchor, only try building that. - if (this.constructing == true && this.buildings.filter(API3.Filters.and(API3.Filters.isFoundation(), API3.Filters.byMetadata(PlayerID, "baseAnchor", true))).length !== 0) - { - foundations = this.buildings.filter(API3.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.2); - if (this.constructing == true && maxTotalBuilders < 15) - maxTotalBuilders = 15; - - for (var i in foundations) { - var target = foundations[i]; - - if (target.hasClass("Field")) - continue; // we do not build fields - - var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target.id()).length; - var targetNB = this.Config.Economy.targetNumBuilders; // TODO: dynamic that. - if (target.hasClass("House")) - targetNB *= 2; - else if (target.hasClass("Barracks")) - targetNB = 4; - else if (target.hasClass("Fortress")) - targetNB = 7; - if (target.getMetadata(PlayerID, "baseAnchor") == true) - targetNB = 15; - - if (assigned < targetNB) { - if (builderWorkers.length - idleBuilderWorkers.length + addedWorkers < maxTotalBuilders) { - - var addedToThis = 0; - - idleBuilderWorkers.forEach(function(ent) { - if (ent.position() && API3.SquareVectorDistance(ent.position(), target.position()) < 10000 && assigned + addedToThis < targetNB) - { - addedWorkers++; - addedToThis++; - ent.setMetadata(PlayerID, "target-foundation", target.id()); - } - }); - if (assigned + addedToThis < targetNB) - { - var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.position() !== undefined); }).toEntityArray(); - nonBuilderWorkers.sort(function (workerA,workerB) - { - var coeffA = API3.SquareVectorDistance(target.position(),workerA.position()); - if (workerA.getMetadata(PlayerID, "gather-type") === "food") - coeffA *= 3; - var coeffB = API3.SquareVectorDistance(target.position(),workerB.position()); - if (workerB.getMetadata(PlayerID, "gather-type") === "food") - coeffB *= 3; - return (coeffA - coeffB); - }); - var current = 0; - while (assigned + addedToThis < targetNB && current < nonBuilderWorkers.length) - { - addedWorkers++; - addedToThis++; - var ent = nonBuilderWorkers[current++]; - ent.stopMoving(); - ent.setMetadata(PlayerID, "subrole", "builder"); - ent.setMetadata(PlayerID, "target-foundation", target.id()); - }; - } - } - } - } - - // don't repair if we're still under attack, unless it's like a vital (civcentre or wall) building that's getting destroyed. - for (var 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 = m.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 < targetNB/3) { - if (builderWorkers.length + addedWorkers < targetNB*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(), targetNB/3 - assigned); - - nearestNonBuilders.forEach(function(ent) { - ent.stopMoving(); - addedWorkers++; - ent.setMetadata(PlayerID, "subrole", "builder"); - ent.setMetadata(PlayerID, "target-foundation", target.id()); - }); - } - } - } -}; - -m.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 && this.anchor) - { - var terrMap = m.createTerritoryMap(gameState); - if(terrMap.getOwner(this.anchor.position()) !== 0 && terrMap.getOwner(this.anchor.position()) !== PlayerID) - { - // we're in enemy territory. If we're too close from the enemy, destroy us. - var eEnts = gameState.getEnemyStructures().filter(API3.Filters.byClass("CivCentre")).toEntityArray(); - for (var i in eEnts) - { - var entPos = eEnts[i].position(); - entPos = [entPos[0]/4.0,entPos[1]/4.0]; - if (API3.SquareVectorDistance(entPos, this.anchor.position()) < 500) - this.anchor.destroy(); - } - } - } - - -// if (!this.constructing) -// { - if (gameState.ai.playedTurn % 2 === 0) - this.setWorkersIdleByPriority(gameState); - - this.assignRolelessUnits(gameState); - - /*Engine.ProfileStart("Swap Workers"); - var gathererGroups = {}; - gameState.getOwnEntitiesByRole("worker", true).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 m.Worker(ent)); - ent.getMetadata(PlayerID, "worker-object").update(self, gameState); - }); - Engine.ProfileStop(); - - Engine.ProfileStop(); -}; - -return m; - -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/config.js b/binaries/data/mods/public/simulation/ai/aegis/config.js deleted file mode 100644 index 41c41f0c03..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/config.js +++ /dev/null @@ -1,121 +0,0 @@ -var AEGIS = function(m) -{ - -// this defines the medium difficulty -m.Config = function() { - this.difficulty = 2; // 0 is sandbox, 1 is easy, 2 is medium, 3 is hard, 4 is very hard. - - this.Military = { - "fortressLapseTime" : 540, // Time to wait between building 2 fortresses - "defenceBuildingTime" : 900, // Time to wait before building towers or fortresses - "attackPlansStartTime" : 0, // time to wait before attacking. Start as soon as possible. - "techStartTime" : 120, // time to wait before teching. Will only start after town phase so it's irrelevant. - "popForBarracks1" : 25, - "popForBarracks2" : 95, - "timeForBlacksmith" : 900, - }; - this.Economy = { - "villagePopCap" : 40, // How many units we want before aging to town. - "cityPhase" : 840, // time to start trying to reach city phase - "popForMarket" : 50, - "popForFarmstead" : 35, - "dockStartTime" : 240, // Time to wait before building the dock - "techStartTime" : 0, // time to wait before teching. - "targetNumBuilders" : 1.5, // Base number of builders per foundation. - "femaleRatio" : 0.5, // percent of females among the workforce. - "initialFields" : 5 - }; - - // Note: attack settings are set directly in attack_plan.js - // defence - this.Defence = - { - "defenceRatio" : 2, // see defence.js for more info. - "armyCompactSize" : 2000, // squared. Half-diameter of an army. - "armyBreakawaySize" : 3500, // squared. - "armyMergeSize" : 1400, // squared. - "armyStrengthWariness" : 2, // Representation of how important army strength is for its "watch level" (see defense-helper.js). - "prudence" : 1 // Representation of how quickly we'll forget about a dangerous army. - }; - - // military - this.buildings = - { - "moderate" : { - "default" : [ "structures/{civ}_barracks" ] - }, - "advanced" : { - "default" : [], - "hele" : [ "structures/{civ}_gymnasion" ], - "athen" : [ "structures/{civ}_gymnasion" ], - "spart" : [ "structures/{civ}_syssiton" ], - "cart" : [ "structures/{civ}_embassy_celtic", - "structures/{civ}_embassy_iberian", "structures/{civ}_embassy_italiote" ], - "celt" : [ "structures/{civ}_kennel" ], - "pers" : [ "structures/{civ}_fortress", "structures/{civ}_stables", "structures/{civ}_apadana" ], - "rome" : [ "structures/{civ}_army_camp" ], - "maur" : [ "structures/{civ}_elephant_stables"] - }, - "fort" : { - "default" : [ "structures/{civ}_fortress" ], - "celt" : [ "structures/{civ}_fortress_b", "structures/{civ}_fortress_g" ] - } - }; - - this.priorities = - { - "villager" : 30, // should be slightly lower than the citizen soldier one because otherwise they get all the food - "citizenSoldier" : 60, - "ships" : 70, - "house" : 350, - "dropsites" : 120, - "field" : 500, - "economicBuilding" : 90, - "militaryBuilding" : 240, // set to something lower after the first barracks. - "defenceBuilding" : 70, - "civilCentre" : 950, - "majorTech" : 700, - "minorTech" : 40 - }; -}; - -//Config.prototype = new BaseConfig(); - -m.Config.prototype.updateDifficulty = function(difficulty) -{ - this.difficulty = difficulty; - // changing settings based on difficulty. - if (this.difficulty === 1) - { - this.Military.defenceBuildingTime = 1200; - this.Military.attackPlansStartTime = 960; - this.Military.popForBarracks1 = 35; - this.Military.popForBarracks2 = 150; // shouldn't reach it - this.Military.popForBlacksmith = 150; // shouldn't reach it - - this.Economy.cityPhase = 1800; - this.Economy.popForMarket = 80; - this.Economy.techStartTime = 600; - this.Economy.femaleRatio = 0.6; - this.Economy.initialFields = 1; - // Config.Economy.targetNumWorkers will be set by AI scripts. - } - else if (this.difficulty === 0) - { - this.Military.defenceBuildingTime = 450; - this.Military.attackPlansStartTime = 9600000; // never - this.Military.popForBarracks1 = 60; - this.Military.popForBarracks2 = 150; // shouldn't reach it - this.Military.popForBlacksmith = 150; // shouldn't reach it - - this.Economy.cityPhase = 240000; - this.Economy.popForMarket = 200; - this.Economy.techStartTime = 1800; - this.Economy.femaleRatio = 0.2; - this.Economy.initialFields = 1; - // Config.Economy.targetNumWorkers will be set by AI scripts. - } -}; - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/data.json b/binaries/data/mods/public/simulation/ai/aegis/data.json deleted file mode 100644 index 38d4d2a463..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/data.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "Aegis Bot", - "description": "[color=\"255 0 0 255\"]NO LONGER SUPPORTED[/color] \n Aegis is an improvement of earlier bots by Wratii. Note that it doesn't support saved games or naval maps.", - "moduleName" : "AEGIS", - "constructor": "AegisBot", - "useShared": true -} diff --git a/binaries/data/mods/public/simulation/ai/aegis/defence.js b/binaries/data/mods/public/simulation/ai/aegis/defence.js deleted file mode 100755 index 792aaf9762..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/defence.js +++ /dev/null @@ -1,348 +0,0 @@ -var AEGIS = function(m) -{ - -m.Defence = function(Config) -{ - this.armies = []; // array of "army" Objects. See defence-helper.js - this.Config = Config; -} - - -m.Defence.prototype.init = function(gameState) -{ - this.armyMergeSize = this.Config.Defence.armyMergeSize; - - this.dangerMap = new API3.Map(gameState.sharedScript); -} - -m.Defence.prototype.update = function(gameState, events) -{ - Engine.ProfileStart("Defence Manager"); - - this.territoryMap = m.createTerritoryMap(gameState); - - this.releasedDefenders = []; // array of defenders released by armies this turn. - this.checkEnemyArmies(gameState,events); - this.checkEnemyUnits(gameState); - this.assignDefenders(gameState); - - // debug - /*debug (""); - debug (""); - debug ("Armies: " +this.armies.length); - for (var i in this.armies) - this.armies[i].debug(gameState); - */ - - this.MessageProcess(gameState,events); - - Engine.ProfileStop(); -}; - -m.Defence.prototype.makeIntoArmy = function(gameState, entityID) -{ - // Try to add it to an existing army. - for (var o in this.armies) - { - if (this.armies[o].addFoe(gameState,entityID)) - return; // over - } - // Create a new army for it. - var army = new m.DefenseArmy(gameState, this, [], [entityID]); - this.armies.push(army); -} - -// TODO: this algorithm needs to be improved, sorta. -m.Defence.prototype.isDangerous = function(gameState, entity) -{ - if (!entity.position()) - return false; - if (this.territoryMap.getOwner(entity.position()) === entity.owner() || entity.attackTypes() === undefined) - return false; - - var myBuildings = gameState.getOwnStructures(); - for (var i in myBuildings._entities) - if (API3.SquareVectorDistance(myBuildings._entities[i].position(), entity.position()) < 6000) - return true; - - return false; -} - - -m.Defence.prototype.checkEnemyUnits = function(gameState) -{ - var self = this; - - // loop through enemy units - var nbPlayers = gameState.sharedScript.playersData.length - 1; - var i = 1 + gameState.ai.playedTurn % nbPlayers; - if (i === PlayerID && i !== nbPlayers) - i++; - else if (i === PlayerID) - i = 1; - - if (gameState.isPlayerAlly(i)) - return; - - var filter = API3.Filters.and(API3.Filters.byClass("Unit"), API3.Filters.byOwner(i)); - var enemyUnits = gameState.updatingGlobalCollection("player-" +i + "-units", filter); - - enemyUnits.forEach( function (ent) { - // first check: is this unit already part of an army. - if (ent.getMetadata(PlayerID, "PartOfArmy") !== undefined) - return; - - if (ent.attackTypes() === undefined || ent.hasClass("Support") || ent.hasClass("Ship")) - return; - - // check if unit is dangerous "a priori" - if (self.isDangerous(gameState,ent)) - self.makeIntoArmy(gameState,ent.id()); - }); -} - -m.Defence.prototype.checkEnemyArmies = function(gameState, events) -{ - var self = this; - - for (var o = 0; o < this.armies.length; ++o) - { - var army = this.armies[o]; - army.checkEvents(gameState, events); // must be called every turn for all armies - - // this returns a list of IDs: the units that broke away from the army for being too far. - var breakaways = army.update(gameState); - - for (var u in breakaways) - { - // assume dangerosity - this.makeIntoArmy(gameState,breakaways[u]); - } - - if (army.getState(gameState) === 0) - { - army.clear(gameState); - this.armies.splice(o--,1); - continue; - } - } - // Check if we can't merge it with another. - for (var o = 0; o < this.armies.length; ++o) - { - var army = this.armies[o]; - for (var p = o+1; p < this.armies.length; ++p) - { - var otherArmy = this.armies[p]; - if (otherArmy.state !== army.state) - continue; - - if (API3.SquareVectorDistance(army.foePosition, otherArmy.foePosition) < this.armyMergeSize) - { - // no need to clear here. - army.merge(gameState, otherArmy); - this.armies.splice(p--,1); - } - } - } -} - -m.Defence.prototype.assignDefenders = function(gameState, events) -{ - if (this.armies.length === 0) - return; - - var armiesNeeding = []; - // Okay, let's add defenders - // TODO: this is dumb. - for (var i in this.armies) - { - var army = this.armies[i]; - var needsDef = army.needsDefenders(gameState); - if (needsDef === false) - continue; - - // Okay for now needsDef is the total needed strength. - // we're dumb so we don't choose if we have a defender shortage. - armiesNeeding.push([army, needsDef]); - } - - if (armiesNeeding.length === 0) - return; - - // let's get our potential units - // TODO: this should rather be a HQ function that returns viable plans. - var filter = API3.Filters.and(API3.Filters.and(API3.Filters.byHasMetadata(PlayerID,"plan"), - API3.Filters.not(API3.Filters.byHasMetadata(PlayerID, "PartOfArmy"))), - API3.Filters.and(API3.Filters.not(API3.Filters.byMetadata(PlayerID,"subrole","walking")), - API3.Filters.not(API3.Filters.byMetadata(PlayerID,"subrole","attacking")))); - var potentialDefendersOne = gameState.getOwnUnits().filter(filter).toIdArray(); - - filter = API3.Filters.and(API3.Filters.and(API3.Filters.not(API3.Filters.byHasMetadata(PlayerID,"plan")), - API3.Filters.not(API3.Filters.byHasMetadata(PlayerID, "PartOfArmy"))), - API3.Filters.byClassesOr(["Infantry","Cavalry"])); - var potentialDefendersTwo = gameState.getOwnUnits().filter(filter).toIdArray(); - - var potDefs = this.releasedDefenders.concat(potentialDefendersOne).concat(potentialDefendersTwo); - - for (var i in armiesNeeding) - { - var army = armiesNeeding[i][0]; - var need = armiesNeeding[i][1]; - - // TODO: this is what I'll want to improve, along with the choice above. - while (need > 0) - { - if (potDefs.length === 0) - return; // won't do anything anymore. - var ent = gameState.getEntityById(potDefs[0]); - - if (ent.getMetadata(PlayerID, "PartOfArmy") !== undefined) - { - potDefs.splice(0,1); - continue; - } - var str = m.getMaxStrength(ent); - need -= str; - - army.addOwn(gameState,potDefs[0]); - army.assignUnit(gameState, potDefs[0]); - potDefs.splice(0,1); - } - } -} - -// this processes the attackmessages -// So that a unit that gets attacked will not be completely dumb. -// warning: big levels of indentation coming. -m.Defence.prototype.MessageProcess = function(gameState,events) { -/* var self = this; - var attackedEvents = events["Attacked"]; - for (var key in attackedEvents){ - var e = attackedEvents[key]; - if (gameState.isEntityOwn(gameState.getEntityById(e.target))) { - var attacker = gameState.getEntityById(e.attacker); - var ourUnit = gameState.getEntityById(e.target); - - // the attacker must not be already dead, and it must not be me (think catapults that miss). - if (attacker === undefined || attacker.owner() === PlayerID || attacker.position() === undefined) - continue; - - var mapPos = this.dangerMap.gamePosToMapPos(attacker.position()); - this.dangerMap.addInfluence(mapPos[0], mapPos[1], 4, 1, 'constant'); - - // disregard units from attack plans and defence. - if (ourUnit.getMetadata(PlayerID, "role") == "defence" || ourUnit.getMetadata(PlayerID, "role") == "attack") - continue; - - var territory = this.territoryMap.getOwner(attacker.position()); - - if (attacker.owner() == 0) - { - if (ourUnit !== undefined && ourUnit.hasClass("Unit") && !ourUnit.hasClass("Support")) - ourUnit.attack(e.attacker); - else - { - ourUnit.flee(attacker); - ourUnit.setMetadata(PlayerID,"fleeing", gameState.getTimeElapsed()); - } - if (territory === PlayerID) - { - // anyway we'll register the animal as dangerous, and attack it (note: only on our territory. Don't care otherwise). - this.listOfWantedUnits[attacker.id()] = new EntityCollection(gameState.sharedScript); - this.listOfWantedUnits[attacker.id()].addEnt(attacker); - this.listOfWantedUnits[attacker.id()].freeze(); - this.listOfWantedUnits[attacker.id()].registerUpdates(); - - var filter = Filters.byTargetedEntity(attacker.id()); - this.WantedUnitsAttacker[attacker.id()] = this.myUnits.filter(filter); - this.WantedUnitsAttacker[attacker.id()].registerUpdates(); - } - } // Disregard military units except in our territory. Disregard all calls in enemy territory. - else if (territory == PlayerID || (territory != attacker.owner() && ourUnit.hasClass("Support"))) - { - // TODO: this does not differentiate with buildings... - // These ought to be treated differently. - // units in attack plans will react independently, but we still list the attacks here. - if (attacker.hasClass("Structure")) { - // todo: we ultimately have to check wether it's a danger point or an isolated area, and if it's a danger point, mark it as so. - - // Right now, to make the AI less gameable, we'll mark any surrounding resource as inaccessible. - // usual tower range is 80. Be on the safe side. - var close = gameState.getResourceSupplies("wood").filter(Filters.byDistance(attacker.position(), 90)); - close.forEach(function (supply) { //}){ - supply.setMetadata(PlayerID, "inaccessible", true); - }); - } else { - // TODO: right now a soldier always retaliate... Perhaps it should be set in "Defence" mode. - - // TODO: handle the ship case - if (attacker.hasClass("Ship")) - continue; - - // This unit is dangerous. if it's in an army, it's being dealt with. - // if it's not in an army, it means it's either a lone raider, or it has got friends. - // In which case we must check for other dangerous units around, and perhaps armify them. - // TODO: perhaps someday army detection will have improved and this will require change. - var armyID = attacker.getMetadata(PlayerID, "inArmy"); - if (armyID == undefined || !this.enemyArmy[attacker.owner()] || !this.enemyArmy[attacker.owner()][armyID]) { - if (this.reevaluateEntity(gameState, attacker)) - { - var position = attacker.position(); - var close = HQ.enemyWatchers[attacker.owner()].enemySoldiers.filter(Filters.byDistance(position, self.armyCompactSize)); - - if (close.length > 2 || ourUnit.hasClass("Support") || attacker.hasClass("Siege")) - { - // armify it, then armify units close to him. - this.armify(gameState,attacker); - armyID = attacker.getMetadata(PlayerID, "inArmy"); - - close.forEach(function (ent) { //}){ - if (API3.SquareVectorDistance(position, ent.position()) < self.armyCompactSize) - { - ent.setMetadata(PlayerID, "inArmy", armyID); - self.enemyArmy[ent.owner()][armyID].addEnt(ent); - } - }); - return; // don't use too much processing power. If there are other cases, they'll be processed soon enough. - } - } - // Defencemanager will deal with them in the next turn. - } - if (ourUnit !== undefined && ourUnit.hasClass("Unit")) { - if (ourUnit.hasClass("Support")) { - // let's try to garrison this support unit. - if (ourUnit.position()) - { - var buildings = gameState.getOwnEntities().filter(Filters.byCanGarrison()).filterNearest(ourUnit.position(),4).toEntityArray(); - var garrisoned = false; - for (var i in buildings) - { - var struct = buildings[i]; - if (struct.garrisoned() && struct.garrisonMax() - struct.garrisoned().length > 0) - { - garrisoned = true; - ourUnit.garrison(struct); - break; - } - } - if (!garrisoned) { - ourUnit.flee(attacker); - ourUnit.setMetadata(PlayerID,"fleeing", gameState.getTimeElapsed()); - } - } - } else { - // It's a soldier. Right now we'll retaliate - // TODO: check for stronger units against this type, check for fleeing options, etc. - // Check also for neighboring towers and garrison there perhaps? - ourUnit.attack(e.attacker); - } - } - } - } - } - } - */ -}; // nice sets of closing brackets, isn't it? - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/entity-extend.js b/binaries/data/mods/public/simulation/ai/aegis/entity-extend.js deleted file mode 100644 index 90773f38fe..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/entity-extend.js +++ /dev/null @@ -1,69 +0,0 @@ -var AEGIS = function(m) -{ - -// returns some sort of DPS * health factor. If you specify a class, it'll use the modifiers against that class too. -m.getMaxStrength = function(ent, againstClass) -{ - var strength = 0.0; - var attackTypes = ent.attackTypes(); - var armourStrength = ent.armourStrengths(); - var hp = ent.maxHitpoints() / 100.0; // some normalization - for (var typeKey in attackTypes) { - var type = attackTypes[typeKey]; - - if (type == "Slaughter" || type == "Charged") - continue; - - var attackStrength = ent.attackStrengths(type); - var attackRange = ent.attackRange(type); - var attackTimes = ent.attackTimes(type); - for (var str in attackStrength) { - var val = parseFloat(attackStrength[str]); - if (againstClass) - val *= ent.getMultiplierAgainst(type, againstClass); - switch (str) { - case "crush": - strength += (val * 0.085) / 3; - break; - case "hack": - strength += (val * 0.075) / 3; - break; - case "pierce": - strength += (val * 0.065) / 3; - break; - } - } - if (attackRange){ - strength += (attackRange.max * 0.0125) ; - } - for (var str in attackTimes) { - var val = parseFloat(attackTimes[str]); - switch (str){ - case "repeat": - strength += (val / 100000); - break; - case "prepare": - strength -= (val / 100000); - break; - } - } - } - for (var str in armourStrength) { - var val = parseFloat(armourStrength[str]); - switch (str) { - case "crush": - strength += (val * 0.085) / 3; - break; - case "hack": - strength += (val * 0.075) / 3; - break; - case "pierce": - strength += (val * 0.065) / 3; - break; - } - } - return strength * hp; -}; - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/entitycollection-extend.js b/binaries/data/mods/public/simulation/ai/aegis/entitycollection-extend.js deleted file mode 100644 index 065bf83c0b..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/entitycollection-extend.js +++ /dev/null @@ -1,16 +0,0 @@ -var AEGIS = function(m) -{ - -m.EntityCollectionFromIds = function(gameState, idList){ - var ents = {}; - for (var i in idList){ - var id = idList[i]; - if (gameState.entities._entities[id]) { - ents[id] = gameState.entities._entities[id]; - } - } - return new API3.EntityCollection(gameState.sharedScript, ents); -} - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/gamestate-extend.js b/binaries/data/mods/public/simulation/ai/aegis/gamestate-extend.js deleted file mode 100644 index 63cd6f83d3..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/gamestate-extend.js +++ /dev/null @@ -1,57 +0,0 @@ -var AEGIS = function(m) -{ - -// Some functions that could be part of the gamestate but are Aegis specific. - -// The next three are to register that we assigned a gatherer to a resource this turn. -// expects an entity -m.IsSupplyFull = function(gamestate, supply) -{ - if (supply.isFull(PlayerID) === true) - return true; - var count = supply.resourceSupplyGatherers().length; - if (gamestate.turnCache["ressourceGatherer"] && gamestate.turnCache["ressourceGatherer"][supply.id()]) - count += gamestate.turnCache["ressourceGatherer"][supply.id()]; - if (count >= supply.maxGatherers()) - return true; - return false; -} - -// add a gatherer to the turn cache for this supply. -m.AddTCGatherer = function(gamestate, supplyID) -{ - if (gamestate.turnCache["ressourceGatherer"] && gamestate.turnCache["ressourceGatherer"][supplyID]) - ++gamestate.turnCache["ressourceGatherer"][supplyID]; - else if (gamestate.turnCache["ressourceGatherer"]) - gamestate.turnCache["ressourceGatherer"][supplyID] = 1; - else - gamestate.turnCache["ressourceGatherer"] = { "supplyID" : 1 }; -} - -m.GetTCGatherer = function(gamestate, supplyID) -{ - if (gamestate.turnCache["ressourceGatherer"] && gamestate.turnCache["ressourceGatherer"][supplyID]) - return gamestate.turnCache["ressourceGatherer"][supplyID]; - else - return 0; -} - -// The next two are to register that we assigned a gatherer to a resource this turn. -m.AddTCRessGatherer = function(gamestate, resource) -{ - if (gamestate.turnCache["ressourceGatherer-" + resource]) - ++gamestate.turnCache["ressourceGatherer-" + resource]; - else - gamestate.turnCache["ressourceGatherer-" + resource] = 1; -} - -m.GetTCRessGatherer = function(gamestate, resource) -{ - if (gamestate.turnCache["ressourceGatherer-" + resource]) - return gamestate.turnCache["ressourceGatherer-" + resource]; - else - return 0; -} - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/headquarters.js b/binaries/data/mods/public/simulation/ai/aegis/headquarters.js deleted file mode 100644 index 05d319c1cf..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/headquarters.js +++ /dev/null @@ -1,1345 +0,0 @@ -var AEGIS = function(m) -{ -/* Headquarters - * Deal with high level logic for the AI. Most of the interesting stuff gets done here. - * Some tasks: - -defining RESS needs - -BO decisions. - > training workers - > building stuff (though we'll send that to bases) - > researching - -picking strategy (specific manager?) - -diplomacy (specific manager?) - -planning attacks - -picking new CC locations. - */ - -m.HQ = function(Config) { - this.Config = Config; - - this.targetNumBuilders = this.Config.Economy.targetNumBuilders; // number of workers we want building stuff - - this.dockStartTime = this.Config.Economy.dockStartTime * 1000; - this.techStartTime = this.Config.Economy.techStartTime * 1000; - - this.dockFailed = false; // sanity check - this.waterMap = false; // set by the aegis.js file. - - this.econState = "growth"; // existing values: growth, townPhasing. - - // tell if we can't gather from a resource type for sanity checks. - this.outOf = { "food" : false, "wood" : false, "stone" : false, "metal" : false }; - - this.baseManagers = {}; - - // cache the rates. - this.wantedRates = { "food": 0, "wood": 0, "stone":0, "metal": 0 }; - this.currentRates = { "food": 0, "wood": 0, "stone":0, "metal": 0 }; - this.currentRateLastUpdateTime = 0; - - // this means we'll have about a big third of women, and thus we can maximize resource gathering rates. - this.femaleRatio = this.Config.Economy.femaleRatio; - - this.fortressStartTime = 0; - this.fortressLapseTime = this.Config.Military.fortressLapseTime * 1000; - this.defenceBuildingTime = this.Config.Military.defenceBuildingTime * 1000; - this.attackPlansStartTime = this.Config.Military.attackPlansStartTime * 1000; - - this.defenceManager = new m.Defence(this.Config); - this.navalManager = new m.NavalManager(); - - this.TotalAttackNumber = 0; - this.upcomingAttacks = { "CityAttack" : [], "Rush" : [] }; - this.startedAttacks = { "CityAttack" : [], "Rush" : [] }; -}; - -// More initialisation for stuff that needs the gameState -m.HQ.prototype.init = function(gameState, queues){ - // initialize base map. Each pixel is a base ID, or 0 if none - this.basesMap = new API3.Map(gameState.sharedScript, new Uint8Array(gameState.getMap().data.length)); - this.basesMap.setMaxVal(255); - - if (this.Config.Economy.targetNumWorkers) - this.targetNumWorkers = this.Config.Economy.targetNumWorkers; - else if (this.targetNumWorkers === undefined && this.Config.difficulty === 0) - this.targetNumWorkers = Math.max(1, Math.min(40, Math.floor(gameState.getPopulationMax()))); - else if (this.targetNumWorkers === undefined && this.Config.difficulty === 1) - this.targetNumWorkers = Math.max(1, Math.min(60, Math.floor(gameState.getPopulationMax()))); - else if (this.targetNumWorkers === undefined) - this.targetNumWorkers = Math.max(1, Math.min(120,Math.floor(gameState.getPopulationMax()/3.0))); - - - // Let's get our initial situation here. - // TODO: improve on this. - // TODO: aknowledge bases, assign workers already. - var ents = gameState.getEntities().filter(API3.Filters.byOwner(PlayerID)); - - var workersNB = 0; - var hasScout = false; - var treasureAmount = { 'food': 0, 'wood': 0, 'stone': 0, 'metal': 0 }; - var hasCC = false; - - if (ents.filter(API3.Filters.byClass("CivCentre")).length > 0) - hasCC = true; - workersNB = ents.filter(API3.Filters.byClass("Worker")).length; - if (ents.filter(API3.Filters.byClass("Cavalry")).length > 0) - hasScout = true; - - // TODO: take multiple CCs into account. - if (hasCC) - { - var CC = ents.filter(API3.Filters.byClass("CivCentre")).toEntityArray()[0]; - for (var i in treasureAmount) - gameState.getResourceSupplies(i).forEach( function (ent) { - if (ent.resourceSupplyType().generic === "treasure" && API3.SquareVectorDistance(ent.position(), CC.position()) < 5000) - treasureAmount[i] += ent.resourceSupplyMax(); - }); - this.baseManagers[1] = new m.BaseManager(this.Config); - this.baseManagers[1].init(gameState); - this.baseManagers[1].setAnchor(CC); - this.baseManagers[1].initTerritory(this, gameState); - this.baseManagers[1].initGatheringFunctions(this, gameState); - - if (m.DebugEnabled()) - this.basesMap.dumpIm("basesMap.png"); - var self = this; - - ents.forEach( function (ent) { //}){ - self.baseManagers[1].assignEntity(ent); - }); - } - // we now have enough data to decide on a few things. - - // TODO: here would be where we pick our initial strategy. - - // immediatly build a wood dropsite if possible. - if (this.baseManagers[1]) - { - if (gameState.ai.queueManager.getAvailableResources(gameState)["wood"] >= 250) - { - var pos = this.baseManagers[1].findBestDropsiteLocation(gameState, "wood"); - if (pos) - { - queues.dropsites.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_storehouse",{ "base" : 1 }, pos)); - queues.minorTech.addItem(new m.ResearchPlan(gameState, "gather_capacity_wheelbarrow")); - } - } - } - - var map = new API3.Map(gameState.sharedScript, gameState.sharedScript.CCResourceMaps["wood"].map); - if (m.DebugEnabled()) - map.dumpIm("map_CC_Wood.png"); - - //this.reassignIdleWorkers(gameState); - - this.navalManager.init(gameState, queues); - this.defenceManager.init(gameState); - - // TODO: change that to something dynamic. - var civ = gameState.playerData.civ; - - // load units and buildings from the config files - - if (civ in this.Config.buildings.moderate){ - this.bModerate = this.Config.buildings.moderate[civ]; - }else{ - this.bModerate = this.Config.buildings.moderate['default']; - } - - if (civ in this.Config.buildings.advanced){ - this.bAdvanced = this.Config.buildings.advanced[civ]; - }else{ - this.bAdvanced = this.Config.buildings.advanced['default']; - } - - if (civ in this.Config.buildings.fort){ - this.bFort = this.Config.buildings.fort[civ]; - }else{ - this.bFort = this.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]); - } -}; - -m.HQ.prototype.checkEvents = function (gameState, events, queues) { - // TODO: probably check stuffs like a base destruction. - var CreateEvents = events["Create"]; - var ConstructionEvents = events["ConstructionFinished"]; - for (var i in CreateEvents) - { - var evt = CreateEvents[i]; - // Let's check if we have a building set to create a new base. - if (evt && evt.entity) - { - var ent = gameState.getEntityById(evt.entity); - - if (ent === undefined) - continue; // happens when this message is right before a "Destroy" one for the same entity. - - if (ent.isOwn(PlayerID) && ent.getMetadata(PlayerID, "base") === -1) - { - // Okay so let's try to create a new base around this. - var bID = m.playerGlobals[PlayerID].uniqueIDBases; - this.baseManagers[bID] = new m.BaseManager(this.Config); - this.baseManagers[bID].init(gameState, events, true); - this.baseManagers[bID].setAnchor(ent); - this.baseManagers[bID].initTerritory(this, gameState); - - // Let's get a few units out there to build this. - var builders = this.bulkPickWorkers(gameState, bID, 10); - if (builders !== false) - { - builders.forEach(function (worker) { - worker.setMetadata(PlayerID, "base", bID); - worker.setMetadata(PlayerID, "subrole", "builder"); - worker.setMetadata(PlayerID, "target-foundation", ent.id()); - }); - } - } - } - } - for (var i in ConstructionEvents) - { - var evt = ConstructionEvents[i]; - // Let's check if we have a building set to create a new base. - // TODO: move to the base manager. - if (evt.newentity) - { - var ent = gameState.getEntityById(evt.newentity); - - if (ent === undefined) - continue; // happens when this message is right before a "Destroy" one for the same entity. - - if (ent.isOwn(PlayerID) && ent.getMetadata(PlayerID, "baseAnchor") == true) - { - var base = ent.getMetadata(PlayerID, "base"); - if (this.baseManagers[base].constructing) - { - this.baseManagers[base].constructing = false; - this.baseManagers[base].initGatheringFunctions(this, gameState); - } - } - } - } -}; - -// Called by the "town phase" research plan once it's started -m.HQ.prototype.OnTownPhase = function(gameState) -{ - if (this.Config.difficulty >= 2 && this.femaleRatio !== 0.4) - { - this.femaleRatio = 0.4; - gameState.ai.queues["villager"].empty(); - gameState.ai.queues["citizenSoldier"].empty(); - for (var i in this.baseManagers) - { - if (this.baseManagers[i].willGather["wood"] === 2) - this.baseManagers[i].willGather["wood"] = 1; // retry. - if (this.baseManagers[i].willGather["stone"] === 2) - this.baseManagers[i].willGather["stone"] = 1; // retry. - if (this.baseManagers[i].willGather["metal"] === 2) - this.baseManagers[i].willGather["metal"] = 1; // retry. - } - } -} - -// This code trains females and citizen workers, trying to keep close to a ratio of females/CS -// TODO: this should choose a base depending on which base need workers -// TODO: also there are several things that could be greatly improved here. -m.HQ.prototype.trainMoreWorkers = function(gameState, queues) -{ - // Get some data. - // Count the workers in the world and in progress - var numFemales = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("units/{civ}_support_female_citizen"), true); - - // counting the workers that aren't part of a plan - var numWorkers = 0; - gameState.getOwnUnits().forEach (function (ent) { - if (ent.getMetadata(PlayerID, "role") == "worker" && ent.getMetadata(PlayerID, "plan") == undefined) - numWorkers++; - }); - var numInTraining = 0; - gameState.getOwnTrainingFacilities().forEach(function(ent) { - ent.trainingQueue().forEach(function(item) { - if (item.metadata && item.metadata.role && item.metadata.role == "worker" && item.metadata.plan == undefined) - numWorkers += item.count; - numInTraining += item.count; - }); - }); - var numQueuedF = queues.villager.countQueuedUnits(); - var numQueuedS = queues.citizenSoldier.countQueuedUnits(); - var numQueued = numQueuedS + numQueuedF; - var numTotal = numWorkers + numQueued; - - // If we have too few, train more - // should plan enough to always have females… - // TODO: 15 here should be changed to something more sensible, such as nb of producing buildings. - if (numTotal > this.targetNumWorkers || numQueued > 50 || (numQueuedF > 20 && numQueuedS > 20) || numInTraining > 15) - return; - - if (numTotal >= this.Config.Economy.villagePopCap && gameState.currentPhase() === 1 && !gameState.isResearching(gameState.townPhase())) - return; - - // default template and size - var template = gameState.applyCiv("units/{civ}_support_female_citizen"); - var size = Math.min(5, Math.max(Math.ceil(numTotal / 10), 1)); - - // Choose whether we want soldiers instead. - // TODO: we might want to adjust our female ratio. - if ((numFemales+numQueuedF)/numTotal > this.femaleRatio && numQueuedS < 20) { - if (numTotal < 35) - template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], [ ["cost",1], ["speed",0.5], ["costsResource", 0.5, "stone"], ["costsResource", 0.5, "metal"]]); - else - template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], [ ["strength",1] ]); - - if (!template) - template = gameState.applyCiv("units/{civ}_support_female_citizen"); - else - size = Math.min(5, Math.ceil(numTotal / 12)); - } - - // TODO: improve that logic. - /* - if (numFemales/numWorkers > this.femaleRatio && numQueuedS > 0 && numWorkers > 25) - queues.villager.paused = true; - else - queues.villager.paused = false; - */ - - // TODO: perhaps assign them a default resource and check the base according to that. - - // base "0" means "auto" - if (template === gameState.applyCiv("units/{civ}_support_female_citizen")) - queues.villager.addItem(new m.TrainingPlan(gameState, template, { "role" : "worker", "base" : 0 }, size, size )); - else - queues.citizenSoldier.addItem(new m.TrainingPlan(gameState, template, { "role" : "worker", "base" : 0 }, size, size)); -}; - -// picks the best template based on parameters and classes -m.HQ.prototype.findBestTrainableUnit = function(gameState, classes, parameters) { - var units = gameState.findTrainableUnits(classes, ["Hero"]); // heroes are not used inside aegis - - 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 += m.getMaxStrength(a[1]) * param[1]; - bTopParam += m.getMaxStrength(b[1]) * param[1]; - } - if (param[0] == "siegeStrength") { - aTopParam += m.getMaxStrength(a[1], "Structure") * param[1]; - bTopParam += m.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]; - } - // requires a third parameter which is the resource - if (param[0] == "costsResource") { - if (a[1].cost()[param[2]]) - aTopParam *= param[1]; - if (b[1].cost()[param[2]]) - bTopParam *= param[1]; - } - if (param[0] == "canGather") { - // checking against wood, could be anything else really. - if (a[1].resourceGatherRates() && a[1].resourceGatherRates()["wood.tree"]) - 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]; -}; - -// Tries to research any available tech -// Only one at once. Also does military tech (selection is completely random atm) -// TODO: Lots, lots, lots here. -m.HQ.prototype.tryResearchTechs = function(gameState, queues) { - if (queues.minorTech.length() === 0) - { - var possibilities = gameState.findAvailableTech(); - if (possibilities.length === 0) - return; - // randomly pick one. No worries about pairs in that case. - var p = Math.floor((Math.random()*possibilities.length)); - queues.minorTech.addItem(new m.ResearchPlan(gameState, possibilities[p][0])); - } -} - -// We're given a worker and a resource type -// We'll assign the worker for the best base for that resource type. -// TODO: improve choice alogrithm -m.HQ.prototype.switchWorkerBase = function(gameState, worker, type) { - var bestBase = 0; - var bestBaseState = -1; - - for (var i in this.baseManagers) - { - if (this.baseManagers[i].willGather[type] === 1 || (this.baseManagers[i].willGather[type] === 2 && bestBaseState !== 1)) - { - if (this.baseManagers[i].accessIndex === this.baseManagers[worker.getMetadata(PlayerID,"base")].accessIndex - || this.navalManager.canReach(gameState, this.baseManagers[i].accessIndex, this.baseManagers[worker.getMetadata(PlayerID,"base")].accessIndex)) - { - bestBaseState = this.baseManagers[i].willGather[type] - bestBase = i; - break; - } - } - } - if (bestBase && bestBase !== worker.getMetadata(PlayerID,"base")) - { - worker.setMetadata(PlayerID,"base",bestBase); - return true; - } else { - return false; - } -}; - -// returns an entity collection of workers through BaseManager.pickBuilders -// TODO: better the choice algo. -m.HQ.prototype.bulkPickWorkers = function(gameState, newBaseID, number) { - var accessIndex = this.baseManagers[newBaseID].accessIndex; - if (!accessIndex) - return false; - // sorting bases by whether they are on the same accessindex or not. - var baseBest = m.AssocArraytoArray(this.baseManagers).sort(function (a,b) { - if (a.accessIndex === accessIndex && b.accessIndex !== accessIndex) - return -1; - else if (b.accessIndex === accessIndex && a.accessIndex !== accessIndex) - return 1; - return 0; - }); - - var needed = number; - var workers = new API3.EntityCollection(gameState.sharedScript); - for (var i in baseBest) - { - baseBest[i].pickBuilders(gameState, workers, needed); - if (workers.length < number) - needed = number - workers.length; - else - break; - } - if (workers.length == 0) - return false; - return workers; -}; - -// returns the current gather rate -// This is not per-se exact, it performs a few adjustments ad-hoc to account for travel distance, stuffs like that. -m.HQ.prototype.GetCurrentGatherRates = function(gameState) { - var self = this; - -// if (gameState.getTimeElapsed() - this.currentRateLastUpdateTime < 10000 && this.currentRateLastUpdateTime !== 0 && gameState.ai.playedTurn > 3) -// return this.currentRates; - - this.currentRateLastUpdateTime = gameState.getTimeElapsed(); - - for (var type in this.wantedRates) - this.currentRates[type] = 0; - - for (var i in this.baseManagers) - this.baseManagers[i].getGatherRates(gameState, this.currentRates); - - return this.currentRates; -}; - - -/* Pick the resource which most needs another worker - * How this works: - * We get the rates we would want to have to be able to deal with our plans - * We get our current rates - * We compare; we pick the one where the discrepancy is highest. - * Need to balance long-term needs and possible short-term needs. - */ -m.HQ.prototype.pickMostNeededResources = function(gameState) { - var self = this; - - this.wantedRates = gameState.ai.queueManager.wantedGatherRates(gameState); - - var currentRates = {}; - for (var type in this.wantedRates) - currentRates[type] = 0; - currentRates = this.GetCurrentGatherRates(gameState); - - // let's get our ideal number. - var types = Object.keys(this.wantedRates); - - types.sort(function(a, b) { - var va = (Math.max(0,self.wantedRates[a] - currentRates[a]))/ (currentRates[a]+1); - var vb = (Math.max(0,self.wantedRates[b] - currentRates[b]))/ (currentRates[b]+1); - - // If they happen to be equal (generally this means "0" aka no need), make it fair. - if (va === vb) - return (self.wantedRates[b]/(currentRates[b]+1)) - (self.wantedRates[a]/(currentRates[a]+1)); - return vb-va; - }); - return types; -}; - -// If all the CC's are destroyed then build a new one -// TODO: rehabilitate. -m.HQ.prototype.buildNewCC= function(gameState, queues) { - var numCCs = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_civil_centre"), true); - numCCs += queues.civilCentre.length(); - - // no use trying to lay foundations that will be destroyed - if (gameState.defcon() > 2) - for (var i = numCCs; i < 1; i++) { - gameState.ai.queueManager.clear(); - this.baseNeed["food"] = 0; - this.baseNeed["wood"] = 50; - this.baseNeed["stone"] = 50; - this.baseNeed["metal"] = 50; - queues.civilCentre.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre")); - } - return (gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_civil_centre"), true) == 0 && gameState.currentPhase() > 1); -}; - -// Returns the best position to build a new Civil Centre -// Whose primary function would be to reach new resources of type "resource". -m.HQ.prototype.findBestEcoCCLocation = function(gameState, resource){ - - var CCPlate = gameState.getTemplate("structures/{civ}_civil_centre"); - - // 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 obstructions = m.createObstructionMap(gameState, 0); - obstructions.expandInfluences(); - - // copy the resource map as initialization. - var friendlyTiles = new API3.Map(gameState.sharedScript, gameState.sharedScript.CCResourceMaps[resource].map, true); - friendlyTiles.setMaxVal(255); - var ents = gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).toEntityArray(); - var eEnts = gameState.getEnemyStructures().filter(API3.Filters.byClass("CivCentre")).toEntityArray(); - - var dps = gameState.getOwnDropsites().toEntityArray(); - - for (var j = 0; j < friendlyTiles.length; ++j) - { - // We check for our other CCs: the distance must not be too big. Anything bigger will result in scrapping. - // This ensures territorial continuity. - // TODO: maybe whenever I get around to implement multi-base support (details below, requires being part of the team. If you're not, ask wraitii directly by PM). - // (see www.wildfiregames.com/forum/index.php?showtopic=16702&#entry255631 ) - // TODO: figure out what I was trying to say above. - - var canBuild = true; - var canBuild2 = false; - - var pos = [j%friendlyTiles.width+0.5, Math.floor(j/friendlyTiles.width)+0.5]; - - for (var i in ents) - { - var entPos = ents[i].position(); - entPos = [entPos[0]/4.0,entPos[1]/4.0]; - - var dist = API3.SquareVectorDistance(entPos, pos); - if (dist < 3500 || dist > 7900) - friendlyTiles.map[j] /= 2.0; - if (dist < 2120) - { - canBuild = false; - continue; - } else if (dist < 9200 || this.waterMap) - canBuild2 = true; - } - // checking for bases. - if (this.basesMap.map[j] !== 0) - canBuild = false; - - if (!canBuild2) - canBuild = false; - if (canBuild) - { - // Checking for enemy CCs - for (var i in eEnts) - { - var entPos = eEnts[i].position(); - entPos = [entPos[0]/4.0,entPos[1]/4.0]; - // 7100 works well as a limit. - if (API3.SquareVectorDistance(entPos, pos) < 2500) - { - canBuild = false; - continue; - } - } - } - if (!canBuild) - { - friendlyTiles.map[j] = 0; - continue; - } - - for (var i in dps) - { - var dpPos = dps[i].position(); - if (dpPos === undefined) - { - // Probably a mauryan elephant, skip - continue; - } - dpPos = [dpPos[0]/4.0,dpPos[1]/4.0]; - var dist = API3.SquareVectorDistance(dpPos, pos); - if (dist < 600) - { - friendlyTiles.map[j] = 0; - continue; - } else if (dist < 1500) - friendlyTiles.map[j] /= 2.0; - } - - friendlyTiles.map[j] *= 1.5; - - for (var i in gameState.sharedScript.CCResourceMaps) - if (friendlyTiles.map[j] !== 0 && i !== "food") - { - var val = friendlyTiles.map[j] + gameState.sharedScript.CCResourceMaps[i].map[j]; - if (val < 255) - friendlyTiles.map[j] = val; - else - friendlyTiles.map[j] = 255; - } - } - - - var best = friendlyTiles.findBestTile(6, obstructions); - var bestIdx = best[0]; - - if (m.DebugEnabled()) - { - friendlyTiles.map[bestIdx] = 270; - friendlyTiles.dumpIm("cc_placement_base_" + gameState.getTimeElapsed() + "_" + resource + "_" + best[1] + ".png",301); - //obstructions.dumpIm("cc_placement_base_" + gameState.getTimeElapsed() + "_" + resource + "_" + best[1] + "_obs.png", 20); - } - - // not good enough. - 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; - - m.debug ("Best for value " + best[1] + " at " + uneval([x,z])); - - return [x,z]; -}; - -m.HQ.prototype.buildTemple = function(gameState, queues){ - if (gameState.currentPhase() >= 2 ) { - if (queues.economicBuilding.countQueuedUnits() === 0 && - gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_temple"), true) === 0){ - queues.economicBuilding.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_temple", { "base" : 1 })); - } - } -}; - -m.HQ.prototype.buildMarket = function(gameState, queues){ - if (gameState.getPopulation() > this.Config.Economy.popForMarket && gameState.currentPhase() >= 2 ) { - if (queues.economicBuilding.countQueuedUnitsWithClass("BarterMarket") === 0 && - gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_market"), true) === 0){ - //only ever build one storehouse/CC/market at a time - queues.economicBuilding.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_market", { "base" : 1 })); - } - } -}; - -// Build a farmstead to go to town phase faster and prepare for research. Only really active on higher diff mode. -m.HQ.prototype.buildFarmstead = function(gameState, queues){ - if (gameState.getPopulation() > this.Config.Economy.popForFarmstead - && (gameState.currentPhase() > 1 || gameState.isResearching(gameState.townPhase()))) { - // achtung: "DropsiteFood" does not refer to CCs. - if (queues.economicBuilding.countQueuedUnitsWithClass("DropsiteFood") === 0 && - gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_farmstead"), true) === 0){ - //only ever build one storehouse/CC/market at a time - queues.economicBuilding.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_farmstead", { "base" : 1 })); - // add the farming plough to the research we want. - queues.minorTech.addItem(new m.ResearchPlan(gameState, "gather_farming_plows")); - } - } -}; - -// TODO: generic this, probably per-base -m.HQ.prototype.buildDock = function(gameState, queues){ - if (!this.waterMap || this.dockFailed) - return; - if (gameState.getTimeElapsed() > this.dockStartTime) { - if (queues.economicBuilding.countQueuedUnitsWithClass("NavalMarket") === 0 && - gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_dock"), true) === 0) { - var tp = "" - if (gameState.civ() == "cart" && gameState.currentPhase() > 1) - tp = "structures/{civ}_super_dock"; - else if (gameState.civ() !== "cart") - tp = "structures/{civ}_dock"; - if (tp !== "") - { - var remaining = this.navalManager.getUnconnectedSeas(gameState, this.baseManagers[1].accessIndex); - queues.economicBuilding.addItem(new m.ConstructionPlan(gameState, tp, { "base" : 1, "sea" : remaining[0] })); - } - } - } -}; - -// Try to barter unneeded resources for needed resources. -// once per turn because the info doesn't update between a turn and fixing isn't worth it. -m.HQ.prototype.tryBartering = function(gameState) { - var markets = gameState.getOwnEntitiesByType(gameState.applyCiv("structures/{civ}_market"), true).toEntityArray(); - - if (markets.length === 0) - return false; - - // Available resources after account substraction - var available = gameState.ai.queueManager.getAvailableResources(gameState); - - var rates = this.GetCurrentGatherRates(gameState) - - var prices = gameState.getBarterPrices(); - // calculates conversion rates - var getBarterRate = function (prices,buy,sell) { return Math.round(100 * prices["sell"][sell] / prices["buy"][buy]); }; - - // loop through each queues checking if we could barter and finish a queue quickly. - for (var j in gameState.ai.queues) - { - var queue = gameState.ai.queues[j]; - if (queue.paused || queue.length() === 0) - continue; - - var account = gameState.ai.queueManager.accounts[j]; - var elem = queue.queue[0]; - var elemCost = elem.getCost(); - for each (var ress in elemCost.types) - { - if (available[ress] >= 0) - continue; // don't care if we still have available resources or our rate is good enough - var need = elemCost[ress] - account[ress]; - if (need <= 0 || rates[ress] >= need/50) // don't care if we don't need resources for our first item - continue; - - if (ress == "food" && need < 400) - continue; - - // pick the best resource to barter. - var bestToBarter = ""; - var bestRate = 0; - for each (var otherRess in elemCost.types) - { - if (ress === otherRess) - continue; - // I wanna keep some - if (available[otherRess] < 130 + need) - return false; - var barterRate = getBarterRate(prices, ress, otherRess); - if (barterRate > bestRate) - { - bestRate = barterRate; - bestToBarter = otherRess; - } - } - if (bestToBarter !== "") - { - markets[0].barter(buy,sell,100); - m.debug ("Snipe bartered " + sell +" for " + buy + ", value 100"); - return true; - } - } - } - // now barter for big needs. - var needs = gameState.ai.queueManager.currentNeeds(gameState); - for each (var sell in needs.types) { - for each (var buy in needs.types) { - if (buy != sell && needs[sell] <= 0 && available[sell] > 500) { // if we don't need it and have a buffer - if (needs[buy] > rates[buy]*80) { // if we need that other resource terribly. - markets[0].barter(buy,sell,100); - m.debug ("Gross bartered " +sell +" for " + buy + ", value 100"); - return true; - } - } - } - } - return false; -}; - -// build more houses if needed. -// kinda ugly, lots of special cases to both build enough houses but not tooo many… -m.HQ.prototype.buildMoreHouses = function(gameState,queues) { - - if (gameState.getPopulationLimit() < gameState.getPopulationMax()) { - var numPlanned = queues.house.length(); - if (numPlanned < 3 || (numPlanned < 5 && gameState.getPopulation() > 80)) - { - var plan = new m.ConstructionPlan(gameState, "structures/{civ}_house", { "base" : 1 }); - // make the difficulty available to the isGo function without having to pass it as argument - var difficulty = this.Config.difficulty; - // change the starting condition to "less than 15 slots left". - plan.isGo = function (gameState) { - var HouseNb = gameState.countEntitiesByType(gameState.applyCiv("foundation|structures/{civ}_house"), true); - - var freeSlots = 0; - if (gameState.civ() == "gaul" || gameState.civ() == "brit" || gameState.civ() == "iber" || - gameState.civ() == "maur" || gameState.civ() == "ptol") - freeSlots = gameState.getPopulationLimit() + HouseNb*5 - gameState.getPopulation(); - else - freeSlots = gameState.getPopulationLimit() + HouseNb*10 - gameState.getPopulation(); - if (gameState.getPopulation() > 55 && difficulty > 1) - return (freeSlots <= 21); - else if (gameState.getPopulation() >= 30 && difficulty !== 0) - return (freeSlots <= 15); - else - return (freeSlots <= 10); - } - queues.house.addItem(plan); - } - if (numPlanned > 0 && this.econState == "townPhasing") - { - var houseQueue = queues.house.queue; - var count = gameState.getOwnStructures().filter(API3.Filters.byClass("Village")).length; - count += queues.militaryBuilding.length(); // barracks - for (var i = 0; i < numPlanned; ++i) - { - if (houseQueue[i].isGo(gameState)) - ++count; - else if (count < 5) - { - houseQueue[i].isGo = function () { return true; } - ++count; - } - } - } - } -}; - -// checks if we have bases for all resource types (bar food for now) or if we need to expand. -m.HQ.prototype.checkBasesRessLevel = function(gameState,queues) { - if (gameState.currentPhase() === 1 && !gameState.isResearching(gameState.townPhase())) - return; - var count = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 } - var capacity = { "wood" : 0, "stone" : 0, "metal" : 0 } - var need = { "food": false, "wood" : true, "stone" : true, "metal" : true }; - var posss = []; - - for (var i in this.baseManagers) - { - var base = this.baseManagers[i]; - for (var type in count) - { - if (type == "food") - { - count[type] = 1; - capacity[type] = 20000; - need[type] = (base.willGather[type] !== 1); - continue; - } - if (base.getResourceLevel(gameState, type, "dropsites") > 4000*Math.max(this.Config.difficulty,2)) - count[type]++; - capacity[type] += base.getWorkerCapacity(gameState, type, true); - if (base.willGather[type] === 1) - need[type] = false; - } - } - for (var type in count) - { - if (count[type] === 0 || need[type] - || capacity[type] < gameState.getOwnUnits().filter(API3.Filters.and(API3.Filters.byMetadata(PlayerID, "subrole", "gatherer"), API3.Filters.byMetadata(PlayerID, "gather-type", type))).length * 1.05) - { - // plan a new base. - if (gameState.countFoundationsByType(gameState.applyCiv("structures/{civ}_civil_centre"), true) === 0 && queues.civilCentre.length() === 0) { - // In endgame when the whole map is claimed by players, we won't find a spot for a new CC. - // findBestEcoCCLocation needs to search the whole map for a good spot and is currently way too slow, so we wait quite long - // until we check again. The "PlayerID * 10" part is to distribute the load across multiple turns. - // TODO: this is a workaround. the current solution is bad for various reasons: - // 1. findBestEcoCCLocation could be much more efficient for the case when nearly all territory is occupied. - // 2. It doesn't make sense to check the whole map for a good spot for all resource types if we could see in the beginning - // that no free territory is available to build a CC. - // 3. Trying to build a new CC should not only be triggered by the need for more resources. - // Opportunity (having destroyed an enemy CC and some soldiers standing around) is also a good reason for a new CC. - // 4. Last but not least it causes the AI to react slowly when new territory becomes available. - if (this.outOf[type] && gameState.ai.playedTurn % 100 !== PlayerID * 10) - continue; - - var pos = this.findBestEcoCCLocation(gameState, type); - if (!pos) - { - // Okay so we'll set us as out of this. - this.outOf[type] = true; - } else { - //warn ("planning new base"); - // base "-1" means new base. - queues.civilCentre.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_civil_centre",{ "base" : -1 }, pos)); - } - } - } - } -}; - -// Deals with building fortresses and towers. -// Currently build towers next to every useful dropsites. -// TODO: Fortresses are placed randomly atm. -m.HQ.prototype.buildDefences = function(gameState, queues){ - - var workersNumber = gameState.getOwnEntitiesByRole("worker", true).filter(API3.Filters.not(API3.Filters.byHasMetadata(PlayerID,"plan"))).length; - - if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv('structures/{civ}_defense_tower'), true) - + queues.defenceBuilding.length() < gameState.getEntityLimits()["DefenseTower"] && queues.defenceBuilding.length() === 0 && gameState.currentPhase() > 1) { - for (var i in this.baseManagers) - { - for (var j in this.baseManagers[i].dropsites) - { - var amnts = this.baseManagers[i].dropsites[j]; - var dpEnt = gameState.getEntityById(j); - if (dpEnt !== undefined && dpEnt.getMetadata(PlayerID, "defenseTower") !== true) - if (amnts["wood"] || amnts["metal"] || amnts["stone"]) - { - var position = dpEnt.position(); - if (position) { - queues.defenceBuilding.addItem(new m.ConstructionPlan(gameState, 'structures/{civ}_defense_tower', { "base" : i }, position)); - } - dpEnt.setMetadata(PlayerID, "defenseTower", true); - } - } - } - } - - var numFortresses = 0; - for (var i in this.bFort){ - numFortresses += gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bFort[i]), true); - } - - if (queues.defenceBuilding.length() < 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 m.ConstructionPlan(gameState, this.bFort[0], { "base" : 1 })); - m.debug ("Building a fortress"); - } - } - if (gameState.countEntitiesByType(gameState.applyCiv(this.bFort[i]), true) >= 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); - } - } - } -}; - -m.HQ.prototype.buildBlacksmith = function(gameState, queues){ - if (gameState.getTimeElapsed() > this.Config.Military.timeForBlacksmith*1000) { - if (queues.militaryBuilding.length() === 0 && - gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_blacksmith"), true) === 0) { - var tp = gameState.getTemplate(gameState.applyCiv("structures/{civ}_blacksmith")); - if (tp.available(gameState)) - queues.militaryBuilding.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_blacksmith", { "base" : 1 })); - } - } -}; - -// 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. -m.HQ.prototype.constructTrainingBuildings = function(gameState, queues) { - Engine.ProfileStart("Build buildings"); - var workersNumber = gameState.getOwnEntitiesByRole("worker", true).filter(API3.Filters.not(API3.Filters.byHasMetadata(PlayerID, "plan"))).length; - - var barrackNb = gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0]), true); - - // first barracks. - if (workersNumber > this.Config.Military.popForBarracks1 || (this.econState == "townPhasing" && gameState.getOwnStructures().filter(API3.Filters.byClass("Village")).length < 5)) { - if (barrackNb + queues.militaryBuilding.length() < 1) { - m.debug ("Trying to build barracks"); - var plan = new m.ConstructionPlan(gameState, this.bModerate[0], { "base" : 1 }); - plan.onStart = function(gameState) { gameState.ai.queueManager.changePriority("militaryBuilding", 130); }; - queues.militaryBuilding.addItem(plan); - } - } - - // second barracks. - if (barrackNb < 2 && workersNumber > this.Config.Military.popForBarracks2) - if (queues.militaryBuilding.length() < 1) - queues.militaryBuilding.addItem(new m.ConstructionPlan(gameState, this.bModerate[0], { "base" : 1 })); - - // third barracks (optional 4th/5th for some civs as they rely on barracks more.) - if (barrackNb === 2 && barrackNb + queues.militaryBuilding.length() < 3 && workersNumber > 125) - if (queues.militaryBuilding.length() === 0) - { - queues.militaryBuilding.addItem(new m.ConstructionPlan(gameState, this.bModerate[0], { "base" : 1 })); - if (gameState.civ() == "gaul" || gameState.civ() == "brit" || gameState.civ() == "iber") { - queues.militaryBuilding.addItem(new m.ConstructionPlan(gameState, this.bModerate[0], { "base" : 1 })); - queues.militaryBuilding.addItem(new m.ConstructionPlan(gameState, this.bModerate[0], { "base" : 1 })); - } - } - - //build advanced military buildings - if (workersNumber >= this.Config.Military.popForBarracks2 - 15 && gameState.currentPhase() > 2){ - if (queues.militaryBuilding.length() === 0){ - var inConst = 0; - for (var i in this.bAdvanced) - inConst += gameState.countFoundationsByType(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]), true) < 1){ - queues.militaryBuilding.addItem(new m.ConstructionPlan(gameState, this.bAdvanced[i], { "base" : 1 })); - } - } - } - } - // build second advanced building except for some civs. - if (gameState.civ() !== "gaul" && gameState.civ() !== "brit" && gameState.civ() !== "iber" && - workersNumber > 130 && gameState.currentPhase() > 2) - { - var inConst = 0; - for (var i in this.bAdvanced) - inConst += gameState.countEntitiesByType(gameState.applyCiv(this.bAdvanced[i]), true); - if (inConst == 1) { - var i = Math.floor(Math.random() * this.bAdvanced.length); - if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bAdvanced[i]), true) < 1){ - queues.militaryBuilding.addItem(new m.ConstructionPlan(gameState, this.bAdvanced[i], { "base" : 1 })); - queues.militaryBuilding.addItem(new m.ConstructionPlan(gameState, this.bAdvanced[i], { "base" : 1 })); - } - } - } - - Engine.ProfileStop(); -}; - -// TODO: use pop(). Currently unused as this is too gameable. -m.HQ.prototype.garrisonAllFemales = function(gameState) { - var buildings = gameState.getOwnStructures().filter(API3.Filters.byCanGarrison()).toEntityArray(); - var females = gameState.getOwnUnits().filter(API3.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; -}; -m.HQ.prototype.ungarrisonAll = function(gameState) { - this.hasGarrisonedFemales = false; - var buildings = gameState.getOwnStructures().filter(API3.Filters.and(API3.Filters.byClass("Structure"),API3.Filters.byCanGarrison())).toEntityArray(); - buildings.forEach( function (struct) { - if (struct.garrisoned() && struct.garrisoned().length) - struct.unloadAll(); - }); -}; - -m.HQ.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); - } - } -} -m.HQ.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); - } - } -} -m.HQ.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); - } - } -} -m.HQ.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); - } - } -} - - -// Some functions are run every turn -// Others once in a while -m.HQ.prototype.update = function(gameState, queues, events) { - Engine.ProfileStart("Headquarters update"); - - this.checkEvents(gameState,events,queues); - //this.buildMoreHouses(gameState); - - this.trainMoreWorkers(gameState, queues); - - // sandbox doesn't expand. - if (this.Config.difficulty !== 0) - this.checkBasesRessLevel(gameState, queues); - - this.buildMoreHouses(gameState,queues); - - if (gameState.getTimeElapsed() > this.techStartTime && gameState.currentPhase() > 2 ) - this.tryResearchTechs(gameState,queues); - - if (this.Config.difficulty > 1) - this.tryBartering(gameState); - - this.buildFarmstead(gameState, queues); - this.buildMarket(gameState, queues); - // Deactivated: the temple had no useful purpose for the AI now. - //if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_market"), true) === 1) - // this.buildTemple(gameState, queues); - this.buildDock(gameState, queues); // not if not a water map. - - Engine.ProfileStart("Constructing military buildings and building defences"); - this.constructTrainingBuildings(gameState, queues); - - this.buildBlacksmith(gameState, queues); - - if(gameState.getTimeElapsed() > this.defenceBuildingTime) - this.buildDefences(gameState, queues); - Engine.ProfileStop(); - - for (var i in this.baseManagers) - { - this.baseManagers[i].checkEvents(gameState, events, queues) - if ( ( (+i + gameState.ai.playedTurn) % (m.playerGlobals[PlayerID].uniqueIDBases - 1)) === 0) - this.baseManagers[i].update(gameState, queues, events); - } - - this.navalManager.update(gameState, queues, events); - - this.defenceManager.update(gameState, events, this); - - Engine.ProfileStart("Looping through attack plans"); - - // TODO: bump this into a function. - // 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) { - m.debug ("Military Manager: " +attack.getType() +" plan " +attack.getName() +" aborted."); - if (updateStep === 3) { - this.attackPlansEncounteredWater = true; - m.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); - - m.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); - - m.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) { - m.debug ("Military Manager: " +attack.getType() +" plan " +attack.getName() +" is now finished."); - attack.Abort(gameState); - this.startedAttacks[attackType].splice(i--,1); - } - } - } - } - - // creating plans after updating because an aborted plan might be reused in that case. - - // TODO: remove the limitation to attacks when on water maps. - if (!this.waterMap && !this.attackPlansEncounteredWater) - { - if (gameState.ai.aggressiveness > 0.75 && gameState.countEntitiesByType(gameState.applyCiv(this.bModerate[0]), true) >= 1 - && gameState.getTimeElapsed() > this.attackPlansStartTime && gameState.getTimeElapsed() < 360000) - { - if (this.upcomingAttacks["Rush"].length === 0) - { - // we have a barracks and we want to rush, rush. - var AttackPlan = new m.CityAttack(gameState, this, this.Config, this.TotalAttackNumber, -1, "Rush"); - if (!AttackPlan.failed) - { - m.debug ("Headquarters: Rushing plan " +this.TotalAttackNumber); - this.TotalAttackNumber++; - this.upcomingAttacks["Rush"].push(AttackPlan); - } - } - } - // if we have a barracks, there's no water, we're at age >= 1 and we've decided to attack. - else if (gameState.countEntitiesByType(gameState.applyCiv(this.bModerate[0]), true) >= 1 && !this.attackPlansEncounteredWater - && gameState.getTimeElapsed() > this.attackPlansStartTime && (gameState.currentPhase() > 1 || gameState.isResearching(gameState.townPhase()))) { - if (gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_dock"), true) === 0 && this.waterMap) - { - // wait till we get a dock. - } else if (this.upcomingAttacks["CityAttack"].length === 0) { - // basically only the first plan, really. - var Lalala = undefined; - if (gameState.getTimeElapsed() < 12*60000) - Lalala = new m.CityAttack(gameState, this, this.Config, this.TotalAttackNumber, -1); - else if (this.Config.difficulty !== 0) - Lalala = new m.CityAttack(gameState, this, this.Config, this.TotalAttackNumber, -1, "superSized"); - - if (Lalala.failed) - this.attackPlansEncounteredWater = true; // hack - else { - m.debug ("Military Manager: Creating the plan " +this.TotalAttackNumber); - this.TotalAttackNumber++; - this.upcomingAttacks["CityAttack"].push(Lalala); - } - } - } - } - - /* - // 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 m.CityAttack(gameState, this,this.totalStartedAttackNumber, -1, "harass_raid"); - if (!Lalala.createSupportPlans(gameState, this, )) { - m.debug ("Military Manager: harrassing plan not a valid option"); - this.HarassRaiding = false; - } else { - m.debug ("Military Manager: Creating the harass raid plan " +this.totalStartedAttackNumber); - - this.totalStartedAttackNumber++; - this.preparingRaidNumber++; - this.currentAttacks.push(Lalala); - } - } - */ - Engine.ProfileStop(); - - /* - Engine.ProfileStop(); - - Engine.ProfileStart("Build new Dropsites"); - this.buildDropsites(gameState, queues); - Engine.ProfileStop(); - - if (this.Config.difficulty !== 0) - this.tryBartering(gameState); - - this.buildFarmstead(gameState, queues); - this.buildMarket(gameState, queues); - // Deactivated: the temple had no useful purpose for the AI now. - //if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_market"), true === 1) - // this.buildTemple(gameState, queues); - this.buildDock(gameState, queues); // not if not a water map. -*/ - Engine.ProfileStop(); // Heaquarters update -}; - -return m; - -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/license_gpl-2.0.txt b/binaries/data/mods/public/simulation/ai/aegis/license_gpl-2.0.txt deleted file mode 100644 index d511905c16..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/license_gpl-2.0.txt +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/binaries/data/mods/public/simulation/ai/aegis/map-module.js b/binaries/data/mods/public/simulation/ai/aegis/map-module.js deleted file mode 100644 index b19f5b9916..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/map-module.js +++ /dev/null @@ -1,149 +0,0 @@ -var AEGIS = function(m) -{ - -// other map functions -m.TERRITORY_PLAYER_MASK = 0x3F; - -m.createObstructionMap = function(gameState, accessIndex, template){ - var passabilityMap = gameState.getMap(); - var territoryMap = gameState.ai.territoryMap; - - // default values - var placementType = "land"; - var buildOwn = true; - var buildAlly = true; - var buildNeutral = true; - var buildEnemy = false; - // 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"); - buildEnemy = template.hasBuildTerritory("enemy"); - } - - var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction") | gameState.getPassabilityClassMask("building-land"); - - if (placementType == "shore") - { - // TODO: this won't change much, should be cached, it's slow. - var obstructionTiles = new Uint8Array(passabilityMap.data.length); - var okay = false; - for (var x = 0; x < passabilityMap.width; ++x) - { - for (var y = 0; y < passabilityMap.height; ++y) - { - var i = x + y*passabilityMap.width; - var tilePlayer = (territoryMap.data[i] & m.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) - { - var index = x + stuff[0] + (y+stuff[1])*passabilityMap.width; - 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,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 - okay = true; - } - } - // checking for accessibility: if a neighbor is inaccessible, this is too. If it's not on the same "accessible map" as us, we crash-i~u. - var radius = 3; - for (var xx = -radius;xx <= radius; xx++) - for (var yy = -radius;yy <= radius; yy++) - { - var id = x + xx + (y+yy)*passabilityMap.width; - if (id > 0 && id < passabilityMap.data.length) - if (gameState.ai.terrainAnalyzer.map[id] === 0 || gameState.ai.terrainAnalyzer.map[id] == 30 || gameState.ai.terrainAnalyzer.map[id] == 40) - okay = false; - } - obstructionTiles[i] = okay ? 255 : 0; - } - } - } else { - var playerID = PlayerID; - - var obstructionTiles = new Uint8Array(passabilityMap.data.length); - for (var i = 0; i < passabilityMap.data.length; ++i) - { - var tilePlayer = (territoryMap.data[i] & m.TERRITORY_PLAYER_MASK); - var invalidTerritory = ( - (!buildOwn && tilePlayer == playerID) || - (!buildAlly && gameState.isPlayerAlly(tilePlayer) && tilePlayer != playerID) || - (!buildNeutral && tilePlayer == 0) || - (!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer != 0) - ); - 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 API3.Map(gameState.sharedScript, obstructionTiles); - map.setMaxVal(255); - - if (template && template.buildDistance()) { - var minDist = template.buildDistance().MinDistance; - var category = template.buildDistance().FromCategory; - if (minDist !== undefined && category !== undefined){ - gameState.getOwnStructures().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'); - } - }); - } - } - - return map; -}; - - -m.createTerritoryMap = function(gameState) { - var map = gameState.ai.territoryMap; - - var ret = new API3.Map(gameState.sharedScript, map.data); - - ret.getOwner = function(p) { - return this.point(p) & m.TERRITORY_PLAYER_MASK; - } - ret.getOwnerIndex = function(p) { - return this.map[p] & m.TERRITORY_PLAYER_MASK; - } - return ret; -}; - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/naval-manager.js b/binaries/data/mods/public/simulation/ai/aegis/naval-manager.js deleted file mode 100644 index 5fd41f5011..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/naval-manager.js +++ /dev/null @@ -1,290 +0,0 @@ -var AEGIS = function(m) -{ - -/* 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. - */ - -m.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 -m.NavalManager.prototype.init = function(gameState, queues) { - // finished docks - this.docks = gameState.getOwnStructures().filter(API3.Filters.and(API3.Filters.byClass("Dock"), API3.Filters.not(API3.Filters.isFoundation()))); - this.docks.allowQuickIter(); - this.docks.registerUpdates(); - - this.ships = gameState.getOwnEntities().filter(API3.Filters.byClass("Ship")); - // note: those two can overlap (some transport ships are warships too and vice-versa). - this.tpShips = this.ships.filter(API3.Filters.byCanGarrison()); - this.warships = this.ships.filter(API3.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 API3.EntityCollection(gameState.sharedScript)); - this.seaTpShips.push(new API3.EntityCollection(gameState.sharedScript)); - this.seaWarships.push(new API3.EntityCollection(gameState.sharedScript)); - this.wantedTpShips.push(0); - this.wantedWarships.push(0); - } else { - var collec = this.ships.filter(API3.Filters.byStaticMetadata(PlayerID, "sea", i)); - collec.registerUpdates(); - this.seaShips.push(collec); - collec = this.tpShips.filter(API3.Filters.byStaticMetadata(PlayerID, "sea", i)); - collec.registerUpdates(); - this.seaTpShips.push(collec); - var collec = this.warships.filter(API3.Filters.byStaticMetadata(PlayerID, "sea", i)); - collec.registerUpdates(); - this.seaWarships.push(collec); - - this.wantedTpShips.push(1); - this.wantedWarships.push(1); - } - - this.landZoneDocked.push([]); - } -}; - -m.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. -m.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) - { - m.debug ("cannot reach because of " + path[i+1]); - return false; // we wn't be able to board on that sea - } - } - return true; -}; - - -m.NavalManager.prototype.checkEvents = function (gameState, queues, events) { - var evts = events["ConstructionFinished"]; - // TODO: probably check stuffs like a base destruction. - for (var i in evts) - { - var evt = evts[i]; - if (evt && evt.newentity) - { - var entity = gameState.getEntityById(evt.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); - } - } - } -}; - -m.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. -m.NavalManager.prototype.askForTransport = function(entity, startPos, endPos) { - this.askedPlans.push([entity, startPos, endPos]); -}; - -// creates aforementionned plans -m.NavalManager.prototype.createPlans = function(gameState) { - var startID = {}; - - for (var 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 m.TransportPlan(gameState, startID[i][k].units, startID[i][k].dest, false) - this.transportPlans.push (tpPlan); - } -}; - -// TODO: work on this. -m.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 m.TrainingPlan(gameState, "units/{civ}_ship_bireme", { "sea" : i }, 1, 1 )); - } - } -}; - -// bumps up the number of ships we want if we need more. -m.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 -m.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 (var ship in this.seaTpShips[zone]._entities) - { - if (!ship.getMetadata(PlayerID, "tpplan")) - { - m.debug ("Assigning ship " + ship.id() + " to plan" + plan.ID); - plan.assignShip(gameState, ship); - return true; - } - } - } - } - } - return false; -}; - -m.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 -m.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[0], rallyPos[1]); - 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(); -}; - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/plan-transport.js b/binaries/data/mods/public/simulation/ai/aegis/plan-transport.js deleted file mode 100644 index d600dd20f6..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/plan-transport.js +++ /dev/null @@ -1,479 +0,0 @@ -var AEGIS = function(m) -{ - -/* - 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? - -m.TransportPlan = function(gameState, units, destination, allAtOnce, escortSize, onlyIfOK) { - var self = this; - - this.ID = m.playerGlobals[PlayerID].uniqueIDTPlans++; - - var unitsID = []; - if (units.length !== undefined) - unitsID = units; - else - unitsID = [units]; - - this.units = m.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(); - - m.debug ("Starting a new plan with ID " + this.ID + " to " + destination); - m.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 -m.TransportPlan.prototype.countFreeSlots = function(onlyTrulyFree) -{ - var slots = 0; - this.transportShips.forEach(function (ent) { //}){ - slots += ent.garrisonMax(); - if (onlyTrulyFree) - slots -= ent.garrisoned().length; - }); - return slots; -}; - -m.TransportPlan.prototype.assignShip = function(gameState, ship) -{ - ship.setMetadata(PlayerID,"tpplan", this.ID); -} - -m.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) - }); -} - -m.TransportPlan.prototype.releaseAllShips = function(gameState) -{ - this.ships.forEach(function (ent) { ent.setMetadata(PlayerID,"tpplan", undefined) }); -} - -m.TransportPlan.prototype.needTpShips = function() -{ - if ((this.allAtOnce && this.countFreeSlots() >= this.units.length) || this.transportShips.length > 0) - return false; - return true; -} - -m.TransportPlan.prototype.needEscortShips = function() -{ - return !((this.onlyIfOK && this.escortShips.length < this.escortSize) || !this.onlyIfOK); -} - -// returns the zone for which we are needing our ships -m.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) - */ -m.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]; - - m.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]; - m.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 API3.Map(gameState.sharedScript); - - // wanted map. - var friendlyTiles = new API3.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]; - m.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; - - m.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 (API3.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 (API3.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 (var 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 API3.Map(gameState.sharedScript); - - // wanted map. - var friendlyTiles = new API3.Map(gameState.sharedScript); - - var wantedIndex = -1; - - if (this.path.length >= 3) - { - this.path.splice(0,2); - wantedIndex = this.path[0]; - } else { - m.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 (API3.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 (API3.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) - { - m.debug ("plan " + this.ID + " going back for more"); - // basically reset. - delete this.boardingSpot; - delete this.unboardingSpot; - this.state = "unstarted"; - this.releaseAllShips(); - return true; - } - m.debug ("plan " + this.ID + " is finished"); - return false; - } - } - - return true; -} - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/queue-manager.js b/binaries/data/mods/public/simulation/ai/aegis/queue-manager.js deleted file mode 100644 index a0b08b6d08..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/queue-manager.js +++ /dev/null @@ -1,560 +0,0 @@ -var AEGIS = function(m) -{ - -// This takes the input queues and picks which items to fund with resources until no more resources are left to distribute. -// -// Currently this manager keeps accounts for each queue, split between the 4 main resources -// -// Each time resources are available (ie not in any account), it is split between the different queues -// Mostly based on priority of the queue, and existing needs. -// Each turn, the queue Manager checks if a queue can afford its next item, then it does. -// -// A consequence of the system it's not really revertible. Once a queue has an account of 500 food, it'll keep it -// If for some reason the AI stops getting new food, and this queue lacks, say, wood, no other queues will -// be able to benefit form the 500 food (even if they only needed food). -// This is not to annoying as long as all goes well. If the AI loses many workers, it starts being problematic. -// -// It also has the effect of making the AI more or less always sit on a few hundreds resources since most queues -// 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. -// -// This system should be improved. It's probably not flexible enough. - -m.QueueManager = function(Config, queues, priorities) { - this.Config = Config; - this.queues = queues; - this.priorities = priorities; - this.accounts = {}; - - // the sorting is updated on priority change. - var self = this; - this.queueArrays = []; - for (var p in this.queues) { - this.accounts[p] = new API3.Resources(); - this.queueArrays.push([p,this.queues[p]]); - } - this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) }); - - this.curItemQueue = []; -}; - -m.QueueManager.prototype.getAvailableResources = function(gameState, noAccounts) { - var resources = gameState.getResources(); - if (noAccounts) - return resources; - for (var key in this.queues) { - resources.subtract(this.accounts[key]); - } - return resources; -}; - -m.QueueManager.prototype.getTotalAccountedResources = function(gameState) { - var resources = new API3.Resources(); - for (var key in this.queues) { - resources.add(this.accounts[key]); - } - return resources; -}; - -m.QueueManager.prototype.currentNeeds = function(gameState) { - var needed = new API3.Resources(); - //queueArrays because it's faster. - for (var i in this.queueArrays) - { - var name = this.queueArrays[i][0]; - var queue = this.queueArrays[i][1]; - if (queue.length() == 0 || !queue.queue[0].isGo(gameState)) - continue; - // we need resource if the account is smaller than the cost - var costs = queue.queue[0].getCost(); - for each (var ress in costs.types) - costs[ress] = Math.max(0, costs[ress] - this.accounts[name][ress]); - - needed.add(costs); - } - return needed; -}; - -m.QueueManager.prototype.futureNeeds = function(gameState) { - var needs = new API3.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 start all elements in our queues -// TODO: many things. -m.QueueManager.prototype.wantedGatherRates = function(gameState, shortTerm) { - // global rates - var rates = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 }; - // per-queue. - var qTime = gameState.getTimeElapsed(); - var time = gameState.getTimeElapsed(); - var qCosts = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 }; - - var currentRess = this.getAvailableResources(gameState); - - //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]; - - // we'll move temporally along the queue. - for (var j = 0; j < queue.length(); ++j) - { - var elem = queue.queue[j]; - var cost = elem.getCost(); - - var timeMultiplier = Math.max(1,(qTime-time)/25000); - if (shortTerm) - timeMultiplier += 0.8; - - if (!elem.isGo(gameState)) - { - // assume we'll be wanted in four minutes. - // TODO: work on this. - for (var type in qCosts) - qCosts[type] += cost[type] / timeMultiplier; - qTime += 240000; - break; // disregard other stuffs. - } - // Assume we want it in 30 seconds from current time. - // Costs are made higher based on priority and lower based on current time. - // TODO: work on this. - for (var type in qCosts) - { - if (cost[type] === 0) - continue; - qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name])) / timeMultiplier; - } - qTime += 30000; // TODO: this needs a lot more work. - } - for (var 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; -}; - -/*m.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 (var 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 (var j in array) - { - log (j + ";"); - for (var i = 0; i < this.totor.length; ++i) - { - log (this.totor[i][j] + ";"); - } - } - log(); - for (var j in array) - { - log (j + ";"); - for (var i = 0; i < this.totor.length; ++i) - { - log (this.currentGathR[i][j] + ";"); - } - } - log(); - for (var j in array) - { - log (j + ";"); - for (var i = 0; i < this.totor.length; ++i) - { - log (this.currentGathRWanted[i].indexOf(j) + ";"); - } - } - log(); - for (var j in array) - { - log (j + ";"); - for (var i = 0; i < this.totor.length; ++i) - { - log (this.ressLev[i][j] + ";"); - } - } - } -}; -*/ - -m.QueueManager.prototype.printQueues = function(gameState){ - m.debug("QUEUES"); - for (var i in this.queues){ - var qStr = ""; - var q = this.queues[i]; - if (q.queue.length > 0) - m.debug((i + ":")); - for (var j in q.queue){ - qStr = " " + q.queue[j].type + " "; - if (q.queue[j].number) - qStr += "x" + q.queue[j].number; - m.debug (qStr); - } - } - m.debug ("Accounts"); - for (var p in this.accounts) - { - m.debug(p + ": " + uneval(this.accounts[p])); - } - m.debug("Needed Resources:" + uneval(this.futureNeeds(gameState,false))); - m.debug ("Wanted Gather Rates:" + uneval(this.wantedGatherRates(gameState))); - m.debug ("Current Resources:" + uneval(gameState.getResources())); - m.debug ("Available Resources:" + uneval(this.getAvailableResources(gameState))); -}; - -// nice readable HTML version. -m.QueueManager.prototype.HTMLprintQueues = function(gameState){ - if (!m.DebugEnabled()) - return; - var strToSend = []; - strToSend.push(" Aegis Queue Manager "); - for (var i in this.queues){ - strToSend.push(""); - - var q = this.queues[i]; - var str = ""); - for (var j in q.queue) { - if (q.queue[j].isGo(gameState)) - strToSend.push(""; - strToSend.push(qStr); - } - strToSend.push(""); - } - strToSend.push("
Aegis Build Order
" + i + " (" + this.priorities[i] + ")
"; - for each (var k in this.accounts[i].types) - if(k != "population") - { - str += this.accounts[i][k] + k.substr(0,1).toUpperCase() ; - if (k != "metal") str += " / "; - } - strToSend.push(str + "
"); - else - strToSend.push(""); - - var qStr = ""; - if (q.queue[j].number) - qStr += q.queue[j].number + " "; - qStr += q.queue[j].type; - qStr += "
"; - var costs = q.queue[j].getCost(); - for each (var k in costs.types) { - qStr += costs[k] + k.substr(0,1).toUpperCase() ; - if (k != "metal") qStr += " / "; - } - qStr += "
"); - /*strToSend.push("

Accounts

"); - for (var p in this.accounts) - { - strToSend.push("

" + p + ": " + uneval(this.accounts[p]) + "

"); - }*/ - strToSend.push("

Wanted Gather Rate:" + uneval(this.wantedGatherRates(gameState)) + "

"); - strToSend.push("

Current Resources:" + uneval(gameState.getResources()) + "

"); - strToSend.push("

Available Resources:" + uneval(this.getAvailableResources(gameState)) + "

"); - strToSend.push(""); - for each (var logged in strToSend) - log(logged); -}; - -m.QueueManager.prototype.clear = function(){ - this.curItemQueue = []; - for (var i in this.queues) - this.queues[i].empty(); -}; - -m.QueueManager.prototype.update = function(gameState) { - var self = this; - - for (var i in this.priorities){ - if (!(this.priorities[i] > 0)){ - this.priorities[i] = 1; // TODO: make the Queue Manager not die when priorities are zero. - warn("QueueManager received bad priorities, please report this error: " + uneval(this.priorities)); - } - } - - Engine.ProfileStart("Queue Manager"); - - // Let's assign resources to plans that need'em - var availableRes = this.getAvailableResources(gameState); - for (var ress in availableRes) - { - if (ress === "population") - continue; - - if (availableRes[ress] > 0) - { - var totalPriority = 0; - var tempPrio = {}; - var maxNeed = {}; - // Okay so this is where it gets complicated. - // 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: - // -if a queue has enough of resource X for the 1st element, its priority is decreased (/2). - // -queues accounts are capped at "resources for the first + 80% of the next" - // 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) { - // 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 (var 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] = 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 (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 = tempPrio[j]/totalPriority * availableRes[ress]; - var maxAdd = Math.floor(Math.min(maxNeed[j], toAdd)); - this.accounts[j][ress] += maxAdd; - } - } else { - // We have no available resources, see if we can't "compact" them in one queue. - // compare queues 2 by 2, and if one with a higher priority could be completed by our amount, give it. - // TODO: this isn't perfect compression. - for (var j in this.queues) - { - var queue = this.queues[j]; - var queueCost = queue.maxAccountWanted(gameState); - if (this.queues[j].length() === 0 || this.queues[j].paused) - continue; - - for (var i in this.queues) - { - if (i === j) - continue; - var otherQueue = this.queues[i]; - if (this.priorities[i] >= this.priorities[j] || otherQueue.switched !== 0) - continue; - - for (var ress in queueCost) - { - if (this.accounts[j][ress] >= queueCost[ress]) - continue; - if (this.accounts[j][ress] + this.accounts[i][ress] >= queueCost[ress]) - { - // we would be helped by it. Check if it's worth it. - for (var otherRess in queueCost) - if (otherRess !== ress && queueCost[otherRess] + 100 >= queueCost[ress]) - continue; - var diff = Math.min(queueCost[ress] - this.accounts[j][ress],this.accounts[i][ress]); - this.accounts[j][ress] += diff; - this.accounts[i][ress] -= diff; - ++otherQueue.switched; - //warn ("switching " + ress + " from " + i + " to " + j + " in amount " + diff); - } - } - } - } - } - } - - Engine.ProfileStart("Pick items from queues"); - - //m.debug ("start"); - //m.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 API3.Resources(); - total.add(this.accounts[name]); - if (total.canAfford(item.getCost())) - { - if (item.canStart(gameState)) - { - this.accounts[name].subtract(item.getCost()); - queue.startNext(gameState); - queue.switched = 0; - } - } - } else if (queue.length() === 0) { - this.accounts[name].reset(); - queue.switched = 0; - } - } - //m.debug (uneval(this.accounts)); - - Engine.ProfileStop(); - - if (gameState.ai.playedTurn % 15 === 2) - this.HTMLprintQueues(gameState); - - Engine.ProfileStop(); -}; - -m.QueueManager.prototype.pauseQueue = function(queue, scrapAccounts) { - if (this.queues[queue]) - { - this.queues[queue].paused = true; - if (scrapAccounts) - this.accounts[queue].reset(); - } -} - -m.QueueManager.prototype.unpauseQueue = function(queue) { - if (this.queues[queue]) - this.queues[queue].paused = false; -} - -m.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; - } -} - -m.QueueManager.prototype.unpauseAll = function(but) { - for (var p in this.queues) - if (p != but) - this.queues[p].paused = false; -} - - -m.QueueManager.prototype.addQueue = function(queueName, priority) { - if (this.queues[queueName] == undefined) { - this.queues[queueName] = new m.Queue(); - this.priorities[queueName] = priority; - this.accounts[queueName] = new API3.Resources(); - - var self = this; - this.queueArrays = []; - for (var p in this.queues) - this.queueArrays.push([p,this.queues[p]]); - this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) }); - } -} -m.QueueManager.prototype.removeQueue = function(queueName) { - if (this.queues[queueName] !== undefined) { - if ( this.curItemQueue.indexOf(queueName) !== -1) { - this.curItemQueue.splice(this.curItemQueue.indexOf(queueName),1); - } - delete this.queues[queueName]; - delete this.priorities[queueName]; - delete this.accounts[queueName]; - - var self = this; - this.queueArrays = []; - for (var p in this.queues) - this.queueArrays.push([p,this.queues[p]]); - this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) }); - } -} -m.QueueManager.prototype.changePriority = function(queueName, newPriority) { - var self = this; - if (this.queues[queueName] !== undefined) - this.priorities[queueName] = newPriority; - this.queueArrays = []; - for (var p in this.queues) - this.queueArrays.push([p,this.queues[p]]); - this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) }); -} - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/queue.js b/binaries/data/mods/public/simulation/ai/aegis/queue.js deleted file mode 100644 index 8ecf1dd353..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/queue.js +++ /dev/null @@ -1,111 +0,0 @@ -var AEGIS = function(m) -{ - -/* - * Holds a list of wanted items to train or construct - */ - -m.Queue = function() { - this.queue = []; - this.paused = false; - this.switched = 0; -}; - -m.Queue.prototype.empty = function() { - this.queue = []; -}; - -m.Queue.prototype.addItem = function(plan) { - for (var i in this.queue) - { - if (plan.category === "unit" && this.queue[i].type == plan.type && this.queue[i].number + plan.number <= this.queue[i].maxMerge) - { - this.queue[i].addItem(plan.number) - return; - } - } - this.queue.push(plan); -}; - -m.Queue.prototype.getNext = function() { - if (this.queue.length > 0) { - return this.queue[0]; - } else { - return null; - } -}; - -m.Queue.prototype.startNext = function(gameState) { - if (this.queue.length > 0) { - this.queue.shift().start(gameState); - return true; - } else { - return false; - } -}; - -// 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 -m.Queue.prototype.maxAccountWanted = function(gameState) { - var cost = new API3.Resources(); - 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.4); - cost.add(costs); - } - return cost; -}; - -m.Queue.prototype.queueCost = function(){ - var cost = new API3.Resources(); - for (var key in this.queue){ - cost.add(this.queue[key].getCost()); - } - return cost; -}; - -m.Queue.prototype.length = function() { - return this.queue.length; -}; - -m.Queue.prototype.countQueuedUnits = function(){ - var count = 0; - for (var i in this.queue){ - count += this.queue[i].number; - } - return count; -}; - -m.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; - } - return count; -}; -m.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; - } - return count; -}; - -m.Queue.prototype.countAllByType = function(t){ - var count = 0; - - for (var i = 0; i < this.queue.length; i++){ - if (this.queue[i].type === t){ - count += this.queue[i].number; - } - } - return count; -}; - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/queueplan--.js b/binaries/data/mods/public/simulation/ai/aegis/queueplan--.js deleted file mode 100644 index 3ddaaa7d34..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/queueplan--.js +++ /dev/null @@ -1,71 +0,0 @@ -var AEGIS = function(m) -{ -/* - * Common functions and variables to all queue plans. - * has a "--" suffix because it needs to be loaded before the other queueplan files. - */ - -m.QueuePlan = function(gameState, type, metadata) { - this.type = gameState.applyCiv(type); - this.metadata = metadata; - - this.template = gameState.getTemplate(this.type); - if (!this.template) - { - warn ("Tried to add the inexisting tempalte " + this.type + " to Aegis. Please report thison the forums") - return false; - } - this.ID = m.playerGlobals[PlayerID].uniqueIDBOPlans++; - this.cost = new API3.Resources(this.template.cost()); - this.number = 1; - - this.category = ""; - this.lastIsGo = undefined; - - return true; -}; - -// if true, the queue manager will begin increasing this plan's account. -m.QueuePlan.prototype.isGo = function(gameState) { - return true; -}; - -// can we start this plan immediately? -m.QueuePlan.prototype.canStart = function(gameState) { - return false; -}; - -// needs to be updated if you want this to do something -m.QueuePlan.prototype.onStart = function(gameState) { -} - -// process the plan. -m.QueuePlan.prototype.start = function(gameState) { - // should call onStart. -}; - -m.QueuePlan.prototype.getCost = function() { - var costs = new API3.Resources(); - costs.add(this.cost); - if (this.number !== 1) - costs.multiply(this.number); - return costs; -}; - -// On Event functions. -// Can be used to do some specific stuffs -// Need to be updated to actually do something if you want them to. -// this is called by "Start" if it succeeds. -m.QueuePlan.prototype.onStart = function(gameState) { -} - -// This is called by "isGo()" if it becomes true while it was false. -m.QueuePlan.prototype.onGo = function(gameState) { -} - -// This is called by "isGo()" if it becomes false while it was true. -m.QueuePlan.prototype.onNotGo = function(gameState) { -} - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/queueplan-building.js b/binaries/data/mods/public/simulation/ai/aegis/queueplan-building.js deleted file mode 100644 index 6c1d706ce6..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/queueplan-building.js +++ /dev/null @@ -1,223 +0,0 @@ -var AEGIS = function(m) -{ - -// Defines a construction plan, ie a building. -// We'll try to fing a good position if non has been provided - -m.ConstructionPlan = function(gameState, type, metadata, position) { - if (!m.QueuePlan.call(this, gameState, type, metadata)) - return false; - - this.position = position ? position : 0; - - this.category = "building"; - - return true; -}; - -m.ConstructionPlan.prototype = Object.create(m.QueuePlan.prototype); - -// checks other than resource ones. -// TODO: change this. -// TODO: if there are specific requirements here, maybe try to do them? -m.ConstructionPlan.prototype.canStart = function(gameState) { - if (gameState.buildingsBuilt > 0) - return false; - - if (!this.isGo(gameState)) - 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); -}; - -m.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 - // do the building themselves - all we care about is that there is - // some unit that can start the foundation - - var pos = this.findGoodPosition(gameState); - if (!pos){ - if (this.template.hasClass("Naval")) - gameState.ai.HQ.dockFailed = true; - m.debug("No room to place " + this.type); - return; - } - if (this.template.hasClass("Naval")) - m.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, this.metadata); - } - } else { - // try with the lowest, move towards us unless we're same - if (pos.x == pos.xx && pos.z == pos.zz) - builders[0].construct(this.type, pos.x, pos.z, pos.angle, this.metadata); - else - { - for (var step = 0; step <= 1; step += 0.2) - { - builders[0].construct(this.type, (step*pos.x + (1-step)*pos.xx), (step*pos.z + (1-step)*pos.zz), pos.angle, this.metadata); - } - } - } - this.onStart(gameState); -}; - -m.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 = m.createObstructionMap(gameState,0, template); - - //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 API3.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.floor(this.position[0] / cellSize); - var z = Math.floor(this.position[1] / cellSize); - friendlyTiles.addInfluence(x, z, 255); - } else { - // No position was specified so try and find a sensible place to build - if (this.metadata && this.metadata.base !== undefined) - for each (var px in gameState.ai.HQ.baseManagers[this.metadata.base].territoryIndices) - friendlyTiles.map[px] = 20; - gameState.getOwnStructures().forEach(function(ent) { - var pos = ent.position(); - var x = Math.round(pos[0] / cellSize); - var z = Math.round(pos[1] / cellSize); - - if (template.hasClass("Field")) { - if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf("food") !== -1) - friendlyTiles.addInfluence(x, z, 20, 50); - } else if (template.hasClass("House")) { - if (ent.hasClass("House")) - { - friendlyTiles.addInfluence(x, z, 15,40); // houses are close to other houses - alreadyHasHouses = true; - } else { - friendlyTiles.addInfluence(x, z, 15, -40); // and further away from other stuffs - } - } else if (template.hasClass("Farmstead")) { - // move farmsteads away to make room. - friendlyTiles.addInfluence(x, z, 25, -25); - } else { - if (template.hasClass("GarrisonFortress") && ent.genericName() == "House") - friendlyTiles.addInfluence(x, z, 30, -50); - else if (template.hasClass("Military")) - friendlyTiles.addInfluence(x, z, 10, -40); - - // If this is not a field add a negative influence near the CivCentre because we want to leave this - // area for fields. - if (ent.hasClass("CivCentre")) - friendlyTiles.addInfluence(x, z, 20, -20); - } - }); - - if (template.hasClass("Farmstead")) - { - for (var j = 0; j < gameState.sharedScript.resourceMaps["wood"].map.length; ++j) - { - var value = friendlyTiles.map[j] - (gameState.sharedScript.resourceMaps["wood"].map[j])/3; - friendlyTiles.map[j] = value >= 0 ? value : 0; - } - } - - 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); - - // Find target building's approximate obstruction radius, and expand by a bit to make sure we're not too close, this - // allows room for units to walk between buildings. - // note: not for houses and dropsites who ought to be closer to either each other or a resource. - // also not for fields who can be stacked quite a bit - var radius = 0; - - if (template.hasClass("GarrisonFortress")) - radius = Math.floor(template.obstructionRadius() / cellSize) + 2; - else if (template.buildCategory() === "Dock") - radius = 1; - else if (template.resourceDropsiteTypes() === undefined) - radius = Math.ceil(template.obstructionRadius() / cellSize) + 1; - else - radius = Math.ceil(template.obstructionRadius() / cellSize); - - // further contract cause walls - // Note: I'm currently destroying them so that doesn't matter. - //if (gameState.playerData.civ == "iber") - // radius *= 0.95; - - // Find the best non-obstructed - if (template.hasClass("House") && !alreadyHasHouses) { - // try to get some space first - var bestTile = friendlyTiles.findBestTile(10, obstructionMap); - var bestIdx = bestTile[0]; - var bestVal = bestTile[1]; - } - - if (bestVal === undefined || bestVal === -1) { - var bestTile = friendlyTiles.findBestTile(radius, obstructionMap); - var bestIdx = bestTile[0]; - var bestVal = bestTile[1]; - } - if (bestVal === -1) { - return false; - } - - //friendlyTiles.setInfluence((bestIdx % friendlyTiles.width), Math.floor(bestIdx / friendlyTiles.width), 1, 200); - //friendlyTiles.dumpIm(template.buildCategory() + "_" +gameState.getTimeElapsed() + ".png", 200); - - var x = ((bestIdx % friendlyTiles.width) + 0.5) * cellSize; - var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * cellSize; - - if (template.hasClass("House") || template.hasClass("Field") || template.resourceDropsiteTypes() !== undefined) - var secondBest = obstructionMap.findLowestNeighbor(x,z); - else - var secondBest = [x,z]; - - // default angle - var angle = 3*Math.PI/4; - - return { - "x" : x, - "z" : z, - "angle" : angle, - "xx" : secondBest[0], - "zz" : secondBest[1] - }; -}; - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/queueplan-research.js b/binaries/data/mods/public/simulation/ai/aegis/queueplan-research.js deleted file mode 100644 index 81915fa4d0..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/queueplan-research.js +++ /dev/null @@ -1,50 +0,0 @@ -var AEGIS = function(m) -{ - -m.ResearchPlan = function(gameState, type, rush) { - if (!m.QueuePlan.call(this, gameState, type, {})) - return false; - - if (this.template.researchTime === undefined) - return false; - - this.category = "technology"; - - this.rush = rush ? true : false; - - return true; -}; - -m.ResearchPlan.prototype = Object.create(m.QueuePlan.prototype); - -m.ResearchPlan.prototype.canStart = function(gameState) { - // also checks canResearch - return (gameState.findResearchers(this.type).length !== 0); -}; - -m.ResearchPlan.prototype.start = function(gameState) { - var self = this; - - //m.debug ("Starting the research plan for " + this.type); - var trainers = gameState.findResearchers(this.type).toEntityArray(); - - //for (var i in trainers) - // warn (this.type + " - " +trainers[i].genericName()); - - // Prefer training buildings with short queues - // (TODO: this should also account for units added to the queue by - // plans that have already been executed this turn) - if (trainers.length > 0){ - trainers.sort(function(a, b) { - return (a.trainingQueueTime() - b.trainingQueueTime()); - }); - // drop anything in the queue if we rush it. - if (this.rush) - trainers[0].stopAllProduction(0.45); - trainers[0].research(this.type); - } - this.onStart(gameState); -}; - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/queueplan-training.js b/binaries/data/mods/public/simulation/ai/aegis/queueplan-training.js deleted file mode 100644 index 4b22e884c9..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/queueplan-training.js +++ /dev/null @@ -1,62 +0,0 @@ -var AEGIS = function(m) -{ - -m.TrainingPlan = function(gameState, type, metadata, number, maxMerge) { - if (!m.QueuePlan.call(this, gameState, type, metadata)) - return false; - - this.category = "unit"; - this.cost = new API3.Resources(this.template.cost(), this.template._template.Cost.Population); - - this.number = number !== undefined ? number : 1; - this.maxMerge = maxMerge !== undefined ? maxMerge : 5; - - return true; -}; - -m.TrainingPlan.prototype = Object.create(m.QueuePlan.prototype); - -m.TrainingPlan.prototype.canStart = function(gameState) { - if (this.invalidTemplate) - return false; - - // TODO: we should probably check pop caps - - var trainers = gameState.findTrainers(this.type); - - return (trainers.length != 0); -}; - -m.TrainingPlan.prototype.start = function(gameState) { - //warn("Executing TrainingPlan " + uneval(this)); - var self = this; - var trainers = gameState.findTrainers(this.type).toEntityArray(); - - // Prefer training buildings with short queues - // (TODO: this should also account for units added to the queue by - // plans that have already been executed this turn) - if (trainers.length > 0){ - trainers.sort(function(a, b) { - var aa = a.trainingQueueTime(); - var bb = b.trainingQueueTime(); - if (a.hasClass("Civic") && !self.template.hasClass("Support")) - aa += 10; - if (b.hasClass("Civic") && !self.template.hasClass("Support")) - bb += 10; - 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); - } - this.onStart(gameState); -}; - -m.TrainingPlan.prototype.addItem = function(amount) { - if (amount === undefined) - amount = 1; - this.number += amount; -}; - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/template-manager.js b/binaries/data/mods/public/simulation/ai/aegis/template-manager.js deleted file mode 100755 index affeb586c6..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/template-manager.js +++ /dev/null @@ -1,122 +0,0 @@ -var AEGIS = function(m) -{ - -/* - * Used to know which templates I have, which templates I know I can train, things like that. - * Mostly unused. - */ - -m.TemplateManager = function(gameState) { - var self = this; - - this.knownTemplatesList = []; - this.buildingTemplates = []; - this.unitTemplates = []; - this.templateCounters = {}; - this.templateCounteredBy = {}; - - // this will store templates that exist - this.AcknowledgeTemplates(gameState); - this.getBuildableSubtemplates(gameState); - this.getTrainableSubtemplates(gameState); - this.getBuildableSubtemplates(gameState); - this.getTrainableSubtemplates(gameState); - // should be enough in 100% of the cases. - - this.getTemplateCounters(gameState); - -}; -m.TemplateManager.prototype.AcknowledgeTemplates = function(gameState) -{ - var self = this; - var myEntities = gameState.getOwnEntities(); - myEntities.forEach(function(ent) { // }){ - var template = ent._templateName; - if (self.knownTemplatesList.indexOf(template) === -1) { - self.knownTemplatesList.push(template); - if (ent.hasClass("Unit") && self.unitTemplates.indexOf(template) === -1) - self.unitTemplates.push(template); - else if (self.buildingTemplates.indexOf(template) === -1) - self.buildingTemplates.push(template); - } - - }); -} -m.TemplateManager.prototype.getBuildableSubtemplates = function(gameState) -{ - for each (var templateName in this.knownTemplatesList) { - var template = gameState.getTemplate(templateName); - if (template !== null) { - var buildable = template.buildableEntities(); - if (buildable !== undefined) - for each (var subtpname in buildable) { - if (this.knownTemplatesList.indexOf(subtpname) === -1) { - this.knownTemplatesList.push(subtpname); - var subtemplate = gameState.getTemplate(subtpname); - if (subtemplate.hasClass("Unit") && this.unitTemplates.indexOf(subtpname) === -1) - this.unitTemplates.push(subtpname); - else if (this.buildingTemplates.indexOf(subtpname) === -1) - this.buildingTemplates.push(subtpname); - } - } - } - } -} -m.TemplateManager.prototype.getTrainableSubtemplates = function(gameState) -{ - for each (var templateName in this.knownTemplatesList) { - var template = gameState.getTemplate(templateName); - if (template !== null) { - var trainables = template.trainableEntities(); - if (trainables !== undefined) - for each (var subtpname in trainables) { - if (this.knownTemplatesList.indexOf(subtpname) === -1) { - this.knownTemplatesList.push(subtpname); - var subtemplate = gameState.getTemplate(subtpname); - if (subtemplate.hasClass("Unit") && this.unitTemplates.indexOf(subtpname) === -1) - this.unitTemplates.push(subtpname); - else if (this.buildingTemplates.indexOf(subtpname) === -1) - this.buildingTemplates.push(subtpname); - } - } - } - } -} -m.TemplateManager.prototype.getTemplateCounters = function(gameState) -{ - for (var i in this.unitTemplates) - { - var tp = gameState.getTemplate(this.unitTemplates[i]); - var tpname = this.unitTemplates[i]; - this.templateCounters[tpname] = tp.getCounteredClasses(); - } -} -// features auto-caching -m.TemplateManager.prototype.getCountersToClasses = function(gameState,classes,templateName) -{ - if (templateName !== undefined && this.templateCounteredBy[templateName]) - return this.templateCounteredBy[templateName]; - - var templates = []; - for (var i in this.templateCounters) { - var okay = false; - for each (var ticket in this.templateCounters[i]) { - var okaya = true; - for (var a in ticket[0]) { - if (classes.indexOf(ticket[0][a]) === -1) - okaya = false; - } - if (okaya && templates.indexOf(i) === -1) - templates.push([i, ticket[1]]); - } - } - templates.sort (function (a,b) { return -a[1] + b[1]; }); - - if (templateName !== undefined) - this.templateCounteredBy[templateName] = templates; - return templates; -} - - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/timer.js b/binaries/data/mods/public/simulation/ai/aegis/timer.js deleted file mode 100644 index 2d6e9739c7..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/timer.js +++ /dev/null @@ -1,112 +0,0 @@ -var AEGIS = function(m) -{ - -//The Timer class // The instance of this class is created in the qBot object under the name 'timer' -//The methods that are available to call from this instance are: -//timer.setTimer : Creates a new timer with the given interval (miliseconds). -// Optionally set dalay or a limited repeat value. -//timer.checkTimer : Gives true if called at the time of the interval. -//timer.clearTimer : Deletes the timer permanently. No way to get the same timer back. -//timer.activateTimer : Sets the status of a deactivated timer to active. -//timer.deactivateTimer : Deactivates a timer. Deactivated timers will never give true. - -// Currently totally unused, iirc. - - -//-EmjeR-// Timer class // -m.Timer = function() { - ///Private array. - var alarmList = []; - - ///Private methods - function num_alarms() { - return alarmList.length; - }; - - function get_alarm(id) { - return alarmList[id]; - }; - - function add_alarm(index, alarm) { - alarmList[index] = alarm; - }; - - function delete_alarm(id) { - // Set the array element to undefined - delete alarmList[id]; - }; - - ///Privileged methods - // Add an new alarm to the list - this.setTimer = function(gameState, interval, delay, repeat) { - delay = delay || 0; - repeat = repeat || -1; - - var index = num_alarms(); - - //Add a new alarm to the list - add_alarm(index, new alarm(gameState, index, interval, delay, repeat)); - return index; - }; - - - // Check if a alarm has reached its interval. - this.checkTimer = function(gameState,id) { - var alarm = get_alarm(id); - if (alarm === undefined) - return false; - if (!alarm.active) - return false; - var time = gameState.getTimeElapsed(); - var alarmState = false; - - // If repeat forever (repeat is -1). Or if the alarm has rung less times than repeat. - if (alarm.repeat < 0 || alarm.counter < alarm.repeat) { - var time_diffrence = time - alarm.start_time - alarm.delay - alarm.interval * alarm.counter; - if (time_diffrence > alarm.interval) { - alarmState = true; - alarm.counter++; - } - } - - // Check if the alarm has rung 'alarm.repeat' times if so, delete the alarm. - if (alarm.counter >= alarm.repeat && alarm.repeat != -1) { - this.clearTimer(id); - } - - return alarmState; - }; - - // Remove an alarm from the list. - this.clearTimer = function(id) { - delete_alarm(id); - }; - - // Activate a deactivated alarm. - this.activateTimer = function(id) { - var alarm = get_alarm(id); - alarm.active = true; - }; - - // Deactivate an active alarm but don't delete it. - this.deactivateTimer = function(id) { - var alarm = get_alarm(id); - alarm.active = false; - }; -}; - - -//-EmjeR-// Alarm class // -m.alarm = function(gameState, id, interval, delay, repeat) { - this.id = id; - this.interval = interval; - this.delay = delay; - this.repeat = repeat; - - this.start_time = gameState.getTimeElapsed(); - this.active = true; - this.counter = 0; -}; - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/utils-extend.js b/binaries/data/mods/public/simulation/ai/aegis/utils-extend.js deleted file mode 100755 index 854363590d..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/utils-extend.js +++ /dev/null @@ -1,32 +0,0 @@ -var AEGIS = function(m) -{ - -m.AssocArraytoArray = function(assocArray) { - var endArray = []; - for (var i in assocArray) - endArray.push(assocArray[i]); - return endArray; -}; - -// A is the reference, B must be in "range" of A -// this supposes the range is already squared -m.inRange = function(a, b, range)// checks for X distance -{ - // will avoid unnecessary checking for position in some rare cases... I'm lazy - if (a === undefined || b === undefined || range === undefined) - return undefined; - - var dx = a[0] - b[0]; - var dz = a[1] - b[1]; - return ((dx*dx + dz*dz ) < range); -} -// slower than SquareVectorDistance, faster than VectorDistance but not exactly accurate. -m.ManhattanDistance = function(a, b) -{ - var dx = a[0] - b[0]; - var dz = a[1] - b[1]; - return Math.abs(dx) + Math.abs(dz); -} - -return m; -}(AEGIS); diff --git a/binaries/data/mods/public/simulation/ai/aegis/worker.js b/binaries/data/mods/public/simulation/ai/aegis/worker.js deleted file mode 100644 index dbb06db172..0000000000 --- a/binaries/data/mods/public/simulation/ai/aegis/worker.js +++ /dev/null @@ -1,643 +0,0 @@ -var AEGIS = function(m) -{ - -/** - * This class makes a worker do as instructed by the economy manager - */ - -m.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; -}; - -m.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)){ - // If the worker has no position then no work can be done - return; - } - 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.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.unsatisfactoryResource = false; - this.startGathering(baseManager,gameState); - Engine.ProfileStop(); - - this.startApproachingResourceTime = gameState.getTimeElapsed(); - - } else { - // Should deposit resources - Engine.ProfileStart("Return Resources"); - if (!this.returnResources(gameState)) - { - // no dropsite, abandon cargo. - - // if we were ordered to gather something else, try that. - if (this.ent.resourceCarrying()[0].type !== this.ent.getMetadata(PlayerID, "gather-type")) - 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(); - } - } - Engine.ProfileStop(); - } - // 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") { - - // 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"]); - m.debug ("here " + this.startApproachingResourceAmount + "," + ent.resourceSupplyAmount()); - if (ent && this.startApproachingResourceAmount == ent.resourceSupplyAmount() && this.startEnt == ent.id()) { - m.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(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); - if ((ent && ent.resourceSupplyAmount() == ent.resourceSupplyMax())) { - // if someone gathers from it, it's only that the pathfinder sucks. - m.debug (ent.toString() + " is inaccessible"); - ent.setMetadata(PlayerID, "inaccessible", true); - this.ent.flee(ent); - this.ent.setMetadata(PlayerID, "subrole", "idle"); - 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); - if (ent && !ent.isHurt()) { - // if someone gathers from it, it's only that the pathfinder sucks. - m.debug (ent.toString() + " is inaccessible from Combat"); - ent.setMetadata(PlayerID, "inaccessible", true); - this.ent.flee(ent); - this.ent.setMetadata(PlayerID, "subrole", "idle"); - this.gatheringFrom = undefined; - } - }*/ - } - } else if(subrole === "builder") { - - // check for transport. - /*if (gameState.ai.playedTurn % 5 === 0) - { - if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[2] && this.ent.unitAIState().split(".")[2] === "APPROACHING" && this.ent.unitAIOrderData()[0]["target"]) - { - var ress = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]); - if (ress !== undefined) - { - 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.isIdle()){ - Engine.ProfileStart("Start Hunting"); - this.startHunting(gameState, baseManager); - Engine.ProfileStop(); - } - } -}; - -// 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. -m.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; -}; - -m.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; - } - - /* Procedure for gathering resources - * Basically the AI focuses a lot on dropsites, and we will too - * So what we want is trying to find the best dropsites - * Traits: it needs to have a lot of resources available - * -it needs to have room available for me - * -it needs to be as close as possible (meaning base) - * Once we've found the best dropsite, we'll just pick a random close resource. - * TODO: we probably could pick something better. - */ - - var wantedDropsite = 0; - var wantedDropsiteCoeff = 0; - - var forceFaraway = false; - - // So for now what I'll do is that I'll check dropsites in my base, then in other bases. - - for (var id in baseManager.dropsites) - { - var dropsiteInfo = baseManager.dropsites[id][resource]; - var capacity = baseManager.getDpWorkerCapacity(gameState, id, resource); - if (capacity === undefined) // presumably we're not ready - continue; - if (dropsiteInfo) - { - var coeff = dropsiteInfo[3] + (capacity-dropsiteInfo[5].length)*1000; - if (gameState.getEntityById(id).hasClass("CivilCentre")) - coeff += 20000; - if (coeff > wantedDropsiteCoeff) - { - wantedDropsiteCoeff = coeff; - wantedDropsite = id; - } - } - } - - if (wantedDropsite === 0) - { - for (var id in baseManager.dropsites) - { - var dropsiteInfo = baseManager.dropsites[id][resource]; - var capacity = baseManager.getDpWorkerCapacity(gameState, id, resource, true); - if (capacity === undefined) // presumably we're not ready - continue; - if (dropsiteInfo) - { - var coeff = dropsiteInfo[4] + (capacity-dropsiteInfo[5].length)*100; - if (coeff > wantedDropsiteCoeff) - { - wantedDropsiteCoeff = coeff; - wantedDropsite = id; - forceFaraway = true; - } - } - } - } - // so if we're here we have checked our whole base for a proper dropsite. - // for food, try to build fields if there are any. - if (wantedDropsiteCoeff < 200 && resource === "food" && this.buildAnyField(gameState)) - return; - - // haven't found any, check in other bases - // TODO: we should pick closest/most accessible bases first. - if (wantedDropsiteCoeff < 200) - { - for each (var base in gameState.ai.HQ.baseManagers) - { - // TODO: check we can access that base, and/or pick the best base. - if (base.ID === this.baseID || wantedDropsite !== 0) - continue; - for (var id in base.dropsites) - { - // if we have at least 1000 resources (including faraway) on this d - var dropsiteInfo = base.dropsites[id][resource]; - var capacity = base.getDpWorkerCapacity(gameState, id, resource); - if (capacity === undefined) // presumably we're not ready - continue; - if (dropsiteInfo && dropsiteInfo[3] > 600 && dropsiteInfo[5].length < capacity) - { - // we want to change bases. - this.ent.setMetadata(PlayerID,"base",base.ID); - wantedDropsite = id; - break; - } - } - } - } - - // I know, this is horrible code repetition. - // TODO: avoid horrible code repetition - - // haven't found any, check in other bases - // TODO: we should pick closest/most accessible bases first. - if (wantedDropsiteCoeff < 200) - { - for each (var base in gameState.ai.HQ.baseManagers) - { - if (base.ID === this.baseID || wantedDropsite !== 0) - continue; - for (var id in base.dropsites) - { - // if we have at least 1000 resources (including faraway) on this d - var dropsiteInfo = base.dropsites[id][resource]; - var capacity = baseManager.getDpWorkerCapacity(gameState, id, resource, true); - if (capacity === undefined) // presumably we're not ready - continue; - if (dropsiteInfo && dropsiteInfo[4] > 600 && dropsiteInfo[5].length < capacity) - { - this.ent.setMetadata(PlayerID,"base",base.ID); - wantedDropsite = id; - break; // here I'll break, TODO. - } - } - } - } - - if (wantedDropsite === 0) - { - //TODO: something. - // Okay so we haven't found any appropriate dropsite anywhere. - m.debug("No proper dropsite found for " + resource + ", waiting."); - return; - } else - this.pickResourceNearDropsite(gameState, resource, wantedDropsite, forceFaraway); -}; - - -m.Worker.prototype.pickResourceNearDropsite = function(gameState, resource, dropsiteID, forceFaraway) -{ - // get the entity. - var dropsite = gameState.getEntityById(dropsiteID); - - if (!dropsite) - return false; - - // get the dropsite info - var baseManager = this.ent.getMetadata(PlayerID,"base"); - baseManager = gameState.ai.HQ.baseManagers[baseManager]; - - var dropsiteInfo = baseManager.dropsites[dropsiteID][resource]; - if (!dropsiteInfo) - return false; - - var faraway = (forceFaraway === true) ? true : false; - - var capacity = baseManager.getDpWorkerCapacity(gameState, dropsiteID, resource, faraway); - if (dropsiteInfo[5].length >= capacity || dropsiteInfo[3] < 200) - faraway = true; - - var resources = (faraway === true) ? dropsiteInfo[1] : dropsiteInfo[0]; - - var wantedSupply = 0; - var wantedSupplyCoeff = Math.min(); - - // Pick the best resource - resources.forEach(function(supply) { - if (!supply.position()) - return; - - if (supply.getMetadata(PlayerID, "inaccessible") === true) - return; - - if (m.IsSupplyFull(gameState, supply) === true) - return; - - // TODO: make a quick accessibility check for sanity -/* if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position())) { - //m.debug ("nopath"); - return; - }*/ - - // some simple check for chickens: if they're in a square that's inaccessible, we won't gather from them. - // TODO: make sure this works with rounding. - if (supply.footprintRadius() < 1) - { - var fakeMap = new API3.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]) ) - { - supply.setMetadata(PlayerID, "inaccessible", true) - return; - } - } - - // Factor in distance to the dropsite. - var dist = API3.SquareVectorDistance(supply.position(), dropsite.position()); - - var territoryOwner = m.createTerritoryMap(gameState).getOwner(supply.position()); - if (territoryOwner != PlayerID && territoryOwner != 0) { - dist *= 5.0; - } else if (dist < 40000 && supply.resourceSupplyType().generic == "treasure"){ - // go for treasures if they're not in enemy territory - dist /= 1000; - } - - if (dist < wantedSupplyCoeff) { - wantedSupplyCoeff = dist; - wantedSupply = supply; - } - }); - - if (!wantedSupply) - { - if (resource === "food" && this.buildAnyField(gameState)) - return true; - - //m.debug("Found a proper dropsite for " + resource + " but apparently no resources are available."); - return false; - } - - var pos = wantedSupply.position(); - - // add the worker to the turn cache - m.AddTCGatherer(gameState,wantedSupply.id()); - // helper to check if it's accessible. - this.maxApproachTime = Math.max(30000, API3.VectorDistance(pos,this.ent.position()) * 5000); - this.startApproachingResourceAmount = wantedSupply.resourceSupplyAmount(); - // helper for unsatisfactory resource. - this.startEnt = wantedSupply.id(); - this.ent.gather(wantedSupply); - // sanity. - this.ent.setMetadata(PlayerID, "linked-to-dropsite", dropsiteID); - this.ent.setMetadata(PlayerID, "target-foundation", undefined); - return true; -}; - -// Makes the worker deposit the currently carried resources at the closest dropsite -m.Worker.prototype.returnResources = function(gameState){ - if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0){ - return true; // assume we're OK. - } - var resource = this.ent.resourceCarrying()[0].type; - var self = this; - - if (!this.ent.position()){ - // TODO: work out what to do when entity has no position - return true; - } - - var closestDropsite = undefined; - var dist = Math.min(); - gameState.getOwnDropsites(resource).forEach(function(dropsite){ - if (dropsite.position()){ - var d = API3.SquareVectorDistance(self.ent.position(), dropsite.position()); - if (d < dist){ - dist = d; - closestDropsite = dropsite; - } - } - }); - - if (!closestDropsite){ - m.debug("No dropsite found to deposit " + resource); - return false; - } - - this.ent.returnResources(closestDropsite); - return true; -}; - -m.Worker.prototype.startHunting = function(gameState, baseManager){ - var ent = this.ent; - - if (!ent.position() || !baseManager.isHunting) - return; - - // So here we're doing it basic. We check what we can hunt, we hunt it. No fancies. - - var resources = gameState.getResourceSupplies("food"); - - if (resources.length === 0){ - m.debug("No food found to hunt!"); - return; - } - - var supplies = []; - var nearestSupplyDist = Math.min(); - var nearestSupply = undefined; - - resources.forEach(function(supply) { //}){ - if (!supply.position()) - return; - - if (supply.getMetadata(PlayerID, "inaccessible") === true) - return; - - if (supply.isFull() === true) - return; - - if (!supply.hasClass("Animal")) - return; - - // measure the distance to the resource - var dist = API3.SquareVectorDistance(supply.position(), ent.position()); - - var territoryOwner = m.createTerritoryMap(gameState).getOwner(supply.position()); - if (territoryOwner != PlayerID && territoryOwner != 0) { - dist *= 3.0; - } - - // quickscope accessbility check - if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position(),false, true)) - return; - - if (dist < nearestSupplyDist) { - nearestSupplyDist = dist; - nearestSupply = supply; - } - }); - - if (nearestSupply) { - var pos = nearestSupply.position(); - - var nearestDropsite = 0; - var minDropsiteDist = 1000000; - // find a fitting dropsites in case we haven't already. - gameState.getOwnDropsites("food").forEach(function (dropsite){ //}){ - if (dropsite.position()){ - var dist = API3.SquareVectorDistance(pos, dropsite.position()); - if (dist < minDropsiteDist){ - minDropsiteDist = dist; - nearestDropsite = dropsite; - } - } - }); - if (!nearestDropsite) - { - baseManager.isHunting = false; - ent.setMetadata(PlayerID, "role", undefined); - m.debug ("No dropsite for hunting food"); - return; - } - if (minDropsiteDist > 35000) { - baseManager.isHunting = false; - ent.setMetadata(PlayerID, "role", undefined); - } else { - ent.gather(nearestSupply); - ent.setMetadata(PlayerID, "target-foundation", undefined); - } - } else { - baseManager.isHunting = false; - ent.setMetadata(PlayerID, "role", undefined); - m.debug("No food found for hunting! (2)"); - } -}; - -m.Worker.prototype.getResourceType = function(type){ - if (!type || !type.generic){ - return undefined; - } - - if (type.generic === "treasure"){ - return type.specific; - }else{ - return type.generic; - } -}; - -m.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; - //m.debug (+rates[tstring] + " for " + tstring + " for " + this.ent._templateName); - if (rates[tstring]) - return rates[tstring]; - return 0; - } - return 0; -}; - -m.Worker.prototype.buildAnyField = function(gameState){ - var self = this; - var foundations = gameState.getOwnFoundations(); - var baseFoundations = foundations.filter(API3.Filters.byMetadata(PlayerID,"base",this.baseID)); - - var maxGatherers = gameState.getTemplate(gameState.applyCiv("structures/{civ}_field")).maxGatherers(); - - var bestFarmEnt = undefined; - var bestFarmCoeff = 10000000; - baseFoundations.forEach(function (found) { - if (found.hasClass("Field")) { - var coeff = API3.SquareVectorDistance(found.position(), self.ent.position()); - if (found.getBuildersNb() && found.getBuildersNb() >= maxGatherers) - return; - if (coeff <= bestFarmCoeff) - { - bestFarmEnt = found; - bestFarmCoeff = coeff; - } - } - }); - if (bestFarmEnt !== undefined) - { - self.ent.repair(bestFarmEnt); - return true; - } - foundations.forEach(function (found) { - if (found.hasClass("Field")) { - var coeff = API3.SquareVectorDistance(found.position(), self.ent.position()); - if (found.getBuildersNb() && found.getBuildersNb() >= found.maxGatherers()) - return; - if (coeff <= bestFarmCoeff) - { - bestFarmEnt = found; - bestFarmCoeff = coeff; - } - } - }); - if (bestFarmEnt !== undefined) - { - self.ent.repair(bestFarmEnt); - self.ent.setMetadata(PlayerID,"base", bestFarmEnt.getMetadata(PlayerID,"base")); - this.startEnt = bestFarmEnt.id(); - self.ent.gather(bestFarmEnt,true); - self.ent.setMetadata(PlayerID, "target-foundation", undefined); - return true; - } - return false; -}; - -return m; -}(AEGIS);