This Trac instance is not used for development anymore!

We migrated our development workflow to git and Gitea.
To test the future redirection, replace trac by ariadne in the page URL.

Changeset 13907 for ps


Ignore:
Timestamp:
09/29/13 15:32:52 (11 years ago)
Author:
wraitii
Message:

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

NEEDS TESTING.

Location:
ps/trunk/binaries/data/mods/public/simulation/ai
Files:
7 added
5 deleted
19 edited

Legend:

Unmodified
Added
Removed
  • ps/trunk/binaries/data/mods/public/simulation/ai/aegis/aegis.js

    r13683 r13907  
    1 function QBotAI(settings) {
     1// "local" global variables for stuffs that will need a unique ID
     2// Note that since order of loading is alphabetic, this means this file must go before any other file using them.
     3var uniqueIDBOPlans = 0;    // training/building/research plans
     4var uniqueIDBases = 1;  // base manager ID. Starts at one because "0" means "no base" on the map
     5var uniqueIDTPlans = 1; // transport plans. starts at 1 because 0 might be used as none.
     6
     7function AegisBot(settings) {
    28    BaseAI.call(this, settings);
    39
     
    713
    814    this.playedTurn = 0;
    9 
    10     this.modules = {
    11             "economy": new EconomyManager(),
    12             "military": new MilitaryAttackManager()
    13     };
     15       
     16    this.priorities = Config.priorities;
    1417
    1518    // this.queues can only be modified by the queue manager or things will go awry.
    16     this.queues = {
    17         house : new Queue(),
    18         citizenSoldier : new Queue(),
    19         villager : new Queue(),
    20         economicBuilding : new Queue(),
    21         dropsites : new Queue(),
    22         field : new Queue(),
    23         militaryBuilding : new Queue(),
    24         defenceBuilding : new Queue(),
    25         civilCentre: new Queue(),
    26         majorTech: new Queue(),
    27         minorTech: new Queue()
    28     };
    29    
    30     this.productionQueues = [];
    31    
    32     this.priorities = Config.priorities;
    33    
     19    this.queues = {};
     20    for (i in this.priorities)
     21        this.queues[i] = new Queue();
     22
    3423    this.queueManager = new QueueManager(this.queues, this.priorities);
    35    
     24
     25    this.HQ = new HQ();
     26
    3627    this.firstTime = true;
    3728
    3829    this.savedEvents = [];
    39    
    40     this.waterMap = false;
    4130   
    4231    this.defcon = 5;
     
    4433}
    4534
    46 QBotAI.prototype = new BaseAI();
    47 
    48 // Bit of a hack: I run the pathfinder early, before the map apears, to avoid a sometimes substantial lag right at the start.
    49 QBotAI.prototype.InitShared = function(gameState, sharedScript) {
     35AegisBot.prototype = new BaseAI();
     36
     37AegisBot.prototype.InitShared = function(gameState, sharedScript) {
     38   
     39    this.HQ.init(gameState,sharedScript.events,this.queues);
     40    debug ("Initialized with the difficulty " + Config.difficulty);
     41
    5042    var ents = gameState.getEntities().filter(Filters.byOwner(PlayerID));
    5143    var myKeyEntities = ents.filter(function(ent) {
     
    6456    }
    6557
     58    this.myIndex = this.accessibility.getAccessValue(myKeyEntities.toEntityArray()[0].position());
     59   
    6660    this.pathFinder = new aStarPath(gameState, false, true);
    6761    this.pathsToMe = [];
     
    8377
    8478    this.pathInfo.angle += Math.PI/3.0;
    85 }
    86 
    87 //Some modules need the gameState to fully initialise
    88 QBotAI.prototype.runInit = function(gameState, events){
    8979
    9080    this.chooseRandomStrategy();
    91 
    92     for (var i in this.modules){
    93         if (this.modules[i].init){
    94             this.modules[i].init(gameState, events);
    95         }
    96     }
    97     debug ("Inited, diff is " + Config.difficulty);
    98     this.timer = new Timer();
    99    
    100    
    101     var ents = gameState.getOwnEntities();
    102     var myKeyEntities = gameState.getOwnEntities().filter(function(ent) {
    103                                                           return ent.hasClass("CivCentre");
    104                                                           });
    105    
    106     if (myKeyEntities.length == 0){
    107         myKeyEntities = gameState.getOwnEntities();
    108     }
    109    
    110     // disband the walls themselves
    111     if (gameState.playerData.civ == "iber") {
    112         gameState.getOwnEntities().filter(function(ent) { //}){
    113                                           if (ent.hasClass("StoneWall") && !ent.hasClass("Tower"))
    114                                           ent.destroy();
    115                                           });
    116     }
    117    
    118     var filter = Filters.byClass("CivCentre");
    119     var enemyKeyEntities = gameState.getEnemyEntities().filter(filter);
    120    
    121     if (enemyKeyEntities.length == 0){
    122         enemyKeyEntities = gameState.getEnemyEntities();
    123     }
    124    
    125     //this.accessibility = new Accessibility(gameState, myKeyEntities.toEntityArray()[0].position());
    126    
    127     this.myIndex = this.accessibility.getAccessValue(myKeyEntities.toEntityArray()[0].position());
    128    
    129     if (enemyKeyEntities.length == 0)
    130         return;
    131        
    132     this.templateManager = new TemplateManager(gameState);
    133 };
    134 
    135 QBotAI.prototype.OnUpdate = function(sharedScript) {
     81}
     82
     83AegisBot.prototype.OnUpdate = function(sharedScript) {
    13684    if (this.gameFinished){
    13785        return;
    13886    }
    13987   
    140     if (this.events.length > 0){
     88    if (this.events.length > 0 && this.turn !== 0){
    14189        this.savedEvents = this.savedEvents.concat(this.events);
    14290    }
    14391   
    14492   
    145    
    14693    // Run the update every n turns, offset depending on player ID to balance the load
    147     // this also means that init at turn 0 always happen and is never run in parallel to the first played turn so I use an else if.
    148     if (this.turn == 0) {
    149        
    150         //Engine.DumpImage("terrain.png", this.accessibility.map, this.accessibility.width,this.accessibility.height,255)
    151         //Engine.DumpImage("Access.png", this.accessibility.passMap, this.accessibility.width,this.accessibility.height,this.accessibility.regionID+1)
    152 
    153         var gameState = sharedScript.gameState[PlayerID];
    154         gameState.ai = this;
    155        
    156         this.runInit(gameState, this.savedEvents);
    157        
    158         // Delete creation events
    159         delete this.savedEvents;
    160         this.savedEvents = [];
    161     } else if ((this.turn + this.player) % 8 == 5) {
    162        
    163         Engine.ProfileStart("Aegis bot");
     94    if ((this.turn + this.player) % 8 == 5) {
     95       
     96        Engine.ProfileStart("Aegis bot (player " + this.player +")");
    16497       
    16598        this.playedTurn++;
    16699       
    167         var gameState = sharedScript.gameState[PlayerID];
    168         gameState.ai = this;
    169        
    170         if (gameState.getOwnEntities().length === 0){
     100        if (this.gameState.getOwnEntities().length === 0){
    171101            Engine.ProfileStop();
    172102            return; // With no entities to control the AI cannot do anything
     
    176106        {
    177107            var pos = [this.pathInfo.mkeyPos[0] + 150*Math.cos(this.pathInfo.angle),this.pathInfo.mkeyPos[1] + 150*Math.sin(this.pathInfo.angle)];
    178             var path = this.pathFinder.getPath(this.pathInfo.ekeyPos, pos, 6, 5);// uncomment for debug:*/, 300000, gameState);
     108            var path = this.pathFinder.getPath(this.pathInfo.ekeyPos, pos, 6, 5);// uncomment for debug:*/, 300000, this.gameState);
    179109            if (path !== undefined && path[1] !== undefined && path[1] == false) {
    180110                // path is viable and doesn't require boating.
     
    192122                {
    193123                    debug ("Assuming this is a water map");
    194                     this.waterMap = true;
     124                    this.HQ.waterMap = true;
    195125                }
    196126                delete this.pathFinder;
     
    199129        }
    200130       
    201         var sfx = "_generic";
    202         if (gameState.civ() == "athen")
    203             sfx = "_athen"
    204 
     131        var townPhase = this.gameState.townPhase();
     132        var cityPhase = this.gameState.cityPhase();
    205133        // try going up phases.
    206         if (gameState.canResearch("phase_town" + sfx,true) && gameState.getTimeElapsed() > (Config.Economy.townPhase*1000)
    207             && gameState.findResearchers("phase_town" + sfx,true).length != 0 && this.queues.majorTech.totalLength() === 0) {
    208             this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_town" + sfx,true));    // we rush the town phase.
     134        // TODO: softcode this.
     135        if (this.gameState.canResearch(townPhase,true) && this.gameState.getTimeElapsed() > (Config.Economy.townPhase*1000) && this.gameState.getPopulation() > 40
     136            && this.gameState.findResearchers(townPhase,true).length != 0 && this.queues.majorTech.length() === 0
     137            && this.gameState.getOwnEntities().filter(Filters.byClass("Village")).length > 5)
     138        {
     139            this.queueManager.pauseQueue("villager", true);
     140            this.queueManager.pauseQueue("citizenSoldier", true);
     141            this.queueManager.pauseQueue("house", true);
     142            this.queues.majorTech.addItem(new ResearchPlan(this.gameState, townPhase,0,-1,true));   // we rush the town phase.
    209143            debug ("Trying to reach town phase");
    210             var nb = gameState.getOwnEntities().filter(Filters.byClass("Village")).length-1;
    211             if (nb < 5)
    212             {
    213                 while (nb < 5 && ++nb)
    214                     this.queues.house.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_house"));
    215             }
    216         } else if (gameState.canResearch("phase_city_generic",true) && gameState.getTimeElapsed() > (Config.Economy.cityPhase*1000)
    217                 && gameState.getOwnEntitiesByRole("worker").length > 85
    218                 && gameState.findResearchers("phase_city_generic", true).length != 0 && this.queues.majorTech.totalLength() === 0) {
     144        }
     145        else if (this.gameState.canResearch(cityPhase,true) && this.gameState.getTimeElapsed() > (Config.Economy.cityPhase*1000)
     146                && this.gameState.getOwnEntitiesByRole("worker").length > 85
     147                && this.gameState.findResearchers(cityPhase, true).length != 0 && this.queues.majorTech.length() === 0) {
    219148            debug ("Trying to reach city phase");
    220             this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_city_generic"));
     149            this.queues.majorTech.addItem(new ResearchPlan(this.gameState, cityPhase));
    221150        }
    222151        // defcon cooldown
    223         if (this.defcon < 5 && gameState.timeSinceDefconChange() > 20000)
     152        if (this.defcon < 5 && this.gameState.timeSinceDefconChange() > 20000)
    224153        {
    225154            this.defcon++;
    226155            debug ("updefconing to " +this.defcon);
    227             if (this.defcon >= 4 && this.modules.military.hasGarrisonedFemales)
    228                 this.modules.military.ungarrisonAll(gameState);
    229         }
    230        
    231         for (var i in this.modules){
    232             this.modules[i].update(gameState, this.queues, this.savedEvents);
    233         }
    234        
    235         this.queueManager.update(gameState);
    236        
     156            if (this.defcon >= 4 && this.HQ.hasGarrisonedFemales)
     157                this.HQ.ungarrisonAll(this.gameState);
     158        }
     159       
     160        this.HQ.update(this.gameState, this.queues, this.savedEvents);
     161
     162        this.queueManager.update(this.gameState);
     163
    237164        /*
    238165         // Use this to debug informations about the metadata.
     
    240167        {
    241168            // some debug informations about units.
    242             var units = gameState.getOwnEntities();
     169            var units = this.gameState.getOwnEntities();
    243170            for (var i in units._entities)
    244171            {
     
    266193           
    267194        //if (this.playedTurn % 5 === 0)
    268         //  this.queueManager.printQueues(gameState);
     195        //  this.queueManager.printQueues(this.gameState);
    269196       
    270197        // Generate some entropy in the random numbers (against humans) until the engine gets random initialised numbers
     
    284211};
    285212
    286 QBotAI.prototype.chooseRandomStrategy = function()
     213AegisBot.prototype.chooseRandomStrategy = function()
    287214{
    288215    // deactivated for now.
     
    293220        this.strategy = "rush";
    294221        // going to rush.
    295         this.modules.economy.targetNumWorkers = 0;
     222        this.HQ.targetNumWorkers = 0;
    296223        Config.Economy.townPhase = 480;
    297224        Config.Economy.cityPhase = 900;
     
    303230// TODO: Remove override when the whole AI state is serialised
    304231// TODO: this currently is very much equivalent to "rungamestateinit" with a few hacks. Should deserialize/serialize properly someday.
    305 QBotAI.prototype.Deserialize = function(data, sharedScript)
     232AegisBot.prototype.Deserialize = function(data, sharedScript)
    306233{
    307234    BaseAI.prototype.Deserialize.call(this, data);
     
    346273
    347274// Override the default serializer
    348 QBotAI.prototype.Serialize = function()
     275AegisBot.prototype.Serialize = function()
    349276{
    350277    //var ret = BaseAI.prototype.Serialize.call(this);
  • ps/trunk/binaries/data/mods/public/simulation/ai/aegis/attack_plan.js

    r13761 r13907  
    77 */
    88
    9 function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , targetFinder) {
     9function CityAttack(gameState, HQ, uniqueID, targetEnemy, type , targetFinder) {
    1010   
    1111    //This is the list of IDs of the units in the plan
     
    331331// Three returns possible: 1 is "keep going", 0 is "failed plan", 2 is "start"
    332332// 3 is a special case: no valid path returned. Right now I stop attacking alltogether.
    333 CityAttack.prototype.updatePreparation = function(gameState, militaryManager,events) {
     333CityAttack.prototype.updatePreparation = function(gameState, HQ,events) {
    334334    var self = this;
    335335   
     
    338338        if (this.target == undefined)
    339339        {
    340             var targets = this.targetFinder(gameState, militaryManager);
     340            var targets = this.targetFinder(gameState, HQ);
    341341            if (targets.length === 0)
    342                 targets = this.defaultTargetFinder(gameState, militaryManager);
     342                targets = this.defaultTargetFinder(gameState, HQ);
    343343               
    344344            if (targets.length !== 0) {
     
    366366        // It will probably carry over a few turns but that's no issue.
    367367        if (this.path === undefined)
    368             this.path = this.pathFinder.getPath(this.rallyPoint,this.targetPos, this.pathSampling, this.pathWidth,250);//,gameState);
     368            this.path = this.pathFinder.getPath(this.rallyPoint,this.targetPos, this.pathSampling, this.pathWidth,175);//,gameState);
    369369        else if (this.path === "toBeContinued")
    370370            this.path = this.pathFinder.continuePath();//gameState);
     
    384384            // okay so we need a ship.
    385385            // Basically we'll add it as a new class to train compulsorily, and we'll recompute our path.
    386             if (!gameState.ai.waterMap)
     386            if (!gameState.ai.HQ.waterMap)
    387387            {
    388388                debug ("This is actually a water map.");
    389                 gameState.ai.waterMap = true;
     389                gameState.ai.HQ.waterMap = true;
     390                return 0;
    390391            }
    391392            debug ("We need a ship.");
    392             var stat = { "priority" : 1.1, "minSize" : 2, "targetSize" : 2, "batchSize" : 1, "classes" : ["Warship"],
    393                 "interests" : [ ["strength",1], ["cost",1] ]  ,"templates" : [] };
    394             if (type === "superSized") {
    395                 this.unitStat["TransportShip"]["minSize"] = 4;
    396                 this.unitStat["TransportShip"]["targetSize"] = 4;
    397             }
    398             this.addBuildOrder(gameState, "TransportShip", stat);
    399393            this.needsShip = true;
    400394            this.pathWidth = 3;
     
    415409                    else
    416410                        this.rallyPoint = this.path[0][0];
     411                    if (i >= 1)
     412                        this.path.splice(0,i-1);
    417413                    break;
    418414                }
     
    474470        // We still have time left to recruit units and do stuffs.
    475471       
    476         // TODO: check why this can happen instead of resorting to this "hack".
    477         if (this.buildOrder.length === 0 || this.buildOrder[0] === undefined) {
    478             debug ("Ending plan: no build orders");
    479             return 0;   // will abort the plan, should return something else
    480         }
    481 
    482        
    483472        // let's sort by training advancement, ie 'current size / target size'
    484473        // count the number of queued units too.
     
    486475        this.buildOrder.sort(function (a,b) { //}) {
    487476            var aQueued = gameState.countOwnQueuedEntitiesWithMetadata("special","Plan_"+self.name+"_"+a[4]);
    488             aQueued += self.queue.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+a[4]);
    489             aQueued += self.queueChamp.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+a[4]);
     477            aQueued += self.queue.countQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+a[4]);
     478            aQueued += self.queueChamp.countQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+a[4]);
    490479            a[0] = (a[2].length + aQueued)/a[3]["targetSize"];
    491480                             
    492481            var bQueued = gameState.countOwnQueuedEntitiesWithMetadata("special","Plan_"+self.name+"_"+b[4]);
    493             bQueued += self.queue.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+b[4]);
    494             bQueued += self.queueChamp.countTotalQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+b[4]);
     482            bQueued += self.queue.countQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+b[4]);
     483            bQueued += self.queueChamp.countQueuedUnitsWithMetadata("special","Plan_"+self.name+"_"+b[4]);
    495484            b[0] = (b[2].length + bQueued)/b[3]["targetSize"];
    496485                             
     
    513502        var inTraining = gameState.countOwnQueuedEntitiesWithMetadata("special",specialData);
    514503               
    515         var queued = this.queue.countTotalQueuedUnitsWithMetadata("special",specialData) + this.queueChamp.countTotalQueuedUnitsWithMetadata("special",specialData)
     504        var queued = this.queue.countQueuedUnitsWithMetadata("special",specialData) + this.queueChamp.countQueuedUnitsWithMetadata("special",specialData)
    516505       
    517506        if (queued + inTraining + this.buildOrder[0][2].length <= this.buildOrder[0][3]["targetSize"]) {
     
    522511           
    523512            if (this.buildOrder[0][0] < 1 && queue.length() <= 5) {
    524                 var template = militaryManager.findBestTrainableUnit(gameState, this.buildOrder[0][1], this.buildOrder[0][3]["interests"] );
     513                var template = HQ.findBestTrainableSoldier(gameState, this.buildOrder[0][1], this.buildOrder[0][3]["interests"] );
    525514                //debug ("tried " + uneval(this.buildOrder[0][1]) +", and " + template);
    526515                // HACK (TODO replace) : if we have no trainable template... Then we'll simply remove the buildOrder, effectively removing the unit from the plan.
    527516                if (template === undefined) {
    528517                    // TODO: this is a complete hack.
    529                     if (this.needsShip && this.buildOrder[0][4] == "TransportShip") {
    530                         Engine.ProfileStop();
    531                         Engine.ProfileStop();
    532                         return 0;
    533                     }
    534518                    delete this.unitStat[this.buildOrder[0][4]];    // deleting the associated unitstat.
    535519                    this.buildOrder.splice(0,1);
     
    539523                    if (gameState.getTimeElapsed() > 1800000)
    540524                        max *= 2;
    541                     if (gameState.getTemplate(template).hasClasses(["CitizenSoldier", "Infantry"]))
    542                         queue.addItem( new UnitTrainingPlan(gameState,template, { "role" : "worker", "plan" : this.name, "special" : specialData }, this.buildOrder[0][3]["batchSize"],max ) );
     525                    if (gameState.getTemplate(template).hasClass("CitizenSoldier"))
     526                        queue.addItem( new TrainingPlan(gameState,template, { "role" : "worker", "plan" : this.name, "special" : specialData, "base" : 1 }, this.buildOrder[0][3]["batchSize"],max ) );
    543527                    else
    544                         queue.addItem( new UnitTrainingPlan(gameState,template, { "role" : "attack", "plan" : this.name, "special" : specialData }, this.buildOrder[0][3]["batchSize"],max ) );
     528                        queue.addItem( new TrainingPlan(gameState,template, { "role" : "attack", "plan" : this.name, "special" : specialData, "base" : 1 }, this.buildOrder[0][3]["batchSize"],max ) );
    545529                }
    546530            }
     
    550534           
    551535            // find our target
    552             var targets = this.targetFinder(gameState, militaryManager);
     536            var targets = this.targetFinder(gameState, HQ);
    553537            if (targets.length === 0){
    554                 targets = this.defaultTargetFinder(gameState, militaryManager);
     538                targets = this.defaultTargetFinder(gameState, HQ);
    555539            }
    556540            if (targets.length) {
     
    633617        for (var unitCat in this.unit) {
    634618            this.unit[unitCat].forEach(function (ent) {
    635                 if (ent.getMetadata(PlayerID, "role") != "defence" && !ent.hasClass("Warship"))
     619                if (ent.getMetadata(PlayerID, "role") != "defence")
    636620                {
    637621                    ent.setMetadata(PlayerID,"role", "attack");
     
    643627        for (var unitCat in this.unit) {
    644628            this.unit[unitCat].forEach(function (ent) {
    645                 if (ent.getMetadata(PlayerID, "role") != "worker" && ent.getMetadata(PlayerID, "role") != "defence" && !ent.hasClass("Warship"))
     629                if (ent.getMetadata(PlayerID, "role") != "worker" && ent.getMetadata(PlayerID, "role") != "defence")
    646630                    ent.move(self.rallyPoint[0],self.rallyPoint[1]);
    647631            });
     
    651635
    652636// Default target finder aims for conquest critical targets
    653 CityAttack.prototype.defaultTargetFinder = function(gameState, militaryManager){
     637CityAttack.prototype.defaultTargetFinder = function(gameState, HQ){
    654638    var targets = undefined;
    655639   
    656     targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "CivCentre",true);
     640    targets = HQ.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "CivCentre",true);
    657641    if (targets.length == 0) {
    658         targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "ConquestCritical");
     642        targets = HQ.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "ConquestCritical");
    659643    }
    660644    // If there's nothing, attack anything else that's less critical
    661645    if (targets.length == 0) {
    662         targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "Town",true);
     646        targets = HQ.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "Town",true);
    663647    }
    664648    if (targets.length == 0) {
    665         targets = militaryManager.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "Village",true);
     649        targets = HQ.enemyWatchers[this.targetPlayer].getEnemyBuildings(gameState, "Village",true);
    666650    }
    667651    // no buildings, attack anything conquest critical, even units (it's assuming it won't move).
     
    673657
    674658// tupdate
    675 CityAttack.prototype.raidingTargetFinder = function(gameState, militaryManager, Target){
     659CityAttack.prototype.raidingTargetFinder = function(gameState, HQ, Target){
    676660    var targets = undefined;
    677661    if (Target == "villager")
     
    694678        return targets;
    695679    } else {
    696         return this.defaultTargetFinder(gameState, militaryManager);
     680        return this.defaultTargetFinder(gameState, HQ);
    697681    }
    698682};
     
    701685// If we're here, it's because we have in our IDlist enough units.
    702686// now the IDlist units are treated turn by turn
    703 CityAttack.prototype.StartAttack = function(gameState, militaryManager){
     687CityAttack.prototype.StartAttack = function(gameState, HQ){
    704688   
    705689    // check we have a target and a path.
     
    712696       
    713697        this.unitCollection.forEach(function(ent) { ent.setMetadata(PlayerID, "subrole", "walking"); ent.setMetadata(PlayerID, "role", "attack") ;});
    714 
    715         this.unitCollectionNoWarship = this.unitCollection.filter(Filters.not(Filters.byClass("Warship")));
    716         this.unitCollectionNoWarship.registerUpdates();
    717        
    718         this.unitCollection.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
     698        // optimize our collection now.
     699        this.unitCollection.freeze();
     700        this.unitCollection.allowQuickIter();
     701       
     702        this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]);
    719703        this.unitCollection.setStance("aggressive");
    720704        this.unitCollection.filter(Filters.byClass("Siege")).setStance("defensive");
     
    730714
    731715// Runs every turn after the attack is executed
    732 CityAttack.prototype.update = function(gameState, militaryManager, events){
     716CityAttack.prototype.update = function(gameState, HQ, events){
    733717    var self = this;
    734718       
     
    772756                            attackedNB++;
    773757                        }
    774                         //if (militaryManager.enemyWatchers[attacker.owner()]) {
     758                        //if (HQ.enemyWatchers[attacker.owner()]) {
    775759                            //toProcess[attacker.id()] = attacker;
    776                             //var armyID = militaryManager.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id());
     760                            //var armyID = HQ.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id());
    777761                            //armyToProcess[armyID[0]] = armyID[1];
    778762                        //}
     
    852836    if (this.state === "walking"){
    853837       
    854         this.position = this.unitCollectionNoWarship.getCentrePosition();
     838        this.position = this.unitCollection.getCentrePosition();
    855839
    856840        // probably not too good.
     
    881865       
    882866        if (this.lastPosition && SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) {
    883             this.unitCollectionNoWarship.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
     867            this.unitCollection.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
    884868            // 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.
    885869            var walls = gameState.getEnemyEntities().filter(Filters.and(Filters.byOwner(this.targetPlayer), Filters.byClass("StoneWall")));
     
    903887       
    904888        // check if our land units are close enough from the next waypoint.
    905         if (SquareVectorDistance(this.unitCollectionNoWarship.getCentrePosition(), this.targetPos) < 7500 ||
    906             SquareVectorDistance(this.unitCollectionNoWarship.getCentrePosition(), this.path[0][0]) < 850) {
     889        if (SquareVectorDistance(this.unitCollection.getCentrePosition(), this.targetPos) < 7500 ||
     890            SquareVectorDistance(this.unitCollection.getCentrePosition(), this.path[0][0]) < 650) {
    907891            if (this.unitCollection.filter(Filters.byClass("Siege")).length !== 0
    908                 && SquareVectorDistance(this.unitCollectionNoWarship.getCentrePosition(), this.targetPos) > 7500
    909                 && SquareVectorDistance(this.unitCollection.filter(Filters.byClass("Siege")).getCentrePosition(), this.path[0][0]) > 850)
     892                && SquareVectorDistance(this.unitCollection.getCentrePosition(), this.targetPos) >= 7500
     893                && SquareVectorDistance(this.unitCollection.filter(Filters.byClass("Siege")).getCentrePosition(), this.path[0][0]) >= 650)
    910894            {
    911895            } else {
     896
     897                for (var i = 0; i < this.path.length; ++i)
     898                {
     899                    debug ("path waypoint " + i + "," + this.path[i][1] + " at " + uneval(this.path[i][0]));
     900                }
     901                debug ("position is " + this.unitCollection.getCentrePosition());
     902
    912903                // okay so here basically two cases. The first one is "we need a boat at this point".
    913904                // the second one is "we need to unload at this point". The third is "normal".
     
    916907                    this.path.shift();
    917908                    if (this.path.length > 0){
    918                         this.unitCollectionNoWarship.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
     909                        this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]);
    919910                    } else {
    920911                        debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination.");
     
    922913                        this.state = "arrived";
    923914                    }
    924                 } else if (this.path[0][1] === true)
     915                } else
    925916                {
    926                     // okay we must load our units.
    927                     // check if we have some kind of ships.
    928                     var ships = this.unitCollection.filter(Filters.byClass("Warship"));
    929                     if (ships.length === 0) {
     917                    // TODO: make this require an escort later on.
     918                    this.path.shift();
     919                    if (this.path.length === 0) {
     920                        debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination.");
     921                        // we must assume we've arrived at the end of the trail.
     922                        this.state = "arrived";
     923                    } else {
     924                        /*
     925                        var plan = new TransportPlan(gameState, this.unitCollection.toIdArray(), this.path[0][0], false);
     926                        this.tpPlanID = plan.ID;
     927                        HQ.navalManager.transportPlans.push(plan);
     928                        debug ("Transporting over sea");
     929                        this.state = "transporting";
     930                    */
     931                        // TODO: fix this above
     932                        //right now we'll abort.
    930933                        Engine.ProfileStop();
    931                         return 0; // abort
     934                        return 0;
    932935                    }
    933                
    934                     debug ("switch to boarding");
    935                     this.state = "boarding";
    936                 }
    937             }
    938         }
    939     } else if (this.state === "shipping") {
    940         this.position = this.unitCollection.filter(Filters.byClass("Warship")).getCentrePosition();
    941        
    942         if (!this.lastPosition)
    943             this.lastPosition = [0,0];
    944 
    945         if (SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) {
    946             this.unitCollection.filter(Filters.byClass("Warship")).move(this.path[0][0][0], this.path[0][0][1]);
    947         }
    948         if (SquareVectorDistance(this.position, this.path[0][0]) < 1600) {
    949             if (this.path[0][1] !== true)
    950             {
    951                 this.path.shift();
    952                 if (this.path.length > 0){
    953                     this.unitCollection.filter(Filters.byClass("Warship")).move(this.path[0][0][0], this.path[0][0][1]);
    954                 } else {
    955                     debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination, but it's still on the ship…");
    956                     Engine.ProfileStop();
    957                     return 0; // abort
    958                 }
    959             } else if (this.path[0][1] === true)
    960             {
    961                 debug ("switch to unboarding");
    962                 // we unload
    963                 this.state = "unboarding";
    964             }
    965         }
    966     } else if (this.state === "boarding") {
    967         this.position = this.unitCollectionNoWarship.getCentrePosition();
    968 
    969         var ships = this.unitCollection.filter(Filters.byClass("Warship"));
    970         if (ships.length === 0) {
    971             Engine.ProfileStop();
    972             return 0; // abort
    973         }
    974        
    975         var globalPos = this.unitCollectionNoWarship.getCentrePosition();
    976         var shipPos = ships.getCentrePosition();
    977 
    978         if (globalPos !== undefined && SquareVectorDistance(globalPos,shipPos) > 800)
    979         {   // get them closer
    980             ships.moveIndiv(globalPos[0],globalPos[1]);
    981             this.unitCollectionNoWarship.moveIndiv(shipPos[0],shipPos[1]);
    982         } else {
    983             // okay try to garrison.
    984             var shipsArray = ships.toEntityArray();
    985             this.unitCollectionNoWarship.forEach(function (ent) { //}){
    986                 if (ent.position()) // if we're not garrisoned
    987                     for (var shipId = 0; shipId < shipsArray.length; shipId++) {
    988                         if (shipsArray[shipId].garrisoned().length < shipsArray[shipId].garrisonMax())
    989                         {
    990                             ent.garrison(shipsArray[shipId]);
    991                             break;
    992                         }
    993                     }
    994             });
    995             var garrLength = 0;
    996             for (var shipId = 0; shipId < shipsArray.length; shipId++)
    997                 garrLength += shipsArray[shipId].garrisoned().length;
    998            
    999             if (garrLength == this.unitCollectionNoWarship.length) {
    1000                 // okay.
    1001                 this.path.shift();
    1002                 if (this.path.length > 0){
    1003                     ships.move(this.path[0][0][0], this.path[0][0][1]);
    1004                     debug ("switch to shipping");
    1005                     this.state = "shipping";
    1006                 } else {
    1007                     debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination.");
    1008                     // we must assume we've arrived at the end of the trail.
    1009                     this.state = "arrived";
    1010                 }
    1011             }
    1012         }
    1013     } else if (this.state === "unboarding") {
    1014        
    1015         var ships = this.unitCollection.filter(Filters.byClass("Warship"));
    1016         if (ships.length === 0) {
    1017             Engine.ProfileStop();
    1018             return 0; // abort
    1019         }
    1020 
    1021         this.position = ships.getCentrePosition();
    1022        
    1023         // the procedure is pretty simple: we move the ships to the next point and try to unload until all units are over.
    1024         // TODO: make it better, like avoiding collisions, and so on.
    1025 
    1026         if (this.path.length > 1)
    1027             ships.move(this.path[1][0][0], this.path[1][0][1]);
    1028 
    1029         ships.forEach(function (ship) {
    1030             ship.unloadAll();
    1031         });
    1032        
    1033         var shipsArray = ships.toEntityArray();
    1034         var garrLength = 0;
    1035         for (var shipId = 0; shipId < shipsArray.length; shipId++)
    1036             garrLength += shipsArray[shipId].garrisoned().length;
    1037            
    1038         if (garrLength == 0) {
    1039             // release the ships
    1040            
    1041             ships.forEach(function (ent) {
    1042                 ent.setMetadata(PlayerID, "role",undefined);
    1043                 ent.setMetadata(PlayerID, "subrole",undefined);
    1044                 ent.setMetadata(PlayerID, "plan",undefined);
    1045             });
    1046             for (var shipId = 0; shipId < shipsArray.length; shipId++)
    1047                 this.unitCollection.removeEnt(shipsArray[shipId]);
    1048            
    1049             this.path.shift();
    1050             if (this.path.length > 0){
    1051                 this.unitCollection.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
    1052                 debug ("switch to walking");
    1053                 this.state = "walking";
    1054             } else {
    1055                 debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination.");
    1056                 // we must assume we've arrived at the end of the trail.
    1057                 this.state = "arrived";
    1058             }
    1059         }
    1060        
     936                }
     937            }
     938        }
     939    } else if (this.state === "transporting") {
     940        // check that we haven't finished transporting, ie the plan
     941        if (!HQ.navalManager.checkActivePlan(this.tpPlanID))
     942        {
     943            this.state = "walking";
     944        }
    1061945    }
    1062946
     
    1095979   
    1096980    if (this.state === "") {
    1097        
    1098981        // Units attacked will target their attacker unless they're siege. Then we take another non-siege unit to attack them.
    1099982        for (var key in events) {
     
    1107990                        if (ourUnit.hasClass("Siege"))
    1108991                        {
    1109                             var help = this.unitCollection.filter(Filters.and(Filters.not(Filters.byClass("Siege")),Filters.isIdle()));
    1110                             if (help.length === 0)
    1111                                 help = this.unitCollection.filter(Filters.not(Filters.byClass("Siege")));
    1112                             if (help.length > 0)
    1113                                 help.toEntityArray()[0].attack(attacker.id());
    1114                             if (help.length > 1)
    1115                                 help.toEntityArray()[1].attack(attacker.id());
    1116 
     992                            var collec = this.unitCollection.filterNearest(ourUnit.position(), 8).filter(Filters.not(Filters.byClass("Siege"))).toEntityArray();
     993                            if (collec.length !== 0)
     994                            {
     995                                collec[0].attack(attacker.id());
     996                                if (collec.length !== 1)
     997                                {
     998                                    collec[1].attack(attacker.id());
     999                                    if (collec.length !== 2)
     1000                                    {
     1001                                        collec[2].attack(attacker.id());
     1002                                    }
     1003                                }
     1004                            }
    11171005                        } else {
    11181006                            ourUnit.attack(attacker.id());
     
    11231011        }
    11241012       
    1125        
    1126         var enemyUnits = gameState.getEnemyEntities().filter(Filters.and(Filters.byOwner(this.targetPlayer), Filters.byClass("Unit")));
    1127         var enemyStructures = gameState.getEnemyEntities().filter(Filters.and(Filters.byOwner(this.targetPlayer), Filters.byClass("Structure")));
     1013        var enemyUnits = gameState.getGEC("player-" +this.targetPlayer + "-units");
     1014        var enemyStructures = gameState.getGEC("player-" +this.targetPlayer + "-structures");
    11281015
    11291016        if (this.unitCollUpdateArray === undefined || this.unitCollUpdateArray.length === 0)
    11301017        {
    1131             this.unitCollUpdateArray = this.unitCollection.toEntityArray();
     1018            this.unitCollUpdateArray = this.unitCollection.toIdArray();
    11321019        } else {
     1020            // some stuffs for locality and speed
     1021            var territoryMap = Map.createTerritoryMap(gameState);
     1022            var timeElapsed = gameState.getTimeElapsed();
     1023           
    11331024            // Let's check a few units each time we update. Currently 10
    1134             for (var check = 0; check < Math.min(this.unitCollUpdateArray.length,10); check++)
     1025            var lgth = Math.min(this.unitCollUpdateArray.length,10);
     1026            for (var check = 0; check < lgth; check++)
    11351027            {
    1136                 var ent = this.unitCollUpdateArray[0];
    1137                
    1138                 // if the unit is not in my territory, make it move.
    1139                 var territoryMap = Map.createTerritoryMap(gameState);
     1028                var ent = gameState.getEntityById(this.unitCollUpdateArray[0]);
     1029                if (!ent)
     1030                    continue;
     1031                var orderData = ent.unitAIOrderData();
     1032                if (orderData.length !== 0)
     1033                    orderData = orderData[0];
     1034                else
     1035                    orderData = undefined;
     1036
     1037                // if the unit is in my territory, make it move.
    11401038                if (territoryMap.point(ent.position()) - 64 === PlayerID)
    11411039                    ent.move(this.targetPos[0],this.targetPos[1]);
     1040               
    11421041                // update it.
    11431042                var needsUpdate = false;
    11441043                if (ent.isIdle())
    11451044                    needsUpdate = true;
    1146                 if (ent.hasClass("Siege") && (!ent.unitAIOrderData() || !ent.unitAIOrderData()["target"] || !gameState.getEntityById(ent.unitAIOrderData()["target"]).hasClass("ConquestCritical")) )
     1045                if (ent.hasClass("Siege") && (!orderData || !orderData["target"] || !gameState.getEntityById(orderData["target"]) || !gameState.getEntityById(orderData["target"]).hasClass("ConquestCritical")) )
    11471046                    needsUpdate = true;
    1148                 else if (ent.unitAIOrderData() && ent.unitAIOrderData()["target"] && gameState.getEntityById(ent.unitAIOrderData()["target"]).hasClass("Structure"))
     1047                else if (!ent.hasClass("Siege") && orderData && orderData["target"] && gameState.getEntityById(orderData["target"]) && gameState.getEntityById(orderData["target"]).hasClass("Structure"))
    11491048                    needsUpdate = true; // try to make it attack a unit instead
    1150                
    1151                 if (gameState.getTimeElapsed() - ent.getMetadata(PlayerID, "lastAttackPlanUpdateTime") < 10000)
     1049
     1050                if (timeElapsed - ent.getMetadata(PlayerID, "lastAttackPlanUpdateTime") < 10000)
    11521051                    needsUpdate = false;
    11531052               
    1154                 if (needsUpdate || arrivedthisTurn)
     1053                if (needsUpdate === true || arrivedthisTurn)
    11551054                {
    1156                     ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", gameState.getTimeElapsed());
     1055                    ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", timeElapsed);
    11571056                    var mStruct = enemyStructures.filter(function (enemy) { //}){
    11581057                        if (!enemy.position() || (enemy.hasClass("StoneWall") && ent.canAttackClass("StoneWall"))) {
    11591058                            return false;
    11601059                        }
    1161                         if (SquareVectorDistance(enemy.position(),ent.position()) > 2000) {
     1060                        if (SquareVectorDistance(enemy.position(),ent.position()) > 3000) {
    11621061                            return false;
    11631062                        }
     
    11651064                    });
    11661065                    var mUnit;
    1167                     if (ent.hasClass("Cavalry")) {
     1066                    if (ent.hasClass("Cavalry") && ent.countersClasses(["Support"])) {
    11681067                        mUnit = enemyUnits.filter(function (enemy) { //}){
    11691068                            if (!enemy.position()) {
     
    11721071                            if (!enemy.hasClass("Support"))
    11731072                                return false;
    1174                             if (SquareVectorDistance(enemy.position(),ent.position()) > 2000) {
     1073                            if (SquareVectorDistance(enemy.position(),ent.position()) > 10000) {
    11751074                                return false;
    11761075                            }
     
    11781077                        });
    11791078                    }
    1180                     if (!ent.hasClass("Cavalry") || mUnit.length === 0) {
     1079                    if (!(ent.hasClass("Cavalry") && ent.countersClasses(["Support"])) || mUnit.length === 0) {
    11811080                        mUnit = enemyUnits.filter(function (enemy) { //}){
    11821081                            if (!enemy.position()) {
    11831082                                return false;
    11841083                            }
    1185                             if (SquareVectorDistance(enemy.position(),ent.position()) > 2000) {
     1084                            if (SquareVectorDistance(enemy.position(),ent.position()) > 10000) {
    11861085                                return false;
    11871086                            }
     
    12121111                            return (valb - vala);
    12131112                        });
    1214                        
     1113                        // TODO: handle ballistas here
    12151114                        if (mStruct.length !== 0) {
    12161115                            if (isGate)
     
    12271126                        }
    12281127                    } else {
    1229                         if (mUnit.length !== 0 && !isGate) {
     1128                        if (mUnit.length !== 0) {
    12301129                            var rand = Math.floor(Math.random() * mUnit.length*0.99);
    12311130                            ent.attack(mUnit[(+rand)].id());
    12321131                            //debug ("Units attacking a unit from " +mUnit[+rand].owner() + " , " +mUnit[+rand].templateName());
     1132                        } else if (SquareVectorDistance(self.targetPos, ent.position()) > 900 ){
     1133                            //debug ("Units moving to " + uneval(self.targetPos));
     1134                            ent.move(self.targetPos[0],self.targetPos[1]);
    12331135                        } else if (mStruct.length !== 0) {
    12341136                            mStruct.sort(function (structa,structb) { //}){
     
    12571159                                //debug ("Units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName());
    12581160                            }
    1259                         } else if (SquareVectorDistance(self.targetPos, ent.position()) > 900 ){
    1260                             //debug ("Units moving to " + uneval(self.targetPos));
    1261                             ent.move(self.targetPos[0],self.targetPos[1]);
    12621161                        }
    12631162                    }
    12641163                }
    1265                
    1266                 this.unitCollUpdateArray.splice(0,1);
    1267             }
     1164            }
     1165            this.unitCollUpdateArray.splice(0,10);
    12681166        }
    12691167        // updating targets.
    12701168        if (!gameState.getEntityById(this.target.id()))
    12711169        {           
    1272             var targets = this.targetFinder(gameState, militaryManager);
     1170            var targets = this.targetFinder(gameState, HQ);
    12731171            if (targets.length === 0){
    1274                 targets = this.defaultTargetFinder(gameState, militaryManager);
     1172                targets = this.defaultTargetFinder(gameState, HQ);
    12751173            }
    12761174            if (targets.length) {
  • ps/trunk/binaries/data/mods/public/simulation/ai/aegis/config.js

    r13683 r13907  
    44        "fortressLapseTime" : 540, // Time to wait between building 2 fortresses
    55        "defenceBuildingTime" : 600, // Time to wait before building towers or fortresses
    6         "attackPlansStartTime" : 0  // time to wait before attacking. Start as soon as possible (first barracks)
     6        "attackPlansStartTime" : 0, // time to wait before attacking. Start as soon as possible (first barracks)
     7        "techStartTime" : 120,  // time to wait before teching. Will only start after town phase so it's irrelevant.
     8        "popForBarracks1" : 15,
     9        "popForBarracks2" : 95,
     10        "timeForBlacksmith" : 900,
    711    },
    812    "Economy" : {
    913        "townPhase" : 180,  // time to start trying to reach town phase (might be a while after. Still need the requirements + ress )
    10         "cityPhase" : 540,  // time to start trying to reach city phase
    11         "farmsteadStartTime" : 400, // Time to wait before building a farmstead.
     14        "cityPhase" : 840000,   // time to start trying to reach city phase
     15        "popForMarket" : 80,
     16        "popForFarmstead" : 45,
    1217        "dockStartTime" : 240,  // Time to wait before building the dock
    13         "techStartTime" : 600,  // time to wait before teching.
    14         "targetNumBuilders" : 1.5, // Base number of builders per foundation. Later updated, but this remains a multiplier.
    15         "femaleRatio" : 0.6 // percent of females among the workforce.
     18        "techStartTime" : 0,    // time to wait before teching.
     19        "targetNumBuilders" : 1.5, // Base number of builders per foundation.
     20        "femaleRatio" : 0.4, // percent of females among the workforce.
     21        "initialFields" : 2
    1622    },
    1723   
     
    5056    // qbot
    5157    "priorities" : {  // Note these are dynamic, you are only setting the initial values
    52         "house" : 200,
    53         "citizenSoldier" : 70,
    54         "villager" : 55,
    55         "economicBuilding" : 70,
     58        "house" : 350,
     59        "villager" : 40,
     60        "citizenSoldier" : 60,
     61        "ships" : 70,
     62        "economicBuilding" : 90,
    5663        "dropsites" : 120,
    57         "field" : 1000,
    58         "militaryBuilding" : 90,
     64        "field" : 500,
     65        "militaryBuilding" : 110,
    5966        "defenceBuilding" : 70,
    60         "majorTech" : 400,
    61         "minorTech" : 40,
    62         "civilCentre" : 10000   // will hog all resources
     67        "majorTech" : 700,
     68        "minorTech" : 50,
     69        "civilCentre" : 400
    6370    },
    64     "difficulty" : 2,   // for now 2 is "hard", ie default. 1 is normal, 0 is easy. 3 is very hard
     71    "difficulty" : 2,   // 0 is sandbox, 1 is easy, 2 is medium, 3 is hard, 4 is very hard.
    6572    "debug" : false
    6673};
     
    7582        if (Config.difficulty === 1)
    7683        {
    77             Config["Military"] = {
    78                 "fortressLapseTime" : 900,
    79                 "defenceBuildingTime" : 720,
    80                 "attackPlansStartTime" : 1200
    81             };
    82             Config["Economy"] = {
    83                 "townPhase" : 360,
    84                 "cityPhase" : 900,
    85                 "farmsteadStartTime" : 600,
    86                 "dockStartTime" : 240,
    87                 "techStartTime" : 1320,
    88                 "targetNumBuilders" : 2,
    89                 "femaleRatio" : 0.5,
    90                 "targetNumWorkers" : 110    // should not make more than 2 barracks.
    91             };
    92             Config["Defence"] = {
    93                 "defenceRatio" : 4.0,
    94                 "armyCompactSize" : 700,
    95                 "armyBreakawaySize" : 900
    96             };
    97         } else if (Config.difficulty === 0)
     84            Config.Military.defenceBuildingTime = 1200;
     85            Config.Military.attackPlansStartTime = 960;
     86            Config.Military.popForBarracks1 = 35;
     87            Config.Military.popForBarracks2 = 150;  // shouldn't reach it
     88            Config.Military.popForBlacksmith = 150; // shouldn't reach it
     89
     90            Config.Economy.cityPhase = 1800;
     91            Config.Economy.popForMarket = 80;
     92            Config.Economy.techStartTime = 600;
     93            Config.Economy.femaleRatio = 0.6;
     94            Config.Economy.initialFields = 1;
     95            // Config.Economy.targetNumWorkers will be set by AI scripts.
     96        }
     97        else if (Config.difficulty === 0)
    9898        {
    99             Config["Military"] = {
    100                 "fortressLapseTime" : 1000000,  // never
    101                 "defenceBuildingTime" : 900,
    102                 "attackPlansStartTime" : 120000 // never
    103             };
    104             Config["Economy"] = {
    105                 "townPhase" : 480,
    106                 "cityPhase" : 1200,
    107                 "farmsteadStartTime" : 1200,
    108                 "dockStartTime" : 240,
    109                 "techStartTime" : 600000,   // never
    110                 "targetNumBuilders" : 1,
    111                 "femaleRatio" : 0.0,    // makes us slower, but also less sucky at defending so it's still fun to attack it.
    112                 "targetNumWorkers" : 70
    113             };
    114             Config["Defence"] = {
    115                 "defenceRatio" : 2.0,
    116                 "armyCompactSize" : 700,
    117                 "armyBreakawaySize" : 900
    118             };
     99            Config.Military.defenceBuildingTime = 450;
     100            Config.Military.attackPlansStartTime = 9600000; // never
     101            Config.Military.popForBarracks1 = 60;
     102            Config.Military.popForBarracks2 = 150;  // shouldn't reach it
     103            Config.Military.popForBlacksmith = 150; // shouldn't reach it
     104
     105            Config.Economy.cityPhase = 240000;
     106            Config.Economy.popForMarket = 200;
     107            Config.Economy.techStartTime = 1800;
     108            Config.Economy.femaleRatio = 0.2;
     109            Config.Economy.initialFields = 1;
     110            // Config.Economy.targetNumWorkers will be set by AI scripts.
    119111        }
    120112    }
  • ps/trunk/binaries/data/mods/public/simulation/ai/aegis/data.json

    r13683 r13907  
    22  "name": "Aegis Bot",
    33  "description": "Wraitii's improvement of qBot. It is more reliable and generally a better player. Note that it doesn't support saved games yet, and there may be other bugs. Please report issues to Wildfire Games (see the link in the main menu).",
    4   "constructor": "QBotAI",
     4  "constructor": "AegisBot",
    55  "useShared": true
    66}
  • ps/trunk/binaries/data/mods/public/simulation/ai/aegis/defence.js

    r13683 r13907  
    4949
    5050
    51 Defence.prototype.update = function(gameState, events, militaryManager){
     51Defence.prototype.update = function(gameState, events, HQ){
    5252   
    5353    Engine.ProfileStart("Defence Manager");
     
    8181
    8282    // First step: we deal with enemy armies, those are the highest priority.
    83     this.defendFromEnemies(gameState, events, militaryManager);
     83    this.defendFromEnemies(gameState, events, HQ);
    8484
    8585    // second step: we loop through messages, and sort things as needed (dangerous buildings, attack by animals, ships, lone units, whatever).
    8686    // TODO : a lot.
    87     this.MessageProcess(gameState,events,militaryManager);
    88    
    89     this.DealWithWantedUnits(gameState,events,militaryManager);
     87    this.MessageProcess(gameState,events,HQ);
     88   
     89    this.DealWithWantedUnits(gameState,events,HQ);
    9090
    9191    /*
     
    139139// Incorporates an entity in an army. If no army fits, it creates a new one around this one.
    140140// an army is basically an entity collection.
    141 Defence.prototype.armify = function(gameState, entity, militaryManager, minNBForArmy) {
     141Defence.prototype.armify = function(gameState, entity, HQ, minNBForArmy) {
    142142    if (entity.position() === undefined)
    143143        return;
     
    161161        }
    162162    }
    163     if (militaryManager)
     163    if (HQ)
    164164    {
    165165        var self = this;
    166         var close = militaryManager.enemyWatchers[entity.owner()].enemySoldiers.filter(Filters.byDistance(entity.position(), self.armyCompactSize));
     166        var close = HQ.enemyWatchers[entity.owner()].enemySoldiers.filter(Filters.byDistance(entity.position(), self.armyCompactSize));
    167167        if (!minNBForArmy || close.length >= minNBForArmy)
    168168        {
     
    224224// This deals with incoming enemy armies, setting the defcon if needed. It will take new soldiers, and assign them to attack
    225225// TODO: still is still pretty dumb, it could use improvements.
    226 Defence.prototype.defendFromEnemies = function(gameState, events, militaryManager) {
     226Defence.prototype.defendFromEnemies = function(gameState, events, HQ) {
    227227    var self = this;
    228228   
     
    265265    for (var enemyID in this.enemyArmy)
    266266    {
    267         //this.enemyUnits[enemyID] = militaryManager.enemyWatchers[enemyID].getAllEnemySoldiers();
     267        //this.enemyUnits[enemyID] = HQ.enemyWatchers[enemyID].getAllEnemySoldiers();
    268268        if (this.enemyUnits[enemyID] === undefined || this.enemyUnits[enemyID].length === 0)
    269269        {
    270             this.enemyUnits[enemyID] = militaryManager.enemyWatchers[enemyID].enemySoldiers.toEntityArray();
     270            this.enemyUnits[enemyID] = HQ.enemyWatchers[enemyID].enemySoldiers.toEntityArray();
    271271        } else {
    272272            // we have some units still to check in this array. Check 15 (TODO: DIFFLEVEL)
     
    284284                        var dangerous = this.evaluateEntity(gameState, this.enemyUnits[enemyID][0]);
    285285                        if (dangerous)
    286                             this.armify(gameState, this.enemyUnits[enemyID][0], militaryManager,2);
     286                            this.armify(gameState, this.enemyUnits[enemyID][0], HQ,2);
    287287                        this.enemyUnits[enemyID].splice(0,1);
    288288                    }
     
    380380            self.nbDefenders--;
    381381        });
    382         militaryManager.ungarrisonAll(gameState);
    383         militaryManager.unpauseAllPlans(gameState);
     382        HQ.ungarrisonAll(gameState);
     383        HQ.unpauseAllPlans(gameState);
    384384        return;
    385385    } else if (this.nbAttackers === 0 && this.nbDefenders !== 0) {
     
    394394            self.nbDefenders--;
    395395        });
    396         militaryManager.ungarrisonAll(gameState);
    397         militaryManager.unpauseAllPlans(gameState);
     396        HQ.ungarrisonAll(gameState);
     397        HQ.unpauseAllPlans(gameState);
    398398        return;
    399399    }
    400400    if ( (this.nbDefenders < 4 && this.nbAttackers >= 5) || this.nbDefenders === 0) {
    401         militaryManager.ungarrisonAll(gameState);
     401        HQ.ungarrisonAll(gameState);
    402402    }
    403403   
     
    450450       
    451451    if (gameState.defcon() > 3)
    452         militaryManager.unpauseAllPlans(gameState);
     452        HQ.unpauseAllPlans(gameState);
    453453   
    454454    if ( (nonDefenders.length + this.nbDefenders > newEnemies.length + this.nbAttackers)
     
    468468    if (gameState.defcon() < 2 && (this.nbAttackers-this.nbDefenders) > 15)
    469469    {
    470         militaryManager.pauseAllPlans(gameState);
     470        HQ.pauseAllPlans(gameState);
    471471    } else if (gameState.defcon() < 3 && this.nbDefenders === 0 && newEnemies.length === 0) {
    472         militaryManager.ungarrisonAll(gameState);
     472        HQ.ungarrisonAll(gameState);
    473473    }*/
    474474   
     
    550550        defs.forEach(function (defender) { //}){
    551551            if (defender.getMetadata(PlayerID, "plan") != undefined && (gameState.defcon() < 4 || defender.getMetadata(PlayerID,"subrole") == "walking"))
    552                 militaryManager.pausePlan(gameState, defender.getMetadata(PlayerID, "plan"));
     552                HQ.pausePlan(gameState, defender.getMetadata(PlayerID, "plan"));
    553553            //debug ("Against " +enemy.id() + " Assigning " + defender.id());
    554554            if (defender.getMetadata(PlayerID, "role") == "worker" || defender.getMetadata(PlayerID, "role") == "attack")
     
    606606// So that a unit that gets attacked will not be completely dumb.
    607607// warning: huge levels of indentation coming.
    608 Defence.prototype.MessageProcess = function(gameState,events, militaryManager) {
     608Defence.prototype.MessageProcess = function(gameState,events, HQ) {
    609609    var self = this;
    610610   
     
    678678                                    {
    679679                                        var position = attacker.position();
    680                                         var close = militaryManager.enemyWatchers[attacker.owner()].enemySoldiers.filter(Filters.byDistance(position, self.armyCompactSize));
     680                                        var close = HQ.enemyWatchers[attacker.owner()].enemySoldiers.filter(Filters.byDistance(position, self.armyCompactSize));
    681681                                       
    682682                                        if (close.length > 2 || ourUnit.hasClass("Support") || attacker.hasClass("Siege"))
     
    737737
    738738// At most, this will put defcon to 4
    739 Defence.prototype.DealWithWantedUnits = function(gameState, events, militaryManager) {
     739Defence.prototype.DealWithWantedUnits = function(gameState, events, HQ) {
    740740    //if (gameState.defcon() < 3)
    741741    //  return;
  • ps/trunk/binaries/data/mods/public/simulation/ai/aegis/enemy-watcher.js

    r13683 r13907  
    1313    var filter = Filters.and(Filters.byClass("Structure"), Filters.byOwner(this.watched));
    1414    this.enemyBuildings = gameState.updatingGlobalCollection("player-" +this.watched + "-structures", filter);
    15                              
     15
     16    filter = Filters.and(Filters.byClass("Unit"), Filters.byOwner(this.watched));
     17    this.enemyUnits = gameState.updatingGlobalCollection("player-" +this.watched + "-units", filter);
     18
    1619    filter = Filters.and(Filters.byClass("Worker"), Filters.byOwner(this.watched));
    1720    this.enemyCivilians = gameState.updatingGlobalCollection("player-" +this.watched + "-civilians", filter);
  • ps/trunk/binaries/data/mods/public/simulation/ai/aegis/entitycollection-extend.js

    r13683 r13907  
    77        }
    88    }
    9     return new EntityCollection(gameState.ai, ents);
     9    return new EntityCollection(gameState.sharedScript, ents);
    1010}
  • ps/trunk/binaries/data/mods/public/simulation/ai/aegis/map-module.js

    r13683 r13907  
    1 const TERRITORY_PLAYER_MASK = 0x3F;
     1// other map functions
    22
    3 //TODO: Make this cope with negative cell values
    4 // This is by default a 16-bit map but can be adapted into 8-bit.
    5 function Map(gameState, originalMap, actualCopy){
    6     // get the map to find out the correct dimensions
    7     var gameMap = gameState.getMap();
    8     this.width = gameMap.width;
    9     this.height = gameMap.height;
    10     this.length = gameMap.data.length;
    11 
    12     this.maxVal = 65535;
    13    
    14     if (originalMap && actualCopy){
    15         this.map = new Uint16Array(this.length);
    16         for (var i = 0; i < originalMap.length; ++i)
    17             this.map[i] = originalMap[i];
    18     } else if (originalMap) {
    19         this.map = originalMap;
    20     } else {
    21         this.map = new Uint16Array(this.length);
    22     }
    23     this.cellSize = gameState.cellSize;
    24 }
    25 Map.prototype.setMaxVal = function(val){
    26     this.maxVal = val;
    27 };
    28 
    29 Map.prototype.gamePosToMapPos = function(p){
    30     return [Math.floor(p[0]/this.cellSize), Math.floor(p[1]/this.cellSize)];
    31 };
    32 
    33 Map.prototype.point = function(p){
    34     var q = this.gamePosToMapPos(p);
    35     return this.map[q[0] + this.width * q[1]];
    36 };
    37 
    38 // returns an 8-bit map.
    39 Map.createObstructionMap = function(gameState, template){
     3Map.createObstructionMap = function(gameState, accessIndex, template){
    404    var passabilityMap = gameState.getMap();
    41     var territoryMap = gameState.ai.territoryMap; 
     5    var territoryMap = gameState.ai.territoryMap;
    426   
    437    // default values
     
    5014    if (template){
    5115        placementType = template.buildPlacementType();
    52         buildOwn = template.hasBuildTerritory("own"); 
    53         buildAlly = template.hasBuildTerritory("ally"); 
    54         buildNeutral = template.hasBuildTerritory("neutral"); 
     16        buildOwn = template.hasBuildTerritory("own");
     17        buildAlly = template.hasBuildTerritory("ally");
     18        buildNeutral = template.hasBuildTerritory("neutral");
    5519        buildEnemy = template.hasBuildTerritory("enemy");
    5620    }
    57 
     21   
    5822    var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction") | gameState.getPassabilityClassMask("building-land");
    5923   
     
    6731            for (var y = 0; y < passabilityMap.height; ++y)
    6832            {
    69                 okay = false;
    7033                var i = x + y*passabilityMap.width;
    7134                var tilePlayer = (territoryMap.data[i] & TERRITORY_PLAYER_MASK);
     35               
     36                if (gameState.ai.myIndex !== gameState.ai.accessibility.landPassMap[i])
     37                {
     38                    obstructionTiles[i] = 0;
     39                    continue;
     40                }
     41                if (gameState.isPlayerEnemy(tilePlayer) && tilePlayer !== 0)
     42                {
     43                    obstructionTiles[i] = 0;
     44                    continue;
     45                }
     46                if ((passabilityMap.data[i] & (gameState.getPassabilityClassMask("building-shore") | gameState.getPassabilityClassMask("default"))))
     47                {
     48                    obstructionTiles[i] = 0;
     49                    continue;
     50                }
    7251
     52                okay = false;
    7353                var positions = [[0,1], [1,1], [1,0], [1,-1], [0,-1], [-1,-1], [-1,0], [-1,1]];
    7454                var available = 0;
     
    7959                    var index3 = x + stuff[0]*3 + (y+stuff[1]*3)*passabilityMap.width;
    8060                    var index4 = x + stuff[0]*4 + (y+stuff[1]*4)*passabilityMap.width;
    81                     if ((passabilityMap.data[index] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index) > 500)
    82                         if ((passabilityMap.data[index2] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index2) > 500)
    83                             if ((passabilityMap.data[index3] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index3) > 500)
    84                                 if ((passabilityMap.data[index4] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index4) > 500) {
     61                   
     62                    if ((passabilityMap.data[index] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index,true) > 500)
     63                        if ((passabilityMap.data[index2] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index2,true) > 500)
     64                            if ((passabilityMap.data[index3] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index3,true) > 500)
     65                                if ((passabilityMap.data[index4] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index4,true) > 500) {
    8566                                    if (available < 2)
    8667                                        available++;
     
    9980                                okay = false;
    10081                    }
    101                 if (gameState.ai.myIndex !== gameState.ai.accessibility.passMap[i])
    102                     okay = false;
    103                 if (gameState.isPlayerEnemy(tilePlayer) && tilePlayer !== 0)
    104                     okay = false;
    105                 if ((passabilityMap.data[i] & (gameState.getPassabilityClassMask("building-shore") | gameState.getPassabilityClassMask("default"))))
    106                     okay = false;
    10782                obstructionTiles[i] = okay ? 255 : 0;
    10883            }
     
    12196                                    (!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer != 0)
    12297                                    );
    123             var tileAccessible = (gameState.ai.myIndex === gameState.ai.accessibility.passMap[i]);
     98            if (accessIndex)
     99                var tileAccessible = (accessIndex === gameState.ai.accessibility.landPassMap[i]);
     100            else
     101                var tileAccessible = true;
    124102            if (placementType === "shore")
    125103                tileAccessible = true;
     
    127105        }
    128106    }
    129        
    130     var map = new Map(gameState, obstructionTiles);
     107   
     108    var map = new Map(gameState.sharedScript, obstructionTiles);
    131109    map.setMaxVal(255);
    132110   
     
    136114        if (minDist !== undefined && category !== undefined){
    137115            gameState.getOwnEntities().forEach(function(ent) {
    138                 if (ent.buildCategory() === category && ent.position()){
    139                     var pos = ent.position();
    140                     var x = Math.round(pos[0] / gameState.cellSize);
    141                     var z = Math.round(pos[1] / gameState.cellSize);
    142                     map.addInfluence(x, z, minDist/gameState.cellSize, -255, 'constant');
    143                 }
    144             });
     116                                               if (ent.buildCategory() === category && ent.position()){
     117                                               var pos = ent.position();
     118                                               var x = Math.round(pos[0] / gameState.cellSize);
     119                                               var z = Math.round(pos[1] / gameState.cellSize);
     120                                               map.addInfluence(x, z, minDist/gameState.cellSize, -255, 'constant');
     121                                               }
     122                                               });
    145123        }
    146124    }
    147    
    148125    return map;
    149126};
     127
     128
    150129
    151130Map.createTerritoryMap = function(gameState) {
    152131    var map = gameState.ai.territoryMap;
    153132   
    154     var ret = new Map(gameState, map.data);
     133    var ret = new Map(gameState.sharedScript, map.data);
    155134   
    156135    ret.getOwner = function(p) {
     
    162141    return ret;
    163142};
    164 
    165 Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) {
    166     strength = strength ? +strength : +maxDist;
    167     type = type ? type : 'linear';
    168    
    169     var x0 = Math.max(0, cx - maxDist);
    170     var y0 = Math.max(0, cy - maxDist);
    171     var x1 = Math.min(this.width, cx + maxDist);
    172     var y1 = Math.min(this.height, cy + maxDist);
    173     var maxDist2 = maxDist * maxDist;
    174    
    175     var str = 0.0;
    176     switch (type){
    177     case 'linear':
    178         str = +strength / +maxDist;
    179         break;
    180     case 'quadratic':
    181         str = +strength / +maxDist2;
    182         break;
    183     case 'constant':
    184         str = +strength;
    185         break;
    186     }
    187    
    188     for ( var y = y0; y < y1; ++y) {
    189         for ( var x = x0; x < x1; ++x) {
    190             var dx = x - cx;
    191             var dy = y - cy;
    192             var r2 = dx*dx + dy*dy;
    193             if (r2 < maxDist2){
    194                 var quant = 0;
    195                 switch (type){
    196                 case 'linear':
    197                     var r = Math.sqrt(r2);
    198                     quant = str * (maxDist - r);
    199                     break;
    200                 case 'quadratic':
    201                     quant = str * (maxDist2 - r2);
    202                     break;
    203                 case 'constant':
    204                     quant = str;
    205                     break;
    206                 }
    207                 if (this.map[x + y * this.width] + quant < 0)
    208                     this.map[x + y * this.width] = 0;
    209                 else if (this.map[x + y * this.width] + quant > this.maxVal)
    210                     this.map[x + y * this.width] = this.maxVal; // avoids overflow.
    211                 else
    212                     this.map[x + y * this.width] += quant;
    213             }
    214         }
    215     }
    216 };
    217 
    218 Map.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type) {
    219     strength = strength ? +strength : +maxDist;
    220     type = type ? type : 'constant';
    221    
    222     var x0 = Math.max(0, cx - maxDist);
    223     var y0 = Math.max(0, cy - maxDist);
    224     var x1 = Math.min(this.width, cx + maxDist);
    225     var y1 = Math.min(this.height, cy + maxDist);
    226     var maxDist2 = maxDist * maxDist;
    227    
    228     var str = 0.0;
    229     switch (type){
    230         case 'linear':
    231             str = strength / maxDist;
    232             break;
    233         case 'quadratic':
    234             str = strength / maxDist2;
    235             break;
    236         case 'constant':
    237             str = strength;
    238             break;
    239     }
    240    
    241     for ( var y = y0; y < y1; ++y) {
    242         for ( var x = x0; x < x1; ++x) {
    243             var dx = x - cx;
    244             var dy = y - cy;
    245             var r2 = dx*dx + dy*dy;
    246             if (r2 < maxDist2){
    247                 var quant = 0;
    248                 switch (type){
    249                     case 'linear':
    250                         var r = Math.sqrt(r2);
    251                         quant = str * (maxDist - r);
    252                         break;
    253                     case 'quadratic':
    254                         quant = str * (maxDist2 - r2);
    255                         break;
    256                     case 'constant':
    257                         quant = str;
    258                         break;
    259                 }
    260                 var machin = this.map[x + y * this.width] * quant;
    261                 if (machin < 0)
    262                     this.map[x + y * this.width] = 0;
    263                 else if (machin > this.maxVal)
    264                     this.map[x + y * this.width] = this.maxVal;
    265                 else
    266                     this.map[x + y * this.width] = machin;
    267             }
    268         }
    269     }
    270 };
    271 // doesn't check for overflow.
    272 Map.prototype.setInfluence = function(cx, cy, maxDist, value) {
    273     value = value ? value : 0;
    274    
    275     var x0 = Math.max(0, cx - maxDist);
    276     var y0 = Math.max(0, cy - maxDist);
    277     var x1 = Math.min(this.width, cx + maxDist);
    278     var y1 = Math.min(this.height, cy + maxDist);
    279     var maxDist2 = maxDist * maxDist;
    280    
    281     for ( var y = y0; y < y1; ++y) {
    282         for ( var x = x0; x < x1; ++x) {
    283             var dx = x - cx;
    284             var dy = y - cy;
    285             var r2 = dx*dx + dy*dy;
    286             if (r2 < maxDist2){
    287                 this.map[x + y * this.width] = value;
    288             }
    289         }
    290     }
    291 };
    292 
    293 /**
    294  * Make each cell's 16-bit/8-bit value at least one greater than each of its
    295  * neighbours' values. (If the grid is initialised with 0s and 65535s or 255s, the
    296  * result of each cell is its Manhattan distance to the nearest 0.)
    297  */
    298 Map.prototype.expandInfluences = function() {
    299     var w = this.width;
    300     var h = this.height;
    301     var grid = this.map;
    302     for ( var y = 0; y < h; ++y) {
    303         var min = this.maxVal;
    304         for ( var x = 0; x < w; ++x) {
    305             var g = grid[x + y * w];
    306             if (g > min)
    307                 grid[x + y * w] = min;
    308             else if (g < min)
    309                 min = g;
    310             ++min;
    311         }
    312 
    313         for ( var x = w - 2; x >= 0; --x) {
    314             var g = grid[x + y * w];
    315             if (g > min)
    316                 grid[x + y * w] = min;
    317             else if (g < min)
    318                 min = g;
    319             ++min;
    320         }
    321     }
    322 
    323     for ( var x = 0; x < w; ++x) {
    324         var min = this.maxVal;
    325         for ( var y = 0; y < h; ++y) {
    326             var g = grid[x + y * w];
    327             if (g > min)
    328                 grid[x + y * w] = min;
    329             else if (g < min)
    330                 min = g;
    331             ++min;
    332         }
    333 
    334         for ( var y = h - 2; y >= 0; --y) {
    335             var g = grid[x + y * w];
    336             if (g > min)
    337                 grid[x + y * w] = min;
    338             else if (g < min)
    339                 min = g;
    340             ++min;
    341         }
    342     }
    343 };
    344 
    345 Map.prototype.findBestTile = function(radius, obstructionTiles){
    346     // Find the best non-obstructed tile
    347     var bestIdx = 0;
    348     var bestVal = -1;
    349     for ( var i = 0; i < this.length; ++i) {
    350         if (obstructionTiles.map[i] > radius) {
    351             var v = this.map[i];
    352             if (v > bestVal) {
    353                 bestVal = v;
    354                 bestIdx = i;
    355             }
    356         }
    357     }
    358    
    359     return [bestIdx, bestVal];
    360 };
    361 
    362 // add to current map by the parameter map pixelwise
    363 Map.prototype.add = function(map){
    364     for (var i = 0; i < this.length; ++i) {
    365         if (this.map[i] + map.map[i] < 0)
    366             this.map[i] = 0;
    367         else if (this.map[i] + map.map[i] > this.maxVal)
    368             this.map[i] = this.maxVal;
    369         else
    370             this.map[i] += map.map[i];
    371     }
    372 };
    373 
    374 Map.prototype.dumpIm = function(name, threshold){
    375     name = name ? name : "default.png";
    376     threshold = threshold ? threshold : this.maxVal;
    377     Engine.DumpImage(name, this.map, this.width, this.height, threshold);
    378 };
  • ps/trunk/binaries/data/mods/public/simulation/ai/aegis/queue-manager.js

    r13683 r13907  
    1616// Particularly noticeable when phasing: the AI often overshoots by a good 200/300 resources before starting.
    1717//
    18 // The fact that there is an outqueue is mostly a relic of qBot.
    19 //
    2018// This system should be improved. It's probably not flexible enough.
    2119
     
    3735
    3836    this.curItemQueue = [];
    39    
    4037};
    4138
     
    5047};
    5148
    52 QueueManager.prototype.futureNeeds = function(gameState, EcoManager) {
     49QueueManager.prototype.getTotalAccountedResources = function(gameState) {
     50    var resources = new Resources();
     51    for (var key in this.queues) {
     52        resources.add(this.accounts[key]);
     53    }
     54    return resources;
     55};
     56
     57QueueManager.prototype.currentNeeds = function(gameState) {
    5358    var needs = new Resources();
    54     // get ouy current resources, not removing accounts.
     59    // get out current resources, not removing accounts.
    5560    var current = this.getAvailableResources(gameState, true);
    5661    //queueArrays because it's faster.
     
    5964        var name = this.queueArrays[i][0];
    6065        var queue = this.queueArrays[i][1];
    61         for (var j = 0; j < Math.min(2,queue.length()); ++j)
    62         {
    63             needs.add(queue.queue[j].getCost());
    64         }
    65     }
    66     if (EcoManager === false) {
    67         return {
    68             "food" : Math.max(needs.food - current.food, 0),
    69             "wood" : Math.max(needs.wood - current.wood, 0),
    70             "stone" : Math.max(needs.stone - current.stone, 0),
    71             "metal" : Math.max(needs.metal - current.metal, 0)
    72         };
    73     } else {
    74         // Return predicted values minus the current stockpiles along with a base rater for all resources
    75         return {
    76             "food" : (Math.max(needs.food - current.food, 0) + EcoManager.baseNeed["food"])/2,
    77             "wood" : (Math.max(needs.wood - current.wood, 0) + EcoManager.baseNeed["wood"])/2,
    78             "stone" : (Math.max(needs.stone - current.stone, 0) + EcoManager.baseNeed["stone"])/2,
    79             "metal" : (Math.max(needs.metal - current.metal, 0) + EcoManager.baseNeed["metal"])/2
    80         };
    81     }
    82 };
     66        if (queue.length() > 0 && queue.getNext().isGo(gameState))
     67            needs.add(queue.getNext().getCost());
     68        else if (queue.length() > 0 && !queue.getNext().isGo(gameState))
     69        {
     70            var cost = queue.getNext().getCost();
     71            cost.multiply(0.5);
     72            needs.add(cost);
     73        }
     74        if (queue.length() > 1 && queue.queue[1].isGo(gameState))
     75            needs.add(queue.queue[1].getCost());
     76    }
     77    return {
     78        "food" : Math.max(25 + needs.food - current.food, 0),
     79        "wood" : Math.max(needs.wood - current.wood, 0),
     80        "stone" : Math.max(needs.stone - current.stone, 0),
     81        "metal" : Math.max(needs.metal - current.metal, 0)
     82    };
     83};
     84
     85QueueManager.prototype.futureNeeds = function(gameState) {
     86    var needs = new Resources();
     87    // get out current resources, not removing accounts.
     88    var current = this.getAvailableResources(gameState, true);
     89    //queueArrays because it's faster.
     90    for (var i in this.queueArrays)
     91    {
     92        var name = this.queueArrays[i][0];
     93        var queue = this.queueArrays[i][1];
     94        for (var j = 0; j < queue.length(); ++j)
     95        {
     96            var costs = queue.queue[j].getCost();
     97            if (!queue.queue[j].isGo(gameState))
     98                costs.multiply(0.5);
     99            needs.add(costs);
     100        }
     101    }
     102    return {
     103        "food" : Math.max(25 + needs.food - current.food, 10),
     104        "wood" : Math.max(needs.wood - current.wood, 10),
     105        "stone" : Math.max(needs.stone - current.stone, 0),
     106        "metal" : Math.max(needs.metal - current.metal, 0)
     107    };
     108};
     109
     110// calculate the gather rates we'd want to be able to use all elements in our queues
     111QueueManager.prototype.wantedGatherRates = function(gameState) {
     112    var rates = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 };
     113    var qTime = gameState.getTimeElapsed();
     114    var qCosts = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 };
     115
     116    var currentRess = this.getAvailableResources(gameState);
     117   
     118    //queueArrays because it's faster.
     119    for (var i in this.queueArrays)
     120    {
     121        qCosts = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 };
     122        qTime = gameState.getTimeElapsed();
     123        var name = this.queueArrays[i][0];
     124        var queue = this.queueArrays[i][1];
     125
     126        for (var j = 0; j < queue.length(); ++j)
     127        {
     128            var elem = queue.queue[j];
     129            var cost = elem.getCost();
     130            if (qTime < elem.startTime)
     131                qTime = elem.startTime;
     132            if (!elem.isGo(gameState))
     133            {
     134                // assume 2 minutes.
     135                // TODO work on this.
     136                for (type in qCosts)
     137                    qCosts[type] += cost[type];
     138                qTime += 120000;
     139                break;  // disregard other stuffs.
     140            }
     141            if (!elem.endTime)
     142            {
     143                // estimate time based on priority + cost + nb
     144                // TODO: work on this.
     145                for (type in qCosts)
     146                    qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name]));
     147                qTime += 30000;
     148            } else {
     149                // TODO: work on this.
     150                for (type in qCosts)
     151                    qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name]));
     152                // TODO: refine based on % completed.
     153                qTime += (elem.endTime-elem.startTime);
     154            }
     155        }
     156        for (j in qCosts)
     157        {
     158            qCosts[j] -= this.accounts[name][j];
     159            var diff = Math.min(qCosts[j], currentRess[j]);
     160            qCosts[j] -= diff;
     161            currentRess[j] -= diff;
     162            rates[j] += qCosts[j]/(qTime/1000);
     163        }
     164    }
     165    return rates;
     166};
     167
     168/*QueueManager.prototype.logNeeds = function(gameState) {
     169 if (!this.totor)
     170 {
     171 this.totor = [];
     172 this.currentGathR = [];
     173 this.currentGathRWanted = [];
     174 this.ressLev = [];
     175}
     176 
     177    if (gameState.ai.playedTurn % 10 !== 0)
     178        return;
     179   
     180   
     181    var array = this.wantedGatherRates(gameState);
     182    this.totor.push( array );
     183   
     184   
     185    var currentRates = {};
     186    for (var type in array)
     187        currentRates[type] = 0;
     188    for (i in gameState.ai.HQ.baseManagers)
     189    {
     190        var base = gameState.ai.HQ.baseManagers[i];
     191        for (var type in array)
     192        {
     193            base.gatherersByType(gameState,type).forEach (function (ent) { //}){
     194                                                          var worker = ent.getMetadata(PlayerID, "worker-object");
     195                                                          if (worker)
     196                                                          currentRates[type] += worker.getGatherRate(gameState);
     197                                                          });
     198        }
     199    }
     200    this.currentGathR.push( currentRates );
     201   
     202    var types = Object.keys(array);
     203   
     204    types.sort(function(a, b) {
     205               var va = (Math.max(0,array[a] - currentRates[a]))/ (currentRates[a]+1);
     206               var vb = (Math.max(0,array[b] - currentRates[b]))/ (currentRates[b]+1);
     207               if (va === vb)
     208               return (array[b]/(currentRates[b]+1)) - (array[a]/(currentRates[a]+1));
     209               return vb-va;
     210               });
     211    this.currentGathRWanted.push( types );
     212
     213    var rss = gameState.getResources();
     214    this.ressLev.push( {"food" : rss["food"],"stone" : rss["stone"],"wood" : rss["wood"],"metal" : rss["metal"]} );
     215   
     216    if (gameState.getTimeElapsed() > 20*60*1000 && !this.once)
     217    {
     218        this.once = true;
     219        for (j in array)
     220        {
     221            log (j + ";");
     222            for (var i = 0; i < this.totor.length; ++i)
     223            {
     224                log (this.totor[i][j] + ";");
     225            }
     226        }
     227        log();
     228        for (j in array)
     229        {
     230            log (j + ";");
     231            for (var i = 0; i < this.totor.length; ++i)
     232            {
     233                log (this.currentGathR[i][j] + ";");
     234            }
     235        }
     236        log();
     237        for (j in array)
     238        {
     239            log (j + ";");
     240            for (var i = 0; i < this.totor.length; ++i)
     241            {
     242                log (this.currentGathRWanted[i].indexOf(j) + ";");
     243            }
     244        }
     245        log();
     246        for (j in array)
     247        {
     248            log (j + ";");
     249            for (var i = 0; i < this.totor.length; ++i)
     250            {
     251                log (this.ressLev[i][j] + ";");
     252            }
     253        }
     254    }
     255};
     256*/
    83257
    84258QueueManager.prototype.printQueues = function(gameState){
    85     debug("OUTQUEUES");
    86     for (var i in this.queues){
    87         var qStr = "";
    88         var q = this.queues[i];
    89         if (q.outQueue.length > 0)
    90             debug((i + ":"));
    91         for (var j in q.outQueue){
    92             qStr = "    " + q.outQueue[j].type + " ";
    93             if (q.outQueue[j].number)
    94                 qStr += "x" + q.outQueue[j].number;
    95             debug (qStr);
    96         }
    97     }
    98    
    99     debug("INQUEUES");
     259    debug("QUEUES");
    100260    for (var i in this.queues){
    101261        var qStr = "";
     
    116276    }
    117277    debug("Needed Resources:" + uneval(this.futureNeeds(gameState,false)));
     278    debug ("Wanted Gather Rates:" + uneval(this.wantedGatherRates(gameState)));
    118279    debug ("Current Resources:" + uneval(gameState.getResources()));
    119280    debug ("Available Resources:" + uneval(this.getAvailableResources(gameState)));
     281};
     282
     283// nice readable HTML version.
     284QueueManager.prototype.HTMLprintQueues = function(gameState){
     285    if (!Config.debug)
     286        return;
     287    log("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\"> <html> <head> <title>Aegis Queue Manager</title> <link rel=\"stylesheet\" href=\"table.css\">  </head> <body> <table> <caption>Aegis Build Order</caption> ");
     288    for (var i in this.queues){
     289        log ("<tr>");
     290       
     291        var q = this.queues[i];
     292        var str = "<th>" + i +"<br>";
     293        for each (k in this.accounts[i].types)
     294            if(k != "population")
     295            {
     296                str += this.accounts[i][k] + k.substr(0,1).toUpperCase() ;
     297                if (k != "metal") str += " / ";
     298            }
     299        log(str + "</th>");
     300        for (var j in q.queue) {
     301            if (q.queue[j].isGo(gameState))
     302                log ("<td>");
     303            else
     304                log ("<td class=\"NotGo\">");
     305
     306            var qStr = "";
     307            qStr += q.queue[j].type;
     308            if (q.queue[j].number)
     309                qStr += "x" + q.queue[j].number;
     310            log (qStr);
     311            log ("</td>");
     312        }
     313        log ("</tr>");
     314    }
     315    log ("</table>");
     316    /*log ("<h3>Accounts</h3>");
     317    for (var p in this.accounts)
     318    {
     319        log("<p>" + p + ": " + uneval(this.accounts[p]) + " </p>");
     320    }*/
     321    log ("<p>Needed Resources:" + uneval(this.futureNeeds(gameState,false)) + "</p>");
     322    log ("<p>Wanted Gather Rate:" + uneval(this.wantedGatherRates(gameState)) + "</p>");
     323    log ("<p>Current Resources:" + uneval(gameState.getResources()) + "</p>");
     324    log ("<p>Available Resources:" + uneval(this.getAvailableResources(gameState)) + "</p>");
     325    log("</body></html>");
    120326};
    121327
     
    137343   
    138344    Engine.ProfileStart("Queue Manager");
    139    
    140     //if (gameState.ai.playedTurn % 10 === 0)
    141     //  this.printQueues(gameState);
    142    
    143     Engine.ProfileStart("Pick items from queues");
    144    
    145     // TODO: this only pushes the first object. SHould probably try to push any possible object to maximize productivity. Perhaps a settinh?
    146     // looking at queues in decreasing priorities and pushing to the current item queues.
    147     for (var i in this.queueArrays)
    148     {
    149         var name = this.queueArrays[i][0];
    150         var queue = this.queueArrays[i][1];
    151         if (queue.length() > 0)
    152         {
    153             var item = queue.getNext();
    154             var total = new Resources();
    155             total.add(this.accounts[name]);
    156             total.subtract(queue.outQueueCost());
    157             if (total.canAfford(item.getCost()))
    158             {
    159                 queue.nextToOutQueue();
    160             }
    161         } else if (queue.totalLength() === 0) {
    162             this.accounts[name].reset();
    163         }
    164     }
    165    
     345           
     346    // Let's assign resources to plans that need'em
    166347    var availableRes = this.getAvailableResources(gameState);
    167     // assign some accounts to queues. This is done by priority, and by need.
    168348    for (var ress in availableRes)
    169349    {
     
    174354            var maxNeed = {};
    175355            // Okay so this is where it gets complicated.
    176             // If a queue requires "ress" for the next elements (in the queue or the outqueue)
     356            // If a queue requires "ress" for the next elements (in the queue)
    177357            // And the account is not high enough for it.
    178358            // Then we add it to the total priority.
     
    183363            // uselessly while it awaits for other resources.
    184364            for (var j in this.queues) {
    185                 var outQueueCost = this.queues[j].outQueueCost();
    186                 var queueCost = this.queues[j].queueCost();
    187                 if (this.accounts[j][ress] < queueCost[ress] + outQueueCost[ress])
     365                // returns exactly the correct amount, ie 0 if we're not go.
     366                var queueCost = this.queues[j].maxAccountWanted(gameState);
     367                if (this.queues[j].length() > 0 && this.accounts[j][ress] < queueCost[ress] && !this.queues[j].paused)
    188368                {
     369                    // check that we're not too forward in this resource compared to others.
     370                    /*var maxp = this.accounts[j][ress] / (queueCost[ress]+1);
     371                    var tooFull = false;
     372                    for (tempRess in availableRes)
     373                        if (tempRess !== ress && queueCost[tempRess] > 0 && (this.accounts[j][tempRess] / (queueCost[tempRess]+1)) - maxp < -0.2)
     374                            tooFull = true;
     375                    if (tooFull)
     376                        continue;*/
     377                   
    189378                    // adding us to the list of queues that need an update.
    190379                    tempPrio[j] = this.priorities[j];
    191                     maxNeed[j] = outQueueCost[ress] + this.queues[j].getNext().getCost()[ress];
    192                     // if we have enough of that resource for the outqueue and our first resource in the queue, diminish our priority.
    193                     if (this.accounts[j][ress] >= outQueueCost[ress] + this.queues[j].getNext().getCost()[ress])
    194                     {
     380                    maxNeed[j] = queueCost[ress] - this.accounts[j][ress];
     381                    // if we have enough of that resource for our first item in the queue, diminish our priority.
     382                    if (this.accounts[j][ress] >= this.queues[j].getNext().getCost()[ress])
    195383                        tempPrio[j] /= 2;
    196                         if (this.queues[j].length() !== 1)
    197                         {
    198                             var halfcost = this.queues[j].queue[1].getCost()[ress]*0.8;
    199                             maxNeed[j] += halfcost;
    200                             if (this.accounts[j][ress] >= outQueueCost[ress] + this.queues[j].getNext().getCost()[ress] + halfcost)
    201                                 delete tempPrio[j];
    202                         }
    203                     }
     384
    204385                    if (tempPrio[j])
    205386                        totalPriority += tempPrio[j];
     387                }
     388                else if (this.accounts[j][ress] > queueCost[ress])
     389                {
     390                    this.accounts[j][ress] = queueCost[ress];
    206391                }
    207392            }
     
    210395            for (var j in tempPrio) {
    211396                // we'll add at much what can be allowed to this queue.
    212                 var toAdd = Math.floor(tempPrio[j]/totalPriority * availableRes[ress]);
    213                 // let's check we're not adding too much.
    214                 var maxAdd = Math.min(maxNeed[j] - this.accounts[j][ress], toAdd);
     397                var toAdd = tempPrio[j]/totalPriority * availableRes[ress];
     398                var maxAdd = Math.floor(Math.min(maxNeed[j], toAdd));
    215399                this.accounts[j][ress] += maxAdd;
    216400            }
    217         }
    218     }
     401        }/* else if (ress != "population" && gameState.ai.playedTurn % 5 === 0) {
     402            // okay here we haev no resource available. We'll try to shift resources to complete plans if possible.
     403            // So basically if 2 queues have resources, and one is higher priority, and it needs resources
     404            // We'll shift from the lower priority to the higher if we can complete it.
     405            var queues = [];
     406            for (var j in this.queues) {
     407                if (this.queues[j].length() && this.queues[j].getNext().isGo(gameState) && this.accounts[j][ress] > 0)
     408                    queues.push(j);
     409            }
     410            if (queues.length > 1)
     411            {
     412                // we'll work from the bottom to the top. ie lowest priority will try to give to highest priority.
     413                queues.sort(function (a,b) { return (self.priorities[a] < self.priorities[b]); });
     414                var under = 0, over = queues.length - 1;
     415                while (under !== over)
     416                {
     417                    var cost = this.queues[queues[over]].getNext().getCost()[ress];
     418                    var totalCost = this.queues[queues[over]].maxAccountWanted(gameState)[ress];
     419                    if (this.accounts[queues[over]] >= cost)
     420                    {
     421                        --over; // check the next one.
     422                        continue;
     423                    }
     424                    // need some discrepancy in priorities
     425                    if (this.priorities[queues[under]] < this.priorities[queues[over]] - 20)
     426                    {
     427                        if (this.accounts[queues[under]] + this.accounts[queues[over]] >= cost)
     428                        {
     429                            var amnt = cost - this.accounts[queues[over]];
     430                            this.accounts[queues[under]] -= amnt;
     431                            this.accounts[queues[over]] += amnt;
     432                            --over;
     433                            debug ("Shifting " + amnt + " from " + queues[under] + " to " +queues[over]);
     434                            continue;
     435                        } else {
     436                            ++under;
     437                            continue;
     438                        }
     439                    } else {
     440                        break;
     441                    }
     442                }
     443                // okaaaay.
     444            }
     445        }*/
     446    }
     447   
     448    Engine.ProfileStart("Pick items from queues");
     449
     450    //debug ("start");
     451    //debug (uneval(this.accounts));
     452    // Start the next item in the queue if we can afford it.
     453    for (var i in this.queueArrays)
     454    {
     455        var name = this.queueArrays[i][0];
     456        var queue = this.queueArrays[i][1];
     457        if (queue.length() > 0 && !queue.paused)
     458        {
     459            var item = queue.getNext();
     460            var total = new Resources();
     461            total.add(this.accounts[name]);
     462            if (total.canAfford(item.getCost()))
     463            {
     464                if (item.canStart(gameState))
     465                {
     466                    this.accounts[name].subtract(item.getCost());
     467                    queue.startNext(gameState);
     468                }
     469            }
     470        } else if (queue.length() === 0) {
     471            this.accounts[name].reset();
     472        }
     473    }
     474    //debug (uneval(this.accounts));
     475   
    219476    Engine.ProfileStop();
    220 
    221     Engine.ProfileStart("Execute items");
    222    
    223     var units_Techs_passed = 0;
    224     // Handle output queues by executing items where possible
    225     for (var p in this.queueArrays) {
    226         var name = this.queueArrays[p][0];
    227         var queue = this.queueArrays[p][1];
    228         var next = queue.outQueueNext();
    229         if (!next)
    230             continue;
    231         if (next.category === "building") {
    232             if (gameState.buildingsBuilt == 0) {
    233                 if (next.canExecute(gameState)) {
    234                     this.accounts[name].subtract(next.getCost())
    235                     //debug ("Starting " + next.type + " substracted " + uneval(next.getCost()))
    236                     queue.executeNext(gameState);
    237                     gameState.buildingsBuilt += 1;
    238                 }
    239             }
    240         } else {
    241             if (units_Techs_passed < 2 && queue.outQueueNext().canExecute(gameState)){
    242                 //debug ("Starting " + next.type + " substracted " + uneval(next.getCost()))
    243                 this.accounts[name].subtract(next.getCost())
    244                 queue.executeNext(gameState);
    245                 units_Techs_passed++;
    246             }
    247         }
    248         if (units_Techs_passed >= 2)
    249             continue;
    250     }
     477   
     478    if (gameState.ai.playedTurn % 30 === 0)
     479        this.HTMLprintQueues(gameState);
     480
    251481    Engine.ProfileStop();
    252     Engine.ProfileStop();
    253 };
     482};
     483
     484QueueManager.prototype.pauseQueue = function(queue, scrapAccounts) {
     485    if (this.queues[queue])
     486    {
     487        this.queues[queue].paused = true;
     488        if (scrapAccounts)
     489            this.accounts[queue].reset();
     490    }
     491}
     492
     493QueueManager.prototype.unpauseQueue = function(queue) {
     494    if (this.queues[queue])
     495        this.queues[queue].paused = false;
     496}
     497
     498QueueManager.prototype.pauseAll = function(scrapAccounts, but) {
     499    for (var p in this.queues)
     500        if (p != but)
     501        {
     502            if (scrapAccounts)
     503                this.accounts[p].reset();
     504            this.queues[p].paused = true;
     505        }
     506}
     507
     508QueueManager.prototype.unpauseAll = function(but) {
     509    for (var p in this.queues)
     510        if (p != but)
     511            this.queues[p].paused = false;
     512}
     513
    254514
    255515QueueManager.prototype.addQueue = function(queueName, priority) {
  • ps/trunk/binaries/data/mods/public/simulation/ai/aegis/queue.js

    r13683 r13907  
    55var Queue = function() {
    66    this.queue = [];
    7     this.outQueue = [];
     7    this.paused = false;
    88};
    99
    1010Queue.prototype.empty = function() {
    1111    this.queue = [];
    12     this.outQueue = [];
    1312};
    14 
    1513
    1614Queue.prototype.addItem = function(plan) {
     
    3432};
    3533
    36 Queue.prototype.outQueueNext = function(){
    37     if (this.outQueue.length > 0) {
    38         return this.outQueue[0];
     34Queue.prototype.startNext = function(gameState) {
     35    if (this.queue.length > 0) {
     36        this.queue.shift().start(gameState);
     37        return true;
    3938    } else {
    40         return null;
     39        return false;
    4140    }
    4241};
    4342
    44 Queue.prototype.outQueueCost = function(){
     43// returns the maximal account we'll accept for this queue.
     44// Currently 100% of the cost of the first element and 80% of that of the second
     45Queue.prototype.maxAccountWanted = function(gameState) {
    4546    var cost = new Resources();
    46     for (var key in this.outQueue){
    47         cost.add(this.outQueue[key].getCost());
     47    if (this.queue.length > 0 && this.queue[0].isGo(gameState))
     48        cost.add(this.queue[0].getCost());
     49    if (this.queue.length > 1 && this.queue[1].isGo(gameState))
     50    {
     51        var costs = this.queue[1].getCost();
     52        costs.multiply(0.8);
     53        cost.add(costs);
    4854    }
    4955    return cost;
     
    5662    }
    5763    return cost;
    58 };
    59 
    60 Queue.prototype.nextToOutQueue = function(){
    61     if (this.queue.length > 0){
    62         this.outQueue.push(this.queue.shift());
    63     }
    64 };
    65 
    66 Queue.prototype.executeNext = function(gameState) {
    67     if (this.outQueue.length > 0) {
    68         this.outQueue.shift().execute(gameState);
    69         return true;
    70     } else {
    71         return false;
    72     }
    7364};
    7465
     
    8576};
    8677
    87 Queue.prototype.countOutQueuedUnits = function(){
    88     var count = 0;
    89     for (var i in this.outQueue){
    90         count += this.outQueue[i].number;
    91     }
    92     return count;
    93 };
    94 
    95 Queue.prototype.countTotalQueuedUnits = function(){
    96     var count = 0;
    97     for (var i in this.queue){
    98         count += this.queue[i].number;
    99     }
    100     for (var i in this.outQueue){
    101         count += this.outQueue[i].number;
    102     }
    103     return count;
    104 };
    105 Queue.prototype.countTotalQueuedUnitsWithClass = function(classe){
     78Queue.prototype.countQueuedUnitsWithClass = function(classe){
    10679    var count = 0;
    10780    for (var i in this.queue){
     
    10982            count += this.queue[i].number;
    11083    }
    111     for (var i in this.outQueue){
    112         if (this.outQueue[i].template && this.outQueue[i].template.hasClass(classe))
    113             count += this.outQueue[i].number;
    114     }
    11584    return count;
    11685};
    117 Queue.prototype.countTotalQueuedUnitsWithMetadata = function(data,value){
     86Queue.prototype.countQueuedUnitsWithMetadata = function(data,value){
    11887    var count = 0;
    11988    for (var i in this.queue){
     
    12190            count += this.queue[i].number;
    12291    }
    123     for (var i in this.outQueue){
    124         if (this.outQueue[i].metadata[data] && this.outQueue[i].metadata[data] == value)
    125             count += this.outQueue[i].number;
    126     }
    12792    return count;
    128 };
    129 
    130 Queue.prototype.totalLength = function(){
    131     return this.queue.length + this.outQueue.length;
    132 };
    133 
    134 Queue.prototype.outQueueLength = function(){
    135     return this.outQueue.length;
    13693};
    13794
     
    144101        }
    145102    }
    146     for (var i = 0; i < this.outQueue.length; i++){
    147         if (this.outQueue[i].type === t){
    148             count += this.outQueue[i].number;
    149         }
    150     }
    151103    return count;
    152104};
  • ps/trunk/binaries/data/mods/public/simulation/ai/aegis/worker.js

    r13683 r13907  
    77    this.maxApproachTime = 45000;
    88    this.unsatisfactoryResource = false;    // if true we'll reguarly check if we can't have better now.
    9 };
    10 
    11 Worker.prototype.update = function(gameState) {
    12    
     9    this.baseID = 0;
     10};
     11
     12Worker.prototype.update = function(baseManager, gameState) {
     13    this.baseID = baseManager.ID;
    1314    var subrole = this.ent.getMetadata(PlayerID, "subrole");
    1415
     
    2021        this.ent.setMetadata(PlayerID,"fleeing", undefined);
    2122   
     23    // Okay so we have a few tasks.
     24    // If we're gathering, we'll check that we haven't run idle.
     25    // ANd we'll also check that we're gathering a resource we want to gather.
     26   
     27    // If we're fighting, let's not start gathering, heh?
     28    // TODO: remove this when we're hunting?
     29    if (this.ent.unitAIState().split(".")[1] === "COMBAT" || this.ent.getMetadata(PlayerID, "role") === "transport")
     30    {
     31        return;
     32    }
     33   
    2234    if (subrole === "gatherer") {
    23         if (this.ent.unitAIState().split(".")[1] !== "GATHER" && this.ent.unitAIState().split(".")[1] !== "COMBAT" && this.ent.unitAIState().split(".")[1] !== "RETURNRESOURCE"){
    24             // TODO: handle combat for hunting animals
    25             if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0 ||
     35        if (this.ent.isIdle()) {
     36            // if we aren't storing resources or it's the same type as what we're about to gather,
     37            // let's just pick a new resource.
     38            if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0 ||
    2639                    this.ent.resourceCarrying()[0].type === this.ent.getMetadata(PlayerID, "gather-type")){
    2740                Engine.ProfileStart("Start Gathering");
    28                 this.startGathering(gameState);
     41                this.unsatisfactoryResource = false;
     42                this.startGathering(baseManager,gameState);
    2943                Engine.ProfileStop();
     44
     45                this.startApproachingResourceTime = gameState.getTimeElapsed();
     46
    3047            } else {
    3148                // Should deposit resources
     
    3552                    // no dropsite, abandon cargo.
    3653                   
    37                     // if we have a new order
     54                    // if we were ordered to gather something else, try that.
    3855                    if (this.ent.resourceCarrying()[0].type !== this.ent.getMetadata(PlayerID, "gather-type"))
    39                         this.startGathering(gameState);
     56                        this.startGathering(baseManager,gameState);
    4057                    else {
     58                        // okay so we haven't found a proper dropsite for the resource we're supposed to gather
     59                        // so let's get idle and the base manager will reassign us, hopefully well.
    4160                        this.ent.setMetadata(PlayerID, "gather-type",undefined);
    4261                        this.ent.setMetadata(PlayerID, "subrole", "idle");
     
    4665                Engine.ProfileStop();
    4766            }
    48             this.startApproachingResourceTime = gameState.getTimeElapsed();
    49            
     67            // debug: show the resource we're gathering from
    5068            //Engine.PostCommand({"type": "set-shading-color", "entities": [this.ent.id()], "rgb": [10,0,0]});
    5169        } else if (this.ent.unitAIState().split(".")[1] === "GATHER") {
    52             if (this.unsatisfactoryResource && (this.ent.id() + gameState.ai.playedTurn) % 20 === 0)
     70           
     71            // check for transport.
     72            if (gameState.ai.playedTurn % 5 === 0)
     73            {
     74                if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[2] === "APPROACHING" && this.ent.unitAIOrderData()[0]["target"])
     75                {
     76                    var ress = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]);
     77                    if (ress !== undefined)
     78                    {
     79                        var index = gameState.ai.accessibility.getAccessValue(ress.position());
     80                        var mIndex = gameState.ai.accessibility.getAccessValue(this.ent.position());
     81                        if (index !== mIndex && index !== 1)
     82                        {
     83                            //gameState.ai.HQ.navalManager.askForTransport(this.ent.id(), this.ent.position(), ress.position());
     84                        }
     85                    }
     86                }
     87            }
     88           
     89            /*
     90            if (gameState.getTimeElapsed() - this.startApproachingResourceTime > this.maxApproachTime)
     91            {
     92                if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[1] === "GATHER"
     93                    && this.ent.unitAIOrderData()[0]["target"])
     94                {
     95                    var ent = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]);
     96                    debug ("here " + this.startApproachingResourceAmount + "," + ent.resourceSupplyAmount());
     97                    if (ent && this.startApproachingResourceAmount == ent.resourceSupplyAmount() && this.startEnt == ent.id()) {
     98                        debug (ent.toString() + " is inaccessible");
     99                        ent.setMetadata(PlayerID, "inaccessible", true);
     100                        this.ent.flee(ent);
     101                        this.ent.setMetadata(PlayerID, "subrole", "idle");
     102                    }
     103                }
     104            }*/
     105           
     106            // we're gathering. Let's check that it's not a resource we'd rather not gather from.
     107            if ((this.ent.id() + gameState.ai.playedTurn) % 6 === 0 && this.checkUnsatisfactoryResource(gameState))
    53108            {
    54109                Engine.ProfileStart("Start Gathering");
    55                 this.startGathering(gameState);
     110                this.startGathering(baseManager,gameState);
    56111                Engine.ProfileStop();
    57112            }
     113            // TODO: reimplement the "reaching time" check.
    58114            /*if (gameState.getTimeElapsed() - this.startApproachingResourceTime > this.maxApproachTime) {
    59115                if (this.gatheringFrom) {
     
    68124                    }
    69125                }
    70             }*/
     126             }*/
    71127        } else if (this.ent.unitAIState().split(".")[1] === "COMBAT") {
    72128            /*if (gameState.getTimeElapsed() - this.startApproachingResourceTime > this.maxApproachTime) {
     
    81137                }
    82138            }*/
    83         } else {
    84             this.startApproachingResourceTime = gameState.getTimeElapsed();
    85139        }
    86140    } else if(subrole === "builder") {
    87         if (this.ent.unitAIState().split(".")[1] !== "REPAIR"){
    88             var target = this.ent.getMetadata(PlayerID, "target-foundation");
    89             if (target.foundationProgress() === undefined && target.needsRepair() == false)
    90                 this.ent.setMetadata(PlayerID, "subrole", "idle");
     141       
     142        // check for transport.
     143        if (gameState.ai.playedTurn % 5 === 0)
     144        {
     145            if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[2] === "APPROACHING" && this.ent.unitAIOrderData()[0]["target"])
     146            {
     147                var ress = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]);
     148                if (ress !== undefined)
     149                {
     150                    var index = gameState.ai.accessibility.getAccessValue(ress.position());
     151                    var mIndex = gameState.ai.accessibility.getAccessValue(this.ent.position());
     152                    if (index !== mIndex && index !== 1)
     153                    {
     154                        //gameState.ai.HQ.navalManager.askForTransport(this.ent.id(), this.ent.position(), ress.position());
     155                    }
     156                }
     157            }
     158        }
     159
     160       
     161        if (this.ent.unitAIState().split(".")[1] !== "REPAIR") {
     162            var target = gameState.getEntityById(this.ent.getMetadata(PlayerID, "target-foundation"));
     163            // okay so apparently we aren't working.
     164            // Unless we've been explicitely told to keep our role, make us idle.
     165            if (!target || target.foundationProgress() === undefined && target.needsRepair() == false)
     166            {
     167                if (!this.ent.getMetadata(PlayerID, "keepSubrole"))
     168                    this.ent.setMetadata(PlayerID, "subrole", "idle");
     169            }
    91170            else
    92171                this.ent.repair(target);
     
    94173        this.startApproachingResourceTime = gameState.getTimeElapsed();
    95174        //Engine.PostCommand({"type": "set-shading-color", "entities": [this.ent.id()], "rgb": [0,10,0]});
     175        // TODO: we should maybe decide on our own to build other buildings, not rely on the assigntofoundation stuff.
    96176    }  else if(subrole === "hunter") {
    97         if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0){
     177        if (this.ent.isIdle()){
    98178            Engine.ProfileStart("Start Hunting");
    99             this.startHunting(gameState);
     179            this.startHunting(gameState, baseManager);
    100180            Engine.ProfileStop();
    101181        }
    102     } else {
    103         this.startApproachingResourceTime = gameState.getTimeElapsed();
    104     }
    105 };
    106 
    107 Worker.prototype.startGathering = function(gameState){
     182    }
     183};
     184
     185// check if our current resource is unsatisfactory
     186// this can happen in two ways:
     187//      -either we were on an unsatisfactory resource last time we started gathering (this.unsatisfactoryResource)
     188//      -Or we auto-moved to a bad resource thanks to the great UnitAI.
     189Worker.prototype.checkUnsatisfactoryResource = function(gameState) {
     190    if (this.unsatisfactoryResource)
     191        return true;
     192    if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[1] === "GATHER" && this.ent.unitAIState().split(".")[2] === "GATHERING" && this.ent.unitAIOrderData()[0]["target"])
     193    {
     194        var ress = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]);
     195        if (!ress || !ress.getMetadata(PlayerID,"linked-dropsite") || !ress.getMetadata(PlayerID,"linked-dropsite-nearby") || gameState.ai.accessibility.getAccessValue(ress.position()) === -1)
     196            return true;
     197    }
     198    return false;
     199};
     200
     201Worker.prototype.startGathering = function(baseManager, gameState) {
    108202    var resource = this.ent.getMetadata(PlayerID, "gather-type");
    109203    var ent = this.ent;
     204    var self = this;
    110205   
    111206    if (!ent.position()){
     
    114209    }
    115210   
    116     this.unsatisfactoryResource = false;
    117 
    118211    // TODO: this is not necessarily optimal.
    119212   
     
    126219    // TODO: this is a huge part of multi-base support. Count only those in the same base as the worker.
    127220    var number = 0;
    128     var ourDropsites = gameState.getOwnDropsites(resource);
    129    
     221   
     222    var ourDropsites = EntityCollectionFromIds(gameState,Object.keys(baseManager.dropsites));
    130223    if (ourDropsites.length === 0)
    131224    {
     
    134227    }
    135228   
     229    var maxPerDP = 20;
     230    if (resource === "food")
     231        maxPerDP = 200;
     232   
    136233    ourDropsites.forEach(function (dropsite) {
    137         if (dropsite.getMetadata(PlayerID, "linked-resources-" +resource) !== undefined
    138             && dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) !== undefined && dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) > 200) {
     234        if (baseManager.dropsites[dropsite.id()][resource] && baseManager.dropsites[dropsite.id()][resource][4] > 1000
     235            && baseManager.dropsites[dropsite.id()][resource][5].length < maxPerDP)
    139236            number++;
    140         }
    141237    });
    142    
    143     //debug ("Available " +resource + " dropsites: " +ourDropsites.length);
    144238   
    145239    // Allright second step, if there are any such dropsites, we pick the closest.
     
    148242    {
    149243        ourDropsites.forEach(function (dropsite) { //}){
    150             if (dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) == undefined)
    151                 return;
    152             if (dropsite.position() && (dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) > 700 || (number === 1 && dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) > 200) ) ) {
     244            if (baseManager.dropsites[dropsite.id()][resource] === undefined)
     245                return;
     246            if (dropsite.position() && (baseManager.dropsites[dropsite.id()][resource][4] > 1000 || (number === 1 && baseManager.dropsites[dropsite.id()][resource][4] > 200) )
     247                && baseManager.dropsites[dropsite.id()][resource][5].length < maxPerDP) {
    153248                var dist = SquareVectorDistance(ent.position(), dropsite.position());
    154249                if (dist < minDropsiteDist){
    155250                    minDropsiteDist = dist;
    156                     nearestResources = dropsite.getMetadata(PlayerID, "linked-resources-" + resource);
     251                    nearestResources = baseManager.dropsites[dropsite.id()][resource][1];
    157252                    nearestDropsite = dropsite;
    158253                }
     
    160255        });
    161256    }
    162     //debug ("Nearest dropsite: " +nearestDropsite);
    163    
    164     // Now if we have no dropsites, we repeat the process with resources "far" from dropsites but still linked with them.
    165     // I add the "close" value for code sanity.
    166     // Again, we choose a dropsite with a lot of resources left, or we pick the only one available (in this case whatever happens).
     257    // we've found no fitting dropsites close enough from us.
     258    // So'll try with far away.
    167259    if (!nearestResources || nearestResources.length === 0) {
    168         //debug ("here(1)");
    169         gameState.getOwnDropsites(resource).forEach(function (dropsite){ //}){
    170             var quantity = dropsite.getMetadata(PlayerID, "resource-quantity-" +resource)+dropsite.getMetadata(PlayerID, "resource-quantity-far-" +resource);
    171             if (dropsite.position() && (quantity) > 700 || number === 1) {
     260        ourDropsites.forEach(function (dropsite) { //}){
     261            if (baseManager.dropsites[dropsite.id()][resource] === undefined)
     262                return;
     263            if (dropsite.position() && baseManager.dropsites[dropsite.id()][resource][4] > 400
     264                && baseManager.dropsites[dropsite.id()][resource][5].length < maxPerDP) {
    172265                var dist = SquareVectorDistance(ent.position(), dropsite.position());
    173266                if (dist < minDropsiteDist){
    174267                    minDropsiteDist = dist;
    175                     nearestResources = dropsite.getMetadata(PlayerID, "linked-resources-" + resource);
     268                    nearestResources = baseManager.dropsites[dropsite.id()][resource][1];
    176269                    nearestDropsite = dropsite;
    177270                }
    178271            }
    179272        });
    180         this.unsatisfactoryResource = true;
    181         //debug ("Nearest dropsite: " +nearestDropsite);
    182     }
    183     // If we still haven't found any fitting dropsite...
    184     // Then we'll just pick any resource, and we'll check for the closest dropsite to that one
     273    }
     274
    185275    if (!nearestResources || nearestResources.length === 0){
     276        if (resource === "food")
     277            if (this.buildAnyField(gameState))
     278                return;
     279
     280        if (this.unsatisfactoryResource == true)
     281            return; // we were already not satisfied, we're still not, change not.
     282
     283        if (gameState.ai.HQ.switchWorkerBase(gameState, this.ent, resource))
     284            return;
     285
    186286        //debug ("No fitting dropsite for " + resource + " found, iterating the map.");
    187287        nearestResources = gameState.getResourceSupplies(resource);
    188288        this.unsatisfactoryResource = true;
     289        // TODO: should try setting up dropsites.
     290    } else {
     291        this.unsatisfactoryResource = false;
    189292    }
    190293   
     
    194297            if (this.buildAnyField(gameState))
    195298                return;
     299            if (gameState.ai.HQ.switchWorkerBase(gameState, this.ent, resource))
     300                return;
    196301            debug("No " + resource + " found! (1)");
    197302        }
    198303        else
     304        {
     305            if (gameState.ai.HQ.switchWorkerBase(gameState, this.ent, resource))
     306                return;
    199307            debug("No " + resource + " found! (1)");
     308        }
    200309        return;
    201310    }
     
    214323    // TODo: add a bonus for resources with a lot of resources left, perhaps, to spread gathering?
    215324    nearestResources.forEach(function(supply) { //}){
    216 
    217325        // sanity check, perhaps sheep could be garrisoned?
    218326        if (!supply.position()) {
     
    225333            return;
    226334        }
    227                                      
    228         if (supply.isFull() === true || (supply.maxGatherers() - supply.resourceSupplyGatherers().length == 0) ||
    229             (gameState.turnCache["ressGathererNB"] && gameState.turnCache["ressGathererNB"][supply.id()]
    230                 && gameState.turnCache["ressGathererNB"][supply.id()] + supply.resourceSupplyGatherers().length >= supply.maxGatherers())) {
    231             return;
    232         }
     335
     336        if (supply.isFull() === true
     337            || (gameState.turnCache["ressGathererNB"] && gameState.turnCache["ressGathererNB"][supply.id()]
     338                && gameState.turnCache["ressGathererNB"][supply.id()] + supply.resourceSupplyGatherers().length >= supply.maxGatherers))
     339            return;
     340
    233341                             
    234         // Don't gather enemy farms
    235         if (!supply.isOwn(PlayerID) && supply.owner() !== 0) {
     342        // Don't gather enemy farms or farms from another base
     343        if ((!supply.isOwn(PlayerID) && supply.owner() !== 0) || (supply.isOwn(PlayerID) && supply.getMetadata(PlayerID,"base") !== self.baseID)) {
    236344            //debug ("enemy");
    237345            return;
     
    239347       
    240348        // quickscope accessbility check.
    241         if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position(), true)) {
     349        if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position())) {
    242350            //debug ("nopath");
    243351            return;
     
    246354        if (supply.footprintRadius() < 1)
    247355        {
    248             var fakeMap = new Map(gameState,gameState.getMap().data);
     356            var fakeMap = new Map(gameState.sharedScript,gameState.getMap().data);
    249357            var id = fakeMap.gamePosToMapPos(supply.position())[0] + fakeMap.width*fakeMap.gamePosToMapPos(supply.position())[1];
    250358            if ( (gameState.sharedScript.passabilityClasses["pathfinderObstruction"] & gameState.getMap().data[id]) )
     
    269377        var territoryOwner = Map.createTerritoryMap(gameState).getOwner(supply.position());
    270378        if (territoryOwner != PlayerID && territoryOwner != 0) {
    271             dist *= 3.0;
     379            dist *= 5.0;
    272380            //return;
    273         }
    274        
    275         // Go for treasure as a priority
    276         if (dist < 40000 && supply.resourceSupplyType().generic == "treasure"){
     381        } else if (dist < 40000 && supply.resourceSupplyType().generic == "treasure"){
     382            // go for treasures if they're not in enemy territory
    277383            dist /= 1000;
    278384        }
     
    283389        }
    284390    });
    285    
    286391    if (nearestSupply) {
    287392        var pos = nearestSupply.position();
     
    311416            tried = this.buildAnyField(gameState);
    312417            if (!tried && SquareVectorDistance(pos,this.ent.position()) > 62500) {
     418                // TODO: ought to change behavior here.
    313419                return; // wait. a farm should appear.
    314420            }
    315421        }
    316422        if (!tried) {
    317            
    318423            if (!gameState.turnCache["ressGathererNB"])
    319424            {
     
    325430                gameState.turnCache["ressGathererNB"][nearestSupply.id()]++;
    326431           
    327             this.maxApproachTime = Math.max(25000, VectorDistance(pos,this.ent.position()) * 1000);
     432            this.maxApproachTime = Math.max(30000, VectorDistance(pos,this.ent.position()) * 5000);
     433            this.startApproachingResourceAmount = ent.resourceSupplyAmount();
     434            this.startEnt = ent.id();
    328435            ent.gather(nearestSupply);
    329436            ent.setMetadata(PlayerID, "target-foundation", undefined);
    330 
    331             // check if the resource we've started gathering from is now full, in which case inform the dropsite.
    332             if (gameState.turnCache["ressGathererNB"][nearestSupply.id()] + nearestSupply.resourceSupplyGatherers().length >= nearestSupply.maxGatherers()
    333                 && nearestSupply.getMetadata(PlayerID, "linked-dropsite") != undefined)
    334             {
    335                 var dropsite = gameState.getEntityById(nearestSupply.getMetadata(PlayerID, "linked-dropsite"));
    336                 if (dropsite == undefined || dropsite.getMetadata(PlayerID, "linked-resources-" + resource) === undefined)
    337                     return;
    338                 if (nearestSupply.getMetadata(PlayerID, "linked-dropsite-nearby") == true) {
    339                     dropsite.setMetadata(PlayerID, "resource-quantity-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) - (+nearestSupply.getMetadata(PlayerID, "dp-update-value")));
    340                     dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(nearestSupply);
    341                     dropsite.getMetadata(PlayerID, "nearby-resources-" + resource).updateEnt(nearestSupply);
    342                 } else {
    343                     dropsite.setMetadata(PlayerID, "resource-quantity-far-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) - (+nearestSupply.getMetadata(PlayerID, "dp-update-value")));
    344                     dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(nearestSupply);
    345                 }
    346 
    347             }
    348        
    349437        }
    350438    } else {
     
    352440            return;
    353441
    354         debug("No " + resource + " found! (2)");
     442        if (gameState.ai.HQ.switchWorkerBase(gameState, this.ent, resource))
     443            return;
     444       
     445        if (resource !== "food")
     446            debug("No " + resource + " found! (2)");
    355447        // If we had a fitting closest dropsite with a lot of resources, mark it as not good. It means it's probably full. Then retry.
    356448        // it'll be resetted next time it's counted anyway.
     
    359451            nearestDropsite.setMetadata(PlayerID, "resource-quantity-" +resource, 0);
    360452            nearestDropsite.setMetadata(PlayerID, "resource-quantity-far-" +resource, 0);
    361             this.startGathering(gameState);
     453            this.startGathering(baseManager, gameState);
    362454        }
    363455    }
     
    398490};
    399491
    400 Worker.prototype.startHunting = function(gameState){
     492Worker.prototype.startHunting = function(gameState, baseManager){
    401493    var ent = this.ent;
    402494   
    403     if (!ent.position() || ent.getMetadata(PlayerID, "stoppedHunting"))
     495    if (!ent.position() || !baseManager.isHunting)
    404496        return;
    405497   
     
    439531       
    440532        // quickscope accessbility check
    441         if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position(), true))
     533        if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position(),false, true))
    442534            return;
    443535       
     
    465557        if (!nearestDropsite)
    466558        {
    467             ent.setMetadata(PlayerID, "stoppedHunting", true);
     559            baseManager.isHunting = false;
    468560            ent.setMetadata(PlayerID, "role", undefined);
    469561            debug ("No dropsite for hunting food");
    470562            return;
    471563        }
    472         if (minDropsiteDist > 45000) {
    473             ent.setMetadata(PlayerID, "stoppedHunting", true);
     564        if (minDropsiteDist > 35000) {
     565            baseManager.isHunting = false;
    474566            ent.setMetadata(PlayerID, "role", undefined);
    475567        } else {
     
    478570        }
    479571    } else {
    480         ent.setMetadata(PlayerID, "stoppedHunting", true);
     572        baseManager.isHunting = false;
    481573        ent.setMetadata(PlayerID, "role", undefined);
    482574        debug("No food found for hunting! (2)");
     
    496588};
    497589
     590Worker.prototype.getGatherRate = function(gameState) {
     591    if (this.ent.getMetadata(PlayerID,"subrole") !== "gatherer")
     592        return 0;
     593    var rates = this.ent.resourceGatherRates();
     594   
     595    if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[1] === "GATHER" && this.ent.unitAIOrderData()[0]["target"])
     596    {
     597        var ress = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]);
     598        if (!ress)
     599            return 0;
     600        var type = ress.resourceSupplyType();
     601        if (type.generic == "treasure")
     602            return 1000;
     603        var tstring = type.generic + "." + type.specific;
     604        //debug (+rates[tstring] + " for " + tstring + " for " + this.ent._templateName);
     605        if (rates[tstring])
     606            return rates[tstring];
     607        return 0;
     608    }
     609    return 0;
     610};
     611
    498612Worker.prototype.buildAnyField = function(gameState){
    499613    var self = this;
    500614    var okay = false;
    501     var foundations = gameState.getOwnFoundations();
     615    var foundations = gameState.getOwnFoundations().filter(Filters.byMetadata(PlayerID,"base",this.baseID));
    502616    foundations.filterNearest(this.ent.position(), foundations.length);
    503617    foundations.forEach(function (found) {
     
    508622        }
    509623    });
     624    if (!okay)
     625    {
     626        var foundations = gameState.getOwnFoundations();
     627        foundations.filterNearest(this.ent.position(), foundations.length);
     628        foundations.forEach(function (found) {
     629            if (found._template.BuildRestrictions.Category === "Field" && !okay) {
     630                self.ent.repair(found);
     631                self.ent.setMetadata(PlayerID,"base", found.getMetadata(PlayerID,"base"));
     632                okay = true;
     633                return;
     634            }
     635        });
     636    }
    510637    return okay;
    511638};
  • ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/base.js

    r13326 r13907  
    1313
    1414    this.turn = 0;
     15    this.timeElapsed = 0;
    1516}
    1617
     
    3637    this.passabilityClasses = sharedAI.passabilityClasses;
    3738    this.passabilityMap = sharedAI.passabilityMap;
     39    this.territoryMap = sharedAI.territoryMap;
     40    this.timeElapsed = state.timeElapsed;
    3841
    39     var gameState = sharedAI.gameState[PlayerID];
    40     gameState.ai = this;
     42    this.gameState = sharedAI.gameState[PlayerID];
     43    this.gameState.ai = this;
     44    this.sharedScript = sharedAI;
    4145       
    42     this.InitShared(gameState, sharedAI);
    43    
    44     delete gameState.ai;
     46    this.InitShared(this.gameState, this.sharedScript);
    4547}
    4648
     
    6062    this.accessibility = sharedAI.accessibility;
    6163    this.terrainAnalyzer = sharedAI.terrainAnalyzer;
    62     this.techModifications = sharedAI.techModifications[this._player];
     64    this.techModifications = sharedAI._techModifications[this._player];
    6365
    6466    Engine.ProfileStop();
  • ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/entity.js

    r13683 r13907  
    3333        return this._template.Identity.RequiredTechnology;
    3434    },
    35 
     35                           
     36    available: function(gameState) {
     37        if (!this._template.Identity || !this._template.Identity.RequiredTechnology)
     38            return true;
     39        return gameState.isResearched(this._template.Identity.RequiredTechnology);
     40    },
     41                           
     42    // specifically
    3643    phase: function() {
    3744        if (!this._template.Identity || !this._template.Identity.RequiredTechnology)
     
    413420        return (territories && territories.indexOf(territory) != -1);
    414421    },
    415    
     422
     423    hasTerritoryInfluence: function() {
     424        return (this._template.TerritoryInfluence !== undefined);
     425    },
     426
     427    territoryInfluenceRadius: function() {
     428        if (this._template.TerritoryInfluence !== undefined)
     429            return (this._template.TerritoryInfluence.Radius);
     430        else
     431            return -1;
     432    },
     433
     434    territoryInfluenceWeight: function() {
     435        if (this._template.TerritoryInfluence !== undefined)
     436            return (this._template.TerritoryInfluence.Weight);
     437        else
     438            return -1;
     439    },
     440
    416441    visionRange: function() {
    417442        if (!this._template.Vision)
     
    428453    _init: function(sharedAI, entity)
    429454    {
    430         this._super.call(this, sharedAI.GetTemplate(entity.template), sharedAI.techModifications[entity.owner]);
     455        this._super.call(this, sharedAI.GetTemplate(entity.template), sharedAI._techModifications[entity.owner]);
    431456
    432457        this._ai = sharedAI;
     
    463488    },
    464489   
    465     deleteMetadata: function(player) {
     490    deleteAllMetadata: function(player) {
    466491        delete this._ai._entityMetadata[player][this.id()];
     492    },
     493                   
     494    deleteMetadata: function(player, key) {
     495        this._ai.deleteMetadata(player, this, key);
    467496    },
    468497
     
    501530
    502531    foundationProgress: function() {
    503         if (typeof this._entity.foundationProgress === "undefined")
     532        if (this._entity.foundationProgress == undefined)
    504533            return undefined;
    505534        return this._entity.foundationProgress;
     
    538567        if (this._entity.resourceSupplyGatherers !== undefined)
    539568            return (this.maxGatherers() === this._entity.resourceSupplyGatherers.length);
     569
    540570        return undefined;
    541571    },
     
    548578
    549579    garrisoned: function() { return new EntityCollection(this._ai, this._entity.garrisoned); },
     580   
     581    canGarrisonInside: function() { return this._entity.garrisoned.length < this.garrisonMax(); },
    550582
    551583    // TODO: visibility
     
    659691    },
    660692
    661     construct: function(template, x, z, angle) {
     693    construct: function(template, x, z, angle, metadata) {
    662694        // TODO: verify this unit can construct this, just for internal
    663695        // sanity-checking and error reporting
     
    672704            "autorepair": false,
    673705            "autocontinue": false,
    674             "queued": false
     706            "queued": false,
     707            "metadata" : metadata   // can be undefined
    675708        });
    676709        return this;
  • ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/entitycollection.js

    r13225 r13907  
    44    this._entities = entities || {};
    55    this._filters = filters || [];
     6   
     7    this._quickIter = false;    // will make the entity collection store an array (not associative) of entities used when calling "foreach".
     8    // probably should not be usde for very dynamic entity collections.
    69
    710    // Compute length lazily on demand, since it can be
     
    2427// If an entitycollection is frozen, it will never automatically add a unit.
    2528// But can remove one.
     29// this makes it easy to create entity collection that will auto-remove dead units
     30// but never add new ones.
    2631EntityCollection.prototype.freeze = function()
    2732{
     
    3136{
    3237    this.frozen = false;
     38};
     39
     40EntityCollection.prototype.allowQuickIter = function()
     41{
     42    this._quickIter = true;
     43    this._entitiesArray = [];
     44    for each (var ent in this._entities)
     45        this._entitiesArray.push(ent);
    3346};
    3447
     
    4356EntityCollection.prototype.toEntityArray = function()
    4457{
     58    if (this._quickIter === true)
     59        return this._entitiesArray;
    4560    var ret = [];
    4661    for each (var ent in this._entities)
     
    6075{
    6176    // Compute the distance of each entity
    62     var data = []; // [ [id, ent, distance], ... ]
    63     for (var id in this._entities)
    64     {
    65         var ent = this._entities[id];
    66         if (ent.position())
    67             data.push([id, ent, VectorDistance(targetPos, ent.position())]);
     77    var data = []; // [id, ent, distance]
     78   
     79    if (this._quickIter === true)
     80    {
     81        for (var i in this._entitiesArray)
     82        {
     83            var ent = this._entitiesArray[i];
     84            if (ent.position() !== -1)
     85                data.push([ent.id(), ent, SquareVectorDistance(targetPos, ent.position())]);
     86        }
     87    } else {
     88        for (var id in this._entities)
     89        {
     90            var ent = this._entities[id];
     91            if (ent.position() !== -1)
     92                data.push([id, ent, SquareVectorDistance(targetPos, ent.position())]);
     93        }
    6894    }
    6995
     
    7399    // Extract the first n
    74100    var ret = {};
    75     for each (var val in data.slice(0, n))
    76         ret[val[0]] = val[1];
     101    var length = Math.min(n, entData.length);
     102    for (var i = 0; i < length; ++i)
     103        ret[data[i][0]] = data[i][1];
    77104
    78105    return new EntityCollection(this._ai, ret);
     
    85112   
    86113    var ret = {};
    87     for (var id in this._entities)
    88     {
    89         var ent = this._entities[id];
    90         if (filter.func.call(thisp, ent, id, this))
    91             ret[id] = ent;
     114    if (this._quickIter === true)
     115    {
     116        for (var i in this._entitiesArray)
     117        {
     118            var ent = this._entitiesArray[i];
     119            var id = ent.id();
     120            if (filter.func.call(thisp, ent, id, this))
     121                ret[id] = ent;
     122        }
     123    } else {
     124        for (var id in this._entities)
     125        {
     126            var ent = this._entities[id];
     127            if (filter.func.call(thisp, ent, id, this))
     128                ret[id] = ent;
     129        }
    92130    }
    93131   
     
    106144    }
    107145    return new EntityCollection(this._ai, ret);
     146};
     147
     148EntityCollection.prototype.forEach = function(callback)
     149{
     150    if (this._quickIter === true)
     151    {
     152        for (var id in this._entitiesArray)
     153        {
     154            callback(this._entitiesArray[id]);
     155        }
     156        return this;
     157    }
     158    for (var id in this._entities)
     159    {
     160        callback(this._entities[id]);
     161    }
     162    return this;
    108163};
    109164
     
    131186};
    132187
    133 EntityCollection.prototype.forEach = function(callback, thisp)
    134 {
    135     for (var id in this._entities)
    136     {
    137         var ent = this._entities[id];
    138         callback.call(thisp, ent, id, this);
    139     }
    140     return this;
    141 };
    142 
    143188EntityCollection.prototype.move = function(x, z, queued)
    144189{
     
    242287        if (this.length !== undefined)
    243288            this._length--;
     289        if (this._quickIter === true)
     290            this._entitiesArray.splice(this._entitiesArray.indexOf(ent),1);
    244291        delete this._entities[ent.id()];
    245292        return true;
     
    264311            this._length++;
    265312        this._entities[ent.id()] = ent;
     313        if (this._quickIter === true)
     314            this._entitiesArray.push(ent);
    266315        return true;
    267316    }
     
    298347};
    299348
     349EntityCollection.prototype.unregister = function()
     350{
     351    this._ai.removeUpdatingEntityCollection(this);
     352};
     353
    300354EntityCollection.prototype.dynamicProperties = function()
    301355{
  • ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/filters.js

    r13266 r13907  
    4242        "dynamicProperties": ['metadata.' + key]};
    4343    },
     44
     45    // can be used for stuffs which won't change once entities are created.
     46    byStaticMetadata: function(player, key, value){
     47        return {"func" : function(ent){
     48            return (ent.getMetadata(player, key) == value);
     49        },
     50            "dynamicProperties": []};
     51    },
     52   
    4453    byHasMetadata: function(player, key){
    4554        return {"func" : function(ent){
     
    98107    byCanGarrison: function(){
    99108        return {"func" : function(ent){
    100             return ent.garrisonMax();
     109            return ent.garrisonMax() > 0;
    101110        },
    102111            "dynamicProperties": []};
     
    127136            "dynamicProperties": []};
    128137    },
     138   
     139    isGarrisoned: function(){
     140        return {"func" : function(ent){
     141            return ent.position() == -1;    // assumes garrisoned
     142        },
     143            "dynamicProperties": []};
     144    },
    129145
    130146    isSoldier: function(){
     
    185201    isDropsite: function(resourceType){
    186202        return {"func": function(ent){
    187             return (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resourceType) !== -1);
     203            return (ent.resourceDropsiteTypes() && (resourceType === undefined || ent.resourceDropsiteTypes().indexOf(resourceType) !== -1));
    188204        },
    189205        "dynamicProperties": []};
     
    204220           
    205221            // And don't go for the bloody fish! TODO: better accessibility checks
    206             if (ent.hasClass("SeaCreature")){
     222            if (ent.hasClass("SeaCreature")) {
    207223                return false;
    208224            }
  • ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/gamestate.js

    r13683 r13907  
    33 * higher level than the raw data.
    44 */
    5 var GameState = function(SharedScript, state, player) {
     5var GameState = function() {
     6    this.ai = null; // must be updated by the AIs.
     7    this.cellSize = 4; // Size of each map tile
     8
     9    this.buildingsBuilt = 0;
     10    this.turnCache = {};
     11};
     12
     13GameState.prototype.init = function(SharedScript, state, player) {
    614    this.sharedScript = SharedScript;
    715    this.EntCollecNames = SharedScript._entityCollectionsName;
    816    this.EntCollec = SharedScript._entityCollections;
    9     this.timeElapsed = state.timeElapsed;
     17    this.timeElapsed = SharedScript.timeElapsed;
    1018    this.templates = SharedScript._templates;
    1119    this.techTemplates = SharedScript._techTemplates;
    1220    this.entities = SharedScript.entities;
    1321    this.player = player;
    14     this.playerData = state.players[player];
    15     this.techModifications = SharedScript.techModifications[player];
    16     this.buildingsBuilt = 0;
    17    
    18     this.ai = null; // must be updated by the AIs.
    19    
    20     this.cellSize = 4; // Size of each map tile
    21    
    22     this.turnCache = {};
    23 };
     22    this.playerData = this.sharedScript.playersData[this.player];
     23    this.techModifications = SharedScript._techModifications[this.player];
     24};
     25
    2426GameState.prototype.update = function(SharedScript, state) {
    2527    this.sharedScript = SharedScript;
    2628    this.EntCollecNames = SharedScript._entityCollectionsName;
    2729    this.EntCollec = SharedScript._entityCollections;
    28     this.timeElapsed = state.timeElapsed;
     30    this.timeElapsed = SharedScript.timeElapsed;
    2931    this.templates = SharedScript._templates;
    3032    this.techTemplates = SharedScript._techTemplates;
     33    this._entities = SharedScript._entities;
    3134    this.entities = SharedScript.entities;
    32     this.playerData = state.players[this.player];
    33     this.techModifications = SharedScript.techModifications[this.player];
     35    this.playerData = SharedScript.playersData[this.player];
     36    this.techModifications = SharedScript._techModifications[this.player];
    3437
    3538    this.buildingsBuilt = 0;
     
    3740};
    3841
    39 GameState.prototype.updatingCollection = function(id, filter, collection){
     42GameState.prototype.updatingCollection = function(id, filter, collection, allowQuick){
    4043    // automatically add the player ID
    4144    id = this.player + "-" + id;
    42        
    4345    if (!this.EntCollecNames[id]){
    4446        if (collection !== undefined)
     
    4749            this.EntCollecNames[id] = this.entities.filter(filter);
    4850        }
     51        if (allowQuick)
     52            this.EntCollecNames[id].allowQuickIter();
    4953        this.EntCollecNames[id].registerUpdates();
     54        //  warn ("New Collection named " +id);
    5055    }
    5156   
     
    7075};
    7176
    72 GameState.prototype.updatingGlobalCollection = function(id, filter, collection) {
     77GameState.prototype.updatingGlobalCollection = function(id, filter, collection, allowQuick) {
    7378    if (!this.EntCollecNames[id]){
    7479        if (collection !== undefined)
     
    7681        else
    7782            this.EntCollecNames[id] = this.entities.filter(filter);
     83        if (allowQuick)
     84            this.EntCollecNames[id].allowQuickIter();
    7885        this.EntCollecNames[id].registerUpdates();
     86        //warn ("New Global Collection named " +id);
    7987    }
    8088   
     
    104112        return 1;
    105113    return 0;
     114};
     115
     116GameState.prototype.townPhase = function()
     117{
     118    if (this.playerData.civ == "athen")
     119        return "phase_town_athen";
     120    return "phase_town_generic";
     121};
     122
     123GameState.prototype.cityPhase = function()
     124{
     125    return "phase_city_generic";
    106126};
    107127
     
    229249    return str.replace(/\{civ\}/g, this.playerData.civ);
    230250};
     251
    231252GameState.prototype.civ = function() {
    232253    return this.playerData.civ;
     
    350371};
    351372
    352 GameState.prototype.getOwnEntitiesByMetadata = function(key, value){
    353     return this.updatingCollection(key + "-" + value, Filters.byMetadata(this.player, key, value),this.getOwnEntities());
     373GameState.prototype.getOwnEntitiesByMetadata = function(key, value, maintain){
     374    if (maintain === true)
     375        return this.updatingCollection(key + "-" + value, Filters.byMetadata(this.player, key, value),this.getOwnEntities());
     376    return this.getOwnEntities().filter(Filters.byMetadata(this.player, key, value));
    354377};
    355378
    356379GameState.prototype.getOwnEntitiesByRole = function(role){
    357     return this.getOwnEntitiesByMetadata("role", role);
     380    return this.getOwnEntitiesByMetadata("role", role, true);
    358381};
    359382
    360383GameState.prototype.getOwnTrainingFacilities = function(){
    361     return this.updatingCollection("own-training-facilities", Filters.byTrainingQueue(), this.getOwnEntities());
     384    return this.updatingCollection("own-training-facilities", Filters.byTrainingQueue(), this.getOwnEntities(), true);
    362385};
    363386
    364387GameState.prototype.getOwnResearchFacilities = function(){
    365     return this.updatingCollection("own-research-facilities", Filters.byResearchAvailable(), this.getOwnEntities());
    366 };
    367 
    368 GameState.prototype.getOwnEntitiesByType = function(type){
     388    return this.updatingCollection("own-research-facilities", Filters.byResearchAvailable(), this.getOwnEntities(), true);
     389};
     390
     391GameState.prototype.getOwnEntitiesByType = function(type, maintain){
    369392    var filter = Filters.byType(type);
    370     return this.updatingCollection("own-by-type-" + type, filter, this.getOwnEntities());
    371 };
    372 
    373 GameState.prototype.countEntitiesByType = function(type) {
    374     return this.getOwnEntitiesByType(type).length;
     393    if (maintain === true)
     394        return this.updatingCollection("own-by-type-" + type, filter, this.getOwnEntities());
     395    return this.getOwnEntities().filter(filter);
     396
     397};
     398
     399GameState.prototype.countEntitiesByType = function(type, maintain) {
     400    return this.getOwnEntitiesByType(type, maintain).length;
    375401};
    376402
    377403GameState.prototype.countEntitiesAndQueuedByType = function(type) {
    378     var count = this.countEntitiesByType(type);
     404    var count = this.countEntitiesByType(type, true);
    379405   
    380406    // Count building foundations
    381     count += this.countEntitiesByType("foundation|" + type);
    382    
    383     // Count animal resources
    384     count += this.countEntitiesByType("resource|" + type);
    385 
    386     // Count entities in building production queues
    387     this.getOwnTrainingFacilities().forEach(function(ent){
    388         ent.trainingQueue().forEach(function(item) {
    389             if (item.unitTemplate == type){
    390                 count += item.count;
    391             }
     407    if (this.getTemplate(type).hasClass("Structure") === true)
     408        count += this.countEntitiesByType("foundation|" + type, true);
     409    else if (this.getTemplate(type).resourceSupplyType() !== undefined) // animal resources
     410        count += this.countEntitiesByType("resource|" + type, true);
     411    else
     412    {
     413        // Count entities in building production queues
     414        // TODO: maybe this fails for corrals.
     415        this.getOwnTrainingFacilities().forEach(function(ent){
     416            ent.trainingQueue().forEach(function(item) {
     417                if (item.unitTemplate == type){
     418                    count += item.count;
     419                }
     420            });
    392421        });
    393     });
     422    }
    394423   
    395424    return count;
     
    509538
    510539GameState.prototype.getOwnDropsites = function(resource){
    511     return this.updatingCollection("dropsite-own-" + resource, Filters.isDropsite(resource), this.getOwnEntities());
     540    if (resource !== undefined)
     541        return this.updatingCollection("dropsite-own-" + resource, Filters.isDropsite(resource), this.getOwnEntities(), true);
     542    return this.updatingCollection("dropsite-own", Filters.isDropsite(), this.getOwnEntities(), true);
    512543};
    513544
    514545GameState.prototype.getResourceSupplies = function(resource){
    515     return this.updatingGlobalCollection("resource-" + resource, Filters.byResource(resource), this.getEntities());
     546    return this.updatingGlobalCollection("resource-" + resource, Filters.byResource(resource), this.getEntities(), true);
    516547};
    517548
     
    590621                ret.push([techs[1]._templateName, techs[1]] );
    591622        } else {
    592             if (this.canResearch(allResearchable[i]) && template._templateName != "phase_town_generic"
    593                 && template._templateName != "phase_town_athens" && template._templateName != "phase_city_generic")
     623            if (this.canResearch(allResearchable[i]) && template._templateName != this.townPhase() && template._templateName != this.cityPhase())
    594624                ret.push( [allResearchable[i], template] );
    595625        }
  • ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/map-module.js

    r13225 r13907  
    22 * Copied with changes from QuantumState's original for qBot, it's a component for storing 8 bit values.
    33 */
     4
     5const TERRITORY_PLAYER_MASK = 0x3F;
    46
    57function Map(sharedScript, originalMap, actualCopy){
     
    1012    this.length = gameMap.data.length;
    1113   
     14    this.maxVal = 255;
     15
    1216    if (originalMap && actualCopy){
    1317        this.map = new Uint8Array(this.length);
     
    2226}
    2327
     28Map.prototype.setMaxVal = function(val){
     29    this.maxVal = val;
     30};
     31
    2432Map.prototype.gamePosToMapPos = function(p){
    25     return [Math.round(p[0]/this.cellSize), Math.round(p[1]/this.cellSize)];
     33    return [Math.floor(p[0]/this.cellSize), Math.floor(p[1]/this.cellSize)];
    2634};
    2735
     
    4452        case 'linear':
    4553            str = +strength / +maxDist;
    46             break;
     54        break;
    4755        case 'quadratic':
    4856            str = +strength / +maxDist2;
    49             break;
     57        break;
    5058        case 'constant':
    5159            str = +strength;
    52             break;
     60        break;
    5361    }
    5462       
     
    7280                        break;
    7381                }
    74                 if (this.map[x + y * this.width] + quant > 255){
    75                     this.map[x + y * this.width] = 255;
    76                 } else if (this.map[x + y * this.width] + quant < 0){
     82                if (this.map[x + y * this.width] + quant < 0)
    7783                    this.map[x + y * this.width] = 0;
    78                 } else {
     84                else if (this.map[x + y * this.width] + quant > this.maxVal)
     85                    this.map[x + y * this.width] = this.maxVal; // avoids overflow.
     86                else
    7987                    this.map[x + y * this.width] += quant;
    80                 }
    8188            }
    8289        }
     
    127134                }
    128135                var machin = this.map[x + y * this.width] * quant;
    129                 if (machin <= 0){
    130                     this.map[x + y * this.width] = 0; //set anything which would have gone negative to 0
    131                 }else{
     136                if (machin < 0)
     137                    this.map[x + y * this.width] = 0;
     138                else if (machin > this.maxVal)
     139                    this.map[x + y * this.width] = this.maxVal;
     140                else
    132141                    this.map[x + y * this.width] = machin;
    133                 }
    134             }
    135         }
    136     }
    137 };
     142            }
     143        }
     144    }
     145};
     146
     147// doesn't check for overflow.
    138148Map.prototype.setInfluence = function(cx, cy, maxDist, value) {
    139149    value = value ? value : 0;
     
    179189};
    180190/**
    181  * Make each cell's 8-bit value at least one greater than each of its
    182  * neighbours' values. Possible assignment of a cap (maximum).
     191 * Make each cell's 16-bit/8-bit value at least one greater than each of its
     192 * neighbours' values. (If the grid is initialised with 0s and 65535s or 255s, the
     193 * result of each cell is its Manhattan distance to the nearest 0.)
    183194 */
    184195Map.prototype.expandInfluences = function(maximum, map) {
     
    188199   
    189200    if (maximum == undefined)
    190         maximum = 255;
     201        maximum = this.maxVal;
    191202    var w = this.width;
    192203    var h = this.height;
     
    267278// add to current map by the parameter map pixelwise
    268279Map.prototype.add = function(map){
    269     for (var i = 0; i < this.length; ++i){
    270         this.map[i] += +map.map[i];
    271     }
    272 };
     280    for (var i = 0; i < this.length; ++i) {
     281        if (this.map[i] + map.map[i] < 0)
     282            this.map[i] = 0;
     283        else if (this.map[i] + map.map[i] > this.maxVal)
     284            this.map[i] = this.maxVal;
     285        else
     286            this.map[i] += map.map[i];
     287    }
     288};
     289
     290Map.prototype.dumpIm = function(name, threshold){
     291    name = name ? name : "default.png";
     292    threshold = threshold ? threshold : this.maxVal;
     293    Engine.DumpImage(name, this.map, this.width, this.height, threshold);
     294};
  • ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/shared.js

    r13404 r13907  
    2323    this._entityCollectionsByDynProp = {};
    2424    this._entityCollectionsUID = 0;
    25        
     25   
     26    // A few notes about these maps. They're updated by checking for "create" and "destroy" events for all resources
     27    // TODO: change the map when the resource amounts change for at least stone and metal mines.
     28    this.resourceMaps = {}; // Contains maps showing the density of wood, stone and metal
     29    this.CCResourceMaps = {}; // Contains maps showing the density of wood, stone and metal, optimized for CC placement.
     30    // Resource maps data.
     31    // By how much to divide the resource amount for plotting (ie a tree having 200 wood is "4").
     32    this.decreaseFactor = {'wood': 50.0, 'stone': 90.0, 'metal': 90.0, 'food': 40.0};
     33
    2634    this.turn = 0;
    2735}
     
    161169// this is called right at the end of map generation, before you actually reach the map.
    162170SharedScript.prototype.initWithState = function(state) {   
    163     this.passabilityClasses = state.passabilityClasses;
    164     this.passabilityMap = state.passabilityMap;
    165     this.territoryMap = state.territoryMap;
    166 
    167     for (var o in state.players)
    168         this._techModifications[o] = state.players[o].techModifications;
    169    
    170     this.techModifications = this._techModifications;
    171    
    172     this._entities = {};
    173     for (var id in state.entities)
    174     {
    175         this._entities[id] = new Entity(this, state.entities[id]);
    176     }
    177     // entity collection updated on create/destroy event.
    178     this.entities = new EntityCollection(this, this._entities);
    179    
    180     // create the terrain analyzer
    181     this.terrainAnalyzer = new TerrainAnalysis(this, state);
    182     this.accessibility = new Accessibility(state, this.terrainAnalyzer);
    183    
    184     this.gameState = {};
    185     for (var i in this._players)
    186     {
    187         this.gameState[this._players[i]] = new GameState(this,state,this._players[i]);
    188     }
    189 
    190 };
    191 
    192 // General update of the shared script, before each AI's update
    193 // applies entity deltas, and each gamestate.
    194 SharedScript.prototype.onUpdate = function(state)
    195 {
    196     this.ApplyEntitiesDelta(state);
    197    
    198     Engine.ProfileStart("onUpdate");
    199    
    200171    this.events = state.events;
    201172    this.passabilityClasses = state.passabilityClasses;
     
    205176    this.territoryMap = state.territoryMap;
    206177    this.timeElapsed = state.timeElapsed;
    207    
     178
    208179    for (var o in state.players)
    209180        this._techModifications[o] = state.players[o].techModifications;
     181   
     182    this._entities = {};
     183    this.entities = new EntityCollection(this);
     184
     185    /* (var id in state.entities)
     186    {
     187        this._entities[id] = new Entity(this, state.entities[id]);
     188    }
     189    // entity collection updated on create/destroy event.
     190    this.entities = new EntityCollection(this, this._entities);
     191    */
     192   
     193    this.ApplyEntitiesDelta(state);
     194
     195    // create the terrain analyzer
     196    this.terrainAnalyzer = new TerrainAnalysis();
     197    this.terrainAnalyzer.init(this, state);
     198    this.accessibility = new Accessibility();
     199    this.accessibility.init(state, this.terrainAnalyzer);
     200   
     201    // defined in TerrainAnalysis.js
     202    this.updateResourceMaps(this, this.events);
     203
     204    this.gameState = {};
     205    for (var i in this._players)
     206    {
     207        this.gameState[this._players[i]] = new GameState();
     208        this.gameState[this._players[i]].init(this,state,this._players[i]);
     209    }
     210
     211};
     212
     213// General update of the shared script, before each AI's update
     214// applies entity deltas, and each gamestate.
     215SharedScript.prototype.onUpdate = function(state)
     216{
     217    if (this.turn !== 0)
     218        this.ApplyEntitiesDelta(state);
     219
     220    Engine.ProfileStart("onUpdate");
     221   
     222    this.events = state.events;
     223    this.passabilityClasses = state.passabilityClasses;
     224    this.passabilityMap = state.passabilityMap;
     225    this.players = this._players;
     226    this.playersData = state.players;
     227    this.territoryMap = state.territoryMap;
     228    this.timeElapsed = state.timeElapsed;
     229   
     230    for (var o in state.players)
     231        this._techModifications[o] = state.players[o].techModifications;
    210232
    211233    for (var i in this.gameState)
    212234        this.gameState[i].update(this,state);
    213235
     236    if (this.turn !== 0)
     237        this.updateResourceMaps(this, this.events);
    214238    this.terrainAnalyzer.updateMapWithEvents(this);
    215239   
     
    224248{
    225249    Engine.ProfileStart("Shared ApplyEntitiesDelta");
     250
     251    var foundationFinished = {};
    226252   
    227253    for each (var evt in state.events)
     
    237263
    238264            // Update all the entity collections since the create operation affects static properties as well as dynamic
    239             for each (var entCollection in this._entityCollections)
    240             {
    241                 entCollection.updateEnt(this._entities[evt.msg.entity]);
    242             }
    243 
     265            for (var entCollection in this._entityCollections)
     266            {
     267                this._entityCollections[entCollection].updateEnt(this._entities[evt.msg.entity]);
     268            }
    244269        }
    245270        else if (evt.type == "Destroy")
     
    253278                continue;
    254279           
     280            if (foundationFinished[evt.msg.entity])
     281                evt.msg["SuccessfulFoundation"] = true;
     282           
    255283            // The entity was destroyed but its data may still be useful, so
    256284            // remember the entity and this AI's metadata concerning it
     
    281309            }
    282310        }
     311        else if (evt.type == "ConstructionFinished")
     312        {
     313            // we can rely on this being before the "Destroy" command as this is the order defined by FOundation.js
     314            // we'll move metadata.
     315            if (!this._entities[evt.msg.entity])
     316                continue;
     317            var ent = this._entities[evt.msg.entity];
     318            var newEnt = this._entities[evt.msg.newentity];
     319            if (this._entityMetadata[ent.owner()] && this._entityMetadata[ent.owner()][evt.msg.entity] !== undefined)
     320                for (var key in this._entityMetadata[ent.owner()][evt.msg.entity])
     321                {
     322                    this.setMetadata(ent.owner(), newEnt, key, this._entityMetadata[ent.owner()][evt.msg.entity][key])
     323                }
     324            foundationFinished[evt.msg.entity] = true;
     325        }
     326        else if (evt.type == "AIMetadata")
     327        {
     328            if (!this._entities[evt.msg.id])
     329                continue;   // might happen in some rare cases of foundations getting destroyed, perhaps.
     330            // Apply metadata (here for buildings for example)
     331            for (var key in evt.msg.metadata)
     332            {
     333                this.setMetadata(evt.msg.owner, this._entities[evt.msg.id], key, evt.msg.metadata[key])
     334            }
     335        }
    283336    }
    284337   
     
    286339    {
    287340        var changes = state.entities[id];
    288        
     341
    289342        for (var prop in changes)
    290343        {
    291344            this._entities[id]._entity[prop] = changes[prop];
     345           
    292346            this.updateEntityCollections(prop, this._entities[id]);
    293347        }
     
    336390    if (this._entityCollectionsByDynProp[property] !== undefined)
    337391    {
    338         for each (var entCollection in this._entityCollectionsByDynProp[property])
    339         {
    340             entCollection.updateEnt(ent);
     392        for (var entCollectionid in this._entityCollectionsByDynProp[property])
     393        {
     394            this._entityCollectionsByDynProp[property][entCollectionid].updateEnt(ent);
    341395        }
    342396    }
     
    360414        return undefined;
    361415    return metadata[key];
     416};
     417SharedScript.prototype.deleteMetadata = function(player, ent, key)
     418{
     419    var metadata = this._entityMetadata[player][ent.id()];
     420   
     421    if (!metadata || !(key in metadata))
     422        return true;
     423    metadata[key] = undefined;
     424    delete metadata[key];
     425    return true;
    362426};
    363427
  • ps/trunk/binaries/data/mods/public/simulation/ai/common-api-v3/terrain-analysis.js

    r13404 r13907  
    1313 */
    1414
    15 function TerrainAnalysis(sharedScript,rawState){
     15function TerrainAnalysis() {
     16    this.cellSize = 4;
     17}
     18
     19copyPrototype(TerrainAnalysis, Map);
     20
     21TerrainAnalysis.prototype.init = function(sharedScript,rawState) {
    1622    var self = this;
    17     this.cellSize = 4;
    1823
    1924    var passabilityMap = rawState.passabilityMap;
    20 
    2125    this.width = passabilityMap.width;
    2226    this.height = passabilityMap.height;
     
    99103    this.obstructionMaskWater = null;
    100104    this.obstructionMask = null;
    101 };
    102 
    103 copyPrototype(TerrainAnalysis, Map);
     105    delete this.obstructionMaskLand;
     106    delete this.obstructionMaskWater;
     107    delete this.obstructionMask;
     108};
    104109
    105110// Returns the (approximately) closest point which is passable by searching in a spiral pattern
     
    239244
    240245 */
    241 function Accessibility(rawState, terrainAnalyser){
     246function Accessibility() {
     247   
     248}
     249
     250copyPrototype(Accessibility, TerrainAnalysis);
     251
     252Accessibility.prototype.init = function(rawState, terrainAnalyser){
    242253    var self = this;
    243254   
    244255    this.Map(rawState, terrainAnalyser.map);
    245     this.passMap = new Uint8Array(terrainAnalyser.length);
     256    this.landPassMap = new Uint8Array(terrainAnalyser.length);
     257    this.navalPassMap = new Uint8Array(terrainAnalyser.length);
    246258
    247259    this.regionSize = [];
    248     this.regionSize.push(0);
     260    this.regionType = []; // "inaccessible", "land" or "water";
     261    // ID of the region associated with an array of region IDs.
     262    this.regionLinks = [];
    249263   
    250264    // initialized to 0, it's more optimized to start at 1 (I'm checking that if it's not 0, then it's already aprt of a region, don't touch);
     
    252266    // So start at 2.
    253267    this.regionID = 2;
    254     for (var i = 0; i < this.passMap.length; ++i) {
    255         if (this.passMap[i] === 0 && this.map[i] !== 0) {   // any non-painted, non-inacessible area.
    256             this.regionSize.push(0);    // updated
    257             this.floodFill(i,this.regionID,false);
    258             this.regionID++;
    259         } else if (this.passMap[i] === 0) { // any non-painted, inacessible area.
     268   
     269    for (var i = 0; i < this.landPassMap.length; ++i) {
     270        if (this.map[i] !== 0) {    // any non-painted, non-inacessible area.
     271            if (this.landPassMap[i] === 0 && this.floodFill(i,this.regionID,false))
     272                this.regionType[this.regionID++] = "land";
     273            if (this.navalPassMap[i] === 0 && this.floodFill(i,this.regionID,true))
     274                this.regionType[this.regionID++] = "water";
     275        } else if (this.landPassMap[i] === 0) { // any non-painted, inacessible area.
    260276            this.floodFill(i,1,false);
    261         }
    262     }
     277            this.floodFill(i,1,true);
     278        }
     279    }
     280   
     281    // calculating region links. Regions only touching diagonaly are not linked.
     282    // since we're checking all of them, we'll check from the top left to the bottom right
     283    var w = this.width;
     284    for (var x = 0; x < this.width-1; ++x)
     285    {
     286        for (var y = 0; y < this.height-1; ++y)
     287        {
     288            // checking right.
     289            var thisLID = this.landPassMap[x+y*w];
     290            var thisNID = this.navalPassMap[x+y*w];
     291            var rightLID = this.landPassMap[x+1+y*w];
     292            var rightNID = this.navalPassMap[x+1+y*w];
     293            var bottomLID = this.landPassMap[x+y*w+w];
     294            var bottomNID = this.navalPassMap[x+y*w+w];
     295            if (thisLID > 1)
     296            {
     297                if (rightNID > 1)
     298                    if (this.regionLinks[thisLID].indexOf(rightNID) === -1)
     299                        this.regionLinks[thisLID].push(rightNID);
     300                if (bottomNID > 1)
     301                    if (this.regionLinks[thisLID].indexOf(bottomNID) === -1)
     302                        this.regionLinks[thisLID].push(bottomNID);
     303            }
     304            if (thisNID > 1)
     305            {
     306                if (rightLID > 1)
     307                    if (this.regionLinks[thisNID].indexOf(rightLID) === -1)
     308                        this.regionLinks[thisNID].push(rightLID);
     309                if (bottomLID > 1)
     310                    if (this.regionLinks[thisNID].indexOf(bottomLID) === -1)
     311                        this.regionLinks[thisNID].push(bottomLID);
     312                if (thisLID > 1)
     313                    if (this.regionLinks[thisNID].indexOf(thisLID) === -1)
     314                        this.regionLinks[thisNID].push(thisLID);
     315            }
     316        }
     317    }
     318   
     319    //warn(uneval(this.regionLinks));
     320
     321    //Engine.DumpImage("LandPassMap.png", this.landPassMap, this.width, this.height, 255);
     322    //Engine.DumpImage("NavalPassMap.png", this.navalPassMap, this.width, this.height, 255);
    263323}
    264 copyPrototype(Accessibility, TerrainAnalysis);
    265 
    266 Accessibility.prototype.getAccessValue = function(position){
     324
     325Accessibility.prototype.getAccessValue = function(position, onWater) {
    267326    var gamePos = this.gamePosToMapPos(position);
    268     return this.passMap[gamePos[0] + this.width*gamePos[1]];
     327    if (onWater === true)
     328        return this.navalPassMap[gamePos[0] + this.width*gamePos[1]];
     329    var ret = this.landPassMap[gamePos[0] + this.width*gamePos[1]];
     330    if (ret === 1)
     331    {
     332        // quick spiral search.
     333        var indx = [ [-1,-1],[-1,0],[-1,1],[0,1],[1,1],[1,0],[1,-1],[0,-1]]
     334        for (i in indx)
     335        {
     336            ret = this.landPassMap[gamePos[0]+indx[0] + this.width*(gamePos[1]+indx[0])]
     337            if (ret !== undefined && ret !== 1)
     338                return ret;
     339        }
     340    }
     341    return ret;
    269342};
    270343
     
    284357// Hardcore means is also checks for isAccessible at the end (it checks for either water or land though, beware).
    285358// This is a blind check and not a pathfinder: for all it knows there is a huge block of trees in the middle.
    286 Accessibility.prototype.pathAvailable = function(gameState, start,end, hardcore){
     359Accessibility.prototype.pathAvailable = function(gameState, start, end, onWater, hardcore){
    287360    var pstart = this.gamePosToMapPos(start);
    288361    var istart = pstart[0] + pstart[1]*this.width;
    289362    var pend = this.gamePosToMapPos(end);
    290363    var iend = pend[0] + pend[1]*this.width;
    291 
    292     if (this.passMap[istart] === this.passMap[iend]) {
    293         if (hardcore && (this.isAccessible(gameState, end,true) || this.isAccessible(gameState, end,false)))
     364    if (onWater)
     365    {
     366        if (this.navalPassMap[istart] === this.navalPassMap[iend]) {
     367            if (hardcore && this.isAccessible(gameState, end,false))
     368                return true;
     369            else if (hardcore)
     370                return false;
    294371            return true;
    295         else if (hardcore)
    296             return false;
    297         return true;
     372        }
     373    } else {
     374        if (this.landPassMap[istart] === this.landPassMap[iend]) {
     375            if (hardcore && this.isAccessible(gameState, end,true))
     376                return true;
     377            else if (hardcore)
     378                return false;
     379            return true;
     380        }
    298381    }
    299382    return false;
    300383};
    301 Accessibility.prototype.getRegionSize = function(position){
     384
     385Accessibility.prototype.getTrajectTo = function(start, end, noBound) {
     386    var pstart = this.gamePosToMapPos(start);
     387    var istart = pstart[0] + pstart[1]*this.width;
     388    var pend = this.gamePosToMapPos(end);
     389    var iend = pend[0] + pend[1]*this.width;
     390   
     391    var onLand = true;
     392    if (this.landPassMap[istart] <= 1 && this.navalPassMap[istart] > 1)
     393        onLand = false;
     394    if (this.landPassMap[istart] <= 1 && this.navalPassMap[istart] <= 1)
     395        return false;
     396   
     397    var endRegion = this.landPassMap[iend];
     398    if (endRegion <= 1 && this.navalPassMap[iend] > 1)
     399        endRegion = this.navalPassMap[iend];
     400    else if (endRegion <= 1)
     401        return false;
     402   
     403    if (onLand)
     404        var startRegion = this.landPassMap[istart];
     405    else
     406        var startRegion = this.navalPassMap[istart];
     407
     408    return this.getTrajectToIndex(startRegion, endRegion, noBound);
     409}
     410
     411// Return a "path" of accessibility indexes from one point to another, including the start and the end indexes (unless specified otherwise)
     412// this can tell you what sea zone you need to have a dock on, for example.
     413// assumes a land unit unless start point is over deep water.
     414// if the path is more complicated than "land->sea->land" (or "sea->land->sea"), it will run A* to try and figure it out
     415// Thus it can handle arbitrarily complicated paths (theoretically).
     416Accessibility.prototype.getTrajectToIndex = function(istart, iend, noBound){
     417    var startRegion = istart;
     418    var currentRegion = istart;
     419   
     420    var endRegion = iend;
     421   
     422    // optimizations to avoid needless memory usage
     423    // if it's the same, return the path
     424    if (startRegion === endRegion)
     425        return [startRegion];
     426    else if (this.regionLinks[startRegion].indexOf(endRegion) !== -1)
     427        return [startRegion, endRegion];
     428    else
     429    {
     430        var rgs = this.regionLinks[startRegion];
     431        for (p in rgs)
     432        {
     433            if (this.regionLinks[rgs[p]].indexOf(endRegion) !== -1)
     434                return [startRegion, rgs[p], endRegion];
     435        }
     436    }
     437    // it appears to be difficult.
     438    // computing A* over a graph with all nodes equally good (might want to change this sometimes), currently it returns the shortest path switch-wise.
     439    this.openList = [];
     440    this.parentSquare = new Uint8Array(this.regionSize.length);
     441    this.isOpened = new Boolean(this.regionSize.length);
     442    this.gCostArray = new Uint8Array(this.regionSize.length);
     443
     444    this.isOpened[currentRegion] = true;
     445    this.openList.push(currentRegion);
     446    this.gCostArray[currentRegion] = 0;
     447    this.parentSquare[currentRegion] = currentRegion;
     448
     449    var w = this.width;
     450    var h = this.height;
     451   
     452    //creation of variables used in the loop
     453    var found = false;
     454
     455    // on to A*
     456    while (found === false && this.openList.length !== 0) {
     457        var currentDist = 300;
     458        var ti = 0;
     459        for (var i in this.openList)
     460        {
     461            var sum = this.gCostArray[this.openList[i]];
     462            if (sum < currentDist)
     463            {
     464                ti = i;
     465                currentRegion = this.openList[i];
     466                currentDist = sum;
     467            }
     468        }
     469        this.openList.splice(ti,1);
     470        this.isOpened[currentRegion] = false;
     471
     472        // special case, might make it faster (usually oceans connect multiple land masses, sometimes all of them)
     473        if (this.regionType[currentRegion] == "water" && endLand)
     474        {
     475            var idx = this.regionLinks[currentRegion].indexOf(endRegion);
     476            if (idx !== -1)
     477            {
     478                this.parentSquare[endRegion] = currentRegion;
     479                this.gCostArray[endRegion] = this.gCostArray[currentRegion] + 1;
     480                found = true;
     481                break;
     482            }
     483        }
     484        for (var i in this.regionLinks[currentRegion])
     485        {
     486            var region = this.regionLinks[currentRegion][i];
     487            if(this.isOpened[region] === undefined)
     488            {
     489                this.parentSquare[region] = currentRegion;
     490                this.gCostArray[region] = this.gCostArray[currentRegion] + 1;
     491                this.openList.push(region);
     492                this.isOpened[region] = true;
     493                if (region === endRegion)
     494                {
     495                    found = true;
     496                    break;
     497                }
     498            } else {
     499                if (this.gCostArray[region] > 1 + this.gCostArray[currentRegion])
     500                {
     501                    this.parentSquare[region] = currentRegion;
     502                    this.gCostArray[region] = 1 + this.gCostArray[currentRegion];
     503                }
     504            }
     505        }
     506    }
     507    var path = [];
     508    if (found) {
     509        currentRegion = endRegion;
     510        if (!noBound)
     511            path.push(currentRegion);
     512        while (this.parentSquare[currentRegion] !== startRegion)
     513        {
     514            currentRegion = this.parentSquare[currentRegion];
     515            path.push(currentRegion);
     516        }
     517        if (!noBound)
     518            path.push(startRegion);
     519    } else {
     520        delete this.parentSquare;
     521        delete this.isOpened;
     522        delete this.gCostArray;
     523        return false;
     524    }
     525   
     526    delete this.parentSquare;
     527    delete this.isOpened;
     528    delete this.gCostArray;
     529   
     530    return path;
     531};
     532
     533Accessibility.prototype.getRegionSize = function(position, onWater){
    302534    var pos = this.gamePosToMapPos(position);
    303535    var index = pos[0] + pos[1]*this.width;
    304     if (this.regionSize[this.passMap[index]] === undefined)
     536    var ID = (onWater === true) ? this.navalPassMap[index] : this.landPassMap[index];
     537    if (this.regionSize[ID] === undefined)
    305538        return 0;
    306     return this.regionSize[this.passMap[index]];
    307 };
    308 Accessibility.prototype.getRegionSizei = function(index) {
    309     if (this.regionSize[this.passMap[index]] === undefined)
     539    return this.regionSize[ID];
     540};
     541
     542Accessibility.prototype.getRegionSizei = function(index, onWater) {
     543    if (this.regionSize[this.landPassMap[index]] === undefined && (!onWater || this.regionSize[this.navalPassMap[index]] === undefined))
    310544        return 0;
    311     return this.regionSize[this.passMap[index]];
    312 };
    313 
    314 // Implementation of a fast flood fill. Reasonably good performances. Runs once at startup.
     545    if (onWater && this.regionSize[this.navalPassMap[index]] > this.regionSize[this.landPassMap[index]])
     546        return this.regionSize[this.navalPassMap[index]];
     547    return this.regionSize[this.landPassMap[index]];
     548};
     549
     550// Implementation of a fast flood fill. Reasonably good performances for JS.
    315551// TODO: take big zones of impassable trees into account?
    316552Accessibility.prototype.floodFill = function(startIndex, value, onWater)
    317553{
    318554    this.s = startIndex;
    319     if (this.passMap[this.s] !== 0) {
     555    if ((!onWater && this.landPassMap[this.s] !== 0) || (onWater && this.navalPassMap[this.s] !== 0) ) {
    320556        return false;   // already painted.
    321557    }
    322558   
    323559    this.floodFor = "land";
    324     if (this.map[this.s] === 200 || (this.map[this.s] === 201 && onWater === true))
     560    if (this.map[this.s] === 0)
     561    {
     562        this.landPassMap[this.s] = 1;
     563        this.navalPassMap[this.s] = 1;
     564        return false;
     565    }
     566    if (onWater === true)
     567    {
     568        if (this.map[this.s] !== 200 && this.map[this.s] !== 201)
     569        {
     570            this.navalPassMap[this.s] = 1;  // impassable for naval
     571            return false;   // do nothing
     572        }
    325573        this.floodFor = "water";
    326     else if (this.map[this.s] === 0)
    327         this.floodFor = "impassable";
    328 
     574    } else if (this.map[this.s] === 200) {
     575        this.landPassMap[this.s] = 1;   // impassable for land
     576        return false;
     577    }
     578   
     579    // here we'll be able to start.
     580    for (var i = this.regionSize.length; i <= value; ++i)
     581    {
     582        this.regionLinks.push([]);
     583        this.regionSize.push(0);
     584        this.regionType.push("inaccessible");
     585    }
    329586    var w = this.width;
    330587    var h = this.height;
     
    348605            if (index < 0)
    349606                break;
    350             if (this.floodFor === "impassable" && this.map[index] === 0 && this.passMap[index] === 0) {
     607            if (this.floodFor === "land" && this.landPassMap[index] === 0 && this.map[index] !== 0 && this.map[index] !== 200) {
    351608                loop = true;
    352             } else if (this.floodFor === "land" && this.passMap[index] === 0 && this.map[index] !== 0 && this.map[index] !== 200) {
    353                 loop = true;
    354             } else if (this.floodFor === "water" && this.passMap[index] === 0 && (this.map[index] === 200 || (this.map[index] === 201 && this.onWater)) ) {
     609            } else if (this.floodFor === "water" && this.navalPassMap[index] === 0 && (this.map[index] === 200 || this.map[index] === 201)) {
    355610                loop = true;
    356611            } else {
     
    364619        do {
    365620            var index = +newIndex + w*y;
    366             if (this.floodFor === "impassable" && this.map[index] === 0 && this.passMap[index] === 0) {
    367                 this.passMap[index] = value;
     621           
     622            if (this.floodFor === "land" && this.landPassMap[index] === 0 && this.map[index] !== 0 && this.map[index] !== 200) {
     623                this.landPassMap[index] = value;
    368624                this.regionSize[value]++;
    369             } else if (this.floodFor === "land" && this.passMap[index] === 0 && this.map[index] !== 0 && this.map[index] !== 200) {
    370                 this.passMap[index] = value;
    371                 this.regionSize[value]++;
    372             } else if (this.floodFor === "water" && this.passMap[index] === 0 && (this.map[index] === 200 || (this.map[index] === 201 && this.onWater)) ) {
    373                 this.passMap[index] = value;
     625            } else if (this.floodFor === "water" && this.navalPassMap[index] === 0 && (this.map[index] === 200 || this.map[index] === 201)) {
     626                this.navalPassMap[index] = value;
    374627                this.regionSize[value]++;
    375628            } else {
     
    379632            if (index%w > 0)
    380633            {
    381                 if (this.floodFor === "impassable" && this.map[index -1] === 0 && this.passMap[index -1] === 0) {
     634                if (this.floodFor === "land" && this.landPassMap[index -1] === 0 && this.map[index -1] !== 0 && this.map[index -1] !== 200) {
    382635                    if(!reachLeft) {
    383636                        IndexArray.push(index -1);
    384637                        reachLeft = true;
    385638                    }
    386                 } else if (this.floodFor === "land" && this.passMap[index -1] === 0 && this.map[index -1] !== 0 && this.map[index -1] !== 200) {
     639                } else if (this.floodFor === "water" && this.navalPassMap[index -1] === 0 && (this.map[index -1] === 200 || this.map[index -1] === 201)) {
    387640                    if(!reachLeft) {
    388641                        IndexArray.push(index -1);
    389642                        reachLeft = true;
    390643                    }
    391                 } else if (this.floodFor === "water" && this.passMap[index -1] === 0 && (this.map[index -1] === 200 || (this.map[index -1] === 201 && this.onWater)) ) {
    392                     if(!reachLeft) {
    393                         IndexArray.push(index -1);
    394                         reachLeft = true;
    395                     }
    396644                } else if(reachLeft) {
    397645                    reachLeft = false;
     
    400648            if (index%w < w - 1)
    401649            {
    402                 if (this.floodFor === "impassable" && this.map[index +1] === 0 && this.passMap[index +1] === 0) {
     650                if (this.floodFor === "land" && this.landPassMap[index +1] === 0 && this.map[index +1] !== 0 && this.map[index +1] !== 200) {
    403651                    if(!reachRight) {
    404652                        IndexArray.push(index +1);
    405653                        reachRight = true;
    406654                    }
    407                 } else if (this.floodFor === "land" && this.passMap[index +1] === 0 && this.map[index +1] !== 0 && this.map[index +1] !== 200) {
     655                } else if (this.floodFor === "water" && this.navalPassMap[index +1] === 0 && (this.map[index +1] === 200 || this.map[index +1] === 201)) {
    408656                    if(!reachRight) {
    409657                        IndexArray.push(index +1);
    410658                        reachRight = true;
    411659                    }
    412                 } else if (this.floodFor === "water" && this.passMap[index +1] === 0 && (this.map[index +1] === 200 || (this.map[index +1] === 201 && this.onWater)) ) {
    413                     if(!reachRight) {
    414                         IndexArray.push(index +1);
    415                         reachRight = true;
    416                     }
    417660                } else if(reachRight) {
    418661                    reachRight = false;
     
    420663            }
    421664            ++y;
    422         } while (index/w < w)   // should actually break
     665        } while (index/w < w-1) // should actually break
    423666    }
    424667    return true;
    425668}
    426669
    427 function landSizeCounter(rawState,terrainAnalyzer) {
    428     var self = this;
    429    
    430     this.passMap = terrainAnalyzer.map;
    431 
    432     var map = new Uint8Array(this.passMap.length);
    433     this.Map(rawState,map);
    434 
    435    
    436     for (var i = 0; i < this.passMap.length; ++i) {
    437         if (this.passMap[i] !== 0)
    438             this.map[i] = 255;
    439         else
    440             this.map[i] = 0;
    441     }
    442    
    443     this.expandInfluences();
    444 }
    445 copyPrototype(landSizeCounter, TerrainAnalysis);
    446 
    447 // Implementation of A* as a flood fill. Possibility of (clever) oversampling
    448 // for efficiency or for disregarding too small passages.
    449 // can operate over several turns, though default is only one turn.
    450 landSizeCounter.prototype.getAccessibleLandSize = function(position, sampling, mode, OnlyBuildable, sizeLimit, iterationLimit)
    451 {
    452     if (sampling === undefined)
    453         this.Sampling = 1;
    454     else
    455         this.Sampling = sampling < 1 ? 1 : sampling;
    456    
    457     // this checks from the actual starting point. If that is inaccessible (0), it returns undefined;
    458     if (position.length !== undefined) {
    459         // this is an array
    460         if (position[0] < 0 || this.gamePosToMapPos(position)[0] >= this.width || position[1] < 0 || this.gamePosToMapPos(position)[1] >= this.height)
    461             return undefined;
    462        
    463         var s = this.gamePosToMapPos(position);
    464         this.s = s[0] + w*s[1];
    465         if (this.map[this.s] === 0 || this.map[this.s] === 200 || (OnlyBuildable === true && this.map[this.s] === 201) ) {
    466             return undefined;
    467         }
    468     } else {
    469         this.s = position;
    470         if (this.map[this.s] === 0 || this.map[this.s] === 200 || (OnlyBuildable === true && this.map[this.s] === 201) ) {
    471             return undefined;
    472         }
    473     }
    474        
    475     if (mode === undefined)
    476         this.mode = "default";
    477     else
    478         this.mode = mode;
    479 
    480     if (sizeLimit === undefined)
    481         this.sizeLimit = 300000;
    482     else
    483         this.sizeLimit = sizeLimit;
    484 
    485     var w = this.width;
    486     var h = this.height;
    487    
    488     // max map size is 512*512, this is higher.
    489     this.iterationLimit = 300000;
    490     if (iterationLimit !== undefined)
    491         this.iterationLimit = iterationLimit;
    492    
    493     this.openList = [];
    494     this.isOpened = new Boolean(this.map.length);
    495     this.gCostArray = new Uint16Array(this.map.length);
    496 
    497     this.currentSquare = this.s;
    498     this.isOpened[this.s] = true;
    499     this.openList.push(this.s);
    500     this.gCostArray[this.s] = 0;
    501    
    502     this.countedValue = 1;
    503     this.countedArray = [this.s];
    504    
    505     if (OnlyBuildable !== undefined)
    506         this.onlyBuildable = OnlyBuildable;
    507     else
    508         this.onlyBuildable = true;
    509    
    510     return this.continueLandSizeCalculation();
    511 }
    512 landSizeCounter.prototype.continueLandSizeCalculation = function()
    513 {
    514     var w = this.width;
    515     var h = this.height;
    516     var positions = [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1]];
    517     var cost = [10,10,10,10,15,15,15,15];
    518    
    519     //creation of variables used in the loop
    520     var nouveau = false;
    521     var shortcut = false;
    522     var Sampling = this.Sampling;
    523     var infinity = Math.min();
    524     var currentDist = infinity;
    525    
    526     var iteration = 0;
    527     while (this.openList.length !== 0 && iteration < this.iterationLimit && this.countedValue < this.sizeLimit && this.countedArray.length < this.sizeLimit){
    528         currentDist = infinity;
    529         for (var i in this.openList)
    530         {
    531             var sum = this.gCostArray[this.openList[i]];
    532             if (sum < currentDist)
    533             {
    534                 this.currentSquare = this.openList[i];
    535                 currentDist = sum;
    536             }
    537         }
    538         this.openList.splice(this.openList.indexOf(this.currentSquare),1);
    539 
    540         shortcut = false;
    541         this.isOpened[this.currentSquare] = false;
    542         for (var i in positions) {
    543             var index = 0 + this.currentSquare + positions[i][0]*Sampling + w*Sampling*positions[i][1];
    544             if (this.passMap[index] !== 0 && this.passMap[index] !== 200 && this.map[index] >= Sampling && (!this.onlyBuildable || this.passMap[index] !== 201)) {
    545                 if(this.isOpened[index] === undefined) {
    546                     if (this.mode === "default")
    547                         this.countedValue++;
    548                     else if (this.mode === "array")
    549                         this.countedArray.push(index);
    550                     this.gCostArray[index] = this.gCostArray[this.currentSquare] + cost[i] * Sampling;
    551                     this.openList.push(index);
    552                     this.isOpened[index] = true;
    553                 }
    554             }
    555         }
    556         iteration++;
    557     }
    558    
    559     if (iteration === this.iterationLimit && this.openList.length !== 0 && this.countedValue !== this.sizeLimit && this.countedArray.length !== this.sizeLimit)
    560     {
    561         // we've got to assume that we stopped because we reached the upper limit of iterations
    562         return "toBeContinued";
    563     }
    564        
    565     delete this.parentSquare;
    566     delete this.isOpened;
    567     delete this.fCostArray;
    568     delete this.gCostArray;
    569    
    570     if (this.mode === "default")
    571         return this.countedValue;
    572     else if (this.mode === "array")
    573         return this.countedArray;
    574     return undefined;
    575 }
     670// TODO: make it regularly update stone+metal mines and their resource levels.
     671// creates and maintains a map of unused resource density
     672// this also takes dropsites into account.
     673// resources that are "part" of a dropsite are not counted.
     674SharedScript.prototype.updateResourceMaps = function(sharedScript, events) {
     675   
     676    for (var resource in this.decreaseFactor){
     677        // if there is no resourceMap create one with an influence for everything with that resource
     678        if (! this.resourceMaps[resource]){
     679            // We're creting them 8-bit. Things could go above 255 if there are really tons of resources
     680            // But at that point the precision is not really important anyway. And it saves memory.
     681            this.resourceMaps[resource] = new Map(sharedScript, new Uint8Array(sharedScript.passabilityMap.data.length));
     682            this.resourceMaps[resource].setMaxVal(255);
     683            this.CCResourceMaps[resource] = new Map(sharedScript, new Uint8Array(sharedScript.passabilityMap.data.length));
     684            this.CCResourceMaps[resource].setMaxVal(255);
     685        }
     686    }
     687   
     688    // Look for destroy events and subtract the entities original influence from the resourceMap
     689    // TODO: perhaps do something when dropsites appear/disappear.
     690    for (var key in events) {
     691        var e = events[key];
     692        if (e.type === "Destroy") {
     693            if (e.msg.entityObj){
     694                var ent = e.msg.entityObj;
     695                if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic !== "treasure") {
     696                    var resource = ent.resourceSupplyType().generic;
     697                    var x = Math.floor(ent.position()[0] / 4);
     698                    var z = Math.floor(ent.position()[1] / 4);
     699                    var strength = Math.floor(ent.resourceSupplyMax()/this.decreaseFactor[resource]);
     700                   
     701                    if (resource === "wood" || resource === "food")
     702                    {
     703                        this.resourceMaps[resource].addInfluence(x, z, 2, 5,'constant');
     704                        this.resourceMaps[resource].addInfluence(x, z, 9.0, -strength,'constant');
     705                        this.CCResourceMaps[resource].addInfluence(x, z, 15, -strength/2.0,'constant');
     706                    }
     707                    else if (resource === "stone" || resource === "metal")
     708                    {
     709                        this.resourceMaps[resource].addInfluence(x, z, 8, 50);
     710                        this.resourceMaps[resource].addInfluence(x, z, 12.0, -strength/1.5);
     711                        this.resourceMaps[resource].addInfluence(x, z, 12.0, -strength/2.0,'constant');
     712                        this.CCResourceMaps[resource].addInfluence(x, z, 30, -strength,'constant');
     713                    }
     714                }
     715            }
     716        } else if (e.type === "Create") {
     717            if (e.msg.entity){
     718                var ent = sharedScript._entities[e.msg.entity];
     719                if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic !== "treasure"){
     720                    var resource = ent.resourceSupplyType().generic;
     721                   
     722                    var x = Math.floor(ent.position()[0] / 4);
     723                    var z = Math.floor(ent.position()[1] / 4);
     724                    var strength = Math.floor(ent.resourceSupplyMax()/this.decreaseFactor[resource]);
     725                    if (resource === "wood" || resource === "food")
     726                    {
     727                        this.CCResourceMaps[resource].addInfluence(x, z, 15, strength/2.0,'constant');
     728                        this.resourceMaps[resource].addInfluence(x, z, 9.0, strength,'constant');
     729                        this.resourceMaps[resource].addInfluence(x, z, 2, -5,'constant');
     730                    }
     731                    else if (resource === "stone" || resource === "metal")
     732                    {
     733                        this.CCResourceMaps[resource].addInfluence(x, z, 30, strength,'constant');
     734                        this.resourceMaps[resource].addInfluence(x, z, 12.0, strength/1.5);
     735                        this.resourceMaps[resource].addInfluence(x, z, 12.0, strength/2.0,'constant');
     736                        this.resourceMaps[resource].addInfluence(x, z, 8, -50);
     737                    }
     738                }
     739            }
     740        }
     741    }
     742};
Note: See TracChangeset for help on using the changeset viewer.