diff --git a/binaries/data/mods/public/simulation/ai/common-api/gamestate.js b/binaries/data/mods/public/simulation/ai/common-api/gamestate.js index c216909e04..f538a1edc5 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/gamestate.js +++ b/binaries/data/mods/public/simulation/ai/common-api/gamestate.js @@ -20,6 +20,7 @@ m.GameState.prototype.init = function(SharedScript, state, player) this.player = player; this.playerData = SharedScript.playersData[this.player]; this.gameType = SharedScript.gameType; + this.victoryConditions = new Set([this.gameType]); this.alliedVictory = SharedScript.alliedVictory; this.ceasefireActive = SharedScript.ceasefireActive; this.ceasefireTimeRemaining = SharedScript.ceasefireTimeRemaining; @@ -123,6 +124,11 @@ m.GameState.prototype.getBarterPrices = function() return this.playerData.barterPrices; }; +m.GameState.prototype.getVictoryConditions = function() +{ + return this.victoryConditions; +}; + m.GameState.prototype.getGameType = function() { return this.gameType; diff --git a/binaries/data/mods/public/simulation/ai/petra/attackManager.js b/binaries/data/mods/public/simulation/ai/petra/attackManager.js index 3c556f465d..19f70045b5 100644 --- a/binaries/data/mods/public/simulation/ai/petra/attackManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/attackManager.js @@ -439,7 +439,7 @@ m.AttackManager.prototype.getAttackInPreparation = function(type) }; /** - * determine which player should be attacked: when called when starting the attack, + * Determine which player should be attacked: when called when starting the attack, * attack.targetPlayer is undefined and in that case, we keep track of the chosen target * for future attacks. */ @@ -447,67 +447,24 @@ m.AttackManager.prototype.getEnemyPlayer = function(gameState, attack) { let enemyPlayer; - // first check if there is a preferred enemy based on our victory conditions - if (gameState.getGameType() === "wonder") - { - let moreAdvanced; - let enemyWonder; - let wonders = gameState.getEnemyStructures().filter(API3.Filters.byClass("Wonder")); - for (let wonder of wonders.values()) - { - if (wonder.owner() === 0) - continue; - let progress = wonder.foundationProgress(); - if (progress === undefined) - { - enemyWonder = wonder; - break; - } - if (enemyWonder && moreAdvanced > progress) - continue; - enemyWonder = wonder; - moreAdvanced = progress; - } - if (enemyWonder) - { - enemyPlayer = enemyWonder.owner(); - if (attack.targetPlayer === undefined) - this.currentEnemyPlayer = enemyPlayer; - return enemyPlayer; - } - } - else if (gameState.getGameType() === "capture_the_relic") - { - // Target the player with the most relics (including gaia) - let allRelics = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")); - let maxRelicsOwned = 0; - for (let i = 0; i < gameState.sharedScript.playersData.length; ++i) - { - if (!gameState.isPlayerEnemy(i) || this.defeated[i] || - i === 0 && !gameState.ai.HQ.gameTypeManager.tryCaptureGaiaRelic) - continue; + // First check if there is a preferred enemy based on our victory conditions. + // If both wonder and relic, choose randomly between them TODO should combine decisions - let relicsCount = allRelics.filter(relic => relic.owner() === i).length; - if (relicsCount <= maxRelicsOwned) - continue; - maxRelicsOwned = relicsCount; - enemyPlayer = i; - } - if (enemyPlayer !== undefined) - { - if (attack.targetPlayer === undefined) - this.currentEnemyPlayer = enemyPlayer; - if (enemyPlayer === 0) - gameState.ai.HQ.gameTypeManager.resetCaptureGaiaRelic(gameState); - return enemyPlayer; - } - } + if (gameState.getVictoryConditions().has("wonder")) + enemyPlayer = this.getWonderEnemyPlayer(gameState); + + if (gameState.getVictoryConditions().has("capture_the_relic")) + if (!enemyPlayer || randBool()) + enemyPlayer = this.getWonderEnemyPlayer(gameState) || enemyPlayer; + + if (enemyPlayer) + return enemyPlayer; let veto = {}; for (let i in this.defeated) veto[i] = true; // No rush if enemy too well defended (i.e. iberians) - if (attack.type === "Rush") + if (attack.type == "Rush") { for (let i = 1; i < gameState.sharedScript.playersData.length; ++i) { @@ -526,7 +483,7 @@ m.AttackManager.prototype.getEnemyPlayer = function(gameState, attack) // then if not a huge attack, continue attacking our previous target as long as it has some entities, // otherwise target the most accessible one - if (attack.type !== "HugeAttack") + if (attack.type != "HugeAttack") { if (attack.targetPlayer === undefined && this.currentEnemyPlayer !== undefined && !this.defeated[this.currentEnemyPlayer] && @@ -539,7 +496,7 @@ m.AttackManager.prototype.getEnemyPlayer = function(gameState, attack) let ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")); for (let ourcc of ccEnts.values()) { - if (ourcc.owner() !== PlayerID) + if (ourcc.owner() != PlayerID) continue; let ourPos = ourcc.position(); let access = gameState.ai.accessibility.getAccessValue(ourPos); @@ -550,7 +507,7 @@ m.AttackManager.prototype.getEnemyPlayer = function(gameState, attack) if (!gameState.isPlayerEnemy(enemycc.owner())) continue; let enemyPos = enemycc.position(); - if (access !== gameState.ai.accessibility.getAccessValue(enemyPos)) + if (access != gameState.ai.accessibility.getAccessValue(enemyPos)) continue; let dist = API3.SquareVectorDistance(ourPos, enemyPos); if (distmin && dist > distmin) @@ -597,6 +554,69 @@ m.AttackManager.prototype.getEnemyPlayer = function(gameState, attack) return enemyPlayer; }; +/** + * Target the player with the most advanced wonder. + * TODO currently the first built wonder is kept, should chek on the minimum wonderDuration left instead. + */ +m.AttackManager.prototype.getWonderEnemyPlayer = function(gameState) +{ + let enemyPlayer; + let enemyWonder; + let moreAdvanced; + for (let wonder of gameState.getEnemyStructures().filter(API3.Filters.byClass("Wonder"))) + { + if (wonder.owner() == 0) + continue; + let progress = wonder.foundationProgress(); + if (progress === undefined) + { + enemyWonder = wonder; + break; + } + if (enemyWonder && moreAdvanced > progress) + continue; + enemyWonder = wonder; + moreAdvanced = progress; + } + if (enemyWonder) + { + enemyPlayer = enemyWonder.owner(); + if (attack.targetPlayer === undefined) + this.currentEnemyPlayer = enemyPlayer; + } + return enemyPlayer; +}; + +/** + * Target the player with the most relics (including gaia). + */ +m.AttackManager.prototype.getRelicEnemyPlayer = function(gameState) +{ + let enemyPlayer; + let allRelics = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")); + let maxRelicsOwned = 0; + for (let i = 0; i < gameState.sharedScript.playersData.length; ++i) + { + if (!gameState.isPlayerEnemy(i) || this.defeated[i] || + i == 0 && !gameState.ai.HQ.gameTypeManager.tryCaptureGaiaRelic) + continue; + + let relicsCount = allRelics.filter(relic => relic.owner() == i).length; + if (relicsCount <= maxRelicsOwned) + continue; + maxRelicsOwned = relicsCount; + enemyPlayer = i; + } + if (enemyPlayer !== undefined) + { + if (attack.targetPlayer === undefined) + this.currentEnemyPlayer = enemyPlayer; + if (enemyPlayer == 0) + gameState.ai.HQ.gameTypeManager.resetCaptureGaiaRelic(gameState); + } + return enemyPlayer; +}; + /** f.e. if we have changed diplomacy with another player. */ m.AttackManager.prototype.cancelAttacksAgainstPlayer = function(gameState, player) { diff --git a/binaries/data/mods/public/simulation/ai/petra/attackPlan.js b/binaries/data/mods/public/simulation/ai/petra/attackPlan.js index e84f1a6dc2..7d864d4901 100644 --- a/binaries/data/mods/public/simulation/ai/petra/attackPlan.js +++ b/binaries/data/mods/public/simulation/ai/petra/attackPlan.js @@ -853,7 +853,7 @@ m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand let minDist = Math.min(); for (let ent of targets.values()) { - if (this.targetPlayer == 0 && gameState.getGameType() == "capture_the_relic" && + if (this.targetPlayer == 0 && gameState.getVictoryConditions().has("capture_the_relic") && (!ent.hasClass("Relic") || gameState.ai.HQ.gameTypeManager.targetedGaiaRelics.has(ent.id()))) continue; // Do not bother with some pointless targets @@ -877,7 +877,7 @@ m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand if (!target) return undefined; - if (this.targetPlayer == 0 && gameState.getGameType() == "capture_the_relic" && target.hasClass("Relic")) + if (this.targetPlayer == 0 && gameState.getVictoryConditions().has("capture_the_relic") && target.hasClass("Relic")) gameState.ai.HQ.gameTypeManager.targetedGaiaRelics.add(target.id()); // Rushes can change their enemy target if nothing found with the preferred enemy // Obstruction also can change the enemy target @@ -887,23 +887,24 @@ m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand /** * Default target finder aims for conquest critical targets - * We must apply the same selection (isValidTarget) as done in getNearestTarget + * We must apply the *same* selection (isValidTarget) as done in getNearestTarget */ m.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy) { - let targets; - if (gameState.getGameType() == "wonder") - targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("Wonder")); - else if (gameState.getGameType() == "regicide") - targets = gameState.getEnemyUnits(playerEnemy).filter(API3.Filters.byClass("Hero")); - else if (gameState.getGameType() == "capture_the_relic") - targets = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")).filter(relic => relic.owner() == playerEnemy); - if (targets) - targets = targets.filter(this.isValidTarget, this); - if (targets && targets.hasEntities()) + let targets = new API3.EntityCollection(gameState.sharedScript); + if (gameState.getVictoryConditions().has("wonder")) + for (let ent of gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("Wonder")).values()) + targets.addEnt(ent); + if (gameState.getVictoryConditions().has("regicide")) + for (let ent of gameState.getEnemyUnits(playerEnemy).filter(API3.Filters.byClass("Hero")).values()) + targets.addEnt(ent); + if (gameState.getVictoryConditions().has("capture_the_relic")) + for (let ent of gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")).filter(relic => relic.owner() == playerEnemy).values()) + targets.addEnt(ent); + targets = targets.filter(this.isValidTarget, this); + if (targets.hasEntities()) return targets; - // We must apply the *same* selection as done in getNearestTarget let validTargets = gameState.getEnemyStructures(playerEnemy).filter(this.isValidTarget, this); targets = validTargets.filter(API3.Filters.byClass("CivCentre")); if (!targets.hasEntities()) diff --git a/binaries/data/mods/public/simulation/ai/petra/baseManager.js b/binaries/data/mods/public/simulation/ai/petra/baseManager.js index c62c4b9541..7954c77327 100644 --- a/binaries/data/mods/public/simulation/ai/petra/baseManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/baseManager.js @@ -749,7 +749,7 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) if (gameState.ai.HQ.isNearInvadingArmy(target.position())) if (!target.hasClass("CivCentre") && !target.hasClass("StoneWall") && - (!target.hasClass("Wonder") || gameState.getGameType() != "wonder")) + (!target.hasClass("Wonder") || !gameState.getVictoryConditions().has("wonder"))) continue; // if our territory has shrinked since this foundation was positioned, do not build it @@ -774,7 +774,7 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) targetNB = 3; if (target.getMetadata(PlayerID, "baseAnchor") == true || - target.hasClass("Wonder") && gameState.getGameType() == "wonder") + target.hasClass("Wonder") && gameState.getVictoryConditions().has("wonder")) { targetNB = 15; maxTotalBuilders = Math.max(maxTotalBuilders, 15); @@ -848,7 +848,7 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) if (gameState.ai.HQ.isNearInvadingArmy(target.position())) if (target.healthLevel() > 0.5 || !target.hasClass("CivCentre") && !target.hasClass("StoneWall") && - (!target.hasClass("Wonder") || gameState.getGameType() != "wonder")) + (!target.hasClass("Wonder") || !gameState.getVictoryConditions().has("wonder"))) continue; else if (noRepair && !target.hasClass("CivCentre")) continue; @@ -862,7 +862,7 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) if (target.hasClass("Fortress") || target.hasClass("Wonder")) targetNB = 3; if (target.getMetadata(PlayerID, "baseAnchor") == true || - target.hasClass("Wonder") && gameState.getGameType() == "wonder") + target.hasClass("Wonder") && gameState.getVictoryConditions().has("wonder")) { maxTotalBuilders = Math.ceil(workers.length * Math.max(0.3, builderRatio)); targetNB = 5; diff --git a/binaries/data/mods/public/simulation/ai/petra/config.js b/binaries/data/mods/public/simulation/ai/petra/config.js index 661a7660c0..3175319d4b 100644 --- a/binaries/data/mods/public/simulation/ai/petra/config.js +++ b/binaries/data/mods/public/simulation/ai/petra/config.js @@ -209,7 +209,7 @@ m.Config.prototype.setConfig = function(gameState) this.Economy.targetNumTraders = 2 + this.difficulty; - if (gameState.getGameType() === "wonder") + if (gameState.getVictoryConditions().has("wonder")) { this.Economy.workPhase3 = Math.floor(0.9 * this.Economy.workPhase3); this.Economy.workPhase4 = Math.floor(0.9 * this.Economy.workPhase4); diff --git a/binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js b/binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js index 75dc3e9c76..75e626f82a 100644 --- a/binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js @@ -315,7 +315,7 @@ m.DiplomacyManager.prototype.lastManStandingCheck = function(gameState) if (gameState.isPlayerNeutral(i)) // be more inclined to turn against neutral players turnFactor += this.betrayWeighting; - if (gameState.getGameType() === "wonder") + if (gameState.getVictoryConditions().has("wonder")) { let wonder = gameState.getEnemyStructures(i).filter(API3.Filters.byClass("Wonder"))[0]; if (wonder) @@ -330,7 +330,7 @@ m.DiplomacyManager.prototype.lastManStandingCheck = function(gameState) } } - if (gameState.getGameType() === "capture_the_relic") + if (gameState.getVictoryConditions().has("capture_the_relic")) { let relicsCount = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")) .filter(relic => relic.owner() === i).length; diff --git a/binaries/data/mods/public/simulation/ai/petra/gameTypeManager.js b/binaries/data/mods/public/simulation/ai/petra/gameTypeManager.js index 23bd4ce80a..a9c42cbf25 100644 --- a/binaries/data/mods/public/simulation/ai/petra/gameTypeManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/gameTypeManager.js @@ -27,13 +27,13 @@ m.GameTypeManager = function(Config) */ m.GameTypeManager.prototype.init = function(gameState) { - if (gameState.getGameType() === "wonder") + if (gameState.getVictoryConditions().has("wonder")) { for (let wonder of gameState.getOwnEntitiesByClass("Wonder", true).values()) this.criticalEnts.set(wonder.id(), { "guardsAssigned": 0, "guards": new Map() }); } - if (gameState.getGameType() === "regicide") + if (gameState.getVictoryConditions().has("regicide")) { for (let hero of gameState.getOwnEntitiesByClass("Hero", true).values()) { @@ -57,7 +57,7 @@ m.GameTypeManager.prototype.init = function(gameState) */ m.GameTypeManager.prototype.checkEvents = function(gameState, events) { - if (gameState.getGameType() === "wonder") + if (gameState.getVictoryConditions().has("wonder")) { for (let evt of events.Create) { @@ -89,7 +89,7 @@ m.GameTypeManager.prototype.checkEvents = function(gameState, events) } } - if (gameState.getGameType() === "regicide") + if (gameState.getVictoryConditions().has("regicide")) { for (let evt of events.Attacked) { @@ -288,8 +288,8 @@ m.GameTypeManager.prototype.checkEvents = function(gameState, events) continue; let ent = gameState.getEntityById(evt.entity); - if (ent && (gameState.getGameType() === "wonder" && ent.hasClass("Wonder") || - gameState.getGameType() === "capture_the_relic" && ent.hasClass("Relic"))) + if (ent && (gameState.getVictoryConditions().has("wonder") && ent.hasClass("Wonder") || + gameState.getVictoryConditions().has("capture_the_relic") && ent.hasClass("Relic"))) { this.criticalEnts.set(ent.id(), { "guardsAssigned": 0, "guards": new Map() }); // Move captured relics to the closest base @@ -328,6 +328,24 @@ m.GameTypeManager.prototype.removeGuardsFromCriticalEnt = function(gameState, cr this.criticalEnts.delete(criticalEntId); }; +/** + * Train more healers and affect them to critical entities if needed + */ +m.GameTypeManager.prototype.manageCriticalEntHealers = function(gameState, queues) +{ + if (this.guardEnts.size > Math.min(gameState.getPopulationMax() / 10, gameState.getPopulation() / 4)) + return; + + for (let [id, data] of this.criticalEnts) + { + if (data.healersAssigned !== undefined && data.healersAssigned < this.healersPerCriticalEnt) + { + this.trainCriticalEntHealer(gameState, queues, id); + return; + } + } +}; + /** * Try to keep some military units guarding any criticalEnts, if we can afford it. * If we have too low a population and require units for other needs, remove guards so they can be reassigned. @@ -556,36 +574,42 @@ m.GameTypeManager.prototype.resetCaptureGaiaRelic = function(gameState) m.GameTypeManager.prototype.update = function(gameState, events, queues) { // Wait a turn for trigger scripts to spawn any critical ents (i.e. in regicide) - if (gameState.ai.playedTurn === 1) + if (gameState.ai.playedTurn == 1) this.init(gameState); +/* for (let [id, data] of this.criticalEnts) + { + API3.warn(" critical " + id + " data " + uneval(data)); + for (let [k, v] of data.guards) + API3.warn(" --- guard " + k + " >>> " + uneval(v)); + } */ + this.checkEvents(gameState, events); - if (gameState.getGameType() === "wonder" && gameState.ai.playedTurn % 10 === 0) - { - gameState.ai.HQ.buildWonder(gameState, queues, true); - this.manageCriticalEntGuards(gameState); - } + if (gameState.ai.playedTurn % 10 != 0 || + !gameState.getVictoryConditions().has("wonder") && !gameState.getVictoryConditions().has("regicide") && + !gameState.getVictoryConditions().has("capture_the_relic")) + return; - if (gameState.getGameType() === "regicide" && gameState.ai.playedTurn % 10 === 0) + this.manageCriticalEntGuards(gameState); + + if (gameState.getVictoryConditions().has("wonder")) + gameState.ai.HQ.buildWonder(gameState, queues, true); + + if (gameState.getVictoryConditions().has("regicide")) { - for (let [id, data] of this.criticalEnts) + for (let id of this.criticalEnts.keys()) { let ent = gameState.getEntityById(id); if (ent && ent.healthLevel() > this.Config.garrisonHealthLevel.high && ent.hasClass("Soldier") && - ent.getStance() !== "aggressive") + ent.getStance() != "aggressive") ent.setStance("aggressive"); - - if (data.healersAssigned < this.healersPerCriticalEnt && - this.guardEnts.size < Math.min(gameState.getPopulationMax() / 10, gameState.getPopulation() / 4)) - this.trainCriticalEntHealer(gameState, queues, id); } - this.manageCriticalEntGuards(gameState); + this.manageCriticalEntHealers(gameState, queues); } - if (gameState.getGameType() === "capture_the_relic" && gameState.ai.playedTurn % 10 === 0) + if (gameState.getVictoryConditions().has("capture_the_relic")) { - this.manageCriticalEntGuards(gameState); if (!this.tryCaptureGaiaRelic && gameState.ai.elapsedTime > this.tryCaptureGaiaRelicLapseTime) this.tryCaptureGaiaRelic = true; @@ -596,8 +620,8 @@ m.GameTypeManager.prototype.update = function(gameState, events, queues) for (let relic of allRelics.values()) { let relicPosition = relic.position(); - if (this.targetedGaiaRelics.has(relic.id()) || relic.owner() !== 0 || - !relicPosition || gameState.ai.HQ.territoryMap.getOwner(relicPosition) !== PlayerID) + if (this.targetedGaiaRelics.has(relic.id()) || relic.owner() != 0 || + !relicPosition || gameState.ai.HQ.territoryMap.getOwner(relicPosition) != PlayerID) continue; gameState.ai.HQ.attackManager.raidTargetEntity(gameState, relic); diff --git a/binaries/data/mods/public/simulation/ai/petra/headquarters.js b/binaries/data/mods/public/simulation/ai/petra/headquarters.js index 9e02341486..02747f13bf 100644 --- a/binaries/data/mods/public/simulation/ai/petra/headquarters.js +++ b/binaries/data/mods/public/simulation/ai/petra/headquarters.js @@ -1310,7 +1310,7 @@ m.HQ.prototype.findDefensiveLocation = function(gameState, template) } enemyStructures = enemyStructures.toEntityArray(); - let wonderMode = gameState.getGameType() === "wonder"; + let wonderMode = gameState.getVictoryConditions().has("wonder"); let wonderDistmin; let wonders; if (wonderMode) @@ -1431,7 +1431,7 @@ m.HQ.prototype.buildTemple = function(gameState, queues) !gameState.getOwnEntitiesByClass("BarterMarket", true).hasEntities()) return; // Try to build a temple earlier if in regicide to recruit healer guards - if (this.currentPhase < 3 && gameState.getGameType() !== "regicide") + if (this.currentPhase < 3 && !gameState.getVictoryConditions().has("regicide")) return; if (!this.canBuild(gameState, "structures/{civ}_temple")) return;