Ticket #4142: 4142_petra_regicide_support_v1.8.patch

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

More refinements.

  • 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        let heroStance = hero.hasClass("Soldier") ? "aggressive" : "passive";
     33        hero.setStance(heroStance);
     34        this.criticalEnts.set(hero.id(), {
     35            "garrisonEmergency": false,
     36            "stance": heroStance,
     37            "healersAssigned": 0,
     38            "guards": new Set() // ids of ents who are currently guarding this hero
     39        });
     40    }
     41};
     42
     43/**
    2044 * In regicide mode, if the hero has less than 70% health, try to garrison it in a healing structure
    2145 * If it is less than 40%, try to garrison in the closest possible structure
    2246 * If the hero cannot garrison, retreat it to the closest base
     
    4872
    4973    for (let evt of events.Attacked)
    5074    {
     75        if (!this.criticalEnts.has(evt.target))
     76            continue;
     77
    5178        let target = gameState.getEntityById(evt.target);
    52         if (!target || !gameState.isEntityOwn(target) || !target.position() ||
    53             !target.hasClass("Hero") || target.healthLevel() > 0.7)
     79        if (!target || !target.position() || target.healthLevel() > 0.7)
    5480            continue;
    5581
    5682        let plan = target.getMetadata(PlayerID, "plan");
     83        let hero = this.criticalEnts.get(evt.target);
    5784        if (plan !== -2 && plan !== -3)
    5885        {
    5986            target.stopMoving();
     
    7299                    army.removeOwn(gameState, target.id());
    73100            }
    74101
    75             this.heroGarrisonEmergency = target.healthLevel() < 0.4;
    76             this.pickHeroRetreatLocation(gameState, target, this.heroGarrisonEmergency);
     102            hero.garrisonEmergency = target.healthLevel() < 0.4;
     103            this.pickCriticalEntRetreatLocation(gameState, target, hero.garrisonEmergency);
    77104        }
    78         else if (target.healthLevel() < 0.4 && !this.heroGarrisonEmergency)
     105        else if (target.healthLevel() < 0.4 && !hero.garrisonEmergency)
    79106        {
    80107            // the hero is severely wounded, try to retreat/garrison quicker
    81108            gameState.ai.HQ.garrisonManager.cancelGarrison(target);
    82             this.pickHeroRetreatLocation(gameState, target, true);
    83             this.heroGarrisonEmergency = true;
     109            this.pickCriticalEntRetreatLocation(gameState, target, true);
     110            hero.garrisonEmergency = true;
    84111        }
    85112    }
    86113
    87     // check if new healers/guards need to be assigned to the hero
     114    // Check if new healers/guards need to be assigned to an ent
    88115    for (let evt of events.Destroy)
    89116    {
    90         if (!evt.entityObj || evt.entityObj.owner() !== PlayerID ||
    91             this.heroGuards.indexOf(evt.entityObj.id()) === -1)
     117        if (!evt.entityObj || evt.entityObj.owner() !== PlayerID)
    92118            continue;
    93119
    94         this.heroGuards.splice(this.heroGuards.indexOf(evt.entityObj.id()), 1);
    95         if (evt.entityObj.hasClass("Healer"))
    96             --this.healersAssignedToHero;
     120        let entId = evt.entityObj.id();
     121        if (this.criticalEnts.has(entId))
     122        {
     123            for (let guardId of this.criticalEnts.get(entId).guards)
     124                this.guardEnts.set(guardId, false);
     125
     126            this.criticalEnts.delete(entId);
     127            continue;
     128        }
     129
     130        if (!this.guardEnts.has(entId))
     131            continue;
     132
     133        for (let data of this.criticalEnts.values())
     134            if (data.guards.has(entId))
     135            {
     136                data.guards.delete(entId);
     137                if (evt.entityObj.hasClass("Healer"))
     138                    --data.healersAssigned;
     139            }
     140
     141        this.guardEnts.delete(entId);
    97142    }
    98143
    99144    for (let evt of events.TrainingFinished)
     
    100145        for (let entId of evt.entities)
    101146        {
    102147            let ent = gameState.getEntityById(entId);
    103             if (!ent || !ent.isOwn(PlayerID) || ent.getMetadata(PlayerID, "role") !== "regicideHealer")
     148            if (!ent || !ent.isOwn(PlayerID) || ent.getMetadata(PlayerID, "role") !== "criticalEntHealer")
    104149                continue;
    105150
    106             this.assignGuardToRegicideHero(gameState, ent);
     151            this.assignGuardToCriticalEnt(gameState, ent);
    107152        }
    108153
    109154    for (let evt of events.Garrison)
    110155    {
    111         let ent = gameState.getEntityById(evt.entity);
    112         if (!ent || !ent.isOwn(PlayerID) || !ent.hasClass("Hero"))
     156        if (!this.criticalEnts.has(evt.entity))
    113157            continue;
    114158
    115         if (this.heroGarrisonEmergency)
    116             this.heroGarrisonEmergency = false;
     159        let hero = this.criticalEnts.get(evt.entity);
     160        if (hero.garrisonEmergency)
     161            hero.garrisonEmergency = false;
    117162
    118         if (!gameState.getEntityById(evt.holder).hasClass("Ship"))
     163        let holderEnt = gameState.getEntityById(evt.holder);
     164        if (!holderEnt || !holderEnt.hasClass("Ship"))
    119165            continue;
    120166
    121167        // If the hero is garrisoned on a ship, remove its guards
    122         for (let guardId of this.heroGuards)
    123             gameState.getEntityById(guardId).removeGuard();
     168        for (let guardId of hero.guards)
     169        {
     170            let guardEnt = gameState.getEntityById(guardId);
     171            if (!guardEnt)
     172                continue;
    124173
    125         this.heroGuards = [];
     174            guardEnt.removeGuard();
     175            this.guardEnts.set(guardId, false);
     176        }
     177        hero.guards.clear();
    126178    }
    127179
    128180    for (let evt of events.UnGarrison)
    129181    {
     182        if (!this.guardEnts.has(evt.entity) && !this.criticalEnts.has(evt.entity))
     183            continue;
     184
    130185        let ent = gameState.getEntityById(evt.entity);
    131         if (!ent || !ent.isOwn(PlayerID))
     186        if (!ent)
    132187            continue;
    133188
    134189        // 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)
     190        if (ent.getMetadata(PlayerID, "role") === "criticalEntHealer" && !this.guardEnts.get(evt.entity))
    137191        {
    138             this.assignGuardToRegicideHero(gameState, ent);
     192            this.assignGuardToCriticalEnt(gameState, ent);
    139193            continue;
    140194        }
    141195
    142         if (!ent.hasClass("Hero"))
     196        if (!this.criticalEnts.has(evt.entity))
    143197            continue;
    144198
    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);
     199        // If this is a hero, try to assign ents that should be guarding it, but couldn't previously
     200        let criticalEnt = this.criticalEnts.get(evt.entity);
     201        for (let [id, isGuarding] of this.guardEnts)
     202        {
     203            if (criticalEnt.guards.size >= this.healersPerCriticalEnt)
     204                break;
     205
     206            if (!isGuarding)
     207            {
     208                let guardEnt = gameState.getEntityById(id);
     209                if (!guardEnt)
     210                    continue;
     211
     212                this.assignGuardToCriticalEnt(gameState, guardEnt);
     213            }
     214        }
    150215    }
    151216};
    152217
     
    162227    queues.wonder.addPlan(new m.ConstructionPlan(gameState, "structures/{civ}_wonder"));
    163228};
    164229
    165 m.GameTypeManager.prototype.pickHeroRetreatLocation = function(gameState, heroEnt, emergency)
     230m.GameTypeManager.prototype.pickCriticalEntRetreatLocation = function(gameState, criticalEnt, emergency)
    166231{
    167     gameState.ai.HQ.defenseManager.garrisonAttackedUnit(gameState, heroEnt, emergency);
    168     let plan = heroEnt.getMetadata(PlayerID, "plan");
     232    gameState.ai.HQ.defenseManager.garrisonAttackedUnit(gameState, criticalEnt, emergency);
     233    let plan = criticalEnt.getMetadata(PlayerID, "plan");
    169234
    170235    if (plan === -2 || plan === -3)
    171236        return;
    172237
    173     // couldn't find a place to garrison, so the hero will flee from attacks
    174     heroEnt.setStance("passive");
    175     let accessIndex = gameState.ai.accessibility.getAccessValue(heroEnt.position());
    176     let basePos = m.getBestBase(gameState, heroEnt);
     238    // Couldn't find a place to garrison, so the ent will flee from attacks
     239    criticalEnt.setStance("passive");
     240    let accessIndex = gameState.ai.accessibility.getAccessValue(criticalEnt.position());
     241    let basePos = m.getBestBase(gameState, criticalEnt);
    177242    if (basePos && basePos.accessIndex == accessIndex)
    178         heroEnt.move(basePos.anchor.position()[0], basePos.anchor.position()[1]);
     243        criticalEnt.move(basePos.anchor.position()[0], basePos.anchor.position()[1]);
    179244};
    180245
    181 m.GameTypeManager.prototype.trainRegicideHealer = function(gameState, queues)
     246/**
     247 * The number of healers trained per critical ent (dependent on the defensive trait)
     248 * may not be the number of healers actually guarding an ent at any one time.
     249 */
     250m.GameTypeManager.prototype.trainCriticalEntHealer = function(gameState, queues, id)
    182251{
    183252    if (gameState.ai.HQ.saveResources || !gameState.getOwnEntitiesByClass("Temple", true).hasEntities())
    184253        return;
     
    185254
    186255    let template = gameState.applyCiv("units/{civ}_support_healer_b");
    187256
    188     queues.villager.addPlan(new m.TrainingPlan(gameState, template, { "role": "regicideHealer", "base": 0 }, 1, 1));
    189     ++this.healersAssignedToHero;
     257    queues.villager.addPlan(new m.TrainingPlan(gameState, template, { "role": "criticalEntHealer", "base": 0 }, 1, 1));
     258    ++this.criticalEnts.get(id).healersAssigned;
    190259};
    191260
    192261/**
    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)
    195  * request a transport if the accessIndex value is different
     262 * Only send the guard command if the guard's accessIndex is the same as the critical ent
     263 * and the critical ent has a position (i.e. not garrisoned).
     264 * Request a transport if the accessIndex value is different
    196265 */
    197 m.GameTypeManager.prototype.assignGuardToRegicideHero = function(gameState, ent)
     266m.GameTypeManager.prototype.assignGuardToCriticalEnt = function(gameState, guardEnt)
    198267{
    199     let heroEnt = gameState.getOwnEntitiesByClass("Hero", true).toEntityArray()[0];
    200     if (!heroEnt || !heroEnt.position() ||
    201         !ent.position() || ent.getMetadata(PlayerID, "transport") !== undefined || !ent.canGuard())
     268    if (guardEnt.getMetadata(PlayerID, "transport") !== undefined || !guardEnt.canGuard())
    202269        return;
    203270
    204     let entAccess = gameState.ai.accessibility.getAccessValue(ent.position());
    205     let heroAccess = gameState.ai.accessibility.getAccessValue(heroEnt.position());
    206     if (entAccess === heroAccess)
     271    // Assign to the critical ent with the fewest guards
     272    let min = Math.min();
     273    let criticalEntId;
     274    for (let [id, data] of this.criticalEnts)
    207275    {
    208         ent.guard(heroEnt);
    209         this.heroGuards.push(ent.id());
     276        if (data.guards.size > min)
     277            continue;
     278
     279        criticalEntId = id;
     280        min = data.guards.size;
    210281    }
     282
     283    if (!criticalEntId)
     284        return;
     285
     286    let criticalEnt = gameState.getEntityById(criticalEntId);
     287    if (!criticalEnt || !criticalEnt.position() || !guardEnt.position())
     288    {
     289        this.guardEnts.set(guardEnt.id(), false);
     290        return;
     291    }
     292
     293    let guardEntAccess = gameState.ai.accessibility.getAccessValue(guardEnt.position());
     294    let criticalEntAccess = gameState.ai.accessibility.getAccessValue(criticalEnt.position());
     295    if (guardEntAccess === criticalEntAccess)
     296    {
     297        guardEnt.guard(criticalEnt);
     298        this.criticalEnts.get(criticalEntId).guards.add(guardEnt.id());
     299    }
    211300    else
    212         gameState.ai.HQ.navalManager.requireTransport(gameState, ent, entAccess, heroAccess, heroEnt.position());
     301        gameState.ai.HQ.navalManager.requireTransport(gameState, guardEnt, guardEntAccess, criticalEntAccess, criticalEnt.position());
     302    this.guardEnts.set(guardEnt.id(), guardEntAccess === criticalEntAccess);
    213303};
    214304
    215305m.GameTypeManager.prototype.update = function(gameState, events, queues)
    216306{
     307    // Wait a turn for trigger scripts to spawn any critical ents (i.e. in regicide)
     308    if (gameState.ai.playedTurn === 1)
     309        this.init(gameState);
     310
    217311    this.checkEvents(gameState, events);
    218312
    219313    if (gameState.getGameType() === "wonder")
     
    223317        return;
    224318
    225319    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     }
     320        for (let [id, data] of this.criticalEnts)
     321        {
     322            let ent = gameState.getEntityById(id);
     323            if (ent && ent.healthLevel() > 0.7 && ent.hasClass("Soldier") &&
     324                data.stance !== "aggressive")
     325                ent.setStance("aggressive");
     326        }
    231327
    232     if (this.healersAssignedToHero < 2 + Math.round(this.Config.personality.defensive * 2))
    233         this.trainRegicideHealer(gameState, queues);
     328    for (let [id, data] of this.criticalEnts)
     329        if (data.healersAssigned < this.healersPerCriticalEnt &&
     330            this.guardEnts.size < gameState.getPopulationMax() / 10)
     331            this.trainCriticalEntHealer(gameState, queues, id);
    234332};
    235333
    236334m.GameTypeManager.prototype.Serialize = function()
    237335{
    238336    return {
    239         "heroGarrisonEmergency": this.heroGarrisonEmergency,
    240         "healersAssignedToHero": this.healersAssignedToHero,
    241         "heroGuards": this.heroGuards
     337        "criticalEnts": this.criticalEnts,
     338        "guardEnts": this.guardEnts,
     339        "healersPerCriticalEnt": this.healersPerCriticalEnt
    242340    };
    243341};
    244342