Ticket #4142: 4142_gameTypeManager_v1.1.patch

File 4142_gameTypeManager_v1.1.patch, 19.6 KB (added by Sandarac, 7 years ago)

Fixes.

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

     
    592592    {
    593593        for (let ent of gameState.getOwnUnits().values())
    594594        {
    595             if (!ent.position())
     595            if (ent.getMetadata(PlayerID, "allied") || !this.isAvailableUnit(gameState, ent))
    596596                continue;
    597             if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1)
    598                 continue;
    599             if (ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined)
    600                 continue;
    601             if (ent.getMetadata(PlayerID, "allied"))
    602                 continue;
    603597            ent.setMetadata(PlayerID, "plan", plan);
    604598            this.unitCollection.updateEnt(ent);
    605599            added = true;
     
    613607        let num = 0;
    614608        for (let ent of gameState.getOwnUnits().values())
    615609        {
    616             if (!ent.hasClass("Cavalry"))
     610            if (!ent.hasClass("Cavalry") || !this.isAvailableUnit(gameState, ent))
    617611                continue;
    618             if (!ent.position())
    619                 continue;
    620             if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1)
    621                 continue;
    622             if (ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined)
    623                 continue;
    624612            if (num++ < 2)
    625613                continue;
    626614            ent.setMetadata(PlayerID, "plan", plan);
     
    633621    // Assign all units without specific role
    634622    for (let ent of gameState.getOwnEntitiesByRole(undefined, true).values())
    635623    {
    636         if (!ent.hasClass("Unit"))
     624        if (!ent.hasClass("Unit") || !this.isAvailableUnit(gameState, ent))
    637625            continue;
    638         if (!ent.position())
    639             continue;
    640         if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1)
    641             continue;
    642         if (ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined)
    643             continue;
    644626        if (ent.hasClass("Ship") || ent.hasClass("Support") || ent.attackTypes() === undefined)
    645627            continue;
    646         if (gameState.getGameType() === "regicide" && ent.hasClass("Hero") && (this.overseas || ent.healthLevel() < 0.8))
    647             continue;
    648628        ent.setMetadata(PlayerID, "plan", plan);
    649629        this.unitCollection.updateEnt(ent);
    650630        added = true;
     
    652632    // Add units previously in a plan, but which left it because needed for defense or attack finished
    653633    for (let ent of gameState.ai.HQ.attackManager.outOfPlan.values())
    654634    {
    655         if (!ent.position())
     635        if (!this.isAvailableUnit(gameState, ent))
    656636            continue;
    657         if (ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined)
    658             continue;
    659637        ent.setMetadata(PlayerID, "plan", plan);
    660638        this.unitCollection.updateEnt(ent);
    661639        added = true;
     
    669647    let keep = this.type === "Rush" ? Math.round(this.Config.popScaling * (12 + 4*this.Config.personality.defensive)) : 6;
    670648    for (let ent of gameState.getOwnEntitiesByRole("worker", true).values())
    671649    {
    672         if (!ent.position())
     650        if (!ent.hasClass("CitizenSoldier") || !this.isAvailableUnit(gameState, ent))
    673651            continue;
    674         if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1)
    675             continue;
    676         if (ent.getMetadata(PlayerID, "transport") !== undefined)
    677             continue;
    678         if (!ent.hasClass("CitizenSoldier"))
    679             continue;
    680652        let baseID = ent.getMetadata(PlayerID, "base");
    681653        if (baseID)
    682654            numbase[baseID] = numbase[baseID] ? ++numbase[baseID] : 1;
     
    697669    return added;
    698670};
    699671
     672m.AttackPlan.prototype.isAvailableUnit = function(gameState, ent)
     673{
     674    if (!ent.position())
     675        return false;
     676    if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1 ||
     677        ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined)
     678        return false;
     679    // TODO if more than one hero in regicide, prevent only the "right one" from being affected
     680    if (gameState.getGameType() === "regicide" && ent.hasClass("Hero") && (this.overseas || ent.healthLevel() < 0.8))
     681        return false;
     682    return true;
     683};
     684
    700685/** Reassign one (at each turn) Cav unit to fasten raid preparation. */
    701686m.AttackPlan.prototype.reassignCavUnit = function(gameState)
    702687{
     
    849834{
    850835    let targets;
    851836    if (gameState.getGameType() === "wonder")
    852     {
    853837        targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("Wonder"));
    854         if (targets.hasEntities())
    855             return targets;
    856     }
     838    else if (gameState.getGameType() === "regicide")
     839        targets = gameState.getEnemyUnits(playerEnemy).filter(API3.Filters.byClass("Hero"));
     840    if (targets && targets.hasEntities())
     841        return targets;
    857842
    858843    targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("CivCentre"));
    859844    if (!targets.hasEntities())
  • 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") && ent.healthLevel() < 0.8)
     391        if (gameState.getGameType() === "regicide" && ent.hasClass("Hero"))
    392392            return;
    393393        if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1)
    394394        {
     
    457457 * If our defense structures are attacked, garrison soldiers inside when possible
    458458 * and if a support unit is attacked and has less than 55% health, garrison it inside the nearest healing structure
    459459 * and if a ranged siege unit (not used for defense) is attacked, garrison it in the nearest fortress
     460 * If our hero is attacked in regicide game mode, the gameTypeManager will handle it
    460461 */
    461462m.DefenseManager.prototype.checkEvents = function(gameState, events)
    462463{
     
    477478        if (target.hasClass("Ship"))    // TODO integrate ships later   need to be sure it is accessible
    478479            continue;
    479480
     481        // If inside a started attack plan, let the plan deal with this unit
    480482        let plan = target.getMetadata(PlayerID, "plan");
    481 
    482         // Retreat the hero in regicide mode if wounded
    483         if (gameState.getGameType() == "regicide" && target.hasClass("Hero") && target.healthLevel() < 0.7)
    484         {
    485             target.stopMoving();
    486 
    487             if (plan >= 0)
    488             {
    489                 let attackPlan = gameState.ai.HQ.attackManager.getPlan(target.getMetadata(PlayerID, "plan"));
    490                 if (attackPlan)
    491                     attackPlan.removeUnit(target, true);
    492             }
    493 
    494             if (target.getMetadata(PlayerID, "PartOfArmy"))
    495             {
    496                 let army = gameState.ai.HQ.defenseManager.getArmy(target.getMetadata(PlayerID, "PartOfArmy"));
    497                 if (army)
    498                     army.removeOwn(gameState, target.id());
    499             }
    500 
    501             this.garrisonUnitForHealing(gameState, target);
    502 
    503             if (plan >= 0) // couldn't find a place to garrison, so the hero will flee from attacks
    504             {
    505                 target.setStance("passive");
    506                 let accessIndex = gameState.ai.accessibility.getAccessValue(target.position());
    507                 let basePos = m.getBestBase(gameState, target);
    508                 if (basePos && basePos.accessIndex == accessIndex)
    509                     target.move(basePos.anchor.position()[0], basePos.anchor.position()[1]);
    510             }
    511             continue;
    512         }
    513 
    514         // If inside a started attack plan, let the plan deal with this unit
    515483        if (plan !== undefined && plan >= 0)
    516484        {
    517485            let attack = gameState.ai.HQ.attackManager.getPlan(plan);
     
    562530        if (target.hasClass("Support") && target.healthLevel() < 0.55 &&
    563531            !target.getMetadata(PlayerID, "transport") && plan !== -2 && plan !== -3)
    564532        {
    565             this.garrisonUnitForHealing(gameState, target);
     533            this.garrisonAttackedUnit(gameState, target);
    566534            continue;
    567535        }
    568536
     
    640608            return;
    641609        if (ent.hitpoints() < ent.garrisonEjectHealth() * ent.maxHitpoints())
    642610            return;
    643         let entAccess = ent.getMetadata(PlayerID, "access");
    644         if (!entAccess)
    645         {
    646             entAccess = gameState.ai.accessibility.getAccessValue(ent.position());
    647             ent.setMetadata(PlayerID, "access", entAccess);
    648         }
    649         if (entAccess !== unitAccess)
     611        if (m.GetLandAccess(gameState, ent) !== unitAccess)
    650612            return;
    651613        let dist = API3.SquareVectorDistance(ent.position(), unit.position());
    652614        if (dist > distmin)
     
    658620        garrisonManager.garrison(gameState, unit, nearest, "protection");
    659621};
    660622
    661 /** garrison a hurt unit inside the nearest healing structure */
    662 m.DefenseManager.prototype.garrisonUnitForHealing = function(gameState, unit)
     623/**
     624 * Garrison a hurt unit inside a player-owned or allied structure
     625 * If emergency is true, the unit will be garrisoned in the closest possible structure
     626 * Otherwise, it will garrison in the closest healing structure
     627 */
     628m.DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit, emergency = false)
    663629{
    664630    let distmin = Math.min();
    665631    let nearest;
    666632    let unitAccess = gameState.ai.accessibility.getAccessValue(unit.position());
    667633    let garrisonManager = gameState.ai.HQ.garrisonManager;
    668     gameState.getAllyStructures().forEach(function(ent) {
    669         if (!ent.buffHeal())
    670             return;
     634    for (let ent of gameState.getAllyStructures().values())
     635    {
     636        if (!emergency && !ent.buffHeal())
     637            continue;
    671638        if (!MatchesClassList(unit.classes(), ent.garrisonableClasses()))
    672             return;
    673         if (garrisonManager.numberOfGarrisonedUnits(ent) >= ent.garrisonMax())
    674             return;
     639            continue;
     640        if (garrisonManager.numberOfGarrisonedUnits(ent) >= ent.garrisonMax() &&
     641            (!emergency || !ent.garrisoned().length))
     642            continue;
    675643        if (ent.hitpoints() < ent.garrisonEjectHealth() * ent.maxHitpoints())
    676             return;
    677         let entAccess = ent.getMetadata(PlayerID, "access");
    678         if (!entAccess)
    679         {
    680             entAccess = gameState.ai.accessibility.getAccessValue(ent.position());
    681             ent.setMetadata(PlayerID, "access", entAccess);
    682         }
    683         if (entAccess !== unitAccess)
    684             return;
     644            continue;
     645        if (m.GetLandAccess(gameState, ent) !== unitAccess)
     646            continue;
    685647        let dist = API3.SquareVectorDistance(ent.position(), unit.position());
    686648        if (dist > distmin)
    687             return;
     649            continue;
    688650        distmin = dist;
    689651        nearest = ent;
    690     });
    691     if (nearest)
     652    }
     653    if (!nearest)
     654        return;
     655
     656    if (!emergency)
     657    {
    692658        garrisonManager.garrison(gameState, unit, nearest, "protection");
     659        return;
     660    }
     661    if (garrisonManager.numberOfGarrisonedUnits(nearest) >= nearest.garrisonMax()) // make room for this ent
     662        nearest.unload(nearest.garrisoned()[0]);
     663
     664    garrisonManager.garrison(gameState, unit, nearest, nearest.buffHeal() ? "protection" : "emergency");
    693665};
    694666
    695667/**
  • binaries/data/mods/public/simulation/ai/petra/gameTypeManager.js

     
     1var PETRA = function(m)
     2{
     3
     4/**
     5 * Handle events that are important to specific gameTypes
     6 * TODO: Handle when there is more than one hero in regicide
     7 * TODO: Assign military units to guard the hero in regicide
     8 */
     9
     10m.GameTypeManager = function()
     11{
     12    this.heroGarrisonEmergency = false;
     13};
     14
     15/**
     16 * In regicide mode, if the hero has less than 70% health, try to garrison it in a healing structure
     17 * If it is less than 40%, try to garrison in the closest possible structure
     18 * If the hero cannot garrison, retreat it to the closest base
     19 */
     20m.GameTypeManager.prototype.checkEvents = function(gameState, events)
     21{
     22    if (gameState.getGameType() === "wonder")
     23        for (let evt of events.Create)
     24        {
     25            let ent = gameState.getEntityById(evt.entity);
     26            if (!ent || !ent.isOwn(PlayerID) || ent.foundationProgress() === undefined ||
     27                !ent.hasClass("Wonder"))
     28                continue;
     29
     30            // Let's get a few units from other bases to build the wonder.
     31            let base = gameState.ai.HQ.getBaseByID(ent.getMetadata(PlayerID, "base"));
     32            let builders = gameState.ai.HQ.bulkPickWorkers(gameState, base, 10);
     33            if (builders)
     34                for (let worker of builders.values())
     35                {
     36                    worker.setMetadata(PlayerID, "base", base.ID);
     37                    worker.setMetadata(PlayerID, "subrole", "builder");
     38                    worker.setMetadata(PlayerID, "target-foundation", ent.id());
     39                }
     40        }
     41
     42    if (gameState.getGameType() !== "regicide")
     43        return;
     44
     45    for (let evt of events.Attacked)
     46    {
     47        let target = gameState.getEntityById(evt.target);
     48        if (!target || !gameState.isEntityOwn(target) || !target.position() ||
     49            !target.hasClass("Hero") || target.healthLevel() > 0.7)
     50            continue;
     51
     52        let plan = target.getMetadata(PlayerID, "plan");
     53        if (plan !== -2 && plan !== -3)
     54        {
     55            target.stopMoving();
     56
     57            if (plan >= 0)
     58            {
     59                let attackPlan = gameState.ai.HQ.attackManager.getPlan(plan);
     60                if (attackPlan)
     61                    attackPlan.removeUnit(target, true);
     62            }
     63
     64            if (target.getMetadata(PlayerID, "PartOfArmy"))
     65            {
     66                let army = gameState.ai.HQ.defenseManager.getArmy(target.getMetadata(PlayerID, "PartOfArmy"));
     67                if (army)
     68                    army.removeOwn(gameState, target.id());
     69            }
     70
     71            let emergency = target.healthLevel() < 0.4;
     72            this.pickHeroRetreatLocation(gameState, target, emergency);
     73        }
     74        else if (target.healthLevel() < 0.4 && !this.heroGarrisonEmergency)
     75        {
     76            // the hero is severely wounded, try to retreat/garrison quicker
     77            gameState.ai.HQ.garrisonManager.leaveGarrison(target);
     78            target.stopMoving();
     79            this.pickHeroRetreatLocation(gameState, target, true);
     80            this.heroGarrisonEmergency = true;
     81        }
     82    }
     83
     84    if (!this.heroGarrisonEmergency)
     85        return;
     86
     87    for (let evt of events.Garrison)
     88        if (gameState.getEntityById(evt.entity).hasClass("Hero"))
     89            this.heroGarrisonEmergency = false;
     90};
     91
     92m.GameTypeManager.prototype.buildWonder = function(gameState, queues)
     93{
     94    if (queues.wonder && queues.wonder.hasQueuedUnits() ||
     95        gameState.getOwnEntitiesByClass("Wonder", true).hasEntities() ||
     96        !gameState.ai.HQ.canBuild(gameState, "structures/{civ}_wonder"))
     97        return;
     98
     99    if (!queues.wonder)
     100        gameState.ai.queueManager.addQueue("wonder", 1000);
     101    queues.wonder.addPlan(new m.ConstructionPlan(gameState, "structures/{civ}_wonder"));
     102};
     103
     104m.GameTypeManager.prototype.pickHeroRetreatLocation = function(gameState, heroEnt, emergency)
     105{
     106    gameState.ai.HQ.defenseManager.garrisonAttackedUnit(gameState, heroEnt, emergency);
     107    let plan = heroEnt.getMetadata(PlayerID, "plan");
     108
     109    if (plan === -2 || plan === -3)
     110        return;
     111
     112    // couldn't find a place to garrison, so the hero will flee from attacks
     113    heroEnt.setStance("passive");
     114    let accessIndex = gameState.ai.accessibility.getAccessValue(heroEnt.position());
     115    let basePos = m.getBestBase(gameState, heroEnt);
     116    if (basePos && basePos.accessIndex == accessIndex)
     117        heroEnt.move(basePos.anchor.position()[0], basePos.anchor.position()[1]);
     118};
     119
     120m.GameTypeManager.prototype.update = function(gameState, events, queues)
     121{
     122    this.checkEvents(gameState, events);
     123
     124    if (gameState.getGameType() === "wonder")
     125        this.buildWonder(gameState, queues);
     126    else if (gameState.getGameType() === "regicide" && gameState.ai.playedTurn % 50 === 0)
     127    {
     128        let heroEnt = gameState.getOwnEntitiesByClass("Hero", true).toEntityArray()[0];
     129        if (heroEnt && heroEnt.healthLevel() > 0.7)
     130            heroEnt.setStance("aggressive");
     131    }
     132};
     133
     134m.GameTypeManager.prototype.Serialize = function()
     135{
     136    return { "heroGarrisonEmergency": this.heroGarrisonEmergency };
     137};
     138
     139m.GameTypeManager.prototype.Deserialize = function(data)
     140{
     141    for (let key in data)
     142        this[key] = data[key];
     143};
     144
     145return m;
     146}(PETRA);
  • binaries/data/mods/public/simulation/ai/petra/garrisonManager.js

    Property changes on: binaries/data/mods/public/simulation/ai/petra/gameTypeManager.js
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    221221                   MatchesClassList(ent.classes(), "Siege+!Melee"));
    222222    case 'decay':
    223223        return this.decayingStructures.has(holder.id());
     224    case 'emergency': // f.e. hero in regicide mode
     225        return enemiesAround;
    224226    default:
    225227        if (ent.getMetadata(PlayerID, "onBoard") === "onBoard")  // transport is not (yet ?) managed by garrisonManager
    226228            return true;
  • binaries/data/mods/public/simulation/ai/petra/headquarters.js

     
    5151    this.researchManager = new m.ResearchManager(this.Config);
    5252    this.diplomacyManager = new m.DiplomacyManager(this.Config);
    5353    this.garrisonManager = new m.GarrisonManager();
     54    this.gameTypeManager = new m.GameTypeManager();
    5455};
    5556
    5657/** More initialisation for stuff that needs the gameState */
     
    166167                });
    167168            }
    168169        }
    169         else if (ent.hasClass("Wonder") && gameState.getGameType() === "wonder")
    170         {
    171             // Let's get a few units from other bases there to build this.
    172             let base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
    173             let builders = this.bulkPickWorkers(gameState, base, 10);
    174             if (builders !== false)
    175             {
    176                 builders.forEach(function (worker) {
    177                     worker.setMetadata(PlayerID, "base", base.ID);
    178                     worker.setMetadata(PlayerID, "subrole", "builder");
    179                     worker.setMetadata(PlayerID, "target-foundation", ent.id());
    180                 });
    181             }
    182         }
    183170    }
    184171
    185172    for (let evt of events.ConstructionFinished)
     
    16091596        queues.militaryBuilding.addPlan(new m.ConstructionPlan(gameState, "structures/{civ}_blacksmith"));
    16101597};
    16111598
    1612 m.HQ.prototype.buildWonder = function(gameState, queues)
    1613 {
    1614     if (queues.wonder && queues.wonder.hasQueuedUnits())
    1615         return;
    1616     if (gameState.getOwnEntitiesByClass("Wonder", true).hasEntities())
    1617         return;
    1618     if (!this.canBuild(gameState, "structures/{civ}_wonder"))
    1619         return;
    1620 
    1621     if (!queues.wonder)
    1622         gameState.ai.queueManager.addQueue("wonder", 1000);
    1623     queues.wonder.addPlan(new m.ConstructionPlan(gameState, "structures/{civ}_wonder"));
    1624 };
    1625 
    16261599/**
    16271600 * Deals with constructing military buildings (barracks, stables…)
    16281601 * They are mostly defined by Config.js. This is unreliable since changes could be done easily.
     
    21402113        m.chatNewPhase(gameState, phaseName, false);
    21412114    }
    21422115
    2143     if (gameState.getGameType() === "wonder")
    2144         this.buildWonder(gameState, queues);
    2145 
    21462116    if (this.numActiveBase() > 0)
    21472117    {
    21482118        this.trainMoreWorkers(gameState, queues);
     
    22012171
    22022172    this.diplomacyManager.update(gameState, events);
    22032173
     2174    this.gameTypeManager.update(gameState, events, queues);
     2175
    22042176    Engine.ProfileStop();
    22052177};
    22062178
     
    22482220        API3.warn(" researchManager " + uneval(this.researchManager.Serialize()));
    22492221        API3.warn(" diplomacyManager " + uneval(this.diplomacyManager.Serialize()));
    22502222        API3.warn(" garrisonManager " + uneval(this.garrisonManager.Serialize()));
     2223        API3.warn(" gameTypeManager " + uneval(this.gameTypeManager.Serialize()));
    22512224    }
    22522225
    22532226    return {
     
    22612234        "researchManager": this.researchManager.Serialize(),
    22622235        "diplomacyManager": this.diplomacyManager.Serialize(),
    22632236        "garrisonManager": this.garrisonManager.Serialize(),
     2237        "gameTypeManager": this.gameTypeManager.Serialize(),
    22642238    };
    22652239};
    22662240
     
    23042278
    23052279    this.garrisonManager = new m.GarrisonManager();
    23062280    this.garrisonManager.Deserialize(data.garrisonManager);
     2281
     2282    this.gameTypeManager = new m.GameTypeManager();
     2283    this.gameTypeManager.Deserialize(data.gameTypeManager);
    23072284};
    23082285
    23092286return m;