Ticket #4142: 4142_petra_regicide_support_v1.6.patch

File 4142_petra_regicide_support_v1.6.patch, 10.8 KB (added by Sandarac, 7 years ago)

Handle multiple heroes in regicide, and cache entities important for specific victory conditions.

  • binaries/data/mods/public/simulation/ai/petra/attackPlan.js

     
    677677    if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1 ||
    678678        ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined)
    679679        return false;
    680     // TODO if more than one hero in regicide, prevent only the "right one" from being affected
    681     if (gameState.getGameType() === "regicide" && ent.hasClass("Hero") && (this.overseas || ent.healthLevel() < 0.8))
     680    if (gameState.ai.HQ.gameTypeManager.criticalEnts.has(ent.id()) && (this.overseas || ent.healthLevel() < 0.8))
    682681        return false;
    683682    return true;
    684683};
  • binaries/data/mods/public/simulation/ai/petra/defenseManager.js

     
    388388            return;
    389389        if (ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined)
    390390            return;
    391         if (gameState.getGameType() === "regicide" && ent.hasClass("Hero"))
     391        if (gameState.ai.HQ.gameTypeManager.criticalEnts.has(ent.id()))
    392392            return;
    393393        if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1)
    394394        {
  • binaries/data/mods/public/simulation/ai/petra/gameTypeManager.js

     
    44/**
    55 * Handle events that are important to specific gameTypes
    66 * In regicide, train and manage healer guards for the hero
    7  * TODO: Handle when there is more than one hero in regicide
    87 * TODO: Assign military units to guard the hero in regicide
     8 * TODO: Assign guards to the wonder in a wonder game
    99 */
    1010
    1111m.GameTypeManager = function(Config)
    1212{
    1313    this.Config = Config;
    14     this.heroGarrisonEmergency = false;
    15     this.healersAssignedToHero = 0; // Accounts for healers being trained as well
    16     this.heroGuards = []; // Holds id of ents currently guarding the hero
     14    this.criticalEnts = new Map();
     15    this.guardEnts = new Map(); // Holds guard ids and if the ent is currently guarding
     16    this.healersPerCriticalEnt = 2 + Math.round(this.Config.personality.defensive * 2);
    1717};
    1818
    1919/**
     20 * Cache the ids of any inital gameType-critical entities
     21 * In regicide, these are the inital heroes that the player starts with.
     22 */
     23m.GameTypeManager.prototype.init = function(gameState)
     24{
     25    if (gameState.getGameType() !== "regicide")
     26        return;
     27
     28    let heroEnts = gameState.getOwnEntitiesByClass("Hero", true).toEntityArray();
     29    for (let hero of heroEnts)
     30        this.criticalEnts.set(hero.id(), {
     31            "garrisonEmergency": false,
     32            "stance": hero.hasClass("Soldier") ? "aggressive" : "passive",
     33            "healersAssigned": 0,
     34            "guards": [] // ents who are currently guarding this hero
     35        });
     36};
     37
     38/**
    2039 * In regicide mode, if the hero has less than 70% health, try to garrison it in a healing structure
    2140 * If it is less than 40%, try to garrison in the closest possible structure
    2241 * If the hero cannot garrison, retreat it to the closest base
     
    5069    {
    5170        let target = gameState.getEntityById(evt.target);
    5271        if (!target || !gameState.isEntityOwn(target) || !target.position() ||
    53             !target.hasClass("Hero") || target.healthLevel() > 0.7)
     72            !this.criticalEnts.has(evt.target) || target.healthLevel() > 0.7)
    5473            continue;
    5574
    5675        let plan = target.getMetadata(PlayerID, "plan");
     76        let hero = this.criticalEnts.get(evt.target);
    5777        if (plan !== -2 && plan !== -3)
    5878        {
    5979            target.stopMoving();
     
    7292                    army.removeOwn(gameState, target.id());
    7393            }
    7494
    75             this.heroGarrisonEmergency = target.healthLevel() < 0.4;
    76             this.pickHeroRetreatLocation(gameState, target, this.heroGarrisonEmergency);
     95            hero.garrisonEmergency = target.healthLevel() < 0.4;
     96            this.pickHeroRetreatLocation(gameState, target, hero.garrisonEmergency);
    7797        }
    78         else if (target.healthLevel() < 0.4 && !this.heroGarrisonEmergency)
     98        else if (target.healthLevel() < 0.4 && !hero.garrisonEmergency)
    7999        {
    80100            // the hero is severely wounded, try to retreat/garrison quicker
    81101            gameState.ai.HQ.garrisonManager.cancelGarrison(target);
    82102            this.pickHeroRetreatLocation(gameState, target, true);
    83             this.heroGarrisonEmergency = true;
     103            hero.garrisonEmergency = true;
    84104        }
    85105    }
    86106
    87     // check if new healers/guards need to be assigned to the hero
     107    // check if new healers/guards need to be assigned to an ent
    88108    for (let evt of events.Destroy)
    89109    {
    90         if (!evt.entityObj || evt.entityObj.owner() !== PlayerID ||
    91             this.heroGuards.indexOf(evt.entityObj.id()) === -1)
     110        if (!evt.entityObj || evt.entityObj.owner() !== PlayerID)
    92111            continue;
    93112
    94         this.heroGuards.splice(this.heroGuards.indexOf(evt.entityObj.id()), 1);
    95         if (evt.entityObj.hasClass("Healer"))
    96             --this.healersAssignedToHero;
     113        for (let data of this.criticalEnts.values())
     114            if (data.guards.indexOf(evt.entityObj.id()) !== -1)
     115            {
     116                let index = data.guards.indexOf(evt.entityObj.id());
     117                data.guards.splice(index, 1);
     118                this.guardEnts.delete(evt.entityObj.id());
     119
     120                if (evt.entityObj.hasClass("Healer"))
     121                    --data.healersAssigned;
     122            }
    97123    }
    98124
    99125    for (let evt of events.TrainingFinished)
     
    109135    for (let evt of events.Garrison)
    110136    {
    111137        let ent = gameState.getEntityById(evt.entity);
    112         if (!ent || !ent.isOwn(PlayerID) || !ent.hasClass("Hero"))
     138        if (!ent || !ent.isOwn(PlayerID) || !this.criticalEnts.has(evt.entity))
    113139            continue;
    114140
    115         if (this.heroGarrisonEmergency)
    116             this.heroGarrisonEmergency = false;
     141        let hero = this.criticalEnts.get(evt.entity);
     142        if (hero.garrisonEmergency)
     143            hero.garrisonEmergency = false;
    117144
    118145        if (!gameState.getEntityById(evt.holder).hasClass("Ship"))
    119146            continue;
    120147
    121148        // If the hero is garrisoned on a ship, remove its guards
    122         for (let guardId of this.heroGuards)
     149        for (let guardId of hero.guards)
     150        {
    123151            gameState.getEntityById(guardId).removeGuard();
     152            this.guardEnts.delete(guardId);
     153        }
    124154
    125         this.heroGuards = [];
     155        hero.guards = [];
    126156    }
    127157
    128158    for (let evt of events.UnGarrison)
     
    132162            continue;
    133163
    134164        // If this ent travelled to a hero's accessValue, try again to assign as a guard
    135         if (ent.getMetadata(PlayerID, "role") === "regicideHealer" &&
    136             this.heroGuards.indexOf(evt.entity) === -1)
     165        if (ent.getMetadata(PlayerID, "role") === "regicideHealer" && this.guardEnts.has(evt.entity))
    137166        {
    138167            this.assignGuardToRegicideHero(gameState, ent);
    139168            continue;
    140169        }
    141170
    142         if (!ent.hasClass("Hero"))
     171        if (!this.criticalEnts.has(evt.entity))
    143172            continue;
    144173
    145174        // If this is the hero, try to assign ents that should be guarding it, but couldn't previously
    146         let regicideHealers = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "role", "regicideHealer"));
    147         for (let healer of regicideHealers.values())
    148             if (this.heroGuards.indexOf(healer.id()) === -1)
    149                 this.assignGuardToRegicideHero(gameState, healer);
     175        let criticalEnt = this.criticalEnts.get(ent.id());
     176        for (let [id, isGuarding] of this.guardEnts)
     177        {
     178            if (criticalEnt.guards.length >= this.healersPerCriticalEnt)
     179                break;
     180
     181            if (!isGuarding)
     182                this.assignGuardToRegicideHero(gameState, gameState.getEntityById(id));
     183        }
    150184    }
    151185};
    152186
     
    178212        heroEnt.move(basePos.anchor.position()[0], basePos.anchor.position()[1]);
    179213};
    180214
    181 m.GameTypeManager.prototype.trainRegicideHealer = function(gameState, queues)
     215/**
     216 * The number of healers trained per regicide hero (dependant on the defensive trait)
     217 * may not be the number of healers actually guarding a hero.
     218 */
     219m.GameTypeManager.prototype.trainRegicideHealer = function(gameState, queues, id)
    182220{
    183221    if (gameState.ai.HQ.saveResources || !gameState.getOwnEntitiesByClass("Temple", true).hasEntities())
    184222        return;
     
    186224    let template = gameState.applyCiv("units/{civ}_support_healer_b");
    187225
    188226    queues.villager.addPlan(new m.TrainingPlan(gameState, template, { "role": "regicideHealer", "base": 0 }, 1, 1));
    189     ++this.healersAssignedToHero;
     227    ++this.criticalEnts.get(id).healersAssigned;
    190228};
    191229
    192230/**
     
    196234 */
    197235m.GameTypeManager.prototype.assignGuardToRegicideHero = function(gameState, ent)
    198236{
    199     let heroEnt = gameState.getOwnEntitiesByClass("Hero", true).toEntityArray()[0];
     237    // Assign to the ent with the fewest guards
     238    let min = Math.min();
     239    let entId;
     240    for (let [id, data] of this.criticalEnts)
     241    {
     242        if (data.guards.length < min)
     243        {
     244            entId = id;
     245            min = data.guards.length;
     246        }
     247    }
     248
     249    let heroEnt = gameState.getEntityById(entId);
    200250    if (!heroEnt || !heroEnt.position() ||
    201251        !ent.position() || ent.getMetadata(PlayerID, "transport") !== undefined || !ent.canGuard())
    202252        return;
     
    206256    if (entAccess === heroAccess)
    207257    {
    208258        ent.guard(heroEnt);
    209         this.heroGuards.push(ent.id());
     259        this.criticalEnts.get(entId).guards.push(ent.id());
    210260    }
    211261    else
    212262        gameState.ai.HQ.navalManager.requireTransport(gameState, ent, entAccess, heroAccess, heroEnt.position());
     263    this.guardEnts.set(ent.id(), entAccess === heroAccess);
    213264};
    214265
    215266m.GameTypeManager.prototype.update = function(gameState, events, queues)
    216267{
     268    // Wait a turn for trigger scripts to spawn any important ents (i.e. in regicide)
     269    if (gameState.ai.playedTurn === 1)
     270        this.init(gameState);
     271
    217272    this.checkEvents(gameState, events);
    218273
    219274    if (gameState.getGameType() === "wonder")
     
    224279
    225280    if (gameState.ai.playedTurn % 50 === 0)
    226281    {
    227         let heroEnt = gameState.getOwnEntitiesByClass("Hero", true).toEntityArray()[0];
    228         if (heroEnt && heroEnt.healthLevel() > 0.7)
    229             heroEnt.setStance("aggressive");
     282        for (let [id, data] of this.criticalEnts)
     283        {
     284            let ent = gameState.getEntityById(id);
     285            if (ent && ent.healthLevel() > 0.7 && ent.hasClass("Soldier") &&
     286                data.stance !== "aggressive")
     287                ent.setStance("aggressive");
     288        }
    230289    }
    231290
    232     if (this.healersAssignedToHero < 2 + Math.round(this.Config.personality.defensive * 2))
    233         this.trainRegicideHealer(gameState, queues);
     291    for (let [id, data] of this.criticalEnts)
     292        if (data.healersAssigned < this.healersPerCriticalEnt)
     293            this.trainRegicideHealer(gameState, queues, id);
    234294};
    235295
    236296m.GameTypeManager.prototype.Serialize = function()
    237297{
    238298    return {
    239         "heroGarrisonEmergency": this.heroGarrisonEmergency,
    240         "healersAssignedToHero": this.healersAssignedToHero,
    241         "heroGuards": this.heroGuards
     299        "criticalEnts": this.criticalEnts,
     300        "guardEnts": this.guardEnts,
     301        "healersPerCriticalEnt": this.healersPerCriticalEnt
    242302    };
    243303};
    244304