Ticket #4142: 4142_petra_regicide_support_v1.7.patch

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

Fix issues.

  • 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    // Holds ids of all ents who are (or can be) guarding and if the ent is currently guarding
     16    this.guardEnts = new Map();
     17    this.healersPerCriticalEnt = 2 + Math.round(this.Config.personality.defensive * 2);
    1718};
    1819
    1920/**
     21 * Cache the ids of any inital gameType-critical entities.
     22 * In regicide, these are the inital heroes that the player starts with.
     23 */
     24m.GameTypeManager.prototype.init = function(gameState)
     25{
     26    if (gameState.getGameType() !== "regicide")
     27        return;
     28
     29    let heroEnts = gameState.getOwnEntitiesByClass("Hero", true).toEntityArray();
     30    for (let hero of heroEnts)
     31    {
     32        hero.setStance(hero.hasClass("Soldier") ? "aggressive" : "passive");
     33        this.criticalEnts.set(hero.id(), {
     34            "garrisonEmergency": false,
     35            "stance": hero.hasClass("Soldier") ? "aggressive" : "passive",
     36            "healersAssigned": 0,
     37            "guards": new Set() // ids of ents who are currently guarding this hero
     38        });
     39    }
     40};
     41
     42/**
    2043 * In regicide mode, if the hero has less than 70% health, try to garrison it in a healing structure
    2144 * If it is less than 40%, try to garrison in the closest possible structure
    2245 * If the hero cannot garrison, retreat it to the closest base
     
    4871
    4972    for (let evt of events.Attacked)
    5073    {
     74        if (!evt.target || !this.criticalEnts.has(evt.target))
     75            continue;
     76
    5177        let target = gameState.getEntityById(evt.target);
    52         if (!target || !gameState.isEntityOwn(target) || !target.position() ||
    53             !target.hasClass("Hero") || target.healthLevel() > 0.7)
     78        if (!target.position() || target.healthLevel() > 0.7)
    5479            continue;
    5580
    5681        let plan = target.getMetadata(PlayerID, "plan");
     82        let hero = this.criticalEnts.get(evt.target);
    5783        if (plan !== -2 && plan !== -3)
    5884        {
    5985            target.stopMoving();
     
    7298                    army.removeOwn(gameState, target.id());
    7399            }
    74100
    75             this.heroGarrisonEmergency = target.healthLevel() < 0.4;
    76             this.pickHeroRetreatLocation(gameState, target, this.heroGarrisonEmergency);
     101            hero.garrisonEmergency = target.healthLevel() < 0.4;
     102            this.pickHeroRetreatLocation(gameState, target, hero.garrisonEmergency);
    77103        }
    78         else if (target.healthLevel() < 0.4 && !this.heroGarrisonEmergency)
     104        else if (target.healthLevel() < 0.4 && !hero.garrisonEmergency)
    79105        {
    80106            // the hero is severely wounded, try to retreat/garrison quicker
    81107            gameState.ai.HQ.garrisonManager.cancelGarrison(target);
    82108            this.pickHeroRetreatLocation(gameState, target, true);
    83             this.heroGarrisonEmergency = true;
     109            hero.garrisonEmergency = true;
    84110        }
    85111    }
    86112
    87     // check if new healers/guards need to be assigned to the hero
     113    // check if new healers/guards need to be assigned to an ent
    88114    for (let evt of events.Destroy)
    89115    {
    90         if (!evt.entityObj || evt.entityObj.owner() !== PlayerID ||
    91             this.heroGuards.indexOf(evt.entityObj.id()) === -1)
     116        if (!evt.entityObj || evt.entityObj.owner() !== PlayerID)
    92117            continue;
    93118
    94         this.heroGuards.splice(this.heroGuards.indexOf(evt.entityObj.id()), 1);
    95         if (evt.entityObj.hasClass("Healer"))
    96             --this.healersAssignedToHero;
     119        let entId = evt.entityObj.id();
     120        if (this.criticalEnts.has(entId))
     121        {
     122            for (let guardId of this.criticalEnts.get(entId).guards)
     123                this.guardEnts.set(guardId, false);
     124
     125            this.criticalEnts.delete(entId);
     126            continue;
     127        }
     128
     129        if (!this.guardEnts.has(entId))
     130            continue;
     131
     132        for (let data of this.criticalEnts.values())
     133            if (data.guards.has(entId))
     134            {
     135                data.guards.delete(entId);
     136                if (evt.entityObj.hasClass("Healer"))
     137                    --data.healersAssigned;
     138            }
     139
     140        this.guardEnts.delete(entId);
    97141    }
    98142
    99143    for (let evt of events.TrainingFinished)
     
    103147            if (!ent || !ent.isOwn(PlayerID) || ent.getMetadata(PlayerID, "role") !== "regicideHealer")
    104148                continue;
    105149
    106             this.assignGuardToRegicideHero(gameState, ent);
     150            this.assignGuardToCriticalEnt(gameState, ent);
    107151        }
    108152
    109153    for (let evt of events.Garrison)
    110154    {
    111         let ent = gameState.getEntityById(evt.entity);
    112         if (!ent || !ent.isOwn(PlayerID) || !ent.hasClass("Hero"))
     155        if (!this.criticalEnts.has(evt.entity))
    113156            continue;
    114157
    115         if (this.heroGarrisonEmergency)
    116             this.heroGarrisonEmergency = false;
     158        let hero = this.criticalEnts.get(evt.entity);
     159        if (hero.garrisonEmergency)
     160            hero.garrisonEmergency = false;
    117161
    118162        if (!gameState.getEntityById(evt.holder).hasClass("Ship"))
    119163            continue;
    120164
    121165        // If the hero is garrisoned on a ship, remove its guards
    122         for (let guardId of this.heroGuards)
     166        for (let guardId of hero.guards)
     167        {
    123168            gameState.getEntityById(guardId).removeGuard();
    124 
    125         this.heroGuards = [];
     169            this.guardEnts.set(guardId, false);
     170        }
     171        hero.guards.clear();
    126172    }
    127173
    128174    for (let evt of events.UnGarrison)
    129175    {
    130         let ent = gameState.getEntityById(evt.entity);
    131         if (!ent || !ent.isOwn(PlayerID))
     176        if (!this.guardEnts.has(evt.entity) && !this.criticalEnts.has(evt.entity))
    132177            continue;
    133178
     179        let ent = gameState.getEntityById(evt.entity);
    134180        // 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)
     181        if (ent.getMetadata(PlayerID, "role") === "regicideHealer" && !this.guardEnts.get(evt.entity))
    137182        {
    138             this.assignGuardToRegicideHero(gameState, ent);
     183            this.assignGuardToCriticalEnt(gameState, ent);
    139184            continue;
    140185        }
    141186
    142         if (!ent.hasClass("Hero"))
     187        if (!this.criticalEnts.has(evt.entity))
    143188            continue;
    144189
    145         // 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);
     190        // If this is a hero, try to assign ents that should be guarding it, but couldn't previously
     191        let criticalEnt = this.criticalEnts.get(ent.id());
     192        for (let [id, isGuarding] of this.guardEnts)
     193        {
     194            if (criticalEnt.guards.size >= this.healersPerCriticalEnt)
     195                break;
     196
     197            if (!isGuarding)
     198                this.assignGuardToCriticalEnt(gameState, gameState.getEntityById(id));
     199        }
    150200    }
    151201};
    152202
     
    178228        heroEnt.move(basePos.anchor.position()[0], basePos.anchor.position()[1]);
    179229};
    180230
    181 m.GameTypeManager.prototype.trainRegicideHealer = function(gameState, queues)
     231/**
     232 * The number of healers trained per regicide hero (dependent on the defensive trait)
     233 * may not be the number of healers actually guarding a hero at any one time.
     234 */
     235m.GameTypeManager.prototype.trainRegicideHealer = function(gameState, queues, id)
    182236{
    183237    if (gameState.ai.HQ.saveResources || !gameState.getOwnEntitiesByClass("Temple", true).hasEntities())
    184238        return;
     
    186240    let template = gameState.applyCiv("units/{civ}_support_healer_b");
    187241
    188242    queues.villager.addPlan(new m.TrainingPlan(gameState, template, { "role": "regicideHealer", "base": 0 }, 1, 1));
    189     ++this.healersAssignedToHero;
     243    ++this.criticalEnts.get(id).healersAssigned;
    190244};
    191245
    192246/**
    193  * Only send the guard command if the guard's accessIndex is the same as the hero
    194  * and the hero has a position (i.e. not garrisoned)
     247 * Only send the guard command if the guard's accessIndex is the same as the critical ent
     248 * and the critical ent has a position (i.e. not garrisoned)
    195249 * request a transport if the accessIndex value is different
    196250 */
    197 m.GameTypeManager.prototype.assignGuardToRegicideHero = function(gameState, ent)
     251m.GameTypeManager.prototype.assignGuardToCriticalEnt = function(gameState, guardEnt)
    198252{
    199     let heroEnt = gameState.getOwnEntitiesByClass("Hero", true).toEntityArray()[0];
    200     if (!heroEnt || !heroEnt.position() ||
    201         !ent.position() || ent.getMetadata(PlayerID, "transport") !== undefined || !ent.canGuard())
     253    // Assign to the critical ent with the fewest guards
     254    let min = Math.min();
     255    let criticalEntId;
     256    for (let [id, data] of this.criticalEnts)
     257    {
     258        if (data.guards.size > min)
     259            continue;
     260
     261        criticalEntId = id;
     262        min = data.guards.size;
     263    }
     264
     265    if (guardEnt.getMetadata(PlayerID, "transport") !== undefined || !guardEnt.canGuard())
    202266        return;
    203267
    204     let entAccess = gameState.ai.accessibility.getAccessValue(ent.position());
    205     let heroAccess = gameState.ai.accessibility.getAccessValue(heroEnt.position());
     268    let criticalEnt = gameState.getEntityById(criticalEntId);
     269    if (!criticalEnt || !criticalEnt.position() || !guardEnt.position())
     270    {
     271        this.guardEnts.set(guardEnt.id(), false);
     272        return;
     273    }
     274
     275    let entAccess = gameState.ai.accessibility.getAccessValue(guardEnt.position());
     276    let heroAccess = gameState.ai.accessibility.getAccessValue(criticalEnt.position());
    206277    if (entAccess === heroAccess)
    207278    {
    208         ent.guard(heroEnt);
    209         this.heroGuards.push(ent.id());
     279        guardEnt.guard(criticalEnt);
     280        this.criticalEnts.get(criticalEntId).guards.add(guardEnt.id());
    210281    }
    211282    else
    212         gameState.ai.HQ.navalManager.requireTransport(gameState, ent, entAccess, heroAccess, heroEnt.position());
     283        gameState.ai.HQ.navalManager.requireTransport(gameState, guardEnt, entAccess, heroAccess, criticalEnt.position());
     284    this.guardEnts.set(guardEnt.id(), entAccess === heroAccess);
    213285};
    214286
    215287m.GameTypeManager.prototype.update = function(gameState, events, queues)
    216288{
     289    // Wait a turn for trigger scripts to spawn any critical ents (i.e. in regicide)
     290    if (gameState.ai.playedTurn === 1)
     291        this.init(gameState);
     292
    217293    this.checkEvents(gameState, events);
    218294
    219295    if (gameState.getGameType() === "wonder")
     
    223299        return;
    224300
    225301    if (gameState.ai.playedTurn % 50 === 0)
    226     {
    227         let heroEnt = gameState.getOwnEntitiesByClass("Hero", true).toEntityArray()[0];
    228         if (heroEnt && heroEnt.healthLevel() > 0.7)
    229             heroEnt.setStance("aggressive");
    230     }
     302        for (let [id, data] of this.criticalEnts)
     303        {
     304            let ent = gameState.getEntityById(id);
     305            if (ent && ent.healthLevel() > 0.7 && ent.hasClass("Soldier") &&
     306                data.stance !== "aggressive")
     307                ent.setStance("aggressive");
     308        }
    231309
    232     if (this.healersAssignedToHero < 2 + Math.round(this.Config.personality.defensive * 2))
    233         this.trainRegicideHealer(gameState, queues);
     310    for (let [id, data] of this.criticalEnts)
     311        if (data.healersAssigned < this.healersPerCriticalEnt &&
     312            this.guardEnts.size < gameState.getPopulationMax() / 10)
     313            this.trainRegicideHealer(gameState, queues, id);
    234314};
    235315
    236316m.GameTypeManager.prototype.Serialize = function()
    237317{
    238318    return {
    239         "heroGarrisonEmergency": this.heroGarrisonEmergency,
    240         "healersAssignedToHero": this.healersAssignedToHero,
    241         "heroGuards": this.heroGuards
     319        "criticalEnts": this.criticalEnts,
     320        "guardEnts": this.guardEnts,
     321        "healersPerCriticalEnt": this.healersPerCriticalEnt
    242322    };
    243323};
    244324