diff --git a/binaries/data/mods/public/simulation/ai/petra/_petrabot.js b/binaries/data/mods/public/simulation/ai/petra/_petrabot.js index dd15aa160b..905f291058 100644 --- a/binaries/data/mods/public/simulation/ai/petra/_petrabot.js +++ b/binaries/data/mods/public/simulation/ai/petra/_petrabot.js @@ -1,8 +1,11 @@ +import { Config } from "simulation/ai/petra/config.js"; +import { Headquather } from "simulation/ai/petra/headquarters.js"; +import { Queue } from "simulation/ai/petra/queue.js"; +import { QueueManager } from "simulation/ai/petra/queueManager.js"; + Engine.IncludeModule("common-api"); -var PETRA = {}; - -PETRA.PetraBot = function(settings) +export function PetraBot(settings) { API3.BaseAI.call(this, settings); @@ -16,14 +19,14 @@ PETRA.PetraBot = function(settings) "transports": 1 // transport plans start at 1 because 0 might be used as none }; - this.Config = new PETRA.Config(settings.difficulty, settings.behavior); + this.Config = new Config(settings.difficulty, settings.behavior); this.savedEvents = {}; -}; +} -PETRA.PetraBot.prototype = Object.create(API3.BaseAI.prototype); +PetraBot.prototype = Object.create(API3.BaseAI.prototype); -PETRA.PetraBot.prototype.CustomInit = function(gameState) +PetraBot.prototype.CustomInit = function(gameState) { if (this.isDeserialized) { @@ -51,11 +54,11 @@ PETRA.PetraBot.prototype.CustomInit = function(gameState) this.Config.Deserialize(this.data.config); - this.queueManager = new PETRA.QueueManager(this.Config, {}); + this.queueManager = new QueueManager(this.Config, {}); this.queueManager.Deserialize(gameState, this.data.queueManager); this.queues = this.queueManager.queues; - this.HQ = new PETRA.HQ(this.Config); + this.HQ = new Headquarther(this.Config); this.HQ.init(gameState, this.queues); this.HQ.Deserialize(gameState, this.data.HQ); @@ -73,11 +76,11 @@ PETRA.PetraBot.prototype.CustomInit = function(gameState) // this.queues can only be modified by the queue manager or things will go awry. this.queues = {}; for (const i in this.Config.priorities) - this.queues[i] = new PETRA.Queue(); + this.queues[i] = new Queue(); - this.queueManager = new PETRA.QueueManager(this.Config, this.queues); + this.queueManager = new QueueManager(this.Config, this.queues); - this.HQ = new PETRA.HQ(this.Config); + this.HQ = new Headquather(this.Config); this.HQ.init(gameState, this.queues); @@ -86,7 +89,7 @@ PETRA.PetraBot.prototype.CustomInit = function(gameState) } }; -PETRA.PetraBot.prototype.OnUpdate = function(sharedScript) +PetraBot.prototype.OnUpdate = function(sharedScript) { if (this.gameFinished || this.gameState.playerData.state == "defeated") return; @@ -128,7 +131,7 @@ PETRA.PetraBot.prototype.OnUpdate = function(sharedScript) this.turn++; }; -PETRA.PetraBot.prototype.Serialize = function() +PetraBot.prototype.Serialize = function() { const savedEvents = {}; for (const key in this.savedEvents) @@ -159,7 +162,7 @@ PETRA.PetraBot.prototype.Serialize = function() }; }; -PETRA.PetraBot.prototype.Deserialize = function(data, sharedScript) +PetraBot.prototype.Deserialize = function(data, sharedScript) { this.isDeserialized = true; this.data = data; diff --git a/binaries/data/mods/public/simulation/ai/petra/attackManager.js b/binaries/data/mods/public/simulation/ai/petra/attackManager.js index 086ee2d475..3fb0fbb992 100644 --- a/binaries/data/mods/public/simulation/ai/petra/attackManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/attackManager.js @@ -1,25 +1,28 @@ -/** - * Attack Manager - */ -PETRA.AttackManager = function(Config) +import { AttackPlan } from "simulation/ai/petra/attackPlan.js"; +import * as chat from "simulation/ai/petra/chatHelper.js"; +import { Config, DIFFICULTY_VERY_EASY } from "simulation/ai/petra/config.js"; +import { allowCapture, getLandAccess } from "simulation/ai/petra/entityExtend.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + +export function AttackManager(config) { - this.Config = Config; + this.Config = config; this.totalNumber = 0; this.attackNumber = 0; this.rushNumber = 0; this.raidNumber = 0; this.upcomingAttacks = { - [PETRA.AttackPlan.TYPE_RUSH]: [], - [PETRA.AttackPlan.TYPE_RAID]: [], - [PETRA.AttackPlan.TYPE_DEFAULT]: [], - [PETRA.AttackPlan.TYPE_HUGE_ATTACK]: [] + [AttackPlan.TYPE_RUSH]: [], + [AttackPlan.TYPE_RAID]: [], + [AttackPlan.TYPE_DEFAULT]: [], + [AttackPlan.TYPE_HUGE_ATTACK]: [] }; this.startedAttacks = { - [PETRA.AttackPlan.TYPE_RUSH]: [], - [PETRA.AttackPlan.TYPE_RAID]: [], - [PETRA.AttackPlan.TYPE_DEFAULT]: [], - [PETRA.AttackPlan.TYPE_HUGE_ATTACK]: [] + [AttackPlan.TYPE_RUSH]: [], + [AttackPlan.TYPE_RAID]: [], + [AttackPlan.TYPE_DEFAULT]: [], + [AttackPlan.TYPE_HUGE_ATTACK]: [] }; this.bombingAttacks = new Map();// Temporary attacks for siege units while waiting their current attack to start this.debugTime = 0; @@ -27,16 +30,16 @@ PETRA.AttackManager = function(Config) this.rushSize = []; this.currentEnemyPlayer = undefined; // enemy player we are currently targeting this.defeated = {}; -}; +} /** More initialisation for stuff that needs the gameState */ -PETRA.AttackManager.prototype.init = function(gameState) +AttackManager.prototype.init = function(gameState) { this.outOfPlan = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "plan", -1)); this.outOfPlan.registerUpdates(); }; -PETRA.AttackManager.prototype.setRushes = function(allowed) +AttackManager.prototype.setRushes = function(allowed) { if (this.Config.personality.aggressive > this.Config.personalityCut.strong && allowed > 2) { @@ -55,7 +58,7 @@ PETRA.AttackManager.prototype.setRushes = function(allowed) } }; -PETRA.AttackManager.prototype.checkEvents = function(gameState, events) +AttackManager.prototype.checkEvents = function(gameState, events) { for (const evt of events.PlayerDefeated) this.defeated[evt.playerId] = true; @@ -73,7 +76,7 @@ PETRA.AttackManager.prototype.checkEvents = function(gameState, events) { for (const attack of this.upcomingAttacks[attackType]) { - if (attack.state === PETRA.AttackPlan.STATE_COMPLETING) + if (attack.state === AttackPlan.STATE_COMPLETING) { if (attack.targetPlayer === targetPlayer) available += attack.unitCollection.length; @@ -95,7 +98,7 @@ PETRA.AttackManager.prototype.checkEvents = function(gameState, events) { for (const attack of this.upcomingAttacks[attackType]) { - if (attack.state === PETRA.AttackPlan.STATE_COMPLETING || + if (attack.state === AttackPlan.STATE_COMPLETING || attack.targetPlayer !== targetPlayer || attack.unitCollection.length < 3) continue; @@ -110,7 +113,7 @@ PETRA.AttackManager.prototype.checkEvents = function(gameState, events) break; // take only the first attack request into account } if (targetPlayer !== undefined) - PETRA.chatAnswerRequestAttack(gameState, targetPlayer, answer, other); + chat.answerRequestAttack(gameState, targetPlayer, answer, other); for (const evt of events.EntityRenamed) // take care of packing units in bombing attacks { @@ -133,7 +136,7 @@ PETRA.AttackManager.prototype.checkEvents = function(gameState, events) /** * Check for any structure in range from within our territory, and bomb it */ -PETRA.AttackManager.prototype.assignBombers = function(gameState) +AttackManager.prototype.assignBombers = function(gameState) { // First some cleaning of current bombing attacks for (const [targetId, unitIds] of this.bombingAttacks) @@ -171,7 +174,8 @@ PETRA.AttackManager.prototype.assignBombers = function(gameState) if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") != -1) { const subrole = ent.getMetadata(PlayerID, "subrole"); - if (subrole && (subrole === PETRA.Worker.SUBROLE_COMPLETING || subrole === PETRA.Worker.SUBROLE_WALKING || subrole === PETRA.Worker.SUBROLE_ATTACKING)) + if (subrole && (subrole === Worker.SUBROLE_COMPLETING || + subrole === Worker.SUBROLE_WALKING || subrole === Worker.SUBROLE_ATTACKING)) continue; } let alreadyBombing = false; @@ -187,10 +191,10 @@ PETRA.AttackManager.prototype.assignBombers = function(gameState) const range = ent.attackRange("Ranged").max; const entPos = ent.position(); - const access = PETRA.getLandAccess(gameState, ent); + const access = getLandAccess(gameState, ent); for (const struct of gameState.getEnemyStructures().values()) { - if (!ent.canAttackTarget(struct, PETRA.allowCapture(gameState, ent, struct))) + if (!ent.canAttackTarget(struct, allowCapture(gameState, ent, struct))) continue; const structPos = struct.position(); @@ -245,7 +249,7 @@ PETRA.AttackManager.prototype.assignBombers = function(gameState) * Some functions are run every turn * Others once in a while */ -PETRA.AttackManager.prototype.update = function(gameState, queues, events) +AttackManager.prototype.update = function(gameState, queues, events) { if (this.Config.debug > 2 && gameState.ai.elapsedTime > this.debugTime + 60) { @@ -263,10 +267,10 @@ PETRA.AttackManager.prototype.update = function(gameState, queues, events) this.checkEvents(gameState, events); const unexecutedAttacks = { - [PETRA.AttackPlan.TYPE_RUSH]: 0, - [PETRA.AttackPlan.TYPE_RAID]: 0, - [PETRA.AttackPlan.TYPE_DEFAULT]: 0, - [PETRA.AttackPlan.TYPE_HUGE_ATTACK]: 0 + [AttackPlan.TYPE_RUSH]: 0, + [AttackPlan.TYPE_RAID]: 0, + [AttackPlan.TYPE_DEFAULT]: 0, + [AttackPlan.TYPE_HUGE_ATTACK]: 0 }; for (const attackType in this.upcomingAttacks) { @@ -280,27 +284,27 @@ PETRA.AttackManager.prototype.update = function(gameState, queues, events) const updateStep = attack.updatePreparation(gameState); // now we're gonna check if the preparation time is over - if (updateStep === PETRA.AttackPlan.PREPARATION_KEEP_GOING || attack.isPaused()) + if (updateStep === AttackPlan.PREPARATION_KEEP_GOING || attack.isPaused()) { // just chillin' - if (attack.state === PETRA.AttackPlan.STATE_UNEXECUTED) + if (attack.state === AttackPlan.STATE_UNEXECUTED) ++unexecutedAttacks[attackType]; } - else if (updateStep === PETRA.AttackPlan.PREPARATION_FAILED) + else if (updateStep === AttackPlan.PREPARATION_FAILED) { if (this.Config.debug > 1) API3.warn("Attack Manager: " + attack.getType() + " plan " + attack.getName() + " aborted."); attack.Abort(gameState); this.upcomingAttacks[attackType].splice(i--, 1); } - else if (updateStep === PETRA.AttackPlan.PREPARATION_START) + else if (updateStep === AttackPlan.PREPARATION_START) { if (attack.StartAttack(gameState)) { if (this.Config.debug > 1) API3.warn("Attack Manager: Starting " + attack.getType() + " plan " + attack.getName()); if (this.Config.chat) - PETRA.chatLaunchAttack(gameState, attack.targetPlayer, attack.getType()); + chat.launchAttack(gameState, attack.targetPlayer, attack.getType()); this.startedAttacks[attackType].push(attack); } else @@ -335,33 +339,39 @@ PETRA.AttackManager.prototype.update = function(gameState, queues, events) const barracksNb = gameState.getOwnEntitiesByClass("Barracks", true).filter(API3.Filters.isBuilt()).length; if (this.rushNumber < this.maxRushes && barracksNb >= 1) { - if (unexecutedAttacks[PETRA.AttackPlan.TYPE_RUSH] === 0) + if (unexecutedAttacks[AttackPlan.TYPE_RUSH] === 0) { // we have a barracks and we want to rush, rush. const data = { "targetSize": this.rushSize[this.rushNumber] }; - const attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, PETRA.AttackPlan.TYPE_RUSH, data); + const attackPlan = new AttackPlan(gameState, this.Config, this.totalNumber, + AttackPlan.TYPE_RUSH, data); if (!attackPlan.failed) { if (this.Config.debug > 1) API3.warn("Military Manager: Rushing plan " + this.totalNumber + " with maxRushes " + this.maxRushes); this.totalNumber++; attackPlan.init(gameState); - this.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].push(attackPlan); + this.upcomingAttacks[AttackPlan.TYPE_RUSH].push(attackPlan); } this.rushNumber++; } } - else if (unexecutedAttacks[PETRA.AttackPlan.TYPE_DEFAULT] == 0 && unexecutedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK] == 0 && - this.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + this.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length < + else if (unexecutedAttacks[AttackPlan.TYPE_DEFAULT] == 0 && + unexecutedAttacks[AttackPlan.TYPE_HUGE_ATTACK] == 0 && + this.startedAttacks[AttackPlan.TYPE_DEFAULT].length + + this.startedAttacks[AttackPlan.TYPE_HUGE_ATTACK].length < Math.min(2, 1 + Math.round(gameState.getPopulationMax() / 100)) && - (this.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + this.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length == 0 || + (this.startedAttacks[AttackPlan.TYPE_DEFAULT].length + + this.startedAttacks[AttackPlan.TYPE_HUGE_ATTACK].length == 0 || gameState.getPopulationMax() - gameState.getPopulation() > 12)) { if (barracksNb >= 1 && (gameState.currentPhase() > 1 || gameState.isResearching(gameState.getPhaseName(2))) || !gameState.ai.HQ.hasPotentialBase()) // if we have no base ... nothing else to do than attack { - const type = this.attackNumber < 2 || this.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length > 0 ? PETRA.AttackPlan.TYPE_DEFAULT : PETRA.AttackPlan.TYPE_HUGE_ATTACK; - const attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, type); + const type = this.attackNumber < 2 || + this.startedAttacks[AttackPlan.TYPE_HUGE_ATTACK].length > 0 ? + AttackPlan.TYPE_DEFAULT : AttackPlan.TYPE_HUGE_ATTACK; + const attackPlan = new AttackPlan(gameState, this.Config, this.totalNumber, type); if (attackPlan.failed) this.attackPlansEncounteredWater = true; // hack else @@ -376,7 +386,8 @@ PETRA.AttackManager.prototype.update = function(gameState, queues, events) } } - if (unexecutedAttacks[PETRA.AttackPlan.TYPE_RAID] === 0 && gameState.ai.HQ.defenseManager.targetList.length) + if (unexecutedAttacks[AttackPlan.TYPE_RAID] === 0 && + gameState.ai.HQ.defenseManager.targetList.length) { let target; for (const targetId of gameState.ai.HQ.defenseManager.targetList) @@ -393,11 +404,11 @@ PETRA.AttackManager.prototype.update = function(gameState, queues, events) } // Check if we have some unused ranged siege unit which could do something useful while waiting - if (this.Config.difficulty > PETRA.DIFFICULTY_VERY_EASY && gameState.ai.playedTurn % 5 == 0) + if (this.Config.difficulty > DIFFICULTY_VERY_EASY && gameState.ai.playedTurn % 5 == 0) this.assignBombers(gameState); }; -PETRA.AttackManager.prototype.getPlan = function(planName) +AttackManager.prototype.getPlan = function(planName) { for (const attackType in this.upcomingAttacks) { @@ -414,21 +425,21 @@ PETRA.AttackManager.prototype.getPlan = function(planName) return undefined; }; -PETRA.AttackManager.prototype.pausePlan = function(planName) +AttackManager.prototype.pausePlan = function(planName) { const attack = this.getPlan(planName); if (attack) attack.setPaused(true); }; -PETRA.AttackManager.prototype.unpausePlan = function(planName) +AttackManager.prototype.unpausePlan = function(planName) { const attack = this.getPlan(planName); if (attack) attack.setPaused(false); }; -PETRA.AttackManager.prototype.pauseAllPlans = function() +AttackManager.prototype.pauseAllPlans = function() { for (const attackType in this.upcomingAttacks) for (const attack of this.upcomingAttacks[attackType]) @@ -439,7 +450,7 @@ PETRA.AttackManager.prototype.pauseAllPlans = function() attack.setPaused(true); }; -PETRA.AttackManager.prototype.unpauseAllPlans = function() +AttackManager.prototype.unpauseAllPlans = function() { for (const attackType in this.upcomingAttacks) for (const attack of this.upcomingAttacks[attackType]) @@ -450,7 +461,7 @@ PETRA.AttackManager.prototype.unpauseAllPlans = function() attack.setPaused(false); }; -PETRA.AttackManager.prototype.getAttackInPreparation = function(type) +AttackManager.prototype.getAttackInPreparation = function(type) { return this.upcomingAttacks[type].length ? this.upcomingAttacks[type][0] : undefined; }; @@ -460,7 +471,7 @@ PETRA.AttackManager.prototype.getAttackInPreparation = function(type) * attack.targetPlayer is undefined and in that case, we keep track of the chosen target * for future attacks. */ -PETRA.AttackManager.prototype.getEnemyPlayer = function(gameState, attack) +AttackManager.prototype.getEnemyPlayer = function(gameState, attack) { let enemyPlayer; @@ -481,7 +492,7 @@ PETRA.AttackManager.prototype.getEnemyPlayer = function(gameState, attack) for (const i in this.defeated) veto[i] = true; // No rush if enemy too well defended (i.e. iberians) - if (attack.type === PETRA.AttackPlan.TYPE_RUSH) + if (attack.type === AttackPlan.TYPE_RUSH) { for (let i = 1; i < gameState.sharedScript.playersData.length; ++i) { @@ -500,7 +511,7 @@ PETRA.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 !== PETRA.AttackPlan.TYPE_HUGE_ATTACK) + if (attack.type !== AttackPlan.TYPE_HUGE_ATTACK) { if (attack.targetPlayer === undefined && this.currentEnemyPlayer !== undefined && !this.defeated[this.currentEnemyPlayer] && @@ -516,14 +527,14 @@ PETRA.AttackManager.prototype.getEnemyPlayer = function(gameState, attack) if (ourcc.owner() != PlayerID) continue; const ourPos = ourcc.position(); - const access = PETRA.getLandAccess(gameState, ourcc); + const access = getLandAccess(gameState, ourcc); for (const enemycc of ccEnts.values()) { if (veto[enemycc.owner()]) continue; if (!gameState.isPlayerEnemy(enemycc.owner())) continue; - if (access !== PETRA.getLandAccess(gameState, enemycc)) + if (access !== getLandAccess(gameState, enemycc)) continue; const dist = API3.SquareVectorDistance(ourPos, enemycc.position()); if (distmin && dist > distmin) @@ -574,7 +585,7 @@ PETRA.AttackManager.prototype.getEnemyPlayer = function(gameState, attack) * Target the player with the most advanced wonder. * TODO currently the first built wonder is kept, should chek on the minimum wonderDuration left instead. */ -PETRA.AttackManager.prototype.getWonderEnemyPlayer = function(gameState, attack) +AttackManager.prototype.getWonderEnemyPlayer = function(gameState, attack) { let enemyPlayer; let enemyWonder; @@ -606,7 +617,7 @@ PETRA.AttackManager.prototype.getWonderEnemyPlayer = function(gameState, attack) /** * Target the player with the most relics (including gaia). */ -PETRA.AttackManager.prototype.getRelicEnemyPlayer = function(gameState, attack) +AttackManager.prototype.getRelicEnemyPlayer = function(gameState, attack) { let enemyPlayer; const allRelics = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")); @@ -634,7 +645,7 @@ PETRA.AttackManager.prototype.getRelicEnemyPlayer = function(gameState, attack) }; /** f.e. if we have changed diplomacy with another player. */ -PETRA.AttackManager.prototype.cancelAttacksAgainstPlayer = function(gameState, player) +AttackManager.prototype.cancelAttacksAgainstPlayer = function(gameState, player) { for (const attackType in this.upcomingAttacks) for (const attack of this.upcomingAttacks[attackType]) @@ -653,10 +664,11 @@ PETRA.AttackManager.prototype.cancelAttacksAgainstPlayer = function(gameState, p } }; -PETRA.AttackManager.prototype.raidTargetEntity = function(gameState, ent) +AttackManager.prototype.raidTargetEntity = function(gameState, ent) { const data = { "target": ent }; - const attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, PETRA.AttackPlan.TYPE_RAID, data); + const attackPlan = new AttackPlan(gameState, this.Config, this.totalNumber, + AttackPlan.TYPE_RAID, data); if (attackPlan.failed) return null; if (this.Config.debug > 1) @@ -664,14 +676,14 @@ PETRA.AttackManager.prototype.raidTargetEntity = function(gameState, ent) this.raidNumber++; this.totalNumber++; attackPlan.init(gameState); - this.upcomingAttacks[PETRA.AttackPlan.TYPE_RAID].push(attackPlan); + this.upcomingAttacks[AttackPlan.TYPE_RAID].push(attackPlan); return attackPlan; }; /** * Return the number of units from any of our attacking armies around this position */ -PETRA.AttackManager.prototype.numAttackingUnitsAround = function(pos, dist) +AttackManager.prototype.numAttackingUnitsAround = function(pos, dist) { let num = 0; for (const attackType in this.startedAttacks) @@ -691,7 +703,7 @@ PETRA.AttackManager.prototype.numAttackingUnitsAround = function(pos, dist) * data.armyID: transform only the defense army ID into a new attack * data.uniqueTarget: the attack will stop when the target is destroyed or captured */ -PETRA.AttackManager.prototype.switchDefenseToAttack = function(gameState, target, data) +AttackManager.prototype.switchDefenseToAttack = function(gameState, target, data) { if (!target || !target.position()) return false; @@ -702,15 +714,15 @@ PETRA.AttackManager.prototype.switchDefenseToAttack = function(gameState, target } const attackData = data.uniqueTarget ? { "uniqueTargetId": target.id() } : undefined; const pos = target.position(); - const attackType = PETRA.AttackPlan.TYPE_DEFAULT; - const attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, attackType, attackData); + const attackType = AttackPlan.TYPE_DEFAULT; + const attackPlan = new AttackPlan(gameState, this.Config, this.totalNumber, attackType, attackData); if (attackPlan.failed) return false; this.totalNumber++; attackPlan.init(gameState); this.startedAttacks[attackType].push(attackPlan); - const targetAccess = PETRA.getLandAccess(gameState, target); + const targetAccess = getLandAccess(gameState, target); for (const army of gameState.ai.HQ.defenseManager.armies) { if (data.range) @@ -730,11 +742,11 @@ PETRA.AttackManager.prototype.switchDefenseToAttack = function(gameState, target army.removeOwn(gameState, unitId); const unit = gameState.getEntityById(unitId); const accessOk = unit.getMetadata(PlayerID, "transport") !== undefined || - unit.position() && PETRA.getLandAccess(gameState, unit) == targetAccess; + unit.position() && getLandAccess(gameState, unit) == targetAccess; if (unit && accessOk && attackPlan.isAvailableUnit(gameState, unit)) { unit.setMetadata(PlayerID, "plan", attackPlan.name); - unit.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_ATTACK); + unit.setMetadata(PlayerID, "role", Worker.ROLE_ATTACK); attackPlan.unitCollection.updateEnt(unit); } } @@ -745,15 +757,15 @@ PETRA.AttackManager.prototype.switchDefenseToAttack = function(gameState, target return false; } for (const unit of attackPlan.unitCollection.values()) - unit.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_ATTACK); + unit.setMetadata(PlayerID, "role", Worker.ROLE_ATTACK); attackPlan.targetPlayer = target.owner(); attackPlan.targetPos = pos; attackPlan.target = target; - attackPlan.state = PETRA.AttackPlan.STATE_ARRIVED; + attackPlan.state = AttackPlan.STATE_ARRIVED; return true; }; -PETRA.AttackManager.prototype.Serialize = function() +AttackManager.prototype.Serialize = function() { const properties = { "totalNumber": this.totalNumber, @@ -786,7 +798,7 @@ PETRA.AttackManager.prototype.Serialize = function() return { "properties": properties, "upcomingAttacks": upcomingAttacks, "startedAttacks": startedAttacks }; }; -PETRA.AttackManager.prototype.Deserialize = function(gameState, data) +AttackManager.prototype.Deserialize = function(gameState, data) { for (const key in data.properties) this[key] = data.properties[key]; @@ -797,7 +809,7 @@ PETRA.AttackManager.prototype.Deserialize = function(gameState, data) this.upcomingAttacks[key] = []; for (const dataAttack of data.upcomingAttacks[key]) { - const attack = new PETRA.AttackPlan(gameState, this.Config, dataAttack.properties.name); + const attack = new AttackPlan(gameState, this.Config, dataAttack.properties.name); attack.Deserialize(gameState, dataAttack); attack.init(gameState); this.upcomingAttacks[key].push(attack); @@ -810,7 +822,7 @@ PETRA.AttackManager.prototype.Deserialize = function(gameState, data) this.startedAttacks[key] = []; for (const dataAttack of data.startedAttacks[key]) { - const attack = new PETRA.AttackPlan(gameState, this.Config, dataAttack.properties.name); + const attack = new AttackPlan(gameState, this.Config, dataAttack.properties.name); attack.Deserialize(gameState, dataAttack); attack.init(gameState); this.startedAttacks[key].push(attack); diff --git a/binaries/data/mods/public/simulation/ai/petra/attackPlan.js b/binaries/data/mods/public/simulation/ai/petra/attackPlan.js index c842f88bf8..68f1fd22b8 100644 --- a/binaries/data/mods/public/simulation/ai/petra/attackPlan.js +++ b/binaries/data/mods/public/simulation/ai/petra/attackPlan.js @@ -1,15 +1,21 @@ +import { Config, DIFFICULTY_EASY, DIFFICULTY_MEDIUM } from "simulation/ai/petra/config.js"; +import { allowCapture, dumpEntity, getHolder, getLandAccess, isSiegeUnit, returnResources } from + "simulation/ai/petra/entityExtend.js"; +import { TrainingPlan } from "simulation/ai/petra/queueplanTraining.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + /** * This is an attack plan: * It deals with everything in an attack, from picking a target to picking a path to it * To making sure units are built, and pushing elements to the queue manager otherwise * It also handles the actual attack, though much work is needed on that. */ -PETRA.AttackPlan = function(gameState, Config, uniqueID, type = PETRA.AttackPlan.TYPE_DEFAULT, data) +export function AttackPlan(gameState, config, uniqueID, type = AttackPlan.TYPE_DEFAULT, data) { - this.Config = Config; + this.Config = config; this.name = uniqueID; this.type = type; - this.state = PETRA.AttackPlan.STATE_UNEXECUTED; + this.state = AttackPlan.STATE_UNEXECUTED; this.forced = false; // true when this attacked has been forced to help an ally if (data && data.target) @@ -35,7 +41,7 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type = PETRA.AttackPlan { if (!base.anchor || !base.anchor.position()) continue; - const access = PETRA.getLandAccess(gameState, base.anchor); + const access = getLandAccess(gameState, base.anchor); if (!rallyPoint) { rallyPoint = base.anchor.position(); @@ -50,7 +56,7 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type = PETRA.AttackPlan { if (!ent.position()) continue; - const access = PETRA.getLandAccess(gameState, ent); + const access = getLandAccess(gameState, ent); rallyPoint = ent.position(); rallyAccess = access; allAccesses[access] = rallyPoint; @@ -72,7 +78,7 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type = PETRA.AttackPlan continue; if (!structure.position()) continue; - const access = PETRA.getLandAccess(gameState, structure); + const access = getLandAccess(gameState, structure); if (access in allAccesses) { this.overseas = 0; @@ -113,7 +119,7 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type = PETRA.AttackPlan this.unitStat = {}; // neededShips is the minimal number of ships which should be available for transport - if (type === PETRA.AttackPlan.TYPE_RUSH) + if (type === AttackPlan.TYPE_RUSH) { priority = 250; this.unitStat.Infantry = { "priority": 1, "minSize": 10, "targetSize": 20, "batchSize": 2, "classes": ["Infantry"], @@ -124,14 +130,14 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type = PETRA.AttackPlan this.unitStat.Infantry.targetSize = data.targetSize; this.neededShips = 1; } - else if (type === PETRA.AttackPlan.TYPE_RAID) + else if (type === AttackPlan.TYPE_RAID) { priority = 150; this.unitStat.FastMoving = { "priority": 1, "minSize": 3, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving+CitizenSoldier"], "interests": [ ["strength", 1] ] }; this.neededShips = 1; } - else if (type === PETRA.AttackPlan.TYPE_HUGE_ATTACK) + else if (type === AttackPlan.TYPE_HUGE_ATTACK) { priority = 90; // basically we want a mix of citizen soldiers so our barracks have a purpose, and champion units. @@ -170,18 +176,18 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type = PETRA.AttackPlan // Put some randomness on the attack size let variation = randFloat(0.8, 1.2); // and lower priority and smaller sizes for easier difficulty levels - if (this.Config.difficulty < PETRA.DIFFICULTY_EASY) + if (this.Config.difficulty < DIFFICULTY_EASY) { priority *= 0.4; variation *= 0.2; } - else if (this.Config.difficulty < PETRA.DIFFICULTY_MEDIUM) + else if (this.Config.difficulty < DIFFICULTY_MEDIUM) { priority *= 0.8; variation *= 0.6; } - if (this.Config.difficulty < PETRA.DIFFICULTY_EASY) + if (this.Config.difficulty < DIFFICULTY_EASY) { for (const cat in this.unitStat) { @@ -215,7 +221,7 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type = PETRA.AttackPlan // each array is [ratio, [associated classes], associated EntityColl, associated unitStat, name ] this.buildOrders = []; this.canBuildUnits = gameState.ai.HQ.canBuildUnits; - this.siegeState = PETRA.AttackPlan.SIEGE_NOT_TESTED; + this.siegeState = AttackPlan.SIEGE_NOT_TESTED; // some variables used during the attack this.position5TurnsAgo = [0, 0]; @@ -224,30 +230,30 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type = PETRA.AttackPlan this.isBlocked = false; // true when this attack faces walls return true; -}; +} -PETRA.AttackPlan.PREPARATION_FAILED = 0; -PETRA.AttackPlan.PREPARATION_KEEP_GOING = 1; -PETRA.AttackPlan.PREPARATION_START = 2; +AttackPlan.PREPARATION_FAILED = 0; +AttackPlan.PREPARATION_KEEP_GOING = 1; +AttackPlan.PREPARATION_START = 2; -PETRA.AttackPlan.SIEGE_NOT_TESTED = 0; -PETRA.AttackPlan.SIEGE_NO_TRAINER = 1; +AttackPlan.SIEGE_NOT_TESTED = 0; +AttackPlan.SIEGE_NO_TRAINER = 1; /** * Siege added in build orders */ -PETRA.AttackPlan.SIEGE_ADDED = 2; +AttackPlan.SIEGE_ADDED = 2; -PETRA.AttackPlan.STATE_UNEXECUTED = "unexecuted"; -PETRA.AttackPlan.STATE_COMPLETING = "completing"; -PETRA.AttackPlan.STATE_ARRIVED = "arrived"; +AttackPlan.STATE_UNEXECUTED = "unexecuted"; +AttackPlan.STATE_COMPLETING = "completing"; +AttackPlan.STATE_ARRIVED = "arrived"; -PETRA.AttackPlan.TYPE_DEFAULT = "Attack"; -PETRA.AttackPlan.TYPE_HUGE_ATTACK = "HugeAttack"; -PETRA.AttackPlan.TYPE_RAID = "Raid"; -PETRA.AttackPlan.TYPE_RUSH = "Rush"; +AttackPlan.TYPE_DEFAULT = "Attack"; +AttackPlan.TYPE_HUGE_ATTACK = "HugeAttack"; +AttackPlan.TYPE_RAID = "Raid"; +AttackPlan.TYPE_RUSH = "Rush"; -PETRA.AttackPlan.prototype.init = function(gameState) +AttackPlan.prototype.init = function(gameState) { this.queue = gameState.ai.queues["plan_" + this.name]; this.queueChamp = gameState.ai.queues["plan_" + this.name +"_champ"]; @@ -270,27 +276,27 @@ PETRA.AttackPlan.prototype.init = function(gameState) } }; -PETRA.AttackPlan.prototype.getName = function() +AttackPlan.prototype.getName = function() { return this.name; }; -PETRA.AttackPlan.prototype.getType = function() +AttackPlan.prototype.getType = function() { return this.type; }; -PETRA.AttackPlan.prototype.isStarted = function() +AttackPlan.prototype.isStarted = function() { - return this.state !== PETRA.AttackPlan.STATE_UNEXECUTED && this.state !== PETRA.AttackPlan.STATE_COMPLETING; + return this.state !== AttackPlan.STATE_UNEXECUTED && this.state !== AttackPlan.STATE_COMPLETING; }; -PETRA.AttackPlan.prototype.isPaused = function() +AttackPlan.prototype.isPaused = function() { return this.paused; }; -PETRA.AttackPlan.prototype.setPaused = function(boolValue) +AttackPlan.prototype.setPaused = function(boolValue) { this.paused = boolValue; }; @@ -299,7 +305,7 @@ PETRA.AttackPlan.prototype.setPaused = function(boolValue) * Returns true if the attack can be executed at the current time * Basically it checks we have enough units. */ -PETRA.AttackPlan.prototype.canStart = function() +AttackPlan.prototype.canStart = function() { if (!this.canBuildUnits) return true; @@ -311,7 +317,7 @@ PETRA.AttackPlan.prototype.canStart = function() return true; }; -PETRA.AttackPlan.prototype.mustStart = function() +AttackPlan.prototype.mustStart = function() { if (this.isPaused()) return false; @@ -336,12 +342,12 @@ PETRA.AttackPlan.prototype.mustStart = function() if (MaxReachedEverywhere) return true; if (MinReachedEverywhere) - return this.type === PETRA.AttackPlan.TYPE_RAID && this.target && this.target.foundationProgress() && + return this.type === AttackPlan.TYPE_RAID && this.target && this.target.foundationProgress() && this.target.foundationProgress() > 50; return false; }; -PETRA.AttackPlan.prototype.forceStart = function() +AttackPlan.prototype.forceStart = function() { for (const unitCat in this.unitStat) { @@ -352,14 +358,14 @@ PETRA.AttackPlan.prototype.forceStart = function() this.forced = true; }; -PETRA.AttackPlan.prototype.emptyQueues = function() +AttackPlan.prototype.emptyQueues = function() { this.queue.empty(); this.queueChamp.empty(); this.queueSiege.empty(); }; -PETRA.AttackPlan.prototype.removeQueues = function(gameState) +AttackPlan.prototype.removeQueues = function(gameState) { gameState.ai.queueManager.removeQueue("plan_" + this.name); gameState.ai.queueManager.removeQueue("plan_" + this.name + "_champ"); @@ -367,7 +373,7 @@ PETRA.AttackPlan.prototype.removeQueues = function(gameState) }; /** Adds a build order. If resetQueue is true, this will reset the queue. */ -PETRA.AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats, resetQueue) +AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats, resetQueue) { if (!this.isStarted()) { @@ -382,9 +388,9 @@ PETRA.AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats, } }; -PETRA.AttackPlan.prototype.addSiegeUnits = function(gameState) +AttackPlan.prototype.addSiegeUnits = function(gameState) { - if (this.siegeState === PETRA.AttackPlan.SIEGE_ADDED || this.state !== PETRA.AttackPlan.STATE_UNEXECUTED) + if (this.siegeState === AttackPlan.SIEGE_ADDED || this.state !== AttackPlan.STATE_UNEXECUTED) return false; const civ = gameState.getPlayerCiv(); @@ -409,7 +415,7 @@ PETRA.AttackPlan.prototype.addSiegeUnits = function(gameState) } if (hasTrainer.every(e => !e)) { - this.siegeState = PETRA.AttackPlan.SIEGE_NO_TRAINER; + this.siegeState = AttackPlan.SIEGE_NO_TRAINER; return false; } let i = this.name % classes.length; @@ -420,13 +426,13 @@ PETRA.AttackPlan.prototype.addSiegeUnits = function(gameState) i = (i + 1) % classes.length; } - this.siegeState = PETRA.AttackPlan.SIEGE_ADDED; + this.siegeState = AttackPlan.SIEGE_ADDED; let targetSize; - if (this.Config.difficulty < PETRA.DIFFICULTY_MEDIUM) - targetSize = this.type === PETRA.AttackPlan.TYPE_HUGE_ATTACK ? Math.max(this.Config.difficulty, 1) : Math.max(this.Config.difficulty - 1, 0); + if (this.Config.difficulty < DIFFICULTY_MEDIUM) + targetSize = this.type === AttackPlan.TYPE_HUGE_ATTACK ? Math.max(this.Config.difficulty, 1) : Math.max(this.Config.difficulty - 1, 0); else - targetSize = this.type === PETRA.AttackPlan.TYPE_HUGE_ATTACK ? this.Config.difficulty + 1 : this.Config.difficulty - 1; - targetSize = Math.max(Math.round(this.Config.popScaling * targetSize), this.type === PETRA.AttackPlan.TYPE_HUGE_ATTACK ? 1 : 0); + targetSize = this.type === AttackPlan.TYPE_HUGE_ATTACK ? this.Config.difficulty + 1 : this.Config.difficulty - 1; + targetSize = Math.max(Math.round(this.Config.popScaling * targetSize), this.type === AttackPlan.TYPE_HUGE_ATTACK ? 1 : 0); if (!targetSize) return true; // no minsize as we don't want the plan to fail at the last minute though. @@ -437,27 +443,27 @@ PETRA.AttackPlan.prototype.addSiegeUnits = function(gameState) }; /** Three returns possible: 1 is "keep going", 0 is "failed plan", 2 is "start". */ -PETRA.AttackPlan.prototype.updatePreparation = function(gameState) +AttackPlan.prototype.updatePreparation = function(gameState) { // the completing step is used to return resources and regroup the units // so we check that we have no more forced order before starting the attack - if (this.state === PETRA.AttackPlan.STATE_COMPLETING) + if (this.state === AttackPlan.STATE_COMPLETING) { // if our target was destroyed, go back to "unexecuted" state if (this.targetPlayer === undefined || !this.target || !gameState.getEntityById(this.target.id())) { - this.state = PETRA.AttackPlan.STATE_UNEXECUTED; + this.state = AttackPlan.STATE_UNEXECUTED; this.target = undefined; } else { // check that all units have finished with their transport if needed if (this.waitingForTransport()) - return PETRA.AttackPlan.PREPARATION_KEEP_GOING; + return AttackPlan.PREPARATION_KEEP_GOING; // bloqued units which cannot finish their order should not stop the attack if (gameState.ai.elapsedTime < this.maxCompletingTime && this.hasForceOrder()) - return PETRA.AttackPlan.PREPARATION_KEEP_GOING; - return PETRA.AttackPlan.PREPARATION_START; + return AttackPlan.PREPARATION_KEEP_GOING; + return AttackPlan.PREPARATION_START; } } @@ -466,11 +472,11 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState) // if we need a transport, wait for some transport ships if (this.overseas && !gameState.ai.HQ.navalManager.seaTransportShips[this.overseas].length) - return PETRA.AttackPlan.PREPARATION_KEEP_GOING; + return AttackPlan.PREPARATION_KEEP_GOING; - if (this.type !== PETRA.AttackPlan.TYPE_RAID || !this.forced) // Forced Raids have special purposes (as relic capture) + if (this.type !== AttackPlan.TYPE_RAID || !this.forced) // Forced Raids have special purposes (as relic capture) this.assignUnits(gameState); - if (this.type !== PETRA.AttackPlan.TYPE_RAID && gameState.ai.HQ.attackManager.getAttackInPreparation(PETRA.AttackPlan.TYPE_RAID) !== undefined) + if (this.type !== AttackPlan.TYPE_RAID && gameState.ai.HQ.attackManager.getAttackInPreparation(AttackPlan.TYPE_RAID) !== undefined) this.reassignFastUnit(gameState); // reassign some fast units (if any) to fasten raid preparations // Fasten the end game. @@ -504,16 +510,16 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState) if (this.Config.debug > 1) { const am = gameState.ai.HQ.attackManager; - API3.warn(" attacks upcoming: raid " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_RAID].length + - " rush " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].length + - " attack " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + - " huge " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length); - API3.warn(" attacks started: raid " + am.startedAttacks[PETRA.AttackPlan.TYPE_RAID].length + - " rush " + am.startedAttacks[PETRA.AttackPlan.TYPE_RUSH].length + - " attack " + am.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + - " huge " + am.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length); + API3.warn(" attacks upcoming: raid " + am.upcomingAttacks[AttackPlan.TYPE_RAID].length + + " rush " + am.upcomingAttacks[AttackPlan.TYPE_RUSH].length + + " attack " + am.upcomingAttacks[AttackPlan.TYPE_DEFAULT].length + + " huge " + am.upcomingAttacks[AttackPlan.TYPE_HUGE_ATTACK].length); + API3.warn(" attacks started: raid " + am.startedAttacks[AttackPlan.TYPE_RAID].length + + " rush " + am.startedAttacks[AttackPlan.TYPE_RUSH].length + + " attack " + am.startedAttacks[AttackPlan.TYPE_DEFAULT].length + + " huge " + am.startedAttacks[AttackPlan.TYPE_HUGE_ATTACK].length); } - return PETRA.AttackPlan.PREPARATION_FAILED; + return AttackPlan.PREPARATION_FAILED; } } else if (this.mustStart()) @@ -522,7 +528,7 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState) { // keep on while the units finish being trained, then we'll start this.emptyQueues(); - return PETRA.AttackPlan.PREPARATION_KEEP_GOING; + return AttackPlan.PREPARATION_KEEP_GOING; } } else @@ -530,31 +536,31 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState) if (this.canBuildUnits) { // We still have time left to recruit units and do stuffs. - if (this.siegeState === PETRA.AttackPlan.SIEGE_NOT_TESTED || - this.siegeState === PETRA.AttackPlan.SIEGE_NO_TRAINER && gameState.ai.playedTurn % 5 == 0) + if (this.siegeState === AttackPlan.SIEGE_NOT_TESTED || + this.siegeState === AttackPlan.SIEGE_NO_TRAINER && gameState.ai.playedTurn % 5 == 0) this.addSiegeUnits(gameState); this.trainMoreUnits(gameState); // may happen if we have no more training facilities and build orders are canceled if (!this.buildOrders.length) - return PETRA.AttackPlan.PREPARATION_FAILED; // will abort the plan + return AttackPlan.PREPARATION_FAILED; // will abort the plan } - return PETRA.AttackPlan.PREPARATION_KEEP_GOING; + return AttackPlan.PREPARATION_KEEP_GOING; } // if we're here, it means we must start - this.state = PETRA.AttackPlan.STATE_COMPLETING; + this.state = AttackPlan.STATE_COMPLETING; // Raids have their predefined target if (!this.target && !this.chooseTarget(gameState)) - return PETRA.AttackPlan.PREPARATION_FAILED; + return AttackPlan.PREPARATION_FAILED; if (!this.overseas) this.getPathToTarget(gameState); - if (this.type === PETRA.AttackPlan.TYPE_RAID) + if (this.type === AttackPlan.TYPE_RAID) this.maxCompletingTime = this.forced ? 0 : gameState.ai.elapsedTime + 20; else { - if (this.type === PETRA.AttackPlan.TYPE_RUSH || this.forced) + if (this.type === AttackPlan.TYPE_RUSH || this.forced) this.maxCompletingTime = gameState.ai.elapsedTime + 40; else this.maxCompletingTime = gameState.ai.elapsedTime + 60; @@ -586,12 +592,12 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState) ent.setMetadata(PlayerID, "plan", -1); continue; } - ent.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_ATTACK); - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_COMPLETING); + ent.setMetadata(PlayerID, "role", Worker.ROLE_ATTACK); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_COMPLETING); let queued = false; if (ent.resourceCarrying() && ent.resourceCarrying().length) - queued = PETRA.returnResources(gameState, ent); - const index = PETRA.getLandAccess(gameState, ent); + queued = returnResources(gameState, ent); + const index = getLandAccess(gameState, ent); if (index == rallyIndex) ent.moveToRange(rallyPoint[0], rallyPoint[1], 0, 15, queued); else @@ -600,10 +606,10 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState) // reset all queued units this.removeQueues(gameState); - return PETRA.AttackPlan.PREPARATION_KEEP_GOING; + return AttackPlan.PREPARATION_KEEP_GOING; }; -PETRA.AttackPlan.prototype.trainMoreUnits = function(gameState) +AttackPlan.prototype.trainMoreUnits = function(gameState) { // let's sort by training advancement, ie 'current size / target size' // count the number of queued units too. @@ -675,8 +681,9 @@ PETRA.AttackPlan.prototype.trainMoreUnits = function(gameState) const max = firstOrder[3].batchSize; const specialData = "Plan_" + this.name + "_" + firstOrder[4]; const data = { "plan": this.name, "special": specialData, "base": 0 }; - data.role = gameState.getTemplate(template).hasClass("CitizenSoldier") ? PETRA.Worker.ROLE_WORKER : PETRA.Worker.ROLE_ATTACK; - const trainingPlan = new PETRA.TrainingPlan(gameState, template, data, max, max); + data.role = gameState.getTemplate(template).hasClass("CitizenSoldier") ? + Worker.ROLE_WORKER : Worker.ROLE_ATTACK; + const trainingPlan = new TrainingPlan(gameState, template, data, max, max); if (trainingPlan.template) queue.addPlan(trainingPlan); else if (this.Config.debug > 1) @@ -687,7 +694,7 @@ PETRA.AttackPlan.prototype.trainMoreUnits = function(gameState) } }; -PETRA.AttackPlan.prototype.assignUnits = function(gameState) +AttackPlan.prototype.assignUnits = function(gameState) { const plan = this.name; let added = false; @@ -705,7 +712,7 @@ PETRA.AttackPlan.prototype.assignUnits = function(gameState) return added; } - if (this.type === PETRA.AttackPlan.TYPE_RAID) + if (this.type === AttackPlan.TYPE_RAID) { // Raids are quick attacks: assign all FastMoving soldiers except some for hunting. let num = 0; @@ -746,15 +753,15 @@ PETRA.AttackPlan.prototype.assignUnits = function(gameState) // Finally add also some workers for the higher difficulties, // If Rush, assign all kind of workers, keeping only a minimum number of defenders // Otherwise, assign only some idle workers if too much of them - if (this.Config.difficulty <= PETRA.DIFFICULTY_EASY) + if (this.Config.difficulty <= DIFFICULTY_EASY) return added; let num = 0; const numbase = {}; - let keep = this.type !== PETRA.AttackPlan.TYPE_RUSH ? + let keep = this.type !== AttackPlan.TYPE_RUSH ? 6 + 4 * gameState.getNumPlayerEnemies() + 8 * this.Config.personality.defensive : 8; keep = Math.round(this.Config.popScaling * keep); - for (const ent of gameState.getOwnEntitiesByRole(PETRA.Worker.ROLE_WORKER, true).values()) + for (const ent of gameState.getOwnEntitiesByRole(Worker.ROLE_WORKER, true).values()) { if (!ent.hasClass("CitizenSoldier") || !this.isAvailableUnit(gameState, ent)) continue; @@ -764,12 +771,12 @@ PETRA.AttackPlan.prototype.assignUnits = function(gameState) else { API3.warn("Petra problem ent without base "); - PETRA.dumpEntity(ent); + dumpEntity(ent); continue; } if (num++ < keep || numbase[baseID] < 5) continue; - if (this.type !== PETRA.AttackPlan.TYPE_RUSH && ent.getMetadata(PlayerID, "subrole") !== PETRA.Worker.SUBROLE_IDLE) + if (this.type !== AttackPlan.TYPE_RUSH && ent.getMetadata(PlayerID, "subrole") !== Worker.SUBROLE_IDLE) continue; ent.setMetadata(PlayerID, "plan", plan); this.unitCollection.updateEnt(ent); @@ -778,7 +785,7 @@ PETRA.AttackPlan.prototype.assignUnits = function(gameState) return added; }; -PETRA.AttackPlan.prototype.isAvailableUnit = function(gameState, ent) +AttackPlan.prototype.isAvailableUnit = function(gameState, ent) { if (!ent.position()) return false; @@ -791,7 +798,7 @@ PETRA.AttackPlan.prototype.isAvailableUnit = function(gameState, ent) }; /** Reassign one (at each turn) FastMoving unit to fasten raid preparation. */ -PETRA.AttackPlan.prototype.reassignFastUnit = function(gameState) +AttackPlan.prototype.reassignFastUnit = function(gameState) { for (const ent of this.unitCollection.values()) { @@ -799,7 +806,7 @@ PETRA.AttackPlan.prototype.reassignFastUnit = function(gameState) continue; if (!ent.hasClasses(["FastMoving", "CitizenSoldier"])) continue; - const raid = gameState.ai.HQ.attackManager.getAttackInPreparation(PETRA.AttackPlan.TYPE_RAID); + const raid = gameState.ai.HQ.attackManager.getAttackInPreparation(AttackPlan.TYPE_RAID); ent.setMetadata(PlayerID, "plan", raid.name); this.unitCollection.updateEnt(ent); raid.unitCollection.updateEnt(ent); @@ -807,7 +814,7 @@ PETRA.AttackPlan.prototype.reassignFastUnit = function(gameState) } }; -PETRA.AttackPlan.prototype.chooseTarget = function(gameState) +AttackPlan.prototype.chooseTarget = function(gameState) { if (this.targetPlayer === undefined) { @@ -832,7 +839,7 @@ PETRA.AttackPlan.prototype.chooseTarget = function(gameState) this.targetPos = this.target.position(); // redefine a new rally point for this target if we have a base on the same land // find a new one on the pseudo-nearest base (dist weighted by the size of the island) - const targetIndex = PETRA.getLandAccess(gameState, this.target); + const targetIndex = getLandAccess(gameState, this.target); let rallyIndex = gameState.ai.accessibility.getAccessValue(this.rallyPoint); if (targetIndex != rallyIndex) { @@ -894,7 +901,7 @@ PETRA.AttackPlan.prototype.chooseTarget = function(gameState) /** * sameLand true means that we look for a target for which we do not need to take a transport */ -PETRA.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand) +AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand) { this.isBlocked = false; // Temporary variables needed by isValidTarget @@ -911,9 +918,9 @@ PETRA.AttackPlan.prototype.getNearestTarget = function(gameState, position, same } else { - if (this.type === PETRA.AttackPlan.TYPE_RAID) + if (this.type === AttackPlan.TYPE_RAID) targets = this.raidTargetFinder(gameState); - else if (this.type === PETRA.AttackPlan.TYPE_RUSH || this.type === PETRA.AttackPlan.TYPE_DEFAULT) + else if (this.type === AttackPlan.TYPE_RUSH || this.type === AttackPlan.TYPE_DEFAULT) { targets = this.rushTargetFinder(gameState, this.targetPlayer); if (!targets.hasEntities() && (this.hasSiegeUnits() || this.forced)) @@ -938,7 +945,7 @@ PETRA.AttackPlan.prototype.getNearestTarget = function(gameState, position, same continue; let dist = API3.SquareVectorDistance(ent.position(), position); // In normal attacks, disfavor fields - if (this.type !== PETRA.AttackPlan.TYPE_RUSH && this.type !== PETRA.AttackPlan.TYPE_RAID && ent.hasClass("Field")) + if (this.type !== AttackPlan.TYPE_RUSH && this.type !== AttackPlan.TYPE_RAID && ent.hasClass("Field")) dist += 100000; if (dist < minDist) { @@ -966,7 +973,7 @@ PETRA.AttackPlan.prototype.getNearestTarget = function(gameState, position, same * Default target finder aims for conquest critical targets * We must apply the *same* selection (isValidTarget) as done in getNearestTarget */ -PETRA.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy) +AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy) { let targets = new API3.EntityCollection(gameState.sharedScript); if (gameState.getVictoryConditions().has("wonder")) @@ -999,17 +1006,17 @@ PETRA.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy return targets; }; -PETRA.AttackPlan.prototype.isValidTarget = function(ent) +AttackPlan.prototype.isValidTarget = function(ent) { if (!ent.position()) return false; - if (this.sameLand && PETRA.getLandAccess(this.gameState, ent) != this.sameLand) + if (this.sameLand && getLandAccess(this.gameState, ent) != this.sameLand) return false; return !ent.decaying() || ent.getDefaultArrow() || ent.isGarrisonHolder() && ent.garrisoned().length; }; /** Rush target finder aims at isolated non-defended buildings */ -PETRA.AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy) +AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy) { let targets = new API3.EntityCollection(gameState.sharedScript); let buildings; @@ -1058,14 +1065,14 @@ PETRA.AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy) if (target) targets.addEnt(target); - if (!targets.hasEntities() && this.type === PETRA.AttackPlan.TYPE_RUSH && playerEnemy) + if (!targets.hasEntities() && this.type === AttackPlan.TYPE_RUSH && playerEnemy) targets = this.rushTargetFinder(gameState); return targets; }; /** Raid target finder aims at destructing foundations from which our defenseManager has attacked the builders */ -PETRA.AttackPlan.prototype.raidTargetFinder = function(gameState) +AttackPlan.prototype.raidTargetFinder = function(gameState) { const targets = new API3.EntityCollection(gameState.sharedScript); for (const targetId of gameState.ai.HQ.defenseManager.targetList) @@ -1082,9 +1089,9 @@ PETRA.AttackPlan.prototype.raidTargetFinder = function(gameState) * otherwise we may be blocked by walls and try to react accordingly * This is done only when attacker and target are on the same land */ -PETRA.AttackPlan.prototype.checkTargetObstruction = function(gameState, target, position) +AttackPlan.prototype.checkTargetObstruction = function(gameState, target, position) { - if (PETRA.getLandAccess(gameState, target) != gameState.ai.accessibility.getAccessValue(position)) + if (getLandAccess(gameState, target) != gameState.ai.accessibility.getAccessValue(position)) return target; const targetPos = target.position(); @@ -1188,10 +1195,10 @@ PETRA.AttackPlan.prototype.checkTargetObstruction = function(gameState, target, return target; }; -PETRA.AttackPlan.prototype.getPathToTarget = function(gameState, fixedRallyPoint = false) +AttackPlan.prototype.getPathToTarget = function(gameState, fixedRallyPoint = false) { const startAccess = gameState.ai.accessibility.getAccessValue(this.rallyPoint); - const endAccess = PETRA.getLandAccess(gameState, this.target); + const endAccess = getLandAccess(gameState, this.target); if (startAccess != endAccess) return false; @@ -1214,7 +1221,7 @@ PETRA.AttackPlan.prototype.getPathToTarget = function(gameState, fixedRallyPoint }; /** Set rally point at the border of our territory */ -PETRA.AttackPlan.prototype.setRallyPoint = function(gameState) +AttackPlan.prototype.setRallyPoint = function(gameState) { for (let i = 0; i < this.path.length; ++i) { @@ -1241,7 +1248,7 @@ PETRA.AttackPlan.prototype.setRallyPoint = function(gameState) * Executes the attack plan, after this is executed the update function will be run every turn * If we're here, it's because we have enough units. */ -PETRA.AttackPlan.prototype.StartAttack = function(gameState) +AttackPlan.prototype.StartAttack = function(gameState) { if (this.Config.debug > 1) API3.warn("start attack " + this.name + " with type " + this.type); @@ -1256,14 +1263,14 @@ PETRA.AttackPlan.prototype.StartAttack = function(gameState) for (const ent of this.unitCollection.values()) { - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_WALKING); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_WALKING); const stance = ent.isPackable() ? "standground" : "aggressive"; if (ent.getStance() != stance) ent.setStance(stance); } const rallyAccess = gameState.ai.accessibility.getAccessValue(this.rallyPoint); - const targetAccess = PETRA.getLandAccess(gameState, this.target); + const targetAccess = getLandAccess(gameState, this.target); if (rallyAccess == targetAccess) { if (!this.path) @@ -1289,7 +1296,7 @@ PETRA.AttackPlan.prototype.StartAttack = function(gameState) }; /** Runs every turn after the attack is executed */ -PETRA.AttackPlan.prototype.update = function(gameState, events) +AttackPlan.prototype.update = function(gameState, events) { if (!this.unitCollection.hasEntities()) return 0; @@ -1315,16 +1322,16 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) return 0; } - if (this.state === PETRA.AttackPlan.STATE_ARRIVED) + if (this.state === AttackPlan.STATE_ARRIVED) { // let's proceed on with whatever happens now. this.state = ""; this.startingAttack = true; this.unitCollection.forEach(ent => { ent.stopMoving(); - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_ATTACKING); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_ATTACKING); }); - if (this.type === PETRA.AttackPlan.TYPE_RUSH) // try to find a better target for rush + if (this.type === AttackPlan.TYPE_RUSH) // try to find a better target for rush { const newtarget = this.getNearestTarget(gameState, this.position); if (newtarget) @@ -1360,24 +1367,26 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) attackedByStructure[evt.target] = true; continue; } - if (PETRA.isSiegeUnit(ourUnit)) + if (isSiegeUnit(ourUnit)) { // if our siege units are attacked, we'll send some units to deal with enemies. const collec = this.unitCollection.filter(API3.Filters.not(API3.Filters.byClass("Siege"))).filterNearest(ourUnit.position(), 5); for (const ent of collec.values()) { - if (PETRA.isSiegeUnit(ent)) // needed as mauryan elephants are not filtered out + if (isSiegeUnit(ent)) // needed as mauryan elephants are not filtered out continue; - const allowCapture = PETRA.allowCapture(gameState, ent, attacker); - if (!ent.canAttackTarget(attacker, allowCapture)) + const shouldCapture = allowCapture(gameState, ent, attacker); + if (!ent.canAttackTarget(attacker, shouldCapture)) continue; - ent.attack(attacker.id(), allowCapture); + ent.attack(attacker.id(), shouldCapture); ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); } // And if this attacker is a non-ranged siege unit and our unit also, attack it - if (PETRA.isSiegeUnit(attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee") && ourUnit.canAttackTarget(attacker, PETRA.allowCapture(gameState, ourUnit, attacker))) + if (isSiegeUnit(attacker) && attacker.hasClass("Melee") && + ourUnit.hasClass("Melee") && ourUnit.canAttackTarget(attacker, + allowCapture(gameState, ourUnit, attacker))) { - ourUnit.attack(attacker.id(), PETRA.allowCapture(gameState, ourUnit, attacker)); + ourUnit.attack(attacker.id(), allowCapture(gameState, ourUnit, attacker)); ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); } } @@ -1389,15 +1398,15 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) // TODO check that the attacker is from behind the wall continue; } - else if (PETRA.isSiegeUnit(attacker)) + else if (isSiegeUnit(attacker)) { // if our unit is attacked by a siege unit, we'll send some melee units to help it. const collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5); for (const ent of collec.values()) { - const allowCapture = PETRA.allowCapture(gameState, ent, attacker); - if (!ent.canAttackTarget(attacker, allowCapture)) + const shouldCapture = allowCapture(gameState, ent, attacker); + if (!ent.canAttackTarget(attacker, shouldCapture)) continue; - ent.attack(attacker.id(), allowCapture); + ent.attack(attacker.id(), shouldCapture); ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); } } @@ -1407,8 +1416,8 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) const collec = this.unitCollection.filterNearest(ourUnit.position(), 2); for (const ent of collec.values()) { - const allowCapture = PETRA.allowCapture(gameState, ent, attacker); - if (PETRA.isSiegeUnit(ent) || !ent.canAttackTarget(attacker, allowCapture)) + const shouldCapture = allowCapture(gameState, ent, attacker); + if (isSiegeUnit(ent) || !ent.canAttackTarget(attacker, shouldCapture)) continue; const orderData = ent.unitAIOrderData(); if (orderData && orderData.length && orderData[0].target) @@ -1419,7 +1428,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) if (target && !target.hasClasses(["Structure", "Support"])) continue; } - ent.attack(attacker.id(), allowCapture); + ent.attack(attacker.id(), shouldCapture); ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); } // Then the unit under attack: abandon its target (if it was a structure or a support) and retaliate @@ -1436,10 +1445,10 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) continue; } } - const allowCapture = PETRA.allowCapture(gameState, ourUnit, attacker); - if (ourUnit.canAttackTarget(attacker, allowCapture)) + const shouldCapture = allowCapture(gameState, ourUnit, attacker); + if (ourUnit.canAttackTarget(attacker, shouldCapture)) { - ourUnit.attack(attacker.id(), allowCapture); + ourUnit.attack(attacker.id(), shouldCapture); ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); } } @@ -1464,7 +1473,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) continue; if (!(targetId in unitTargets)) { - if (PETRA.isSiegeUnit(target) || target.hasClass("Hero")) + if (isSiegeUnit(target) || target.hasClass("Hero")) unitTargets[targetId] = -8; else if (target.hasClasses(["Champion", "Ship"])) unitTargets[targetId] = -5; @@ -1480,7 +1489,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) let targetClassesUnit; let targetClassesSiege; - if (this.type === PETRA.AttackPlan.TYPE_RUSH) + if (this.type === AttackPlan.TYPE_RUSH) targetClassesUnit = { "attack": ["Unit", "Structure"], "avoid": ["Palisade", "Wall", "Tower", "Fortress"], "vetoEntities": veto }; else { @@ -1525,7 +1534,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) // update the order if needed let needsUpdate = false; let maybeUpdate = false; - const siegeUnit = PETRA.isSiegeUnit(ent); + const siegeUnit = isSiegeUnit(ent); if (ent.isIdle()) needsUpdate = true; else if (siegeUnit && targetId) @@ -1587,18 +1596,18 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) else if (ent.hasClass("FastMoving")) range += 30; range *= range; - const entAccess = PETRA.getLandAccess(gameState, ent); + const entAccess = getLandAccess(gameState, ent); // Checking for gates if we're a siege unit. if (siegeUnit) { const mStruct = enemyStructures.filter(enemy => { - if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy))) + if (!enemy.position() || !ent.canAttackTarget(enemy, allowCapture(gameState, ent, enemy))) return false; if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range) return false; if (enemy.foundationProgress() == 0) return false; - if (PETRA.getLandAccess(gameState, enemy) != entAccess) + if (getLandAccess(gameState, enemy) != entAccess) return false; return true; }).toEntityArray(); @@ -1622,11 +1631,11 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) return valb - vala; }); if (mStruct[0].hasClass("Gate")) - ent.attack(mStruct[0].id(), PETRA.allowCapture(gameState, ent, mStruct[0])); + ent.attack(mStruct[0].id(), allowCapture(gameState, ent, mStruct[0])); else { const rand = randIntExclusive(0, mStruct.length * 0.2); - ent.attack(mStruct[rand].id(), PETRA.allowCapture(gameState, ent, mStruct[rand])); + ent.attack(mStruct[rand].id(), allowCapture(gameState, ent, mStruct[rand])); } } else @@ -1644,7 +1653,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) { const nearby = !ent.hasClasses(["FastMoving", "Ranged"]); const mUnit = enemyUnits.filter(enemy => { - if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy))) + if (!enemy.position() || !ent.canAttackTarget(enemy, allowCapture(gameState, ent, enemy))) return false; if (enemy.hasClass("Animal")) return false; @@ -1653,7 +1662,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) const dist = API3.SquareVectorDistance(enemy.position(), ent.position()); if (dist > range) return false; - if (PETRA.getLandAccess(gameState, enemy) != entAccess) + if (getLandAccess(gameState, enemy) != entAccess) return false; // if already too much units targeting this enemy, let's continue towards our main target if (veto[enemy.id()] && API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500) @@ -1684,7 +1693,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) return valb - vala; }); const rand = randIntExclusive(0, mUnit.length * 0.1); - ent.attack(mUnit[rand].id(), PETRA.allowCapture(gameState, ent, mUnit[rand])); + ent.attack(mUnit[rand].id(), allowCapture(gameState, ent, mUnit[rand])); } // This may prove dangerous as we may be blocked by something we // cannot attack. See similar behaviour at #5741. @@ -1709,11 +1718,11 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) const mStruct = enemyStructures.filter(enemy => { if (this.isBlocked && enemy.id() != this.target.id()) return false; - if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy))) + if (!enemy.position() || !ent.canAttackTarget(enemy, allowCapture(gameState, ent, enemy))) return false; if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range) return false; - if (PETRA.getLandAccess(gameState, enemy) != entAccess) + if (getLandAccess(gameState, enemy) != entAccess) return false; return true; }, this).toEntityArray(); @@ -1737,7 +1746,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) else { const rand = randIntExclusive(0, mStruct.length * 0.2); - ent.attack(mStruct[rand].id(), PETRA.allowCapture(gameState, ent, mStruct[rand])); + ent.attack(mStruct[rand].id(), allowCapture(gameState, ent, mStruct[rand])); } } else if (needsUpdate) // really nothing let's try to help our nearest unit @@ -1757,12 +1766,12 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) if (dist > distmin) return; distmin = dist; - if (!ent.canAttackTarget(target, PETRA.allowCapture(gameState, ent, target))) + if (!ent.canAttackTarget(target, allowCapture(gameState, ent, target))) return; attacker = target; }); if (attacker) - ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker)); + ent.attack(attacker.id(), allowCapture(gameState, ent, attacker)); } } } @@ -1780,7 +1789,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events) return this.unitCollection.length; }; -PETRA.AttackPlan.prototype.UpdateTransporting = function(gameState, events) +AttackPlan.prototype.UpdateTransporting = function(gameState, events) { let done = true; for (const ent of this.unitCollection.values()) @@ -1797,7 +1806,7 @@ PETRA.AttackPlan.prototype.UpdateTransporting = function(gameState, events) if (done) { - this.state = PETRA.AttackPlan.STATE_ARRIVED; + this.state = AttackPlan.STATE_ARRIVED; return; } @@ -1814,16 +1823,16 @@ PETRA.AttackPlan.prototype.UpdateTransporting = function(gameState, events) if (ent.getMetadata(PlayerID, "transport") !== undefined) continue; - const allowCapture = PETRA.allowCapture(gameState, ent, attacker); - if (!ent.isIdle() || !ent.canAttackTarget(attacker, allowCapture)) + const shouldCapture = allowCapture(gameState, ent, attacker); + if (!ent.isIdle() || !ent.canAttackTarget(attacker, shouldCapture)) continue; - ent.attack(attacker.id(), allowCapture); + ent.attack(attacker.id(), shouldCapture); } break; } }; -PETRA.AttackPlan.prototype.UpdateWalking = function(gameState, events) +AttackPlan.prototype.UpdateWalking = function(gameState, events) { // we're marching towards the target // Let's check if any of our unit has been attacked. @@ -1848,7 +1857,7 @@ PETRA.AttackPlan.prototype.UpdateWalking = function(gameState, events) { if (gameState.ai.HQ.territoryMap.getOwner(this.position) === this.targetPlayer || attackedNB > 3) { - this.state = PETRA.AttackPlan.STATE_ARRIVED; + this.state = AttackPlan.STATE_ARRIVED; return true; } } @@ -1888,7 +1897,7 @@ PETRA.AttackPlan.prototype.UpdateWalking = function(gameState, events) { if (this.Config.debug > 1) API3.warn("Attack Plan " + this.type + " " + this.name + " has met walls and is not happy."); - this.state = PETRA.AttackPlan.STATE_ARRIVED; + this.state = AttackPlan.STATE_ARRIVED; return true; } // abort plan @@ -1906,7 +1915,7 @@ PETRA.AttackPlan.prototype.UpdateWalking = function(gameState, events) { if (this.Config.debug > 1) API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination."); - this.state = PETRA.AttackPlan.STATE_ARRIVED; + this.state = AttackPlan.STATE_ARRIVED; return true; } else if (this.path.length && API3.SquareVectorDistance(this.position, this.path[0]) < 1600) @@ -1918,7 +1927,7 @@ PETRA.AttackPlan.prototype.UpdateWalking = function(gameState, events) { if (this.Config.debug > 1) API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination."); - this.state = PETRA.AttackPlan.STATE_ARRIVED; + this.state = AttackPlan.STATE_ARRIVED; return true; } } @@ -1926,7 +1935,7 @@ PETRA.AttackPlan.prototype.UpdateWalking = function(gameState, events) return true; }; -PETRA.AttackPlan.prototype.UpdateTarget = function(gameState) +AttackPlan.prototype.UpdateTarget = function(gameState) { // First update the target position in case it's a unit (and check if it has garrisoned) if (this.target && this.target.hasClass("Unit")) @@ -1934,7 +1943,7 @@ PETRA.AttackPlan.prototype.UpdateTarget = function(gameState) this.targetPos = this.target.position(); if (!this.targetPos) { - const holder = PETRA.getHolder(gameState, this.target); + const holder = getHolder(gameState, this.target); if (holder && gameState.isPlayerEnemy(holder.owner())) { this.target = holder; @@ -1979,7 +1988,7 @@ PETRA.AttackPlan.prototype.UpdateTarget = function(gameState) if (!attack.target || !gameState.getEntityById(attack.target.id()) || !gameState.isPlayerEnemy(attack.target.owner())) continue; - if (accessIndex != PETRA.getLandAccess(gameState, attack.target)) + if (accessIndex != getLandAccess(gameState, attack.target)) continue; if (attack.target.owner() == 0 && attack.targetPlayer != 0) // looks like it has resigned continue; @@ -2014,7 +2023,7 @@ PETRA.AttackPlan.prototype.UpdateTarget = function(gameState) }; /** reset any units */ -PETRA.AttackPlan.prototype.Abort = function(gameState) +AttackPlan.prototype.Abort = function(gameState) { this.unitCollection.unregister(); if (this.unitCollection.hasEntities()) @@ -2035,7 +2044,7 @@ PETRA.AttackPlan.prototype.Abort = function(gameState) { if (!base.anchor || !base.anchor.position()) continue; - if (PETRA.getLandAccess(gameState, base.anchor) != access) + if (getLandAccess(gameState, base.anchor) != access) continue; const newdist = API3.SquareVectorDistance(this.position, base.anchor.position()); if (newdist > dist) @@ -2047,7 +2056,7 @@ PETRA.AttackPlan.prototype.Abort = function(gameState) for (const ent of this.unitCollection.values()) { - if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_ATTACK) + if (ent.getMetadata(PlayerID, "role") === Worker.ROLE_ATTACK) ent.stopMoving(); if (rallyPoint) ent.moveToRange(rallyPoint[0], rallyPoint[1], 0, 15); @@ -2061,12 +2070,12 @@ PETRA.AttackPlan.prototype.Abort = function(gameState) this.removeQueues(gameState); }; -PETRA.AttackPlan.prototype.removeUnit = function(ent, update) +AttackPlan.prototype.removeUnit = function(ent, update) { - if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_ATTACK) + if (ent.getMetadata(PlayerID, "role") === Worker.ROLE_ATTACK) { if (ent.hasClass("CitizenSoldier")) - ent.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_WORKER); + ent.setMetadata(PlayerID, "role", Worker.ROLE_WORKER); else ent.setMetadata(PlayerID, "role", undefined); ent.setMetadata(PlayerID, "subrole", undefined); @@ -2076,13 +2085,13 @@ PETRA.AttackPlan.prototype.removeUnit = function(ent, update) this.unitCollection.updateEnt(ent); }; -PETRA.AttackPlan.prototype.checkEvents = function(gameState, events) +AttackPlan.prototype.checkEvents = function(gameState, events) { for (const evt of events.EntityRenamed) { if (!this.target || this.target.id() != evt.entity) continue; - if (this.type === PETRA.AttackPlan.TYPE_RAID && !this.isStarted()) + if (this.type === AttackPlan.TYPE_RAID && !this.isStarted()) this.target = undefined; else this.target = gameState.getEntityById(evt.newentity); @@ -2102,7 +2111,7 @@ PETRA.AttackPlan.prototype.checkEvents = function(gameState, events) this.target = undefined; } - if (!this.overseas || this.state !== PETRA.AttackPlan.STATE_UNEXECUTED) + if (!this.overseas || this.state !== AttackPlan.STATE_UNEXECUTED) return; // let's check if an enemy has built a structure at our access for (const evt of events.Create) @@ -2112,7 +2121,7 @@ PETRA.AttackPlan.prototype.checkEvents = function(gameState, events) continue; if (!gameState.isPlayerEnemy(ent.owner())) continue; - const access = PETRA.getLandAccess(gameState, ent); + const access = getLandAccess(gameState, ent); for (const base of gameState.ai.HQ.baseManagers()) { if (!base.anchor || !base.anchor.position()) @@ -2125,7 +2134,7 @@ PETRA.AttackPlan.prototype.checkEvents = function(gameState, events) } }; -PETRA.AttackPlan.prototype.waitingForTransport = function() +AttackPlan.prototype.waitingForTransport = function() { for (const ent of this.unitCollection.values()) if (ent.getMetadata(PlayerID, "transport") !== undefined) @@ -2133,15 +2142,15 @@ PETRA.AttackPlan.prototype.waitingForTransport = function() return false; }; -PETRA.AttackPlan.prototype.hasSiegeUnits = function() +AttackPlan.prototype.hasSiegeUnits = function() { for (const ent of this.unitCollection.values()) - if (PETRA.isSiegeUnit(ent)) + if (isSiegeUnit(ent)) return true; return false; }; -PETRA.AttackPlan.prototype.hasForceOrder = function(data, value) +AttackPlan.prototype.hasForceOrder = function(data, value) { for (const ent of this.unitCollection.values()) { @@ -2159,15 +2168,15 @@ PETRA.AttackPlan.prototype.hasForceOrder = function(data, value) * The center position of this attack may be in an inaccessible area. So we use the access * of the unit nearest to this center position. */ -PETRA.AttackPlan.prototype.getAttackAccess = function(gameState) +AttackPlan.prototype.getAttackAccess = function(gameState) { for (const ent of this.unitCollection.filterNearest(this.position, 1).values()) - return PETRA.getLandAccess(gameState, ent); + return getLandAccess(gameState, ent); return 0; }; -PETRA.AttackPlan.prototype.debugAttack = function() +AttackPlan.prototype.debugAttack = function() { API3.warn("---------- attack " + this.name); for (const unitCat in this.unitStat) @@ -2178,7 +2187,7 @@ PETRA.AttackPlan.prototype.debugAttack = function() API3.warn("------------------------------"); }; -PETRA.AttackPlan.prototype.Serialize = function() +AttackPlan.prototype.Serialize = function() { const properties = { "name": this.name, @@ -2206,7 +2215,7 @@ PETRA.AttackPlan.prototype.Serialize = function() return { "properties": properties }; }; -PETRA.AttackPlan.prototype.Deserialize = function(gameState, data) +AttackPlan.prototype.Deserialize = function(gameState, data) { for (const key in data.properties) this[key] = data.properties[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/baseManager.js b/binaries/data/mods/public/simulation/ai/petra/baseManager.js index 1acb382d57..9d09e83816 100644 --- a/binaries/data/mods/public/simulation/ai/petra/baseManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/baseManager.js @@ -1,3 +1,10 @@ +import { Config, DIFFICULTY_EASY, DIFFICULTY_MEDIUM } from "simulation/ai/petra/config.js"; +import { getBestBase, getBuiltEntity, getLandAccess, isFastMoving, isNotWorthBuilding } from + "simulation/ai/petra/entityExtend.js"; +import { createObstructionMap } from "simulation/ai/petra/mapModule.js"; +import { ConstructionPlan } from "simulation/ai/petra/queueplanBuilding.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + /** * Base Manager * Handles lower level economic stuffs. @@ -10,7 +17,7 @@ * -updating whatever needs updating, keeping track of stuffs (rebuilding needs…) */ -PETRA.BaseManager = function(gameState, basesManager) +export function BaseManager(gameState, basesManager) { this.Config = basesManager.Config; this.ID = gameState.ai.uniqueIDs.bases++; @@ -27,42 +34,42 @@ PETRA.BaseManager = function(gameState, basesManager) this.constructing = false; // Defenders to train in this cc when its construction is finished - this.neededDefenders = this.Config.difficulty > PETRA.DIFFICULTY_EASY ? 3 + 2*(this.Config.difficulty - 3) : 0; + this.neededDefenders = this.Config.difficulty > DIFFICULTY_EASY ? 3 + 2*(this.Config.difficulty - 3) : 0; // vector for iterating, to check one use the HQ map. this.territoryIndices = []; this.timeNextIdleCheck = 0; -}; +} -PETRA.BaseManager.STATE_WITH_ANCHOR = "anchored"; +BaseManager.STATE_WITH_ANCHOR = "anchored"; /** * New base with a foundation anchor. */ -PETRA.BaseManager.STATE_UNCONSTRUCTED = "unconstructed"; +BaseManager.STATE_UNCONSTRUCTED = "unconstructed"; /** * Captured base with an anchor. */ -PETRA.BaseManager.STATE_CAPTURED = "captured"; +BaseManager.STATE_CAPTURED = "captured"; /** * Anchorless base, currently with dock. */ -PETRA.BaseManager.STATE_ANCHORLESS = "anchorless"; +BaseManager.STATE_ANCHORLESS = "anchorless"; -PETRA.BaseManager.prototype.init = function(gameState, state) +BaseManager.prototype.init = function(gameState, state) { - if (state === PETRA.BaseManager.STATE_UNCONSTRUCTED) + if (state === BaseManager.STATE_UNCONSTRUCTED) this.constructing = true; - else if (state !== PETRA.BaseManager.STATE_CAPTURED) + else if (state !== BaseManager.STATE_CAPTURED) this.neededDefenders = 0; - this.workerObject = new PETRA.Worker(this); + this.workerObject = new Worker(this); // entitycollections this.units = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "base", this.ID)); - this.workers = this.units.filter(API3.Filters.byMetadata(PlayerID, "role", PETRA.Worker.ROLE_WORKER)); + this.workers = this.units.filter(API3.Filters.byMetadata(PlayerID, "role", Worker.ROLE_WORKER)); this.buildings = gameState.getOwnStructures().filter(API3.Filters.byMetadata(PlayerID, "base", this.ID)); this.mobileDropsites = this.units.filter(API3.Filters.isDropsite()); @@ -82,19 +89,19 @@ PETRA.BaseManager.prototype.init = function(gameState, state) } }; -PETRA.BaseManager.prototype.reset = function(gameState, state) +BaseManager.prototype.reset = function(gameState, state) { - if (state === PETRA.BaseManager.STATE_UNCONSTRUCTED) + if (state === BaseManager.STATE_UNCONSTRUCTED) this.constructing = true; else this.constructing = false; - if (state !== PETRA.BaseManager.STATE_CAPTURED || this.Config.difficulty < PETRA.DIFFICULTY_MEDIUM) + if (state !== BaseManager.STATE_CAPTURED || this.Config.difficulty < DIFFICULTY_MEDIUM) this.neededDefenders = 0; else this.neededDefenders = 3 + 2 * (this.Config.difficulty - 3); }; -PETRA.BaseManager.prototype.assignEntity = function(gameState, ent) +BaseManager.prototype.assignEntity = function(gameState, ent) { ent.setMetadata(PlayerID, "base", this.ID); this.units.updateEnt(ent); @@ -104,7 +111,7 @@ PETRA.BaseManager.prototype.assignEntity = function(gameState, ent) this.assignResourceToDropsite(gameState, ent); }; -PETRA.BaseManager.prototype.setAnchor = function(gameState, anchorEntity) +BaseManager.prototype.setAnchor = function(gameState, anchorEntity) { if (!anchorEntity.hasClass("CivCentre")) API3.warn("Error: Petra base " + this.ID + " has been assigned " + ent.templateName() + " as anchor."); @@ -117,12 +124,12 @@ PETRA.BaseManager.prototype.setAnchor = function(gameState, anchorEntity) } anchorEntity.setMetadata(PlayerID, "base", this.ID); this.buildings.updateEnt(anchorEntity); - this.accessIndex = PETRA.getLandAccess(gameState, anchorEntity); + this.accessIndex = getLandAccess(gameState, anchorEntity); return true; }; /* we lost our anchor. Let's reassign our units and buildings */ -PETRA.BaseManager.prototype.anchorLost = function(gameState, ent) +BaseManager.prototype.anchorLost = function(gameState, ent) { this.anchor = undefined; this.anchorId = undefined; @@ -131,17 +138,18 @@ PETRA.BaseManager.prototype.anchorLost = function(gameState, ent) }; /** Set a building of an anchorless base */ -PETRA.BaseManager.prototype.setAnchorlessEntity = function(gameState, ent) +BaseManager.prototype.setAnchorlessEntity = function(gameState, ent) { if (!this.buildings.hasEntities()) { - if (!PETRA.getBuiltEntity(gameState, ent).resourceDropsiteTypes()) + if (!getBuiltEntity(gameState, ent).resourceDropsiteTypes()) API3.warn("Error: Petra base " + this.ID + " has been assigned " + ent.templateName() + " as origin."); - this.accessIndex = PETRA.getLandAccess(gameState, ent); + this.accessIndex = getLandAccess(gameState, ent); } - else if (this.accessIndex !== PETRA.getLandAccess(gameState, ent)) + else if (this.accessIndex !== getLandAccess(gameState, ent)) API3.warn(" Error: Petra base " + this.ID + " with access " + this.accessIndex + - " has been assigned " + ent.templateName() + " with access" + PETRA.getLandAccess(gameState, ent)); + " has been assigned " + ent.templateName() + " with access" + + getLandAccess(gameState, ent)); ent.setMetadata(PlayerID, "base", this.ID); this.buildings.updateEnt(ent); @@ -152,7 +160,7 @@ PETRA.BaseManager.prototype.setAnchorlessEntity = function(gameState, ent) * Assign the resources around the dropsites of this basis in three areas according to distance, and sort them in each area. * Moving resources (animals) and buildable resources (fields) are treated elsewhere. */ -PETRA.BaseManager.prototype.assignResourceToDropsite = function(gameState, dropsite) +BaseManager.prototype.assignResourceToDropsite = function(gameState, dropsite) { if (this.dropsites[dropsite.id()]) { @@ -167,7 +175,7 @@ PETRA.BaseManager.prototype.assignResourceToDropsite = function(gameState, drops this.dropsites[dropsiteId] = true; if (this.ID == this.basesManager.baselessBase().ID) - accessIndex = PETRA.getLandAccess(gameState, dropsite); + accessIndex = getLandAccess(gameState, dropsite); const maxDistResourceSquare = this.maxDistResourceSquare; for (const type of dropsite.resourceDropsiteTypes()) @@ -188,7 +196,7 @@ PETRA.BaseManager.prototype.assignResourceToDropsite = function(gameState, drops if (supply.hasClasses(["Animal", "Field"])) return; // quick accessibility check - if (PETRA.getLandAccess(gameState, supply) != accessIndex) + if (getLandAccess(gameState, supply) != accessIndex) return; const dist = API3.SquareVectorDistance(supply.position(), dropsitePos); @@ -233,7 +241,7 @@ PETRA.BaseManager.prototype.assignResourceToDropsite = function(gameState, drops }); }; -PETRA.BaseManager.prototype.removeFromAssignedDropsite = function(ent) +BaseManager.prototype.removeFromAssignedDropsite = function(ent) { for (const type in this.dropsiteSupplies) for (const proxim in this.dropsiteSupplies[type]) @@ -246,7 +254,7 @@ PETRA.BaseManager.prototype.removeFromAssignedDropsite = function(ent) }; // completely remove the dropsite resources from our list. -PETRA.BaseManager.prototype.removeDropsite = function(gameState, ent) +BaseManager.prototype.removeDropsite = function(gameState, ent) { if (!ent.id()) return; @@ -277,7 +285,7 @@ PETRA.BaseManager.prototype.removeDropsite = function(gameState, ent) * @return {Object} - The position of the best place to build a new dropsite for the specified resource, * its quality and its template name. */ -PETRA.BaseManager.prototype.findBestDropsiteAndLocation = function(gameState, resource) +BaseManager.prototype.findBestDropsiteAndLocation = function(gameState, resource) { let bestResult = { "quality": 0, @@ -297,7 +305,7 @@ PETRA.BaseManager.prototype.findBestDropsiteAndLocation = function(gameState, re /** * Returns the position of the best place to build a new dropsite for the specified resource and dropsite template. */ -PETRA.BaseManager.prototype.findBestDropsiteLocation = function(gameState, resource, templateName) +BaseManager.prototype.findBestDropsiteLocation = function(gameState, resource, templateName) { const template = gameState.getTemplate(gameState.applyCiv(templateName)); @@ -316,13 +324,13 @@ PETRA.BaseManager.prototype.findBestDropsiteLocation = function(gameState, resou // Then checks for a good spot in the territory. If none, and town/city phase, checks outside // The AI will currently not build a CC if it wouldn't connect with an existing CC. - const obstructions = PETRA.createObstructionMap(gameState, this.accessIndex, template); + const obstructions = createObstructionMap(gameState, this.accessIndex, template); const dpEnts = gameState.getOwnStructures().filter(API3.Filters.isDropsite(resource)).toEntityArray(); // Foundations don't have the dropsite properties yet, so treat them separately. for (const foundation of gameState.getOwnFoundations().toEntityArray()) - if (PETRA.getBuiltEntity(gameState, foundation).isResourceDropsite(resource)) + if (getBuiltEntity(gameState, foundation).isResourceDropsite(resource)) dpEnts.push(foundation); let bestIdx; @@ -387,7 +395,7 @@ PETRA.BaseManager.prototype.findBestDropsiteLocation = function(gameState, resou return { "quality": bestVal, "pos": [x, z] }; }; -PETRA.BaseManager.prototype.getResourceLevel = function(gameState, type, distances = ["nearby", "medium", "faraway"]) +BaseManager.prototype.getResourceLevel = function(gameState, type, distances = ["nearby", "medium", "faraway"]) { let count = 0; const check = {}; @@ -403,7 +411,7 @@ PETRA.BaseManager.prototype.getResourceLevel = function(gameState, type, distanc }; /** check our resource levels and react accordingly */ -PETRA.BaseManager.prototype.checkResourceLevels = function(gameState, queues) +BaseManager.prototype.checkResourceLevels = function(gameState, queues) { for (const type of Resources.GetCodes()) { @@ -423,7 +431,8 @@ PETRA.BaseManager.prototype.checkResourceLevels = function(gameState, queues) { if (count < 600) { - queues.field.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/field", { "favoredBase": this.ID })); + queues.field.addPlan(new ConstructionPlan(gameState, + "structures/{civ}/field", { "favoredBase": this.ID })); gameState.ai.HQ.needFarm = true; } } @@ -434,11 +443,19 @@ PETRA.BaseManager.prototype.checkResourceLevels = function(gameState, queues) if (gameState.ai.HQ.saveResources || gameState.ai.HQ.saveSpace || count > 300 || numFarms > 5) goal = Math.max(goal-1, 1); if (numFound + numQueue < goal) - queues.field.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/field", { "favoredBase": this.ID })); + { + queues.field.addPlan(new ConstructionPlan(gameState, + "structures/{civ}/field", { "favoredBase": this.ID })); + } + } + else if (gameState.ai.HQ.needCorral && + !gameState.getOwnEntitiesByClass("Corral", true).hasEntities() && + !queues.corral.hasQueuedUnits() && + gameState.ai.HQ.canBuild(gameState, "structures/{civ}/corral")) + { + queues.corral.addPlan(new ConstructionPlan(gameState, + "structures/{civ}/corral", { "favoredBase": this.ID })); } - else if (gameState.ai.HQ.needCorral && !gameState.getOwnEntitiesByClass("Corral", true).hasEntities() && - !queues.corral.hasQueuedUnits() && gameState.ai.HQ.canBuild(gameState, "structures/{civ}/corral")) - queues.corral.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/corral", { "favoredBase": this.ID })); continue; } if (!gameState.getOwnEntitiesByClass("Corral", true).hasEntities() && @@ -447,7 +464,8 @@ PETRA.BaseManager.prototype.checkResourceLevels = function(gameState, queues) const count = this.getResourceLevel(gameState, type, prox); // animals are not accounted if (count < 900) { - queues.corral.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/corral", { "favoredBase": this.ID })); + queues.corral.addPlan(new ConstructionPlan(gameState, + "structures/{civ}/corral", { "favoredBase": this.ID })); gameState.ai.HQ.needCorral = true; } } @@ -480,15 +498,22 @@ PETRA.BaseManager.prototype.checkResourceLevels = function(gameState, queues) { const newDP = this.findBestDropsiteAndLocation(gameState, type); if (newDP.quality > 50 && gameState.ai.HQ.canBuild(gameState, newDP.templateName)) - queues.dropsites.addPlan(new PETRA.ConstructionPlan(gameState, newDP.templateName, { "base": this.ID, "type": type }, newDP.pos)); + { + queues.dropsites.addPlan(new ConstructionPlan(gameState, newDP.templateName, + { "base": this.ID, "type": type }, newDP.pos)); + } else if (!gameState.getOwnFoundations().filter(API3.Filters.byClass("CivCentre")).hasEntities() && !queues.civilCentre.hasQueuedUnits()) { // No good dropsite, try to build a new base if no base already planned, // and if not possible, be less strict on dropsite quality. if ((!gameState.ai.HQ.canExpand || !gameState.ai.HQ.buildNewBase(gameState, queues, type)) && - newDP.quality > Math.min(25, 50*0.15/ratio) && - gameState.ai.HQ.canBuild(gameState, newDP.templateName)) - queues.dropsites.addPlan(new PETRA.ConstructionPlan(gameState, newDP.templateName, { "base": this.ID, "type": type }, newDP.pos)); + newDP.quality > Math.min(25, 50*0.15/ratio) && + gameState.ai.HQ.canBuild(gameState, newDP.templateName)) + { + queues.dropsites.addPlan(new ConstructionPlan(gameState, + newDP.templateName, { "base": this.ID, "type": type }, + newDP.pos)); + } } } this.gatherers[type].nextCheck = gameState.ai.playedTurn + 20; @@ -502,7 +527,7 @@ PETRA.BaseManager.prototype.checkResourceLevels = function(gameState, queues) }; /** Adds the estimated gather rates from this base to the currentRates */ -PETRA.BaseManager.prototype.addGatherRates = function(gameState, currentRates) +BaseManager.prototype.addGatherRates = function(gameState, currentRates) { for (const res in currentRates) { @@ -521,14 +546,14 @@ PETRA.BaseManager.prototype.addGatherRates = function(gameState, currentRates) }); if (res == "food") { - this.workersBySubrole(gameState, PETRA.Worker.SUBROLE_HUNTER).forEach(ent => { + this.workersBySubrole(gameState, Worker.SUBROLE_HUNTER).forEach(ent => { if (ent.isIdle() || !ent.position()) return; const gRate = ent.currentGatherRate(); if (gRate) currentRates[res] += Math.log(1+gRate)/1.1; }); - this.workersBySubrole(gameState, PETRA.Worker.SUBROLE_FISHER).forEach(ent => { + this.workersBySubrole(gameState, Worker.SUBROLE_FISHER).forEach(ent => { if (ent.isIdle() || !ent.position()) return; const gRate = ent.currentGatherRate(); @@ -539,7 +564,7 @@ PETRA.BaseManager.prototype.addGatherRates = function(gameState, currentRates) } }; -PETRA.BaseManager.prototype.assignRolelessUnits = function(gameState, roleless) +BaseManager.prototype.assignRolelessUnits = function(gameState, roleless) { if (!roleless) roleless = this.units.filter(API3.Filters.not(API3.Filters.byHasMetadata(PlayerID, "role"))).values(); @@ -547,9 +572,9 @@ PETRA.BaseManager.prototype.assignRolelessUnits = function(gameState, roleless) for (const ent of roleless) { if (ent.hasClasses(["Worker", "CitizenSoldier", "FishingBoat"])) - ent.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_WORKER); + ent.setMetadata(PlayerID, "role", Worker.ROLE_WORKER); else if (ent.hasClass("Support") && ent.hasClass("Elephant")) - ent.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_WORKER); + ent.setMetadata(PlayerID, "role", Worker.ROLE_WORKER); } }; @@ -558,7 +583,7 @@ PETRA.BaseManager.prototype.assignRolelessUnits = function(gameState, roleless) * they can be reassigned by reassignIdleWorkers. * TODO: actually this probably should be in the HQ. */ -PETRA.BaseManager.prototype.setWorkersIdleByPriority = function(gameState) +BaseManager.prototype.setWorkersIdleByPriority = function(gameState) { this.timeNextIdleCheck = gameState.ai.elapsedTime + 8; // change resource only towards one which is more needed, and if changing will not change this order @@ -614,7 +639,7 @@ PETRA.BaseManager.prototype.setWorkersIdleByPriority = function(gameState) * and return remaining number of possible switches. * Prefer FemaleCitizen for food and CitizenSoldier for other resources. */ -PETRA.BaseManager.prototype.switchGatherer = function(gameState, from, to, number) +BaseManager.prototype.switchGatherer = function(gameState, from, to, number) { let num = number; let only; @@ -640,12 +665,12 @@ PETRA.BaseManager.prototype.switchGatherer = function(gameState, from, to, numbe return num; }; -PETRA.BaseManager.prototype.reassignIdleWorkers = function(gameState, idleWorkers) +BaseManager.prototype.reassignIdleWorkers = function(gameState, idleWorkers) { // Search for idle workers, and tell them to gather resources based on demand if (!idleWorkers) { - const filter = API3.Filters.byMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_IDLE); + const filter = API3.Filters.byMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE); idleWorkers = gameState.updatingCollection("idle-workers-base-" + this.ID, filter, this.workers).values(); } @@ -657,7 +682,7 @@ PETRA.BaseManager.prototype.reassignIdleWorkers = function(gameState, idleWorker // Support elephant can only be builders if (ent.hasClass("Support") && ent.hasClass("Elephant")) { - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_IDLE); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE); continue; } @@ -679,35 +704,37 @@ PETRA.BaseManager.prototype.reassignIdleWorkers = function(gameState, idleWorker continue; if (needed.type != "food" && this.basesManager.isResourceExhausted(needed.type)) continue; - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_GATHERER); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_GATHERER); ent.setMetadata(PlayerID, "gather-type", needed.type); this.basesManager.AddTCResGatherer(needed.type); break; } } } - else if (PETRA.isFastMoving(ent) && ent.canGather("food") && ent.canAttackClass("Animal")) - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_HUNTER); + else if (isFastMoving(ent) && ent.canGather("food") && ent.canAttackClass("Animal")) + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_HUNTER); else if (ent.hasClass("FishingBoat")) - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_FISHER); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_FISHER); } }; -PETRA.BaseManager.prototype.workersBySubrole = function(gameState, subrole) +BaseManager.prototype.workersBySubrole = function(gameState, subrole) { return gameState.updatingCollection("subrole-" + subrole +"-base-" + this.ID, API3.Filters.byMetadata(PlayerID, "subrole", subrole), this.workers); }; -PETRA.BaseManager.prototype.gatherersByType = function(gameState, type) +BaseManager.prototype.gatherersByType = function(gameState, type) { - return gameState.updatingCollection("workers-gathering-" + type +"-base-" + this.ID, API3.Filters.byMetadata(PlayerID, "gather-type", type), this.workersBySubrole(gameState, PETRA.Worker.SUBROLE_GATHERER)); + return gameState.updatingCollection("workers-gathering-" + type +"-base-" + + this.ID, API3.Filters.byMetadata(PlayerID, "gather-type", type), + this.workersBySubrole(gameState, Worker.SUBROLE_GATHERER)); }; /** * returns an entity collection of workers. * They are idled immediatly and their subrole set to idle. */ -PETRA.BaseManager.prototype.pickBuilders = function(gameState, workers, number) +BaseManager.prototype.pickBuilders = function(gameState, workers, number) { const availableWorkers = this.workers.filter(ent => { if (!ent.position() || !ent.isBuilder()) @@ -721,13 +748,13 @@ PETRA.BaseManager.prototype.pickBuilders = function(gameState, workers, number) availableWorkers.sort((a, b) => { let vala = 0; let valb = 0; - if (a.getMetadata(PlayerID, "subrole") === PETRA.Worker.SUBROLE_BUILDER) + if (a.getMetadata(PlayerID, "subrole") === Worker.SUBROLE_BUILDER) vala = 100; - if (b.getMetadata(PlayerID, "subrole") === PETRA.Worker.SUBROLE_BUILDER) + if (b.getMetadata(PlayerID, "subrole") === Worker.SUBROLE_BUILDER) valb = 100; - if (a.getMetadata(PlayerID, "subrole") === PETRA.Worker.SUBROLE_IDLE) + if (a.getMetadata(PlayerID, "subrole") === Worker.SUBROLE_IDLE) vala = -50; - if (b.getMetadata(PlayerID, "subrole") === PETRA.Worker.SUBROLE_IDLE) + if (b.getMetadata(PlayerID, "subrole") === Worker.SUBROLE_IDLE) valb = -50; if (a.getMetadata(PlayerID, "plan") === undefined) vala = -20; @@ -739,7 +766,7 @@ PETRA.BaseManager.prototype.pickBuilders = function(gameState, workers, number) for (let i = 0; i < needed; ++i) { availableWorkers[i].stopMoving(); - availableWorkers[i].setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_IDLE); + availableWorkers[i].setMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE); workers.addEnt(availableWorkers[i]); } return; @@ -750,7 +777,7 @@ PETRA.BaseManager.prototype.pickBuilders = function(gameState, workers, number) * try reassigning some other workers who are nearby * AI tries to use builders sensibly, not completely stopping its econ. */ -PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) +BaseManager.prototype.assignToFoundations = function(gameState, noRepair) { let foundations = this.buildings.filter(API3.Filters.and(API3.Filters.isFoundation(), API3.Filters.not(API3.Filters.byClass("Field")))); @@ -761,7 +788,7 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) return; const workers = this.workers.filter(ent => ent.isBuilder()); - const builderWorkers = this.workersBySubrole(gameState, PETRA.Worker.SUBROLE_BUILDER); + const builderWorkers = this.workersBySubrole(gameState, Worker.SUBROLE_BUILDER); const idleBuilderWorkers = builderWorkers.filter(API3.Filters.isIdle()); // if we're constructing and we have the foundations to our base anchor, only try building that. @@ -787,7 +814,7 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) const baseID = this.ID; fromOtherBase.forEach(worker => { worker.setMetadata(PlayerID, "base", baseID); - worker.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER); + worker.setMetadata(PlayerID, "subrole", Worker.SUBROLE_BUILDER); workers.updateEnt(worker); builderWorkers.updateEnt(worker); idleBuilderWorkers.updateEnt(worker); @@ -822,7 +849,7 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) continue; // if our territory has shrinked since this foundation was positioned, do not build it - if (PETRA.isNotWorthBuilding(gameState, target)) + if (isNotWorthBuilding(gameState, target)) continue; let assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target.id()).length; @@ -869,7 +896,7 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) if (assigned >= targetNB || builderTot >= maxTotalBuilders) continue; const nonBuilderWorkers = workers.filter(function(ent) { - if (ent.getMetadata(PlayerID, "subrole") === PETRA.Worker.SUBROLE_BUILDER) + if (ent.getMetadata(PlayerID, "subrole") === Worker.SUBROLE_BUILDER) return false; if (!ent.position()) return false; @@ -902,7 +929,7 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) ++builderTot; const ent = nonBuilderWorkers[current++]; ent.stopMoving(); - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_BUILDER); ent.setMetadata(PlayerID, "target-foundation", target.id()); } } @@ -957,7 +984,7 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) if (assigned >= targetNB || builderTot >= maxTotalBuilders) continue; const nonBuilderWorkers = workers.filter(function(ent) { - if (ent.getMetadata(PlayerID, "subrole") === PETRA.Worker.SUBROLE_BUILDER) + if (ent.getMetadata(PlayerID, "subrole") === Worker.SUBROLE_BUILDER) return false; if (!ent.position()) return false; @@ -974,14 +1001,14 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) ++assigned; ++builderTot; ent.stopMoving(); - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_BUILDER); ent.setMetadata(PlayerID, "target-foundation", target.id()); }); } }; /** Return false when the base is not active (no workers on it) */ -PETRA.BaseManager.prototype.update = function(gameState, queues, events) +BaseManager.prototype.update = function(gameState, queues, events) { if (this.ID == this.basesManager.baselessBase().ID) { @@ -991,13 +1018,13 @@ PETRA.BaseManager.prototype.update = function(gameState, queues, events) { for (const ent of this.units.values()) { - const bestBase = PETRA.getBestBase(gameState, ent); + const bestBase = getBestBase(gameState, ent); if (bestBase.ID != this.ID) bestBase.assignEntity(gameState, ent); } for (const ent of this.buildings.values()) { - const bestBase = PETRA.getBestBase(gameState, ent); + const bestBase = getBestBase(gameState, ent); if (!bestBase) { if (ent.hasClass("Dock")) @@ -1031,7 +1058,7 @@ PETRA.BaseManager.prototype.update = function(gameState, queues, events) // Reassign all remaining entities to its nearest base for (const ent of this.units.values()) { - const base = PETRA.getBestBase(gameState, ent, false, this.ID); + const base = getBestBase(gameState, ent, false, this.ID); base.assignEntity(gameState, ent); } return false; @@ -1042,7 +1069,7 @@ PETRA.BaseManager.prototype.update = function(gameState, queues, events) { if (!ent.position()) continue; - const base = PETRA.getBestBase(gameState, ent); + const base = getBestBase(gameState, ent); if (base.anchor) reassignedBase = base; break; @@ -1116,22 +1143,22 @@ PETRA.BaseManager.prototype.update = function(gameState, queues, events) return true; }; -PETRA.BaseManager.prototype.AddTCGatherer = function(supplyID) +BaseManager.prototype.AddTCGatherer = function(supplyID) { return this.basesManager.AddTCGatherer(supplyID); }; -PETRA.BaseManager.prototype.RemoveTCGatherer = function(supplyID) +BaseManager.prototype.RemoveTCGatherer = function(supplyID) { this.basesManager.RemoveTCGatherer(supplyID); }; -PETRA.BaseManager.prototype.GetTCGatherer = function(supplyID) +BaseManager.prototype.GetTCGatherer = function(supplyID) { return this.basesManager.GetTCGatherer(supplyID); }; -PETRA.BaseManager.prototype.Serialize = function() +BaseManager.prototype.Serialize = function() { return { "ID": this.ID, @@ -1146,7 +1173,7 @@ PETRA.BaseManager.prototype.Serialize = function() }; }; -PETRA.BaseManager.prototype.Deserialize = function(gameState, data) +BaseManager.prototype.Deserialize = function(gameState, data) { for (const key in data) this[key] = data[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/basesManager.js b/binaries/data/mods/public/simulation/ai/petra/basesManager.js index 90e4a26a1a..9eaf97a3dd 100644 --- a/binaries/data/mods/public/simulation/ai/petra/basesManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/basesManager.js @@ -1,10 +1,14 @@ +import { BaseManager } from "simulation/ai/petra/baseManager.js"; +import { getBestBase, getLandAccess } from "simulation/ai/petra/entityExtend.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + /** * Bases Manager * Manages the list of available bases and queries information from those (e.g. resource levels). * Only one base is run every turn. */ -PETRA.BasesManager = function(Config) +export function BasesManager(Config) { this.Config = Config; @@ -17,28 +21,28 @@ PETRA.BasesManager = function(Config) this.noBase = undefined; this.baseManagers = []; -}; +} -PETRA.BasesManager.prototype.init = function(gameState) +BasesManager.prototype.init = function(gameState) { // Initialize base map. Each pixel is a base ID, or 0 if not or not accessible. this.basesMap = new API3.Map(gameState.sharedScript, "territory"); - this.noBase = new PETRA.BaseManager(gameState, this); - this.noBase.init(gameState, PETRA.BaseManager.STATE_WITH_ANCHOR); + this.noBase = new BaseManager(gameState, this); + this.noBase.init(gameState, BaseManager.STATE_WITH_ANCHOR); this.noBase.accessIndex = 0; for (const cc of gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).values()) if (cc.foundationProgress() === undefined) - this.createBase(gameState, cc, PETRA.BaseManager.STATE_WITH_ANCHOR); + this.createBase(gameState, cc, BaseManager.STATE_WITH_ANCHOR); else - this.createBase(gameState, cc, PETRA.BaseManager.STATE_UNCONSTRUCTED); + this.createBase(gameState, cc, BaseManager.STATE_UNCONSTRUCTED); }; /** * Initialization needed after deserialization (only called when deserialising). */ -PETRA.BasesManager.prototype.postinit = function(gameState) +BasesManager.prototype.postinit = function(gameState) { // Rebuild the base maps from the territory indices of each base. this.basesMap = new API3.Map(gameState.sharedScript, "territory"); @@ -66,17 +70,17 @@ PETRA.BasesManager.prototype.postinit = function(gameState) * Otherwise create a new one. * TODO when buildings, criteria should depend on distance */ -PETRA.BasesManager.prototype.createBase = function(gameState, ent, type = PETRA.BaseManager.STATE_WITH_ANCHOR) +BasesManager.prototype.createBase = function(gameState, ent, type = BaseManager.STATE_WITH_ANCHOR) { - const access = PETRA.getLandAccess(gameState, ent); + const access = getLandAccess(gameState, ent); let newbase; for (const base of this.baseManagers) { if (base.accessIndex != access) continue; - if (type !== PETRA.BaseManager.STATE_ANCHORLESS && base.anchor) + if (type !== BaseManager.STATE_ANCHORLESS && base.anchor) continue; - if (type !== PETRA.BaseManager.STATE_ANCHORLESS) + if (type !== BaseManager.STATE_ANCHORLESS) { // TODO we keep the first one, we should rather use the nearest if buildings // and possibly also cut on distance @@ -105,14 +109,14 @@ PETRA.BasesManager.prototype.createBase = function(gameState, ent, type = PETRA. if (!newbase) { - newbase = new PETRA.BaseManager(gameState, this); + newbase = new BaseManager(gameState, this); newbase.init(gameState, type); this.baseManagers.push(newbase); } else newbase.reset(type); - if (type !== PETRA.BaseManager.STATE_ANCHORLESS) + if (type !== BaseManager.STATE_ANCHORLESS) newbase.setAnchor(gameState, ent); else newbase.setAnchorlessEntity(gameState, ent); @@ -121,7 +125,7 @@ PETRA.BasesManager.prototype.createBase = function(gameState, ent, type = PETRA. }; /** TODO check if the new anchorless bases should be added to addBase */ -PETRA.BasesManager.prototype.checkEvents = function(gameState, events) +BasesManager.prototype.checkEvents = function(gameState, events) { let addBase = false; @@ -157,9 +161,9 @@ PETRA.BasesManager.prototype.checkEvents = function(gameState, events) const base = this.getBaseByID(baseID); // Promoted workers should be reset - their new gathering rates/capabilities might differ meaningfully. - if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_WORKER) + if (ent.getMetadata(PlayerID, "role") === Worker.ROLE_WORKER) { - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_IDLE); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE); if (!ent.isGatherer()) { // TODO: Find a way to properly reset the metadata, as this is currently done selectively @@ -189,28 +193,28 @@ PETRA.BasesManager.prototype.checkEvents = function(gameState, events) if (ent.getMetadata(PlayerID, "base") == -1) // Standard base around a cc { // Okay so let's try to create a new base around this. - const newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_UNCONSTRUCTED); + const newbase = this.createBase(gameState, ent, BaseManager.STATE_UNCONSTRUCTED); // Let's get a few units from other bases there to build this. const builders = this.bulkPickWorkers(gameState, newbase, 10); if (builders !== false) { builders.forEach(worker => { worker.setMetadata(PlayerID, "base", newbase.ID); - worker.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER); + worker.setMetadata(PlayerID, "subrole", Worker.SUBROLE_BUILDER); worker.setMetadata(PlayerID, "target-foundation", ent.id()); }); } } else if (ent.getMetadata(PlayerID, "base") == -2) // anchorless base around a dock { - const newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS); + const newbase = this.createBase(gameState, ent, BaseManager.STATE_ANCHORLESS); // Let's get a few units from other bases there to build this. const builders = this.bulkPickWorkers(gameState, newbase, 4); if (builders != false) { builders.forEach(worker => { worker.setMetadata(PlayerID, "base", newbase.ID); - worker.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER); + worker.setMetadata(PlayerID, "subrole", Worker.SUBROLE_BUILDER); worker.setMetadata(PlayerID, "target-foundation", ent.id()); }); } @@ -260,17 +264,17 @@ PETRA.BasesManager.prototype.checkEvents = function(gameState, events) continue; if (ent.hasClass("Unit")) { - PETRA.getBestBase(gameState, ent).assignEntity(gameState, ent); + getBestBase(gameState, ent).assignEntity(gameState, ent); continue; } if (ent.hasClass("CivCentre")) // build a new base around it { let newbase; if (ent.foundationProgress() !== undefined) - newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_UNCONSTRUCTED); + newbase = this.createBase(gameState, ent, BaseManager.STATE_UNCONSTRUCTED); else { - newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_CAPTURED); + newbase = this.createBase(gameState, ent, BaseManager.STATE_CAPTURED); addBase = true; } newbase.assignEntity(gameState, ent); @@ -280,9 +284,9 @@ PETRA.BasesManager.prototype.checkEvents = function(gameState, events) let base; // If dropsite on new island, create a base around it if (!ent.decaying() && ent.resourceDropsiteTypes()) - base = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS); + base = this.createBase(gameState, ent, BaseManager.STATE_ANCHORLESS); else - base = PETRA.getBestBase(gameState, ent) || this.noBase; + base = getBestBase(gameState, ent) || this.noBase; base.assignEntity(gameState, ent); } } @@ -296,12 +300,12 @@ PETRA.BasesManager.prototype.checkEvents = function(gameState, events) continue; // Assign it immediately to something useful to do. - if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_WORKER) + if (ent.getMetadata(PlayerID, "role") === Worker.ROLE_WORKER) { let base; if (ent.getMetadata(PlayerID, "base") === undefined) { - base = PETRA.getBestBase(gameState, ent); + base = getBestBase(gameState, ent); base.assignEntity(gameState, ent); } else @@ -316,12 +320,12 @@ PETRA.BasesManager.prototype.checkEvents = function(gameState, events) continue; const dropsites = gameState.getOwnDropsites(type.generic); const pos = ent.position(); - const access = PETRA.getLandAccess(gameState, ent); + const access = getLandAccess(gameState, ent); let distmin = Math.min(); let goal; for (const dropsite of dropsites.values()) { - if (!dropsite.position() || PETRA.getLandAccess(gameState, dropsite) != access) + if (!dropsite.position() || getLandAccess(gameState, dropsite) != access) continue; const dist = API3.SquareVectorDistance(pos, dropsite.position()); if (dist > distmin) @@ -343,7 +347,7 @@ PETRA.BasesManager.prototype.checkEvents = function(gameState, events) * returns an entity collection of workers through BaseManager.pickBuilders * TODO: when same accessIndex, sort by distance */ -PETRA.BasesManager.prototype.bulkPickWorkers = function(gameState, baseRef, number) +BasesManager.prototype.bulkPickWorkers = function(gameState, baseRef, number) { const accessIndex = baseRef.accessIndex; if (!accessIndex) @@ -378,7 +382,7 @@ PETRA.BasesManager.prototype.bulkPickWorkers = function(gameState, baseRef, numb /** * @return {Object} - Resources (estimation) still gatherable in our territory. */ -PETRA.BasesManager.prototype.getTotalResourceLevel = function(gameState, resources = Resources.GetCodes(), proximity = ["nearby", "medium"]) +BasesManager.prototype.getTotalResourceLevel = function(gameState, resources = Resources.GetCodes(), proximity = ["nearby", "medium"]) { const total = {}; for (const res of resources) @@ -394,7 +398,7 @@ PETRA.BasesManager.prototype.getTotalResourceLevel = function(gameState, resourc * Returns the current gather rate * This is not per-se exact, it performs a few adjustments ad-hoc to account for travel distance, stuffs like that. */ -PETRA.BasesManager.prototype.GetCurrentGatherRates = function(gameState) +BasesManager.prototype.GetCurrentGatherRates = function(gameState) { if (!this.turnCache.currentRates) { @@ -416,7 +420,7 @@ PETRA.BasesManager.prototype.GetCurrentGatherRates = function(gameState) /** Some functions that register that we assigned a gatherer to a resource this turn */ /** Add a gatherer to the turn cache for this supply. */ -PETRA.BasesManager.prototype.AddTCGatherer = function(supplyID) +BasesManager.prototype.AddTCGatherer = function(supplyID) { if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID] !== undefined) ++this.turnCache.resourceGatherer[supplyID]; @@ -429,7 +433,7 @@ PETRA.BasesManager.prototype.AddTCGatherer = function(supplyID) }; /** Remove a gatherer from the turn cache for this supply. */ -PETRA.BasesManager.prototype.RemoveTCGatherer = function(supplyID) +BasesManager.prototype.RemoveTCGatherer = function(supplyID) { if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID]) --this.turnCache.resourceGatherer[supplyID]; @@ -441,7 +445,7 @@ PETRA.BasesManager.prototype.RemoveTCGatherer = function(supplyID) } }; -PETRA.BasesManager.prototype.GetTCGatherer = function(supplyID) +BasesManager.prototype.GetTCGatherer = function(supplyID) { if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID]) return this.turnCache.resourceGatherer[supplyID]; @@ -450,7 +454,7 @@ PETRA.BasesManager.prototype.GetTCGatherer = function(supplyID) }; /** The next two are to register that we assigned a gatherer to a resource this turn. */ -PETRA.BasesManager.prototype.AddTCResGatherer = function(resource) +BasesManager.prototype.AddTCResGatherer = function(resource) { const check = "resourceGatherer-" + resource; if (this.turnCache[check]) @@ -462,7 +466,7 @@ PETRA.BasesManager.prototype.AddTCResGatherer = function(resource) this.turnCache.currentRates[resource] += 0.5; }; -PETRA.BasesManager.prototype.GetTCResGatherer = function(resource) +BasesManager.prototype.GetTCResGatherer = function(resource) { const check = "resourceGatherer-" + resource; if (this.turnCache[check]) @@ -474,7 +478,7 @@ PETRA.BasesManager.prototype.GetTCResGatherer = function(resource) /** * flag a resource as exhausted */ -PETRA.BasesManager.prototype.isResourceExhausted = function(resource) +BasesManager.prototype.isResourceExhausted = function(resource) { const check = "exhausted-" + resource; if (this.turnCache[check] == undefined) @@ -488,26 +492,26 @@ PETRA.BasesManager.prototype.isResourceExhausted = function(resource) * ActiveBases includes only those with a built cc * PotentialBases includes also those with a cc in construction */ -PETRA.BasesManager.prototype.numActiveBases = function() +BasesManager.prototype.numActiveBases = function() { if (!this.turnCache.base) this.updateBaseCache(); return this.turnCache.base.active; }; -PETRA.BasesManager.prototype.hasActiveBase = function() +BasesManager.prototype.hasActiveBase = function() { return !!this.numActiveBases(); }; -PETRA.BasesManager.prototype.numPotentialBases = function() +BasesManager.prototype.numPotentialBases = function() { if (!this.turnCache.base) this.updateBaseCache(); return this.turnCache.base.potential; }; -PETRA.BasesManager.prototype.hasPotentialBase = function() +BasesManager.prototype.hasPotentialBase = function() { return !!this.numPotentialBases(); }; @@ -517,7 +521,7 @@ PETRA.BasesManager.prototype.hasPotentialBase = function() * .potential {number} - Bases that may or may not still be a foundation. * .active {number} - Usable bases. */ -PETRA.BasesManager.prototype.updateBaseCache = function() +BasesManager.prototype.updateBaseCache = function() { this.turnCache.base = { "active": 0, "potential": 0 }; for (const base of this.baseManagers) @@ -530,12 +534,12 @@ PETRA.BasesManager.prototype.updateBaseCache = function() } }; -PETRA.BasesManager.prototype.resetBaseCache = function() +BasesManager.prototype.resetBaseCache = function() { this.turnCache.base = undefined; }; -PETRA.BasesManager.prototype.baselessBase = function() +BasesManager.prototype.baselessBase = function() { return this.noBase; }; @@ -544,7 +548,7 @@ PETRA.BasesManager.prototype.baselessBase = function() * @param {number} baseID * @return {Object} - The base corresponding to baseID. */ -PETRA.BasesManager.prototype.getBaseByID = function(baseID) +BasesManager.prototype.getBaseByID = function(baseID) { if (this.noBase.ID === baseID) return this.noBase; @@ -554,7 +558,7 @@ PETRA.BasesManager.prototype.getBaseByID = function(baseID) /** * flag a resource as exhausted */ -PETRA.BasesManager.prototype.isResourceExhausted = function(resource) +BasesManager.prototype.isResourceExhausted = function(resource) { return this.baseManagers.every(base => !base.dropsiteSupplies[resource].nearby.length && @@ -566,7 +570,7 @@ PETRA.BasesManager.prototype.isResourceExhausted = function(resource) * Count gatherers returning resources in the number of gatherers of resourceSupplies * to prevent the AI always reassigning idle workers to these resourceSupplies (specially in naval maps). */ -PETRA.BasesManager.prototype.assignGatherers = function() +BasesManager.prototype.assignGatherers = function() { for (const base of this.baseManagers) for (const worker of base.workers.values()) @@ -584,7 +588,7 @@ PETRA.BasesManager.prototype.assignGatherers = function() * Assign an entity to the closest base. * Used by the starting strategy. */ -PETRA.BasesManager.prototype.assignEntity = function(gameState, ent, territoryIndex) +BasesManager.prototype.assignEntity = function(gameState, ent, territoryIndex) { let bestbase; for (const base of this.baseManagers) @@ -599,9 +603,9 @@ PETRA.BasesManager.prototype.assignEntity = function(gameState, ent, territoryIn if (!bestbase) // entity outside our territory { if (ent.hasClass("Structure") && !ent.decaying() && ent.resourceDropsiteTypes()) - bestbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS); + bestbase = this.createBase(gameState, ent, BaseManager.STATE_ANCHORLESS); else - bestbase = PETRA.getBestBase(gameState, ent) || this.noBase; + bestbase = getBestBase(gameState, ent) || this.noBase; bestbase.assignEntity(gameState, ent); } // now assign entities garrisoned inside this entity @@ -612,7 +616,7 @@ PETRA.BasesManager.prototype.assignEntity = function(gameState, ent, territoryIn if (ent.position() && bestbase.ID !== this.noBase.ID) { bestbase.assignRolelessUnits(gameState, [ent]); - if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_WORKER) + if (ent.getMetadata(PlayerID, "role") === Worker.ROLE_WORKER) { bestbase.reassignIdleWorkers(gameState, [ent]); bestbase.workerObject.update(gameState, ent); @@ -625,7 +629,7 @@ PETRA.BasesManager.prototype.assignEntity = function(gameState, ent, territoryIn * @param {Object} gameState * @param {Object} rates - The rates to add the gather rates to. */ -PETRA.BasesManager.prototype.addGatherRates = function(gameState, rates) +BasesManager.prototype.addGatherRates = function(gameState, rates) { for (const base of this.baseManagers) base.addGatherRates(gameState, rates); @@ -635,7 +639,7 @@ PETRA.BasesManager.prototype.addGatherRates = function(gameState, rates) * @param {number} territoryIndex * @return {number} - The ID of the base at the given territory index. */ -PETRA.BasesManager.prototype.baseAtIndex = function(territoryIndex) +BasesManager.prototype.baseAtIndex = function(territoryIndex) { return this.basesMap.map[territoryIndex]; }; @@ -643,7 +647,7 @@ PETRA.BasesManager.prototype.baseAtIndex = function(territoryIndex) /** * @param {number} territoryIndex */ -PETRA.BasesManager.prototype.removeBaseFromTerritoryIndex = function(territoryIndex) +BasesManager.prototype.removeBaseFromTerritoryIndex = function(territoryIndex) { const baseID = this.basesMap.map[territoryIndex]; if (baseID == 0) @@ -665,7 +669,7 @@ PETRA.BasesManager.prototype.removeBaseFromTerritoryIndex = function(territoryIn /** * @return {boolean} - Whether the index was added to a base. */ -PETRA.BasesManager.prototype.addTerritoryIndexToBase = function(gameState, territoryIndex, passabilityMap) +BasesManager.prototype.addTerritoryIndexToBase = function(gameState, territoryIndex, passabilityMap) { if (this.baseAtIndex(territoryIndex) != 0) return false; @@ -705,7 +709,7 @@ PETRA.BasesManager.prototype.addTerritoryIndexToBase = function(gameState, terri }; /** Reassign territories when a base is going to be deleted */ -PETRA.BasesManager.prototype.reassignTerritories = function(deletedBase, territoryMap) +BasesManager.prototype.reassignTerritories = function(deletedBase, territoryMap) { const cellSize = territoryMap.cellSize; const width = territoryMap.width; @@ -748,7 +752,7 @@ PETRA.BasesManager.prototype.reassignTerritories = function(deletedBase, territo /** * We will loop only on one active base per turn. */ -PETRA.BasesManager.prototype.update = function(gameState, queues, events) +BasesManager.prototype.update = function(gameState, queues, events) { Engine.ProfileStart("BasesManager update"); @@ -768,7 +772,7 @@ PETRA.BasesManager.prototype.update = function(gameState, queues, events) Engine.ProfileStop(); }; -PETRA.BasesManager.prototype.Serialize = function() +BasesManager.prototype.Serialize = function() { const properties = { "currentBase": this.currentBase @@ -785,23 +789,23 @@ PETRA.BasesManager.prototype.Serialize = function() }; }; -PETRA.BasesManager.prototype.Deserialize = function(gameState, data) +BasesManager.prototype.Deserialize = function(gameState, data) { for (const key in data.properties) this[key] = data.properties[key]; - this.noBase = new PETRA.BaseManager(gameState, this); + this.noBase = new BaseManager(gameState, this); this.noBase.Deserialize(gameState, data.noBase); - this.noBase.init(gameState, PETRA.BaseManager.STATE_WITH_ANCHOR); + this.noBase.init(gameState, BaseManager.STATE_WITH_ANCHOR); this.noBase.Deserialize(gameState, data.noBase); this.baseManagers = []; for (const basedata of data.baseManagers) { // The first call to deserialize set the ID base needed by entitycollections. - const newbase = new PETRA.BaseManager(gameState, this); + const newbase = new BaseManager(gameState, this); newbase.Deserialize(gameState, basedata); - newbase.init(gameState, PETRA.BaseManager.STATE_WITH_ANCHOR); + newbase.init(gameState, BaseManager.STATE_WITH_ANCHOR); newbase.Deserialize(gameState, basedata); this.baseManagers.push(newbase); } diff --git a/binaries/data/mods/public/simulation/ai/petra/buildManager.js b/binaries/data/mods/public/simulation/ai/petra/buildManager.js index 89af88f0fd..dedecca40f 100644 --- a/binaries/data/mods/public/simulation/ai/petra/buildManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/buildManager.js @@ -4,24 +4,24 @@ * It also takes care of the structures we can't currently build and should not try to build endlessly. */ -PETRA.BuildManager = function() +export function BuildManager() { // List of buildings we have builders for, with number of possible builders. this.builderCounters = new Map(); // List of buildings we can't currently build (because no room, no builder or whatever), // with time we should wait before trying again to build it. this.unbuildables = new Map(); -}; +} /** Initialization at start of game */ -PETRA.BuildManager.prototype.init = function(gameState) +BuildManager.prototype.init = function(gameState) { const civ = gameState.getPlayerCiv(); for (const ent of gameState.getOwnUnits().values()) this.incrementBuilderCounters(civ, ent, 1); }; -PETRA.BuildManager.prototype.incrementBuilderCounters = function(civ, ent, increment) +BuildManager.prototype.incrementBuilderCounters = function(civ, ent, increment) { for (const buildable of ent.buildableEntities(civ)) { @@ -43,7 +43,7 @@ PETRA.BuildManager.prototype.incrementBuilderCounters = function(civ, ent, incre }; /** Update the builders counters */ -PETRA.BuildManager.prototype.checkEvents = function(gameState, events) +BuildManager.prototype.checkEvents = function(gameState, events) { this.elapsedTime = gameState.ai.elapsedTime; const civ = gameState.getPlayerCiv(); @@ -99,7 +99,7 @@ PETRA.BuildManager.prototype.checkEvents = function(gameState, events) /** * Get the buildable structures passing a filter. */ -PETRA.BuildManager.prototype.findStructuresByFilter = function(gameState, filter) +BuildManager.prototype.findStructuresByFilter = function(gameState, filter) { const result = []; for (const [templateName, count] of this.builderCounters) @@ -119,30 +119,30 @@ PETRA.BuildManager.prototype.findStructuresByFilter = function(gameState, filter * Get the first buildable structure with a given class * TODO when several available, choose the best one */ -PETRA.BuildManager.prototype.findStructureWithClass = function(gameState, classes) +BuildManager.prototype.findStructureWithClass = function(gameState, classes) { return this.findStructuresByFilter(gameState, API3.Filters.byClasses(classes))[0]; }; -PETRA.BuildManager.prototype.hasBuilder = function(template) +BuildManager.prototype.hasBuilder = function(template) { const numBuilders = this.builderCounters.get(template); return numBuilders && numBuilders > 0; }; -PETRA.BuildManager.prototype.isUnbuildable = function(gameState, template) +BuildManager.prototype.isUnbuildable = function(gameState, template) { return this.unbuildables.has(template) && this.unbuildables.get(template).time > gameState.ai.elapsedTime; }; -PETRA.BuildManager.prototype.setBuildable = function(template) +BuildManager.prototype.setBuildable = function(template) { if (this.unbuildables.has(template)) this.unbuildables.delete(template); }; /** Time is the duration in second that we will wait before checking again if it is buildable */ -PETRA.BuildManager.prototype.setUnbuildable = function(gameState, template, time = 90, reason = "room") +BuildManager.prototype.setUnbuildable = function(gameState, template, time = 90, reason = "room") { if (!this.unbuildables.has(template)) this.unbuildables.set(template, { "reason": reason, "time": gameState.ai.elapsedTime + time }); @@ -158,7 +158,7 @@ PETRA.BuildManager.prototype.setUnbuildable = function(gameState, template, time }; /** Return the number of unbuildables due to missing room */ -PETRA.BuildManager.prototype.numberMissingRoom = function(gameState) +BuildManager.prototype.numberMissingRoom = function(gameState) { let num = 0; for (const unbuildable of this.unbuildables.values()) @@ -168,14 +168,14 @@ PETRA.BuildManager.prototype.numberMissingRoom = function(gameState) }; /** Reset the unbuildables due to missing room */ -PETRA.BuildManager.prototype.resetMissingRoom = function(gameState) +BuildManager.prototype.resetMissingRoom = function(gameState) { for (const [key, unbuildable] of this.unbuildables) if (unbuildable.reason == "room") this.unbuildables.delete(key); }; -PETRA.BuildManager.prototype.Serialize = function() +BuildManager.prototype.Serialize = function() { return { "builderCounters": this.builderCounters, @@ -183,7 +183,7 @@ PETRA.BuildManager.prototype.Serialize = function() }; }; -PETRA.BuildManager.prototype.Deserialize = function(data) +BuildManager.prototype.Deserialize = function(data) { for (const key in data) this[key] = data[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/chatHelper.js b/binaries/data/mods/public/simulation/ai/petra/chatHelper.js index c10c891791..d9a5258342 100644 --- a/binaries/data/mods/public/simulation/ai/petra/chatHelper.js +++ b/binaries/data/mods/public/simulation/ai/petra/chatHelper.js @@ -1,4 +1,6 @@ -PETRA.launchAttackMessages = { +import { AttackPlan } from "simulation/ai/petra/attackPlan.js"; + +const launchAttackMessages = { "hugeAttack": [ markForTranslation("I am starting a massive military campaign against %(_player_)s, come and join me."), markForTranslation("I have set up a huge army to crush %(_player_)s. Join me and you will have your share of the loot."), @@ -11,7 +13,7 @@ PETRA.launchAttackMessages = { ] }; -PETRA.answerRequestAttackMessages = { +const answerRequestAttackMessages = { "join": [ markForTranslation("Let me regroup my army and I will then join you against %(_player_)s."), markForTranslation("I am finishing preparations to attack %(_player_)s.") @@ -25,24 +27,24 @@ PETRA.answerRequestAttackMessages = { ] }; -PETRA.sentTributeMessages = [ +const sentTributeMessages = [ markForTranslation("Here is a gift for you, %(_player_)s. Make good use of it."), markForTranslation("I see you are in a bad situation, %(_player_)s. I hope this helps."), markForTranslation("I can help you this time, %(_player_)s, but you should manage your resources more carefully in the future.") ]; -PETRA.requestTributeMessages = [ +const requestTributeMessages = [ markForTranslation("I am in need of %(resource)s, can you help? I will make it up to you."), markForTranslation("I would participate more efficiently in our common war effort if you could provide me some %(resource)s."), markForTranslation("If you can spare me some %(resource)s, I will be able to strengthen my army.") ]; -PETRA.newTradeRouteMessages = [ +const newTradeRouteMessages = [ markForTranslation("I have set up a new route with %(_player_)s. Trading will be profitable for all of us."), markForTranslation("A new trade route is set up with %(_player_)s. Take your share of the profits.") ]; -PETRA.newDiplomacyMessages = { +const newDiplomacyMessages = { "ally": [ markForTranslation("%(_player_)s and I are now allies.") ], @@ -54,7 +56,7 @@ PETRA.newDiplomacyMessages = { ] }; -PETRA.answerDiplomacyRequestMessages = { +const answerDiplomacyRequestMessages = { "ally": { "decline": [ markForTranslation("I cannot accept your offer to become allies, %(_player_)s."), @@ -114,7 +116,7 @@ PETRA.answerDiplomacyRequestMessages = { } }; -PETRA.sendDiplomacyRequestMessages = { +const sendDiplomacyRequestMessages = { "ally": { "sendRequest": [ markForTranslation("%(_player_)s, it would help both of our civilizations if we formed an alliance. If you become allies with me, I will respond in kind.") @@ -136,7 +138,7 @@ PETRA.sendDiplomacyRequestMessages = { } }; -PETRA.emergencyMessages = { +const emergencyMessages = { "enter": [ markForTranslation("My armies failed while defending my empire. Please honor our alliance and send help!"), markForTranslation("My humble armies feel weak and tired. My civilization depends on our alliance, please send help!"), @@ -148,114 +150,114 @@ PETRA.emergencyMessages = { ] }; -PETRA.chatLaunchAttack = function(gameState, player, type) +export function launchAttack(gameState, player, type) { Engine.PostCommand(PlayerID, { "type": "aichat", - "message": "/allies " + pickRandom(this.launchAttackMessages[type === PETRA.AttackPlan.TYPE_HUGE_ATTACK ? "hugeAttack" : "other"]), + "message": "/allies " + pickRandom(launchAttackMessages[type === AttackPlan.TYPE_HUGE_ATTACK ? "hugeAttack" : "other"]), "translateMessage": true, "translateParameters": ["_player_"], "parameters": { "_player_": player } }); -}; +} -PETRA.chatAnswerRequestAttack = function(gameState, player, answer, other) +export function answerRequestAttack(gameState, player, answer, other) { Engine.PostCommand(PlayerID, { "type": "aichat", - "message": "/allies " + pickRandom(this.answerRequestAttackMessages[answer]), + "message": "/allies " + pickRandom(answerRequestAttackMessages[answer]), "translateMessage": true, "translateParameters": answer != "other" ? ["_player_"] : ["_player_", "_player_2"], "parameters": answer != "other" ? { "_player_": player } : { "_player_": player, "_player_2": other } }); -}; +} -PETRA.chatSentTribute = function(gameState, player) +export function sentTribute(gameState, player) { Engine.PostCommand(PlayerID, { "type": "aichat", - "message": "/allies " + pickRandom(this.sentTributeMessages), + "message": "/allies " + pickRandom(sentTributeMessages), "translateMessage": true, "translateParameters": ["_player_"], "parameters": { "_player_": player } }); -}; +} -PETRA.chatRequestTribute = function(gameState, resource) +export function requestTribute(gameState, resource) { Engine.PostCommand(PlayerID, { "type": "aichat", - "message": "/allies " + pickRandom(this.requestTributeMessages), + "message": "/allies " + pickRandom(requestTributeMessages), "translateMessage": true, "translateParameters": { "resource": "withinSentence" }, "parameters": { "resource": Resources.GetNames()[resource] } }); -}; +} -PETRA.chatNewTradeRoute = function(gameState, player) +export function newTradeRoute(gameState, player) { Engine.PostCommand(PlayerID, { "type": "aichat", - "message": "/allies " + pickRandom(this.newTradeRouteMessages), + "message": "/allies " + pickRandom(newTradeRouteMessages), "translateMessage": true, "translateParameters": ["_player_"], "parameters": { "_player_": player } }); -}; +} -PETRA.chatNewPhase = function(gameState, phase, status) +function newPhase(gameState, phase, status) { Engine.PostCommand(PlayerID, { "type": "aichat", - "message": "/allies " + pickRandom(this.newPhaseMessages[status]), + "message": "/allies " + pickRandom(newPhaseMessages[status]), "translateMessage": true, "translateParameters": ["phase"], "parameters": { "phase": phase } }); -}; +} -PETRA.chatNewDiplomacy = function(gameState, player, newDiplomaticStance) +export function newDiplomacy(gameState, player, newDiplomaticStance) { Engine.PostCommand(PlayerID, { "type": "aichat", - "message": pickRandom(this.newDiplomacyMessages[newDiplomaticStance]), + "message": pickRandom(newDiplomacyMessages[newDiplomaticStance]), "translateMessage": true, "translateParameters": ["_player_"], "parameters": { "_player_": player } }); -}; +} -PETRA.chatAnswerRequestDiplomacy = function(gameState, player, requestType, response, requiredTribute) +export function answerRequestDiplomacy(gameState, player, requestType, response, requiredTribute) { Engine.PostCommand(PlayerID, { "type": "aichat", "message": "/msg " + gameState.sharedScript.playersData[player].name + " " + - pickRandom(this.answerDiplomacyRequestMessages[requestType][response]), + pickRandom(answerDiplomacyRequestMessages[requestType][response]), "translateMessage": true, "translateParameters": requiredTribute ? { "_amount_": null, "_resource_": "withinSentence", "_player_": null } : ["_player_"], "parameters": requiredTribute ? { "_amount_": requiredTribute.wanted, "_resource_": Resources.GetNames()[requiredTribute.type], "_player_": player } : { "_player_": player } }); -}; +} -PETRA.chatNewRequestDiplomacy = function(gameState, player, requestType, status) +export function newRequestDiplomacy(gameState, player, requestType, status) { Engine.PostCommand(PlayerID, { "type": "aichat", "message": "/msg " + gameState.sharedScript.playersData[player].name + " " + - pickRandom(this.sendDiplomacyRequestMessages[requestType][status]), + pickRandom(sendDiplomacyRequestMessages[requestType][status]), "translateMessage": true, "translateParameters": ["_player_"], "parameters": { "_player_": player } }); -}; +} -PETRA.chatEmergency = function(gameState, enable) +export function emergency(gameState, enable) { Engine.PostCommand(PlayerID, { "type": "aichat", - "message": "/allies " + pickRandom(this.emergencyMessages[enable ? "enter" : "exit"]), + "message": "/allies " + pickRandom(emergencyMessages[enable ? "enter" : "exit"]), "translateMessage": true }); -}; +} diff --git a/binaries/data/mods/public/simulation/ai/petra/config.js b/binaries/data/mods/public/simulation/ai/petra/config.js index 20c9a35e67..778af2ebea 100644 --- a/binaries/data/mods/public/simulation/ai/petra/config.js +++ b/binaries/data/mods/public/simulation/ai/petra/config.js @@ -1,12 +1,14 @@ // These integers must be sequential -PETRA.DIFFICULTY_SANDBOX = 0; -PETRA.DIFFICULTY_VERY_EASY = 1; -PETRA.DIFFICULTY_EASY = 2; -PETRA.DIFFICULTY_MEDIUM = 3; -PETRA.DIFFICULTY_HARD = 4; -PETRA.DIFFICULTY_VERY_HARD = 5; +/* eslint-disable prefer-const -- Mods should be able to change them */ +export let DIFFICULTY_SANDBOX = 0; +export let DIFFICULTY_VERY_EASY = 1; +export let DIFFICULTY_EASY = 2; +export let DIFFICULTY_MEDIUM = 3; +export let DIFFICULTY_HARD = 4; +export let DIFFICULTY_VERY_HARD = 5; +/* eslint-enable prefer-const */ -PETRA.Config = function(difficulty = PETRA.DIFFICULTY_MEDIUM, behavior) +export function Config(difficulty = DIFFICULTY_MEDIUM, behavior) { this.difficulty = difficulty; @@ -147,7 +149,7 @@ PETRA.Config = function(difficulty = PETRA.DIFFICULTY_MEDIUM, behavior) "defensive": 0.5 }; - // See PETRA.QueueManager.prototype.wantedGatherRates() + // See QueueManager.prototype.wantedGatherRates() this.queues = { "firstTurn": { @@ -202,11 +204,11 @@ PETRA.Config = function(difficulty = PETRA.DIFFICULTY_MEDIUM, behavior) 0.35, 0.2 ]; -}; +} -PETRA.Config.prototype.setConfig = function(gameState) +Config.prototype.setConfig = function(gameState) { - if (this.difficulty > PETRA.DIFFICULTY_SANDBOX) + if (this.difficulty > DIFFICULTY_SANDBOX) { // Setup personality traits according to the user choice: // The parameter used to define the personality is basically the aggressivity or (1-defensiveness) @@ -249,14 +251,14 @@ PETRA.Config.prototype.setConfig = function(gameState) this.Military.fortressLapseTime = Math.round(this.Military.fortressLapseTime * (1.1 - 0.2 * this.personality.defensive)); this.priorities.defenseBuilding = Math.round(this.priorities.defenseBuilding * (0.9 + 0.2 * this.personality.defensive)); - if (this.difficulty < PETRA.DIFFICULTY_EASY) + if (this.difficulty < DIFFICULTY_EASY) { this.popScaling = 0.5; this.Economy.supportRatio = 0.5; this.Economy.provisionFields = 1; this.Military.numSentryTowers = this.personality.defensive > this.personalityCut.strong ? 1 : 0; } - else if (this.difficulty < PETRA.DIFFICULTY_MEDIUM) + else if (this.difficulty < DIFFICULTY_MEDIUM) { this.popScaling = 0.7; this.Economy.supportRatio = 0.4; @@ -265,7 +267,7 @@ PETRA.Config.prototype.setConfig = function(gameState) } else { - if (this.difficulty == PETRA.DIFFICULTY_MEDIUM) + if (this.difficulty == DIFFICULTY_MEDIUM) this.Military.numSentryTowers = 1; else this.Military.numSentryTowers = 2; @@ -283,9 +285,9 @@ PETRA.Config.prototype.setConfig = function(gameState) } const maxPop = gameState.getPopulationMax(); - if (this.difficulty < PETRA.DIFFICULTY_EASY) + if (this.difficulty < DIFFICULTY_EASY) this.Economy.targetNumWorkers = Math.max(1, Math.min(40, maxPop)); - else if (this.difficulty < PETRA.DIFFICULTY_MEDIUM) + else if (this.difficulty < DIFFICULTY_MEDIUM) this.Economy.targetNumWorkers = Math.max(1, Math.min(60, Math.floor(maxPop/2))); else this.Economy.targetNumWorkers = Math.max(1, Math.min(120, Math.floor(maxPop/3))); @@ -311,7 +313,7 @@ PETRA.Config.prototype.setConfig = function(gameState) this.Economy.targetNumWorkers = Math.max(this.Economy.targetNumWorkers, this.Economy.popPhase2); this.Economy.workPhase3 = Math.min(this.Economy.workPhase3, this.Economy.targetNumWorkers); this.Economy.workPhase4 = Math.min(this.Economy.workPhase4, this.Economy.targetNumWorkers); - if (this.difficulty < PETRA.DIFFICULTY_EASY) + if (this.difficulty < DIFFICULTY_EASY) this.Economy.workPhase3 = Infinity; // prevent the phasing to city phase this.emergencyValues = { @@ -327,7 +329,7 @@ PETRA.Config.prototype.setConfig = function(gameState) API3.warn(" >>> Petra bot: personality = " + uneval(this.personality)); }; -PETRA.Config.prototype.Cheat = function(gameState) +Config.prototype.Cheat = function(gameState) { // Sandbox, Very Easy, Easy, Medium, Hard, Very Hard // rate apply on resource stockpiling as gathering and trading @@ -342,7 +344,7 @@ PETRA.Config.prototype.Cheat = function(gameState) }, gameState.playerData.entity); }; -PETRA.Config.prototype.Serialize = function() +Config.prototype.Serialize = function() { var data = {}; for (const key in this) @@ -351,7 +353,7 @@ PETRA.Config.prototype.Serialize = function() return data; }; -PETRA.Config.prototype.Deserialize = function(data) +Config.prototype.Deserialize = function(data) { for (const key in data) this[key] = data[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/data.json b/binaries/data/mods/public/simulation/ai/petra/data.json index 5b43816ee7..f4a0361c4e 100644 --- a/binaries/data/mods/public/simulation/ai/petra/data.json +++ b/binaries/data/mods/public/simulation/ai/petra/data.json @@ -1,7 +1,7 @@ { "name": "Petra Bot", "description": "Petra is the default 0 A.D. AI bot. Please report issues to Wildfire Games (see the link in the main menu).\n\nThe AI's resource gathering rate and trade gain depend on the difficulty level (Sandbox −58%, Very Easy −44%, Easy −25%, Medium ±0%, Hard +25%, Very Hard +56%). The easiest levels also have a slower research, training, and building rate. In addition, the Sandbox level does not expand nor attack.", - "moduleName" : "PETRA", "constructor": "PetraBot", + "filename": "_petrabot.js", "useShared": true } diff --git a/binaries/data/mods/public/simulation/ai/petra/defenseArmy.js b/binaries/data/mods/public/simulation/ai/petra/defenseArmy.js index 38994490b7..d7c6627ab8 100644 --- a/binaries/data/mods/public/simulation/ai/petra/defenseArmy.js +++ b/binaries/data/mods/public/simulation/ai/petra/defenseArmy.js @@ -1,3 +1,8 @@ +import { allowCapture, getLandAccess, getMaxStrength, isSiegeUnit, returnResources } from + "simulation/ai/petra/entityExtend.js"; +import { TransportPlan } from "simulation/ai/petra/transportPlan.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + /** * Armies used by the defense manager. * An army is a collection of own entities and enemy entities. @@ -7,7 +12,7 @@ * "capturing": army set to capture a gaia building or recover capture points to one of its own structures * It must contain only one foe (the building to capture) and never be merged */ -PETRA.DefenseArmy = function(gameState, foeEntities, type) +export function DefenseArmy(gameState, foeEntities, type) { this.ID = gameState.ai.uniqueIDs.armies++; this.type = type || "default"; @@ -40,7 +45,7 @@ PETRA.DefenseArmy = function(gameState, foeEntities, type) this.recalculatePosition(gameState, true); return true; -}; +} /** * add an entity to the enemy army @@ -48,7 +53,7 @@ PETRA.DefenseArmy = function(gameState, foeEntities, type) * won't recalculate our position but will dirty it. * force is true at army creation or when merging armies, so in this case we should add it even if far */ -PETRA.DefenseArmy.prototype.addFoe = function(gameState, enemyId, force) +DefenseArmy.prototype.addFoe = function(gameState, enemyId, force) { if (this.foeEntities.indexOf(enemyId) !== -1) return false; @@ -73,7 +78,7 @@ PETRA.DefenseArmy.prototype.addFoe = function(gameState, enemyId, force) * returns true if the entity was removed and false otherwise. * TODO: when there is a technology update, we should probably recompute the strengths, or weird stuffs will happen. */ -PETRA.DefenseArmy.prototype.removeFoe = function(gameState, enemyId, enemyEntity) +DefenseArmy.prototype.removeFoe = function(gameState, enemyId, enemyEntity) { const idx = this.foeEntities.indexOf(enemyId); if (idx === -1) @@ -100,7 +105,7 @@ PETRA.DefenseArmy.prototype.removeFoe = function(gameState, enemyId, enemyEntity * adds a defender but doesn't assign him yet. * force is true when merging armies, so in this case we should add it even if no position as it can be in a ship */ -PETRA.DefenseArmy.prototype.addOwn = function(gameState, id, force) +DefenseArmy.prototype.addOwn = function(gameState, id, force) { if (this.ownEntities.indexOf(id) !== -1) return false; @@ -119,13 +124,13 @@ PETRA.DefenseArmy.prototype.addOwn = function(gameState, id, force) else ent.setMetadata(PlayerID, "plan", -3); const subrole = ent.getMetadata(PlayerID, "subrole"); - if (subrole === undefined || subrole !== PETRA.Worker.SUBROLE_DEFENDER) + if (subrole === undefined || subrole !== Worker.SUBROLE_DEFENDER) ent.setMetadata(PlayerID, "formerSubrole", subrole); - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_DEFENDER); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_DEFENDER); return true; }; -PETRA.DefenseArmy.prototype.removeOwn = function(gameState, id, Entity) +DefenseArmy.prototype.removeOwn = function(gameState, id, Entity) { const idx = this.ownEntities.indexOf(id); if (idx === -1) @@ -163,7 +168,7 @@ PETRA.DefenseArmy.prototype.removeOwn = function(gameState, id, Entity) if (ent.getMetadata(PlayerID, "transport") !== undefined) { const plan = gameState.ai.HQ.navalManager.getPlan(ent.getMetadata(PlayerID, "transport")); - if (plan && plan.state === PETRA.TransportPlan.BOARDING && ent.position()) + if (plan && plan.state === TransportPlan.BOARDING && ent.position()) plan.removeUnit(gameState, ent); } @@ -191,7 +196,7 @@ PETRA.DefenseArmy.prototype.removeOwn = function(gameState, id, Entity) * resets the army properly. * assumes we already cleared dead units. */ -PETRA.DefenseArmy.prototype.clear = function(gameState) +DefenseArmy.prototype.clear = function(gameState) { while (this.foeEntities.length > 0) this.removeFoe(gameState, this.foeEntities[0]); @@ -247,7 +252,7 @@ PETRA.DefenseArmy.prototype.clear = function(gameState) const pos = struct.position(); if (!pos || !gameState.isPlayerMutualAlly(gameState.ai.HQ.territoryMap.getOwner(pos))) continue; - if (PETRA.getLandAccess(gameState, struct) !== armyAccess) + if (getLandAccess(gameState, struct) !== armyAccess) continue; const defensiveStruct = struct.hasDefensiveFire(); if (defensiveFound && !defensiveStruct) @@ -290,7 +295,7 @@ PETRA.DefenseArmy.prototype.clear = function(gameState) this.recalculatePosition(gameState); }; -PETRA.DefenseArmy.prototype.assignUnit = function(gameState, entID) +DefenseArmy.prototype.assignUnit = function(gameState, entID) { // we'll assume this defender is ours already. // we'll also override any previous assignment @@ -300,7 +305,7 @@ PETRA.DefenseArmy.prototype.assignUnit = function(gameState, entID) return false; // try to return its resources, and if any, the attack order will be queued - const queued = PETRA.returnResources(gameState, ent); + const queued = returnResources(gameState, ent); let idMin; let distMin; @@ -312,7 +317,7 @@ PETRA.DefenseArmy.prototype.assignUnit = function(gameState, entID) if (!eEnt || !eEnt.position()) // probably can't happen. continue; - if (!ent.canAttackTarget(eEnt, PETRA.allowCapture(gameState, ent, eEnt))) + if (!ent.canAttackTarget(eEnt, allowCapture(gameState, ent, eEnt))) continue; if (eEnt.hasClass("Unit") && eEnt.unitAIOrderData() && eEnt.unitAIOrderData().length && @@ -324,7 +329,7 @@ PETRA.DefenseArmy.prototype.assignUnit = function(gameState, entID) // already enough units against it if (this.assignedAgainst[id].length > 8 || - this.assignedAgainst[id].length > 5 && !eEnt.hasClass("Hero") && !PETRA.isSiegeUnit(eEnt)) + this.assignedAgainst[id].length > 5 && !eEnt.hasClass("Hero") && !isSiegeUnit(eEnt)) continue; const dist = API3.SquareVectorDistance(ent.position(), eEnt.position()); @@ -350,7 +355,7 @@ PETRA.DefenseArmy.prototype.assignUnit = function(gameState, entID) else return false; - const ownIndex = PETRA.getLandAccess(gameState, ent); + const ownIndex = getLandAccess(gameState, ent); const foeEnt = gameState.getEntityById(idFoe); const foePosition = foeEnt.position(); const foeIndex = gameState.ai.accessibility.getAccessValue(foePosition); @@ -358,19 +363,19 @@ PETRA.DefenseArmy.prototype.assignUnit = function(gameState, entID) { this.assignedTo[entID] = idFoe; this.assignedAgainst[idFoe].push(entID); - ent.attack(idFoe, PETRA.allowCapture(gameState, ent, foeEnt), queued); + ent.attack(idFoe, allowCapture(gameState, ent, foeEnt), queued); } else gameState.ai.HQ.navalManager.requireTransport(gameState, ent, ownIndex, foeIndex, foePosition); return true; }; -PETRA.DefenseArmy.prototype.getType = function() +DefenseArmy.prototype.getType = function() { return this.type; }; -PETRA.DefenseArmy.prototype.getState = function() +DefenseArmy.prototype.getState = function() { if (!this.foeEntities.length) return 0; @@ -382,7 +387,7 @@ PETRA.DefenseArmy.prototype.getState = function() * assumes units are in only one army. * also assumes that all have been properly cleaned up (no dead units). */ -PETRA.DefenseArmy.prototype.merge = function(gameState, otherArmy) +DefenseArmy.prototype.merge = function(gameState, otherArmy) { // copy over all parameters. for (const i in otherArmy.assignedAgainst) @@ -407,7 +412,7 @@ PETRA.DefenseArmy.prototype.merge = function(gameState, otherArmy) return true; }; -PETRA.DefenseArmy.prototype.needsDefenders = function(gameState) +DefenseArmy.prototype.needsDefenders = function(gameState) { let defenseRatio; const territoryOwner = gameState.ai.HQ.territoryMap.getOwner(this.foePosition); @@ -436,7 +441,7 @@ PETRA.DefenseArmy.prototype.needsDefenders = function(gameState) /** if not forced, will only recalculate if on a different turn. */ -PETRA.DefenseArmy.prototype.recalculatePosition = function(gameState, force) +DefenseArmy.prototype.recalculatePosition = function(gameState, force) { if (!force && this.positionLastUpdate === gameState.ai.elapsedTime) return; @@ -463,7 +468,7 @@ PETRA.DefenseArmy.prototype.recalculatePosition = function(gameState, force) this.positionLastUpdate = gameState.ai.elapsedTime; }; -PETRA.DefenseArmy.prototype.recalculateStrengths = function(gameState) +DefenseArmy.prototype.recalculateStrengths = function(gameState) { this.ownStrength = 0; this.foeStrength = 0; @@ -475,7 +480,7 @@ PETRA.DefenseArmy.prototype.recalculateStrengths = function(gameState) }; /** adds or remove the strength of the entity either to the enemy or to our units. */ -PETRA.DefenseArmy.prototype.evaluateStrength = function(ent, isOwn, remove) +DefenseArmy.prototype.evaluateStrength = function(ent, isOwn, remove) { if (!ent) return; @@ -489,7 +494,7 @@ PETRA.DefenseArmy.prototype.evaluateStrength = function(ent, isOwn, remove) entStrength = 2; } else - entStrength = PETRA.getMaxStrength(ent, this.Config.debug, this.Config.DamageTypeImportance); + entStrength = getMaxStrength(ent, this.Config.debug, this.Config.DamageTypeImportance); // TODO adapt the getMaxStrength function for animals. // For the time being, just increase it for elephants as the returned value is too small. @@ -505,7 +510,7 @@ PETRA.DefenseArmy.prototype.evaluateStrength = function(ent, isOwn, remove) this.foeStrength += entStrength; }; -PETRA.DefenseArmy.prototype.checkEvents = function(gameState, events) +DefenseArmy.prototype.checkEvents = function(gameState, events) { // Warning the metadata is already cloned in shared.js. Futhermore, changes should be done before destroyEvents // otherwise it would remove the old entity from this army list @@ -564,7 +569,7 @@ PETRA.DefenseArmy.prototype.checkEvents = function(gameState, events) } }; -PETRA.DefenseArmy.prototype.update = function(gameState) +DefenseArmy.prototype.update = function(gameState) { for (const entId of this.ownEntities) { @@ -577,7 +582,7 @@ PETRA.DefenseArmy.prototype.update = function(gameState) else if (orderData.length && orderData[0].target && orderData[0].attackType && orderData[0].attackType === "Capture") { const target = gameState.getEntityById(orderData[0].target); - if (target && !PETRA.allowCapture(gameState, ent, target)) + if (target && !allowCapture(gameState, ent, target)) ent.attack(orderData[0].target, false); } } @@ -629,7 +634,7 @@ PETRA.DefenseArmy.prototype.update = function(gameState) return breakaways; }; -PETRA.DefenseArmy.prototype.Serialize = function() +DefenseArmy.prototype.Serialize = function() { return { "ID": this.ID, @@ -645,7 +650,7 @@ PETRA.DefenseArmy.prototype.Serialize = function() }; }; -PETRA.DefenseArmy.prototype.Deserialize = function(data) +DefenseArmy.prototype.Deserialize = function(data) { for (const key in data) this[key] = data[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/defenseManager.js b/binaries/data/mods/public/simulation/ai/petra/defenseManager.js index 45eed7f2b2..0609d23f38 100644 --- a/binaries/data/mods/public/simulation/ai/petra/defenseManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/defenseManager.js @@ -1,4 +1,11 @@ -PETRA.DefenseManager = function(Config) +import { AttackPlan } from "simulation/ai/petra/attackPlan.js"; +import { DefenseArmy } from "simulation/ai/petra/defenseArmy.js"; +import { allowCapture, getLandAccess, getMaxStrength, isSiegeUnit } from + "simulation/ai/petra/entityExtend.js"; +import { GarrisonManager } from "simulation/ai/petra/garrisonManager.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + +export function DefenseManager(Config) { // Array of "army" Objects. this.armies = []; @@ -12,9 +19,9 @@ PETRA.DefenseManager = function(Config) this.attackingArmies = {}; this.attackingUnits = {}; this.attackedAllies = {}; -}; +} -PETRA.DefenseManager.prototype.update = function(gameState, events) +DefenseManager.prototype.update = function(gameState, events) { Engine.ProfileStart("Defense Manager"); @@ -65,7 +72,7 @@ PETRA.DefenseManager.prototype.update = function(gameState, events) Engine.ProfileStop(); }; -PETRA.DefenseManager.prototype.makeIntoArmy = function(gameState, entityID, type = "default") +DefenseManager.prototype.makeIntoArmy = function(gameState, entityID, type = "default") { if (type == "default") { @@ -74,15 +81,15 @@ PETRA.DefenseManager.prototype.makeIntoArmy = function(gameState, entityID, type return; } - this.armies.push(new PETRA.DefenseArmy(gameState, [entityID], type)); + this.armies.push(new DefenseArmy(gameState, [entityID], type)); }; -PETRA.DefenseManager.prototype.getArmy = function(partOfArmy) +DefenseManager.prototype.getArmy = function(partOfArmy) { return this.armies.find(army => army.ID == partOfArmy); }; -PETRA.DefenseManager.prototype.isDangerous = function(gameState, entity) +DefenseManager.prototype.isDangerous = function(gameState, entity) { if (!entity.position()) return false; @@ -190,7 +197,7 @@ PETRA.DefenseManager.prototype.isDangerous = function(gameState, entity) return false; }; -PETRA.DefenseManager.prototype.checkEnemyUnits = function(gameState) +DefenseManager.prototype.checkEnemyUnits = function(gameState) { const nbPlayers = gameState.sharedScript.playersData.length; const i = gameState.ai.playedTurn % nbPlayers; @@ -266,7 +273,7 @@ PETRA.DefenseManager.prototype.checkEnemyUnits = function(gameState) } }; -PETRA.DefenseManager.prototype.checkEnemyArmies = function(gameState) +DefenseManager.prototype.checkEnemyArmies = function(gameState) { for (let i = 0; i < this.armies.length; ++i) { @@ -379,7 +386,7 @@ PETRA.DefenseManager.prototype.checkEnemyArmies = function(gameState) } }; -PETRA.DefenseManager.prototype.assignDefenders = function(gameState) +DefenseManager.prototype.assignDefenders = function(gameState) { if (!this.armies.length) return; @@ -398,7 +405,7 @@ PETRA.DefenseManager.prototype.assignDefenders = function(gameState) const ent = gameState.getEntityById(entId); if (!ent || !ent.position()) continue; - armyAccess = PETRA.getLandAccess(gameState, ent); + armyAccess = getLandAccess(gameState, ent); break; } if (!armyAccess) @@ -430,8 +437,11 @@ PETRA.DefenseManager.prototype.assignDefenders = function(gameState) { const subrole = ent.getMetadata(PlayerID, "subrole"); if (subrole && - (subrole === PETRA.Worker.SUBROLE_COMPLETING || subrole === PETRA.Worker.SUBROLE_WALKING || subrole === PETRA.Worker.SUBROLE_ATTACKING)) + (subrole === Worker.SUBROLE_COMPLETING || subrole === Worker.SUBROLE_WALKING || + subrole === Worker.SUBROLE_ATTACKING)) + { return; + } } potentialDefenders.push(ent.id()); }); @@ -449,7 +459,7 @@ PETRA.DefenseManager.prototype.assignDefenders = function(gameState) continue; let aMin; let distMin; - const access = ipass == 0 ? PETRA.getLandAccess(gameState, ent) : undefined; + const access = ipass == 0 ? getLandAccess(gameState, ent) : undefined; for (let a = 0; a < armiesNeeding.length; ++a) { if (access && armiesNeeding[a].access != access) @@ -458,7 +468,7 @@ PETRA.DefenseManager.prototype.assignDefenders = function(gameState) // Do not assign defender if it cannot attack at least part of the attacking army. if (!armiesNeeding[a].army.foeEntities.some(eEnt => { const eEntID = gameState.getEntityById(eEnt); - return ent.canAttackTarget(eEntID, PETRA.allowCapture(gameState, ent, eEntID)); + return ent.canAttackTarget(eEntID, allowCapture(gameState, ent, eEntID)); })) continue; @@ -481,7 +491,8 @@ PETRA.DefenseManager.prototype.assignDefenders = function(gameState) else if (aMin === undefined) continue; - armiesNeeding[aMin].need -= PETRA.getMaxStrength(ent, this.Config.debug, this.Config.DamageTypeImportance); + armiesNeeding[aMin].need -= + getMaxStrength(ent, this.Config.debug, this.Config.DamageTypeImportance); armiesNeeding[aMin].army.addOwn(gameState, potentialDefenders[i]); armiesNeeding[aMin].army.assignUnit(gameState, potentialDefenders[i]); potentialDefenders[i] = undefined; @@ -500,7 +511,7 @@ PETRA.DefenseManager.prototype.assignDefenders = function(gameState) gameState.ai.HQ.trainEmergencyUnits(gameState, armiesPos); }; -PETRA.DefenseManager.prototype.abortArmy = function(gameState, army) +DefenseManager.prototype.abortArmy = function(gameState, army) { army.clear(gameState); for (let i = 0; i < this.armies.length; ++i) @@ -518,7 +529,7 @@ PETRA.DefenseManager.prototype.abortArmy = function(gameState, army) * and if a ranged siege unit (not used for defense) is attacked, garrison it in the nearest fortress. * If our hero is attacked with regicide victory condition, the victoryManager will handle it. */ -PETRA.DefenseManager.prototype.checkEvents = function(gameState, events) +DefenseManager.prototype.checkEvents = function(gameState, events) { // Must be called every turn for all armies. for (const army of this.armies) @@ -607,7 +618,7 @@ PETRA.DefenseManager.prototype.checkEvents = function(gameState, events) if (plan !== undefined && plan >= 0) { const attack = gameState.ai.HQ.attackManager.getPlan(plan); - if (attack && attack.state != PETRA.AttackPlan.STATE_UNEXECUTED) + if (attack && attack.state != AttackPlan.STATE_UNEXECUTED) continue; } @@ -689,7 +700,7 @@ PETRA.DefenseManager.prototype.checkEvents = function(gameState, events) continue; if (unitAIStateOrder === "REPAIR" && currentTarget.hasDefensiveFire()) continue; - if (unitAIStateOrder === "COMBAT" && !PETRA.isSiegeUnit(currentTarget) && + if (unitAIStateOrder === "COMBAT" && !isSiegeUnit(currentTarget) && gameState.ai.HQ.capturableTargets.has(orderData[0].target)) { // Take the nearest unit also attacking this structure to help us. @@ -703,8 +714,11 @@ PETRA.DefenseManager.prototype.checkEvents = function(gameState, events) if (allAttacked[entId]) continue; const ent = gameState.getEntityById(entId); - if (!ent || !ent.position() || !ent.canAttackTarget(attacker, PETRA.allowCapture(gameState, ent, attacker))) + if (!ent || !ent.position() || !ent.canAttackTarget(attacker, + allowCapture(gameState, ent, attacker))) + { continue; + } // Check that the unit is still attacking the structure (since the last played turn). const state = ent.unitAIState(); if (!state || !state.split(".")[1] || state.split(".")[1] != "COMBAT") @@ -722,18 +736,18 @@ PETRA.DefenseManager.prototype.checkEvents = function(gameState, events) if (minEnt) { capturableTarget.ents.delete(minEnt.id()); - minEnt.attack(attacker.id(), PETRA.allowCapture(gameState, minEnt, attacker)); + minEnt.attack(attacker.id(), allowCapture(gameState, minEnt, attacker)); } } } - const allowCapture = PETRA.allowCapture(gameState, target, attacker); - if (target.canAttackTarget(attacker, allowCapture)) - target.attack(attacker.id(), allowCapture); + const shouldCapture = allowCapture(gameState, target, attacker); + if (target.canAttackTarget(attacker, shouldCapture)) + target.attack(attacker.id(), shouldCapture); } } }; -PETRA.DefenseManager.prototype.garrisonUnitsInside = function(gameState, target, data) +DefenseManager.prototype.garrisonUnitsInside = function(gameState, target, data) { if (target.hitpoints() < target.garrisonEjectHealth() * target.maxHitpoints()) return false; @@ -750,16 +764,19 @@ PETRA.DefenseManager.prototype.garrisonUnitsInside = function(gameState, target, if (dist >= range*range) return false; } - const access = PETRA.getLandAccess(gameState, target); + const access = getLandAccess(gameState, target); const garrisonManager = gameState.ai.HQ.garrisonManager; const garrisonArrowClasses = target.getGarrisonArrowClasses(); - const typeGarrison = data.type || PETRA.GarrisonManager.TYPE_PROTECTION; + const typeGarrison = data.type || GarrisonManager.TYPE_PROTECTION; let allowMelee = gameState.ai.HQ.garrisonManager.allowMelee(target); if (allowMelee === undefined) { // Should be kept in sync with garrisonManager to avoid garrisoning-ungarrisoning some units. if (data.attacker) - allowMelee = data.attacker.hasClass("Structure") ? data.attacker.attackRange("Ranged") : !PETRA.isSiegeUnit(data.attacker); + { + allowMelee = data.attacker.hasClass("Structure") ? data.attacker.attackRange("Ranged") : + !isSiegeUnit(data.attacker); + } else allowMelee = true; } @@ -768,7 +785,7 @@ PETRA.DefenseManager.prototype.garrisonUnitsInside = function(gameState, target, return false; if (!ent.hasClasses(garrisonArrowClasses)) return false; - if (typeGarrison !== PETRA.GarrisonManager.TYPE_DECAY && !allowMelee && ent.attackTypes().indexOf("Melee") != -1) + if (typeGarrison !== GarrisonManager.TYPE_DECAY && !allowMelee && ent.attackTypes().indexOf("Melee") != -1) return false; if (ent.getMetadata(PlayerID, "transport") !== undefined) return false; @@ -779,11 +796,14 @@ PETRA.DefenseManager.prototype.garrisonUnitsInside = function(gameState, target, { const subrole = ent.getMetadata(PlayerID, "subrole"); // When structure decaying (usually because we've just captured it in enemy territory), also allow units from an attack plan. - if (typeGarrison !== PETRA.GarrisonManager.TYPE_DECAY && subrole && - (subrole === PETRA.Worker.SUBROLE_COMPLETING || subrole === PETRA.Worker.SUBROLE_WALKING || subrole === PETRA.Worker.SUBROLE_ATTACKING)) + if (typeGarrison !== GarrisonManager.TYPE_DECAY && subrole && + (subrole === Worker.SUBROLE_COMPLETING || subrole === Worker.SUBROLE_WALKING || + subrole === Worker.SUBROLE_ATTACKING)) + { return false; + } } - if (PETRA.getLandAccess(gameState, ent) != access) + if (getLandAccess(gameState, ent) != access) return false; return true; }).filterNearest(target.position()); @@ -809,11 +829,11 @@ PETRA.DefenseManager.prototype.garrisonUnitsInside = function(gameState, target, }; /** Garrison a attacked siege ranged unit inside the nearest fortress. */ -PETRA.DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit) +DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit) { let distmin = Math.min(); let nearest; - const unitAccess = PETRA.getLandAccess(gameState, unit); + const unitAccess = getLandAccess(gameState, unit); const garrisonManager = gameState.ai.HQ.garrisonManager; for (const ent of gameState.getAllyStructures().values()) { @@ -825,7 +845,7 @@ PETRA.DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit) continue; if (ent.hitpoints() < ent.garrisonEjectHealth() * ent.maxHitpoints()) continue; - if (PETRA.getLandAccess(gameState, ent) != unitAccess) + if (getLandAccess(gameState, ent) != unitAccess) continue; const dist = API3.SquareVectorDistance(ent.position(), unit.position()); if (dist > distmin) @@ -834,7 +854,7 @@ PETRA.DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit) nearest = ent; } if (nearest) - garrisonManager.garrison(gameState, unit, nearest, PETRA.GarrisonManager.TYPE_PROTECTION); + garrisonManager.garrison(gameState, unit, nearest, GarrisonManager.TYPE_PROTECTION); return nearest !== undefined; }; @@ -843,11 +863,11 @@ PETRA.DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit) * If emergency is true, the unit will be garrisoned in the closest possible structure. * Otherwise, it will garrison in the closest healing structure. */ -PETRA.DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit, emergency = false) +DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit, emergency = false) { let distmin = Math.min(); let nearest; - const unitAccess = PETRA.getLandAccess(gameState, unit); + const unitAccess = getLandAccess(gameState, unit); const garrisonManager = gameState.ai.HQ.garrisonManager; for (const ent of gameState.getAllyStructures().values()) { @@ -862,7 +882,7 @@ PETRA.DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit, continue; if (ent.hitpoints() < ent.garrisonEjectHealth() * ent.maxHitpoints()) continue; - if (PETRA.getLandAccess(gameState, ent) != unitAccess) + if (getLandAccess(gameState, ent) != unitAccess) continue; const dist = API3.SquareVectorDistance(ent.position(), unit.position()); if (dist > distmin) @@ -875,20 +895,21 @@ PETRA.DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit, if (!emergency) { - garrisonManager.garrison(gameState, unit, nearest, PETRA.GarrisonManager.TYPE_PROTECTION); + garrisonManager.garrison(gameState, unit, nearest, GarrisonManager.TYPE_PROTECTION); return true; } if (garrisonManager.numberOfGarrisonedSlots(nearest) >= nearest.garrisonMax()) // make room for this ent nearest.unload(nearest.garrisoned()[0]); - garrisonManager.garrison(gameState, unit, nearest, nearest.buffHeal() ? PETRA.GarrisonManager.TYPE_PROTECTION : PETRA.GarrisonManager.TYPE_EMERGENCY); + garrisonManager.garrison(gameState, unit, nearest, + nearest.buffHeal() ? GarrisonManager.TYPE_PROTECTION : GarrisonManager.TYPE_EMERGENCY); return true; }; /** * Be more inclined to help an ally attacked by several enemies. */ -PETRA.DefenseManager.prototype.GetCooperationLevel = function(ally) +DefenseManager.prototype.GetCooperationLevel = function(ally) { let cooperation = this.Config.personality.cooperative; if (this.attackedAllies[ally] && this.attackedAllies[ally] > 1) @@ -899,7 +920,7 @@ PETRA.DefenseManager.prototype.GetCooperationLevel = function(ally) /** * Switch a defense army into an attack if needed. */ -PETRA.DefenseManager.prototype.switchToAttack = function(gameState, army) +DefenseManager.prototype.switchToAttack = function(gameState, army) { if (!army) return; @@ -908,12 +929,12 @@ PETRA.DefenseManager.prototype.switchToAttack = function(gameState, army) const target = gameState.getEntityById(targetId); if (!target || !target.position() || !gameState.isPlayerEnemy(target.owner())) continue; - const targetAccess = PETRA.getLandAccess(gameState, target); + const targetAccess = getLandAccess(gameState, target); const targetPos = target.position(); for (const entId of army.ownEntities) { const ent = gameState.getEntityById(entId); - if (!ent || !ent.position() || PETRA.getLandAccess(gameState, ent) != targetAccess) + if (!ent || !ent.position() || getLandAccess(gameState, ent) != targetAccess) continue; if (API3.SquareVectorDistance(targetPos, ent.position()) > 14400) continue; @@ -923,7 +944,7 @@ PETRA.DefenseManager.prototype.switchToAttack = function(gameState, army) } }; -PETRA.DefenseManager.prototype.Serialize = function() +DefenseManager.prototype.Serialize = function() { const properties = { "targetList": this.targetList, @@ -940,7 +961,7 @@ PETRA.DefenseManager.prototype.Serialize = function() return { "properties": properties, "armies": armies }; }; -PETRA.DefenseManager.prototype.Deserialize = function(gameState, data) +DefenseManager.prototype.Deserialize = function(gameState, data) { for (const key in data.properties) this[key] = data.properties[key]; @@ -948,7 +969,7 @@ PETRA.DefenseManager.prototype.Deserialize = function(gameState, data) this.armies = []; for (const dataArmy of data.armies) { - const army = new PETRA.DefenseArmy(gameState, []); + const army = new DefenseArmy(gameState, []); army.Deserialize(dataArmy); this.armies.push(army); } diff --git a/binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js b/binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js index afc810990f..565ece4111 100644 --- a/binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js @@ -1,3 +1,5 @@ +import * as chat from "simulation/ai/petra/chatHelper.js"; + /** * Manage the diplomacy: * update our cooperative trait @@ -20,7 +22,7 @@ * sent through AIInterface. It is expected that the other player will change their diplomacy stance to the stance * that we suggested within a period of time, or else the request will be deleted from this.sentDiplomacyRequests. */ -PETRA.DiplomacyManager = function(Config) +export function DiplomacyManager(Config) { this.Config = Config; this.nextTributeUpdate = 90; @@ -32,13 +34,13 @@ PETRA.DiplomacyManager = function(Config) this.receivedDiplomacyRequests = new Map(); this.sentDiplomacyRequests = new Map(); this.sentDiplomacyRequestLapseTime = 120 + randFloat(10, 100); -}; +} /** * If there are any players that are allied/neutral with us but we are not allied/neutral with them, * treat this situation like an ally/neutral request. */ -PETRA.DiplomacyManager.prototype.init = function(gameState) +DiplomacyManager.prototype.init = function(gameState) { this.lastManStandingCheck(gameState); @@ -60,7 +62,7 @@ PETRA.DiplomacyManager.prototype.init = function(gameState) * Check if any allied needs help (tribute) and sent it if we have enough resource * or ask for a tribute if we are in need and one ally can help */ -PETRA.DiplomacyManager.prototype.tributes = function(gameState) +DiplomacyManager.prototype.tributes = function(gameState) { this.nextTributeUpdate = gameState.ai.elapsedTime + 30; const resTribCodes = Resources.GetTributableCodes(); @@ -104,7 +106,7 @@ PETRA.DiplomacyManager.prototype.tributes = function(gameState) { this.nextTributeRequest.set("all", gameState.ai.elapsedTime + 90); this.nextTributeRequest.set(res, gameState.ai.elapsedTime + 240); - PETRA.chatRequestTribute(gameState, res); + chat.requestTribute(gameState, res); if (this.Config.debug > 1) API3.warn("Tribute on " + res + " requested to player " + i); break; @@ -117,12 +119,12 @@ PETRA.DiplomacyManager.prototype.tributes = function(gameState) if (this.Config.debug > 1) API3.warn("Tribute " + uneval(tribute) + " sent to player " + i); if (this.Config.chat) - PETRA.chatSentTribute(gameState, i); + chat.sentTribute(gameState, i); Engine.PostCommand(PlayerID, { "type": "tribute", "player": i, "amounts": tribute }); } }; -PETRA.DiplomacyManager.prototype.checkEvents = function(gameState, events) +DiplomacyManager.prototype.checkEvents = function(gameState, events) { // Increase slowly the cooperative personality trait either when we receive tribute from our allies // or if our allies attack enemies inside our territory @@ -220,7 +222,7 @@ PETRA.DiplomacyManager.prototype.checkEvents = function(gameState, events) { const response = request !== undefined && (request.status === "declinedRequest" || request.status === "allianceBroken") ? "decline" : "declineSuggestNeutral"; - PETRA.chatAnswerRequestDiplomacy(gameState, evt.player, "ally", response); + chat.answerRequestDiplomacy(gameState, evt.player, "ally", response); } else if (gameState.sharedScript.playersData[evt.player].isAlly[PlayerID] && gameState.isPlayerNeutral(evt.player)) this.handleDiplomacyRequest(gameState, evt.player, "ally"); @@ -276,7 +278,7 @@ PETRA.DiplomacyManager.prototype.checkEvents = function(gameState, events) * If the "Last Man Standing" option is enabled, check if the only remaining players are allies or neutral. * If so, turn against the strongest first, but be more likely to first turn against neutral players, if there are any. */ -PETRA.DiplomacyManager.prototype.lastManStandingCheck = function(gameState) +DiplomacyManager.prototype.lastManStandingCheck = function(gameState) { if (gameState.sharedScript.playersData[PlayerID].teamsLocked || gameState.isCeasefireActive() || gameState.getAlliedVictory() && gameState.hasAllies()) @@ -356,7 +358,7 @@ PETRA.DiplomacyManager.prototype.lastManStandingCheck = function(gameState) if (request && request.status !== "allianceBroken") { if (request.status === "waitingForTribute") - PETRA.chatAnswerRequestDiplomacy(gameState, playerToTurnAgainst, request.requestType, "decline"); + chat.answerRequestDiplomacy(gameState, playerToTurnAgainst, request.requestType, "decline"); request.status = request.status === "accepted" ? "allianceBroken" : "declinedRequest"; } // If we had sent this player a diplomacy request, just rescind it @@ -370,7 +372,7 @@ PETRA.DiplomacyManager.prototype.lastManStandingCheck = function(gameState) * Do not become allies with a player if the game would be over. * Overall, be reluctant to become allies with any one player, but be more likely to accept neutral requests. */ -PETRA.DiplomacyManager.prototype.handleDiplomacyRequest = function(gameState, player, requestType) +DiplomacyManager.prototype.handleDiplomacyRequest = function(gameState, player, requestType) { if (gameState.sharedScript.playersData[PlayerID].teamsLocked) return; @@ -431,10 +433,10 @@ PETRA.DiplomacyManager.prototype.handleDiplomacyRequest = function(gameState, pl response = "decline"; } } - PETRA.chatAnswerRequestDiplomacy(gameState, player, requestType, response, requiredTribute); + chat.answerRequestDiplomacy(gameState, player, requestType, response, requiredTribute); }; -PETRA.DiplomacyManager.prototype.changePlayerDiplomacy = function(gameState, player, newDiplomaticStance) +DiplomacyManager.prototype.changePlayerDiplomacy = function(gameState, player, newDiplomaticStance) { if (gameState.isPlayerEnemy(player) && (newDiplomaticStance === "ally" || newDiplomaticStance === "neutral")) gameState.ai.HQ.attackManager.cancelAttacksAgainstPlayer(gameState, player); @@ -442,10 +444,10 @@ PETRA.DiplomacyManager.prototype.changePlayerDiplomacy = function(gameState, pla if (this.Config.debug > 1) API3.warn("diplomacy stance with player " + player + " is now " + newDiplomaticStance); if (this.Config.chat) - PETRA.chatNewDiplomacy(gameState, player, newDiplomaticStance); + chat.newDiplomacy(gameState, player, newDiplomaticStance); }; -PETRA.DiplomacyManager.prototype.checkRequestedTributes = function(gameState) +DiplomacyManager.prototype.checkRequestedTributes = function(gameState) { for (const [player, data] of this.receivedDiplomacyRequests) if (data.status === "waitingForTribute" && gameState.ai.elapsedTime > data.warnTime) @@ -453,13 +455,13 @@ PETRA.DiplomacyManager.prototype.checkRequestedTributes = function(gameState) if (data.sentWarning) { this.receivedDiplomacyRequests.delete(player); - PETRA.chatAnswerRequestDiplomacy(gameState, player, data.requestType, "decline"); + chat.answerRequestDiplomacy(gameState, player, data.requestType, "decline"); } else { data.sentWarning = true; data.warnTime = gameState.ai.elapsedTime + 60; - PETRA.chatAnswerRequestDiplomacy(gameState, player, data.requestType, "waitingForTribute", { + chat.answerRequestDiplomacy(gameState, player, data.requestType, "waitingForTribute", { "wanted": data.wanted, "type": data.type }); @@ -471,7 +473,7 @@ PETRA.DiplomacyManager.prototype.checkRequestedTributes = function(gameState) * Try to become allies with a player who has a lot of mutual enemies in common with us. * TODO: Possibly let human players demand tributes from AIs who send diplomacy requests. */ -PETRA.DiplomacyManager.prototype.sendDiplomacyRequest = function(gameState) +DiplomacyManager.prototype.sendDiplomacyRequest = function(gameState) { let player; let max = 0; @@ -510,21 +512,21 @@ PETRA.DiplomacyManager.prototype.sendDiplomacyRequest = function(gameState) if (this.Config.debug > 0) API3.warn("Sending diplomacy request to player " + player + " with " + requestType); Engine.PostCommand(PlayerID, { "type": "diplomacy-request", "source": PlayerID, "player": player, "to": requestType }); - PETRA.chatNewRequestDiplomacy(gameState, player, requestType, "sendRequest"); + chat.newRequestDiplomacy(gameState, player, requestType, "sendRequest"); }; -PETRA.DiplomacyManager.prototype.checkSentDiplomacyRequests = function(gameState) +DiplomacyManager.prototype.checkSentDiplomacyRequests = function(gameState) { for (const [player, data] of this.sentDiplomacyRequests) if (gameState.ai.elapsedTime > data.timeSent + 60 && !gameState.ai.HQ.saveResources && gameState.getPopulation() > 70) { - PETRA.chatNewRequestDiplomacy(gameState, player, data.requestType, "requestExpired"); + chat.newRequestDiplomacy(gameState, player, data.requestType, "requestExpired"); this.sentDiplomacyRequests.delete(player); } }; -PETRA.DiplomacyManager.prototype.update = function(gameState, events) +DiplomacyManager.prototype.update = function(gameState, events) { this.checkEvents(gameState, events); @@ -552,7 +554,7 @@ PETRA.DiplomacyManager.prototype.update = function(gameState, events) this.checkSentDiplomacyRequests(gameState); }; -PETRA.DiplomacyManager.prototype.Serialize = function() +DiplomacyManager.prototype.Serialize = function() { return { "nextTributeUpdate": this.nextTributeUpdate, @@ -566,7 +568,7 @@ PETRA.DiplomacyManager.prototype.Serialize = function() }; }; -PETRA.DiplomacyManager.prototype.Deserialize = function(data) +DiplomacyManager.prototype.Deserialize = function(data) { for (const key in data) this[key] = data[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/emergencyManager.js b/binaries/data/mods/public/simulation/ai/petra/emergencyManager.js index 82d208219c..8f9c144ea7 100644 --- a/binaries/data/mods/public/simulation/ai/petra/emergencyManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/emergencyManager.js @@ -1,23 +1,25 @@ +import { emergency as chatEmergency } from "simulation/ai/petra/chatHelper.js"; + /** * Checks for emergencies and acts accordingly */ -PETRA.EmergencyManager = function(Config) +export function EmergencyManager(Config) { this.Config = Config; this.referencePopulation = 0; this.referenceStructureCount = 0; this.numRoots = 0; this.hasEmergency = false; -}; +} -PETRA.EmergencyManager.prototype.init = function(gameState) +EmergencyManager.prototype.init = function(gameState) { this.referencePopulation = gameState.getPopulation(); this.referenceStructureCount = gameState.getOwnStructures().length; this.numRoots = this.rootCount(gameState); }; -PETRA.EmergencyManager.prototype.update = function(gameState) +EmergencyManager.prototype.update = function(gameState) { if (this.hasEmergency) { @@ -42,7 +44,7 @@ PETRA.EmergencyManager.prototype.update = function(gameState) this.numRoots = nRoots; }; -PETRA.EmergencyManager.prototype.emergencyUpdate = function(gameState) +EmergencyManager.prototype.emergencyUpdate = function(gameState) { const pop = gameState.getPopulation(); const nStructures = gameState.getOwnStructures().length; @@ -60,7 +62,7 @@ PETRA.EmergencyManager.prototype.emergencyUpdate = function(gameState) } }; -PETRA.EmergencyManager.prototype.rootCount = function(gameState) +EmergencyManager.prototype.rootCount = function(gameState) { let roots = 0; gameState.getOwnStructures().toEntityArray().forEach(ent => { @@ -70,13 +72,13 @@ PETRA.EmergencyManager.prototype.rootCount = function(gameState) return roots; }; -PETRA.EmergencyManager.prototype.setEmergency = function(gameState, enable) +EmergencyManager.prototype.setEmergency = function(gameState, enable) { this.hasEmergency = enable; - PETRA.chatEmergency(gameState, enable); + chatEmergency(gameState, enable); }; -PETRA.EmergencyManager.prototype.Serialize = function() +EmergencyManager.prototype.Serialize = function() { return { "referencePopulation": this.referencePopulation, @@ -86,7 +88,7 @@ PETRA.EmergencyManager.prototype.Serialize = function() }; }; -PETRA.EmergencyManager.prototype.Deserialize = function(data) +EmergencyManager.prototype.Deserialize = function(data) { for (const key in data) this[key] = data[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/entityExtend.js b/binaries/data/mods/public/simulation/ai/petra/entityExtend.js index 04386b11e2..4ed9e2b546 100644 --- a/binaries/data/mods/public/simulation/ai/petra/entityExtend.js +++ b/binaries/data/mods/public/simulation/ai/petra/entityExtend.js @@ -1,18 +1,18 @@ /** returns true if this unit should be considered as a siege unit */ -PETRA.isSiegeUnit = function(ent) +export function isSiegeUnit(ent) { return ent.hasClasses(["Siege", "Elephant+Melee"]); -}; +} /** returns true if this unit should be considered as "fast". */ -PETRA.isFastMoving = function(ent) +export function isFastMoving(ent) { // TODO: use clever logic based on walkspeed comparisons. return ent.hasClass("FastMoving"); -}; +} /** returns some sort of DPS * health factor. If you specify a class, it'll use the modifiers against that class too. */ -PETRA.getMaxStrength = function(ent, debugLevel, DamageTypeImportance, againstClass) +export function getMaxStrength(ent, debugLevel, DamageTypeImportance, againstClass) { let strength = 0; const attackTypes = ent.attackTypes(); @@ -74,22 +74,22 @@ PETRA.getMaxStrength = function(ent, debugLevel, DamageTypeImportance, againstCl // ToDo: Add support for StatusEffects and Capture. return strength * ent.maxHitpoints() / 100.0; -}; +} /** Get access and cache it (except for units as it can change) in metadata if not already done */ -PETRA.getLandAccess = function(gameState, ent) +export function getLandAccess(gameState, ent) { if (ent.hasClass("Unit")) { const pos = ent.position(); if (!pos) { - const holder = PETRA.getHolder(gameState, ent); + const holder = getHolder(gameState, ent); if (holder) - return PETRA.getLandAccess(gameState, holder); + return getLandAccess(gameState, holder); API3.warn("Petra error: entity without position, but not garrisoned"); - PETRA.dumpEntity(ent); + dumpEntity(ent); return undefined; } return gameState.ai.accessibility.getAccessValue(pos); @@ -122,10 +122,10 @@ PETRA.getLandAccess = function(gameState, ent) ent.setMetadata(PlayerID, "access", access); } return access; -}; +} /** Sea access always cached as it never changes */ -PETRA.getSeaAccess = function(gameState, ent) +export function getSeaAccess(gameState, ent) { let sea = ent.getMetadata(PlayerID, "sea"); if (!sea) @@ -149,15 +149,15 @@ PETRA.getSeaAccess = function(gameState, ent) ent.setMetadata(PlayerID, "sea", sea); } return sea; -}; +} -PETRA.setSeaAccess = function(gameState, ent) +export function setSeaAccess(gameState, ent) { - PETRA.getSeaAccess(gameState, ent); -}; + getSeaAccess(gameState, ent); +} /** Decide if we should try to capture (returns true) or destroy (return false) */ -PETRA.allowCapture = function(gameState, ent, target) +export function allowCapture(gameState, ent, target) { if (!target.isCapturable() || !ent.canCapture(target)) return false; @@ -182,7 +182,7 @@ PETRA.allowCapture = function(gameState, ent, target) const capturableTargets = gameState.ai.HQ.capturableTargets; if (!capturableTargets.has(target.id())) { - capture = ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture"); + capture = ent.captureStrength() * getAttackBonus(ent, target, "Capture"); capturableTargets.set(target.id(), { "strength": capture, "ents": new Set([ent.id()]) }); } else @@ -190,7 +190,7 @@ PETRA.allowCapture = function(gameState, ent, target) const capturable = capturableTargets.get(target.id()); if (!capturable.ents.has(ent.id())) { - capturable.strength += ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture"); + capturable.strength += ent.captureStrength() * getAttackBonus(ent, target, "Capture"); capturable.ents.add(ent.id()); } capture = capturable.strength; @@ -200,9 +200,9 @@ PETRA.allowCapture = function(gameState, ent, target) if (target.hasDefensiveFire() && target.isGarrisonHolder() && target.garrisoned()) return capture > antiCapture + sumCapturePoints/50; return capture > antiCapture + sumCapturePoints/80; -}; +} -PETRA.getAttackBonus = function(ent, target, type) +export function getAttackBonus(ent, target, type) { let attackBonus = 1; if (!ent.get("Attack/" + type) || !ent.get("Attack/" + type + "/Bonuses")) @@ -217,10 +217,10 @@ PETRA.getAttackBonus = function(ent, target, type) attackBonus *= bonus.Multiplier; } return attackBonus; -}; +} /** Makes the worker deposit the currently carried resources at the closest accessible dropsite */ -PETRA.returnResources = function(gameState, ent) +export function returnResources(gameState, ent) { if (!ent.resourceCarrying() || !ent.resourceCarrying().length || !ent.position()) return false; @@ -229,7 +229,7 @@ PETRA.returnResources = function(gameState, ent) let closestDropsite; let distmin = Math.min(); - const access = PETRA.getLandAccess(gameState, ent); + const access = getLandAccess(gameState, ent); const dropsiteCollection = gameState.playerData.hasSharedDropsites ? gameState.getAnyDropsites(resource) : gameState.getOwnDropsites(resource); for (const dropsite of dropsiteCollection.values()) @@ -240,7 +240,7 @@ PETRA.returnResources = function(gameState, ent) // owner !== PlayerID can only happen when hasSharedDropsites === true, so no need to test it again if (owner !== PlayerID && (!dropsite.isSharedDropsite() || !gameState.isPlayerMutualAlly(owner))) continue; - if (PETRA.getLandAccess(gameState, dropsite) != access) + if (getLandAccess(gameState, dropsite) != access) continue; const dist = API3.SquareVectorDistance(ent.position(), dropsite.position()); if (dist > distmin) @@ -253,14 +253,14 @@ PETRA.returnResources = function(gameState, ent) return false; ent.returnResources(closestDropsite); return true; -}; +} /** is supply full taking into account gatherers affected during this turn */ -PETRA.IsSupplyFull = function(gameState, ent) +export function isSupplyFull(gameState, ent) { return ent.isFull() === true || ent.resourceSupplyNumGatherers() + gameState.ai.HQ.basesManager.GetTCGatherer(ent.id()) >= ent.maxGatherers(); -}; +} /** * Get the best base (in terms of distance and accessIndex) for an entity. @@ -268,24 +268,24 @@ PETRA.IsSupplyFull = function(gameState, ent) * If nothing found, return the noBase for units and undefined for structures. * If exclude is given, we exclude the base with ID = exclude. */ -PETRA.getBestBase = function(gameState, ent, onlyConstructedBase = false, exclude = false) +export function getBestBase(gameState, ent, onlyConstructedBase = false, exclude = false) { let pos = ent.position(); let accessIndex; if (!pos) { - const holder = PETRA.getHolder(gameState, ent); + const holder = getHolder(gameState, ent); if (!holder || !holder.position()) { API3.warn("Petra error: entity without position, but not garrisoned"); - PETRA.dumpEntity(ent); + dumpEntity(ent); return gameState.ai.HQ.basesManager.baselessBase(); } pos = holder.position(); - accessIndex = PETRA.getLandAccess(gameState, holder); + accessIndex = getLandAccess(gameState, holder); } else - accessIndex = PETRA.getLandAccess(gameState, ent); + accessIndex = getLandAccess(gameState, ent); let distmin = Math.min(); let dist; @@ -326,9 +326,9 @@ PETRA.getBestBase = function(gameState, ent, onlyConstructedBase = false, exclud if (!bestbase && !ent.hasClass("Structure")) bestbase = gameState.ai.HQ.basesManager.baselessBase(); return bestbase; -}; +} -PETRA.getHolder = function(gameState, ent) +export function getHolder(gameState, ent) { for (const holder of gameState.getEntities().values()) { @@ -336,22 +336,22 @@ PETRA.getHolder = function(gameState, ent) return holder; } return undefined; -}; +} /** return the template of the built foundation if a foundation, otherwise return the entity itself */ -PETRA.getBuiltEntity = function(gameState, ent) +export function getBuiltEntity(gameState, ent) { if (ent.foundationProgress() !== undefined) return gameState.getBuiltTemplate(ent.templateName()); return ent; -}; +} /** * return true if it is not worth finishing this building (it would surely decay) * TODO implement the other conditions */ -PETRA.isNotWorthBuilding = function(gameState, ent) +export function isNotWorthBuilding(gameState, ent) { if (gameState.ai.HQ.territoryMap.getOwner(ent.position()) !== PlayerID) { @@ -360,12 +360,12 @@ PETRA.isNotWorthBuilding = function(gameState, ent) return true; } return false; -}; +} /** * Check if the straight line between the two positions crosses an enemy territory */ -PETRA.isLineInsideEnemyTerritory = function(gameState, pos1, pos2, step=70) +export function isLineInsideEnemyTerritory(gameState, pos1, pos2, step=70) { const n = Math.floor(Math.sqrt(API3.SquareVectorDistance(pos1, pos2))/step) + 1; const stepx = (pos2[0] - pos1[0]) / n; @@ -378,9 +378,9 @@ PETRA.isLineInsideEnemyTerritory = function(gameState, pos1, pos2, step=70) return true; } return false; -}; +} -PETRA.gatherTreasure = function(gameState, ent, water = false) +export function gatherTreasure(gameState, ent, water = false) { if (!gameState.ai.HQ.treasures.hasEntities()) return false; @@ -390,16 +390,16 @@ PETRA.gatherTreasure = function(gameState, ent, water = false) return false; let treasureFound; let distmin = Math.min(); - const access = water ? PETRA.getSeaAccess(gameState, ent) : PETRA.getLandAccess(gameState, ent); + const access = water ? getSeaAccess(gameState, ent) : getLandAccess(gameState, ent); for (const treasure of gameState.ai.HQ.treasures.values()) { // let some time for the previous gatherer to reach the treasure before trying again const lastGathered = treasure.getMetadata(PlayerID, "lastGathered"); if (lastGathered && gameState.ai.elapsedTime - lastGathered < 20) continue; - if (!water && access != PETRA.getLandAccess(gameState, treasure)) + if (!water && access != getLandAccess(gameState, treasure)) continue; - if (water && access != PETRA.getSeaAccess(gameState, treasure)) + if (water && access != getSeaAccess(gameState, treasure)) continue; const territoryOwner = gameState.ai.HQ.territoryMap.getOwner(treasure.position()); if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner)) @@ -418,9 +418,9 @@ PETRA.gatherTreasure = function(gameState, ent, water = false) ent.collectTreasure(treasureFound); ent.setMetadata(PlayerID, "treasure", treasureFound.id()); return true; -}; +} -PETRA.dumpEntity = function(ent) +export function dumpEntity(ent) { if (!ent) return; @@ -437,4 +437,4 @@ PETRA.dumpEntity = function(ent) " gather-type " + ent.getMetadata(PlayerID, "gather-type") + " target-foundation " + ent.getMetadata(PlayerID, "target-foundation") + " PartOfArmy " + ent.getMetadata(PlayerID, "PartOfArmy")); -}; +} diff --git a/binaries/data/mods/public/simulation/ai/petra/garrisonManager.js b/binaries/data/mods/public/simulation/ai/petra/garrisonManager.js index 5dffb8bcd4..2e7ee19af9 100644 --- a/binaries/data/mods/public/simulation/ai/petra/garrisonManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/garrisonManager.js @@ -1,3 +1,6 @@ +import { dumpEntity, isSiegeUnit } from "simulation/ai/petra/entityExtend.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + /** * Manage the garrisonHolders * When a unit is ordered to garrison, it must be done through this.garrison() function so that @@ -6,20 +9,20 @@ * Futhermore garrison units have a metadata garrisonType describing its reason (protection, transport, ...) */ -PETRA.GarrisonManager = function(Config) +export function GarrisonManager(Config) { this.Config = Config; this.holders = new Map(); this.decayingStructures = new Map(); -}; +} -PETRA.GarrisonManager.TYPE_FORCE = "force"; -PETRA.GarrisonManager.TYPE_TRADE = "trade"; -PETRA.GarrisonManager.TYPE_PROTECTION = "protection"; -PETRA.GarrisonManager.TYPE_DECAY = "decay"; -PETRA.GarrisonManager.TYPE_EMERGENCY = "emergency"; +GarrisonManager.TYPE_FORCE = "force"; +GarrisonManager.TYPE_TRADE = "trade"; +GarrisonManager.TYPE_PROTECTION = "protection"; +GarrisonManager.TYPE_DECAY = "decay"; +GarrisonManager.TYPE_EMERGENCY = "emergency"; -PETRA.GarrisonManager.prototype.update = function(gameState, events) +GarrisonManager.prototype.update = function(gameState, events) { // First check for possible upgrade of a structure for (const evt of events.EntityRenamed) @@ -115,7 +118,7 @@ PETRA.GarrisonManager.prototype.update = function(gameState, events) API3.warn("Petra garrison error: unit " + ent.id() + " (" + ent.genericName() + ") is expected to garrison in " + id + " (" + holder.genericName() + "), but has no such garrison order " + uneval(ent.unitAIOrderData())); - PETRA.dumpEntity(ent); + dumpEntity(ent); } list.splice(j--, 1); } @@ -151,7 +154,7 @@ PETRA.GarrisonManager.prototype.update = function(gameState, events) continue; if (ent.hasClass("Structure")) around.defenseStructure = true; - else if (PETRA.isSiegeUnit(ent)) + else if (isSiegeUnit(ent)) { if (ent.attackTypes().indexOf("Melee") !== -1) around.meleeSiege = true; @@ -200,12 +203,12 @@ PETRA.GarrisonManager.prototype.update = function(gameState, events) if (!ent || ent.owner() !== PlayerID) this.decayingStructures.delete(id); else if (this.numberOfGarrisonedSlots(ent) < gmin) - gameState.ai.HQ.defenseManager.garrisonUnitsInside(gameState, ent, { "min": gmin, "type": PETRA.GarrisonManager.TYPE_DECAY }); + gameState.ai.HQ.defenseManager.garrisonUnitsInside(gameState, ent, { "min": gmin, "type": GarrisonManager.TYPE_DECAY }); } }; /** TODO should add the units garrisoned inside garrisoned units */ -PETRA.GarrisonManager.prototype.numberOfGarrisonedUnits = function(holder) +GarrisonManager.prototype.numberOfGarrisonedUnits = function(holder) { if (!this.holders.has(holder.id())) return holder.garrisoned().length; @@ -214,7 +217,7 @@ PETRA.GarrisonManager.prototype.numberOfGarrisonedUnits = function(holder) }; /** TODO should add the units garrisoned inside garrisoned units */ -PETRA.GarrisonManager.prototype.numberOfGarrisonedSlots = function(holder) +GarrisonManager.prototype.numberOfGarrisonedSlots = function(holder) { if (!this.holders.has(holder.id())) return holder.garrisonedSlots(); @@ -222,7 +225,7 @@ PETRA.GarrisonManager.prototype.numberOfGarrisonedSlots = function(holder) return holder.garrisonedSlots() + this.holders.get(holder.id()).list.length; }; -PETRA.GarrisonManager.prototype.allowMelee = function(holder) +GarrisonManager.prototype.allowMelee = function(holder) { if (!this.holders.has(holder.id())) return undefined; @@ -231,7 +234,7 @@ PETRA.GarrisonManager.prototype.allowMelee = function(holder) }; /** This is just a pre-garrison state, while the entity walk to the garrison holder */ -PETRA.GarrisonManager.prototype.garrison = function(gameState, ent, holder, type) +GarrisonManager.prototype.garrison = function(gameState, ent, holder, type) { if (this.numberOfGarrisonedSlots(holder) >= holder.garrisonMax() || !ent.canGarrison()) return; @@ -250,7 +253,7 @@ PETRA.GarrisonManager.prototype.garrison = function(gameState, ent, holder, type ent.setMetadata(PlayerID, "plan", -2); else ent.setMetadata(PlayerID, "plan", -3); - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_GARRISONING); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_GARRISONING); ent.setMetadata(PlayerID, "garrisonHolder", holder.id()); ent.setMetadata(PlayerID, "garrisonType", type); ent.garrison(holder); @@ -262,7 +265,7 @@ PETRA.GarrisonManager.prototype.garrison = function(gameState, ent, holder, type This function is for internal use inside garrisonManager. From outside, you should also update the holder and then using cancelGarrison should be the preferred solution */ -PETRA.GarrisonManager.prototype.leaveGarrison = function(ent) +GarrisonManager.prototype.leaveGarrison = function(ent) { ent.setMetadata(PlayerID, "subrole", undefined); if (ent.getMetadata(PlayerID, "plan") === -2) @@ -273,7 +276,7 @@ PETRA.GarrisonManager.prototype.leaveGarrison = function(ent) }; /** Cancel a pre-garrison state */ -PETRA.GarrisonManager.prototype.cancelGarrison = function(ent) +GarrisonManager.prototype.cancelGarrison = function(ent) { ent.stopMoving(); this.leaveGarrison(ent); @@ -286,15 +289,15 @@ PETRA.GarrisonManager.prototype.cancelGarrison = function(ent) list.splice(index, 1); }; -PETRA.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around) +GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around) { switch (ent.getMetadata(PlayerID, "garrisonType")) { - case PETRA.GarrisonManager.TYPE_FORCE: // force the ungarrisoning + case GarrisonManager.TYPE_FORCE: // force the ungarrisoning return false; - case PETRA.GarrisonManager.TYPE_TRADE: // trader garrisoned in ship + case GarrisonManager.TYPE_TRADE: // trader garrisoned in ship return true; - case PETRA.GarrisonManager.TYPE_PROTECTION: // hurt unit for healing or infantry for defense + case GarrisonManager.TYPE_PROTECTION: // hurt unit for healing or infantry for defense { if (holder.buffHeal() && ent.isHealable() && ent.healthLevel() < this.Config.garrisonHealthLevel.high) return true; @@ -312,14 +315,14 @@ PETRA.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around) if (ent.attackTypes() && ent.attackTypes().indexOf("Melee") !== -1) return false; if (around.unit) - return ent.hasClass("Support") || PETRA.isSiegeUnit(ent); // only ranged siege here and below as melee siege already released above - if (PETRA.isSiegeUnit(ent)) + return ent.hasClass("Support") || isSiegeUnit(ent); // only ranged siege here and below as melee siege already released above + if (isSiegeUnit(ent)) return around.meleeSiege; return holder.buffHeal() && ent.needsHeal(); } - case PETRA.GarrisonManager.TYPE_DECAY: + case GarrisonManager.TYPE_DECAY: return ent.captureStrength() && this.decayingStructures.has(holder.id()); - case PETRA.GarrisonManager.TYPE_EMERGENCY: // f.e. hero in regicide mode + case GarrisonManager.TYPE_EMERGENCY: // f.e. hero in regicide mode if (holder.buffHeal() && ent.isHealable() && ent.healthLevel() < this.Config.garrisonHealthLevel.high) return true; if (around.unit || around.defenseStructure || around.meleeSiege || @@ -332,13 +335,13 @@ PETRA.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around) API3.warn("unknown type in garrisonManager " + ent.getMetadata(PlayerID, "garrisonType") + " for " + ent.genericName() + " id " + ent.id() + " inside " + holder.genericName() + " id " + holder.id()); - ent.setMetadata(PlayerID, "garrisonType", PETRA.GarrisonManager.TYPE_PROTECTION); + ent.setMetadata(PlayerID, "garrisonType", GarrisonManager.TYPE_PROTECTION); return true; } }; /** Add this holder in the list managed by the garrisonManager */ -PETRA.GarrisonManager.prototype.registerHolder = function(gameState, holder) +GarrisonManager.prototype.registerHolder = function(gameState, holder) { if (this.holders.has(holder.id())) // already registered return; @@ -351,7 +354,7 @@ PETRA.GarrisonManager.prototype.registerHolder = function(gameState, holder) * do it only for structures useful for defense, except if we are expanding (justCaptured=true) * in which case we also do it for structures useful for unit trainings (TODO only Barracks are done) */ -PETRA.GarrisonManager.prototype.addDecayingStructure = function(gameState, entId, justCaptured) +GarrisonManager.prototype.addDecayingStructure = function(gameState, entId, justCaptured) { if (this.decayingStructures.has(entId)) return true; @@ -365,19 +368,19 @@ PETRA.GarrisonManager.prototype.addDecayingStructure = function(gameState, entId return true; }; -PETRA.GarrisonManager.prototype.removeDecayingStructure = function(entId) +GarrisonManager.prototype.removeDecayingStructure = function(entId) { if (!this.decayingStructures.has(entId)) return; this.decayingStructures.delete(entId); }; -PETRA.GarrisonManager.prototype.Serialize = function() +GarrisonManager.prototype.Serialize = function() { return { "holders": this.holders, "decayingStructures": this.decayingStructures }; }; -PETRA.GarrisonManager.prototype.Deserialize = function(data) +GarrisonManager.prototype.Deserialize = function(data) { for (const key in data) this[key] = data[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/headquarters.js b/binaries/data/mods/public/simulation/ai/petra/headquarters.js index d6ab065115..f1940886ad 100644 --- a/binaries/data/mods/public/simulation/ai/petra/headquarters.js +++ b/binaries/data/mods/public/simulation/ai/petra/headquarters.js @@ -1,3 +1,25 @@ +import { AttackManager } from "simulation/ai/petra/attackManager.js"; +import { AttackPlan } from "simulation/ai/petra/attackPlan.js"; +import { BasesManager } from "simulation/ai/petra/basesManager.js"; +import { BuildManager } from "simulation/ai/petra/buildManager.js"; +import { Config, DIFFICULTY_SANDBOX, DIFFICULTY_EASY } from "simulation/ai/petra/config.js"; +import { DefenseManager } from "simulation/ai/petra/defenseManager.js"; +import { DiplomacyManager } from "simulation/ai/petra/diplomacyManager.js"; +import { EmergencyManager } from "simulation/ai/petra/emergencyManager.js"; +import { allowCapture, getAttackBonus, getLandAccess, getMaxStrength, isLineInsideEnemyTerritory, + setSeaAccess } from "simulation/ai/petra/entityExtend.js"; +import { GarrisonManager } from "simulation/ai/petra/garrisonManager.js"; +import { createBorderMap, createObstructionMap, createTerritoryMap, fullBorder_Mask, fullFrontier_Mask, + largeFrontier_Mask, narrowFrontier_Mask, outside_Mask } from "simulation/ai/petra/mapModule.js"; +import { NavalManager } from "simulation/ai/petra/navalManager.js"; +import { ConstructionPlan } from "simulation/ai/petra/queueplanBuilding.js"; +import { TrainingPlan } from "simulation/ai/petra/queueplanTraining.js"; +import { ResearchManager } from "simulation/ai/petra/researchManager.js"; +import "simulation/ai/petra/startingStrategy.js"; +import { TradeManager } from "simulation/ai/petra/tradeManager.js"; +import { VictoryManager } from "simulation/ai/petra/victoryManager.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + /** * Headquarters * Deal with high level logic for the AI. Most of the interesting stuff gets done here. @@ -11,9 +33,10 @@ * -planning attacks -> attackManager * -picking new CC locations. */ -PETRA.HQ = function(Config) + +export function Headquather(config) { - this.Config = Config; + this.Config = config; this.phasing = 0; // existing values: 0 means no, i > 0 means phasing towards phase i // Cache various quantities. @@ -34,29 +57,29 @@ PETRA.HQ = function(Config) this.extraTowers = Math.round(Math.min(this.Config.difficulty, 3) * this.Config.personality.defensive); this.extraFortresses = Math.round(Math.max(Math.min(this.Config.difficulty - 1, 2), 0) * this.Config.personality.defensive); - this.basesManager = new PETRA.BasesManager(this.Config); - this.attackManager = new PETRA.AttackManager(this.Config); - this.buildManager = new PETRA.BuildManager(); - this.defenseManager = new PETRA.DefenseManager(this.Config); - this.tradeManager = new PETRA.TradeManager(this.Config); - this.navalManager = new PETRA.NavalManager(this.Config); - this.researchManager = new PETRA.ResearchManager(this.Config); - this.diplomacyManager = new PETRA.DiplomacyManager(this.Config); - this.garrisonManager = new PETRA.GarrisonManager(this.Config); - this.victoryManager = new PETRA.VictoryManager(this.Config); - this.emergencyManager = new PETRA.EmergencyManager(this.Config); + this.basesManager = new BasesManager(this.Config); + this.attackManager = new AttackManager(this.Config); + this.buildManager = new BuildManager(); + this.defenseManager = new DefenseManager(this.Config); + this.tradeManager = new TradeManager(this.Config); + this.navalManager = new NavalManager(this.Config); + this.researchManager = new ResearchManager(this.Config); + this.diplomacyManager = new DiplomacyManager(this.Config); + this.garrisonManager = new GarrisonManager(this.Config); + this.victoryManager = new VictoryManager(this.Config); + this.emergencyManager = new EmergencyManager(this.Config); this.capturableTargets = new Map(); this.capturableTargetsTime = 0; -}; +} /** More initialisation for stuff that needs the gameState */ -PETRA.HQ.prototype.init = function(gameState, queues) +Headquather.prototype.init = function(gameState, queues) { - this.territoryMap = PETRA.createTerritoryMap(gameState); + this.territoryMap = createTerritoryMap(gameState); // create borderMap: flag cells on the border of the map // then this map will be completed with our frontier in updateTerritories - this.borderMap = PETRA.createBorderMap(gameState); + this.borderMap = createBorderMap(gameState); // list of allowed regions this.landRegions = {}; // try to determine if we have a water map @@ -73,7 +96,7 @@ PETRA.HQ.prototype.init = function(gameState, queues) /** * initialization needed after deserialization (only called when deserialization) */ -PETRA.HQ.prototype.postinit = function(gameState) +Headquather.prototype.postinit = function(gameState) { this.basesManager.postinit(gameState); this.updateTerritories(gameState); @@ -84,7 +107,7 @@ PETRA.HQ.prototype.postinit = function(gameState) * otherwise return undefined * for the moment, only the case land-sea-land is supported */ -PETRA.HQ.prototype.getSeaBetweenIndices = function(gameState, index1, index2) +Headquather.prototype.getSeaBetweenIndices = function(gameState, index1, index2) { const path = gameState.ai.accessibility.getTrajectToIndex(index1, index2); if (path && path.length == 3 && gameState.ai.accessibility.regionType[path[1]] == "water") @@ -99,7 +122,7 @@ PETRA.HQ.prototype.getSeaBetweenIndices = function(gameState, index1, index2) return undefined; }; -PETRA.HQ.prototype.checkEvents = function(gameState, events) +Headquather.prototype.checkEvents = function(gameState, events) { this.buildManager.checkEvents(gameState, events); @@ -153,16 +176,16 @@ PETRA.HQ.prototype.checkEvents = function(gameState, events) ent.setMetadata(PlayerID, "PartOfArmy", undefined); if (ent.hasClass("Trader")) { - ent.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER); + ent.setMetadata(PlayerID, "role", Worker.ROLE_TRADER); ent.setMetadata(PlayerID, "route", undefined); } if (ent.hasClass("Worker")) { - ent.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_WORKER); - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_IDLE); + ent.setMetadata(PlayerID, "role", Worker.ROLE_WORKER); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE); } if (ent.hasClass("Ship")) - PETRA.setSeaAccess(gameState, ent); + setSeaAccess(gameState, ent); if (!ent.hasClasses(["Support", "Ship"]) && ent.attackTypes() !== undefined) ent.setMetadata(PlayerID, "plan", -1); } @@ -212,7 +235,7 @@ PETRA.HQ.prototype.checkEvents = function(gameState, events) if (plan !== undefined && plan >= 0) { const attack = this.attackManager.getPlan(plan); - if (!attack || attack.state !== PETRA.AttackPlan.STATE_UNEXECUTED) + if (!attack || attack.state !== AttackPlan.STATE_UNEXECUTED) ent.setMetadata(PlayerID, "plan", -1); } } @@ -235,7 +258,7 @@ PETRA.HQ.prototype.checkEvents = function(gameState, events) } // Then deals with decaying structures: destroy them if being lost to enemy (except in easier difficulties) - if (this.Config.difficulty < PETRA.DIFFICULTY_EASY) + if (this.Config.difficulty < DIFFICULTY_EASY) return; for (const entId of this.decayingStructures) { @@ -274,7 +297,7 @@ PETRA.HQ.prototype.checkEvents = function(gameState, events) } }; -PETRA.HQ.prototype.handleNewBase = function(gameState) +Headquather.prototype.handleNewBase = function(gameState) { if (!this.firstBaseConfig) // This is our first base, let us configure our starting resources. @@ -289,7 +312,7 @@ PETRA.HQ.prototype.handleNewBase = function(gameState) }; /** Ensure that all requirements are met when phasing up*/ -PETRA.HQ.prototype.checkPhaseRequirements = function(gameState, queues) +Headquather.prototype.checkPhaseRequirements = function(gameState, queues) { if (gameState.getNumberOfPhases() == this.currentPhase) return; @@ -313,21 +336,21 @@ PETRA.HQ.prototype.checkPhaseRequirements = function(gameState, queues) if (!gameState.getOwnEntitiesByClass("Market", true).hasEntities() && this.canBuild(gameState, "structures/{civ}/market")) { - plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/market", { "phaseUp": true }); + plan = new ConstructionPlan(gameState, "structures/{civ}/market", { "phaseUp": true }); queue = "economicBuilding"; break; } if (!gameState.getOwnEntitiesByClass("Temple", true).hasEntities() && this.canBuild(gameState, "structures/{civ}/temple")) { - plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/temple", { "phaseUp": true }); + plan = new ConstructionPlan(gameState, "structures/{civ}/temple", { "phaseUp": true }); queue = "economicBuilding"; break; } if (!gameState.getOwnEntitiesByClass("Forge", true).hasEntities() && this.canBuild(gameState, "structures/{civ}/forge")) { - plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/forge", { "phaseUp": true }); + plan = new ConstructionPlan(gameState, "structures/{civ}/forge", { "phaseUp": true }); queue = "militaryBuilding"; break; } @@ -341,7 +364,7 @@ PETRA.HQ.prototype.checkPhaseRequirements = function(gameState, queues) { const structure = this.buildManager.findStructureWithClass(gameState, [entityReq.class]); if (structure && this.canBuild(gameState, structure)) - plan = new PETRA.ConstructionPlan(gameState, structure, { "phaseUp": true }); + plan = new ConstructionPlan(gameState, structure, { "phaseUp": true }); } } @@ -364,12 +387,12 @@ PETRA.HQ.prototype.checkPhaseRequirements = function(gameState, queues) }; /** Called by any "phase" research plan once it's started */ -PETRA.HQ.prototype.OnPhaseUp = function(gameState, phase) +Headquather.prototype.OnPhaseUp = function(gameState, phase) { }; /** This code trains citizen workers, trying to keep close to a ratio of worker/soldiers */ -PETRA.HQ.prototype.trainMoreWorkers = function(gameState, queues) +Headquather.prototype.trainMoreWorkers = function(gameState, queues) { // default template const requirementsDef = [ ["costsResource", 1, "food"] ]; @@ -380,7 +403,7 @@ PETRA.HQ.prototype.trainMoreWorkers = function(gameState, queues) let numberOfWorkers = 0; // all workers let numberOfSupports = 0; // only support workers (i.e. non fighting) gameState.getOwnUnits().forEach(ent => { - if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_WORKER && ent.getMetadata(PlayerID, "plan") === undefined) + if (ent.getMetadata(PlayerID, "role") === Worker.ROLE_WORKER && ent.getMetadata(PlayerID, "plan") === undefined) { ++numberOfWorkers; if (ent.hasClass("Support")) @@ -392,7 +415,7 @@ PETRA.HQ.prototype.trainMoreWorkers = function(gameState, queues) for (const item of ent.trainingQueue()) { numberInTraining += item.count; - if (item.metadata && item.metadata.role && item.metadata.role === PETRA.Worker.ROLE_WORKER && + if (item.metadata && item.metadata.role && item.metadata.role === Worker.ROLE_WORKER && item.metadata.plan === undefined) { numberOfWorkers += item.count; @@ -443,8 +466,11 @@ PETRA.HQ.prototype.trainMoreWorkers = function(gameState, queues) let alpha = 0.85; if (!gameState.isTemplateAvailable(gameState.applyCiv("structures/{civ}/field"))) supportRatio = Math.min(this.supportRatio, 0.1); - if (this.attackManager.rushNumber < this.attackManager.maxRushes || this.attackManager.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].length) + if (this.attackManager.rushNumber < this.attackManager.maxRushes || + this.attackManager.upcomingAttacks[AttackPlan.TYPE_RUSH].length) + { alpha = 0.7; + } if (gameState.isCeasefireActive()) alpha += (1 - alpha) * Math.min(Math.max(gameState.ceasefireTimeRemaining - 120, 0), 180) / 180; const supportMax = supportRatio * this.targetNumWorkers; @@ -469,13 +495,13 @@ PETRA.HQ.prototype.trainMoreWorkers = function(gameState, queues) // If the template variable is empty, the default unit (Support unit) will be used // base "0" means automatic choice of base if (!template && templateDef) - queues.villager.addPlan(new PETRA.TrainingPlan(gameState, templateDef, { "role": PETRA.Worker.ROLE_WORKER, "base": 0, "support": true }, size, size)); + queues.villager.addPlan(new TrainingPlan(gameState, templateDef, { "role": Worker.ROLE_WORKER, "base": 0, "support": true }, size, size)); else if (template) - queues.citizenSoldier.addPlan(new PETRA.TrainingPlan(gameState, template, { "role": PETRA.Worker.ROLE_WORKER, "base": 0 }, size, size)); + queues.citizenSoldier.addPlan(new TrainingPlan(gameState, template, { "role": Worker.ROLE_WORKER, "base": 0 }, size, size)); }; /** picks the best template based on parameters and classes */ -PETRA.HQ.prototype.findBestTrainableUnit = function(gameState, classes, requirements) +Headquather.prototype.findBestTrainableUnit = function(gameState, classes, requirements) { let units; if (classes.indexOf("Hero") != -1) @@ -519,13 +545,13 @@ PETRA.HQ.prototype.findBestTrainableUnit = function(gameState, classes, requirem { if (param[0] == "strength") { - aValue += PETRA.getMaxStrength(a[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance) * param[1]; - bValue += PETRA.getMaxStrength(b[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance) * param[1]; + aValue += getMaxStrength(a[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance) * param[1]; + bValue += getMaxStrength(b[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance) * param[1]; } else if (param[0] == "siegeStrength") { - aValue += PETRA.getMaxStrength(a[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance, "Structure") * param[1]; - bValue += PETRA.getMaxStrength(b[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance, "Structure") * param[1]; + aValue += getMaxStrength(a[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance, "Structure") * param[1]; + bValue += getMaxStrength(b[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance, "Structure") * param[1]; } else if (param[0] == "speed") { @@ -560,12 +586,12 @@ PETRA.HQ.prototype.findBestTrainableUnit = function(gameState, classes, requirem * returns an entity collection of workers through BaseManager.pickBuilders * TODO: when same accessIndex, sort by distance */ -PETRA.HQ.prototype.bulkPickWorkers = function(gameState, baseRef, number) +Headquather.prototype.bulkPickWorkers = function(gameState, baseRef, number) { return this.basesManager.bulkPickWorkers(gameState, baseRef, number); }; -PETRA.HQ.prototype.getTotalResourceLevel = function(gameState, resources, proximity) +Headquather.prototype.getTotalResourceLevel = function(gameState, resources, proximity) { return this.basesManager.getTotalResourceLevel(gameState, resources, proximity); }; @@ -574,7 +600,7 @@ PETRA.HQ.prototype.getTotalResourceLevel = function(gameState, resources, proxim * Returns the current gather rate * This is not per-se exact, it performs a few adjustments ad-hoc to account for travel distance, stuffs like that. */ -PETRA.HQ.prototype.GetCurrentGatherRates = function(gameState) +Headquather.prototype.GetCurrentGatherRates = function(gameState) { return this.basesManager.GetCurrentGatherRates(gameState); }; @@ -582,7 +608,7 @@ PETRA.HQ.prototype.GetCurrentGatherRates = function(gameState) /** * Returns the wanted gather rate. */ -PETRA.HQ.prototype.GetWantedGatherRates = function(gameState) +Headquather.prototype.GetWantedGatherRates = function(gameState) { if (!this.turnCache.wantedRates) this.turnCache.wantedRates = gameState.ai.queueManager.wantedGatherRates(gameState); @@ -598,7 +624,7 @@ PETRA.HQ.prototype.GetWantedGatherRates = function(gameState) * We compare; we pick the one where the discrepancy is highest. * Need to balance long-term needs and possible short-term needs. */ -PETRA.HQ.prototype.pickMostNeededResources = function(gameState, allowedResources = []) +Headquather.prototype.pickMostNeededResources = function(gameState, allowedResources = []) { const wantedRates = this.GetWantedGatherRates(gameState); const currentRates = this.GetCurrentGatherRates(gameState); @@ -633,7 +659,7 @@ PETRA.HQ.prototype.pickMostNeededResources = function(gameState, allowedResource * Returns the best position to build a new Civil Center * Whose primary function would be to reach new resources of type "resource". */ -PETRA.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource, proximity, fromStrategic) +Headquather.prototype.findEconomicCCLocation = function(gameState, template, resource, proximity, fromStrategic) { // This builds a map. The procedure is fairly simple. It adds the resource maps // (which are dynamically updated and are made so that they will facilitate DP placement) @@ -642,7 +668,7 @@ PETRA.HQ.prototype.findEconomicCCLocation = function(gameState, template, resour Engine.ProfileStart("findEconomicCCLocation"); // obstruction map - const obstructions = PETRA.createObstructionMap(gameState, 0, template); + const obstructions = createObstructionMap(gameState, 0, template); let halfSize = 0; if (template.get("Footprint/Square")) halfSize = Math.max(+template.get("Footprint/Square/@depth"), +template.get("Footprint/Square/@width")) / 2; @@ -740,7 +766,7 @@ PETRA.HQ.prototype.findEconomicCCLocation = function(gameState, template, resour if (dist < minDist) minDist = dist; - accessible = accessible || index == PETRA.getLandAccess(gameState, cc.ent); + accessible = accessible || index == getLandAccess(gameState, cc.ent); } if (norm == 0) continue; @@ -762,7 +788,7 @@ PETRA.HQ.prototype.findEconomicCCLocation = function(gameState, template, resour } // Not near any of our dropsite, except for oversea docks - oversea = !accessible && dpList.some(dp => PETRA.getLandAccess(gameState, dp.ent) == index); + oversea = !accessible && dpList.some(dp => getLandAccess(gameState, dp.ent) == index); if (!oversea) { for (const dp of dpList) @@ -781,7 +807,7 @@ PETRA.HQ.prototype.findEconomicCCLocation = function(gameState, template, resour continue; } - if (this.borderMap.map[j] & PETRA.fullBorder_Mask) // disfavor the borders of the map + if (this.borderMap.map[j] & fullBorder_Mask) // disfavor the borders of the map norm *= 0.5; let val = 2 * gameState.sharedScript.ccResourceMaps[resource].map[j]; @@ -833,7 +859,7 @@ PETRA.HQ.prototype.findEconomicCCLocation = function(gameState, template, resour * Returns the best position to build a new Civil Center * Whose primary function would be to assure territorial continuity with our allies */ -PETRA.HQ.prototype.findStrategicCCLocation = function(gameState, template) +Headquather.prototype.findStrategicCCLocation = function(gameState, template) { // This builds a map. The procedure is fairly simple. // We minimize the Sum((dist - 300)^2) where the sum is on the three nearest allied CC @@ -856,7 +882,7 @@ PETRA.HQ.prototype.findStrategicCCLocation = function(gameState, template) Engine.ProfileStart("findStrategicCCLocation"); // obstruction map - const obstructions = PETRA.createObstructionMap(gameState, 0, template); + const obstructions = createObstructionMap(gameState, 0, template); let halfSize = 0; if (template.get("Footprint/Square")) halfSize = Math.max(+template.get("Footprint/Square/@depth"), +template.get("Footprint/Square/@width")) / 2; @@ -936,7 +962,7 @@ PETRA.HQ.prototype.findStrategicCCLocation = function(gameState, template) currentVal += delta*delta; } // disfavor border of the map - if (this.borderMap.map[j] & PETRA.fullBorder_Mask) + if (this.borderMap.map[j] & fullBorder_Mask) currentVal += 10000; if (bestVal !== undefined && currentVal > bestVal) @@ -979,7 +1005,7 @@ PETRA.HQ.prototype.findStrategicCCLocation = function(gameState, template) * To do so, we suppose that the gain/distance is an increasing function of distance and look for the max distance * for performance reasons. */ -PETRA.HQ.prototype.findMarketLocation = function(gameState, template) +Headquather.prototype.findMarketLocation = function(gameState, template) { let markets = gameState.updatingCollection("diplo-ExclusiveAllyMarkets", API3.Filters.byClass("Trade"), gameState.getExclusiveAllyEntities()).toEntityArray(); if (!markets.length) @@ -993,7 +1019,7 @@ PETRA.HQ.prototype.findMarketLocation = function(gameState, template) return false; // obstruction map - const obstructions = PETRA.createObstructionMap(gameState, 0, template); + const obstructions = createObstructionMap(gameState, 0, template); let halfSize = 0; if (template.get("Footprint/Square")) halfSize = Math.max(+template.get("Footprint/Square/@depth"), +template.get("Footprint/Square/@width")) / 2; @@ -1016,7 +1042,7 @@ PETRA.HQ.prototype.findMarketLocation = function(gameState, template) for (let j = 0; j < this.territoryMap.length; ++j) { // do not try on the narrow border of our territory - if (this.borderMap.map[j] & PETRA.narrowFrontier_Mask) + if (this.borderMap.map[j] & narrowFrontier_Mask) continue; if (this.baseAtIndex(j) == 0) // only in our territory continue; @@ -1037,12 +1063,12 @@ PETRA.HQ.prototype.findMarketLocation = function(gameState, template) { if (isNavalMarket && template.hasClasses(["Naval+Trade"])) { - if (PETRA.getSeaAccess(gameState, market) != gameState.ai.accessibility.getAccessValue(pos, true)) + if (getSeaAccess(gameState, market) != gameState.ai.accessibility.getAccessValue(pos, true)) continue; gainMultiplier = traderTemplatesGains.navalGainMultiplier; } - else if (PETRA.getLandAccess(gameState, market) == index && - !PETRA.isLineInsideEnemyTerritory(gameState, market.position(), pos)) + else if (getLandAccess(gameState, market) == index && + !isLineInsideEnemyTerritory(gameState, market.position(), pos)) gainMultiplier = traderTemplatesGains.landGainMultiplier; else continue; @@ -1099,7 +1125,7 @@ PETRA.HQ.prototype.findMarketLocation = function(gameState, template) * Returns the best position to build defensive buildings (fortress and towers) * Whose primary function is to defend our borders */ -PETRA.HQ.prototype.findDefensiveLocation = function(gameState, template) +Headquather.prototype.findDefensiveLocation = function(gameState, template) { // We take the point in our territory which is the nearest to any enemy cc // but requiring a minimal distance with our other defensive structures @@ -1132,7 +1158,7 @@ PETRA.HQ.prototype.findDefensiveLocation = function(gameState, template) } // obstruction map - const obstructions = PETRA.createObstructionMap(gameState, 0, template); + const obstructions = createObstructionMap(gameState, 0, template); let halfSize = 0; if (template.get("Footprint/Square")) halfSize = Math.max(+template.get("Footprint/Square/@depth"), +template.get("Footprint/Square/@width")) / 2; @@ -1158,9 +1184,9 @@ PETRA.HQ.prototype.findDefensiveLocation = function(gameState, template) if (!wonderMode) { // do not try if well inside or outside territory - if (!(this.borderMap.map[j] & PETRA.fullFrontier_Mask)) + if (!(this.borderMap.map[j] & fullFrontier_Mask)) continue; - if (this.borderMap.map[j] & PETRA.largeFrontier_Mask && isTower) + if (this.borderMap.map[j] & largeFrontier_Mask && isTower) continue; } if (this.baseAtIndex(j) == 0) // inaccessible cell @@ -1233,7 +1259,7 @@ PETRA.HQ.prototype.findDefensiveLocation = function(gameState, template) return [x, z, this.baseAtIndex(bestJdx)]; }; -PETRA.HQ.prototype.buildTemple = function(gameState, queues) +Headquather.prototype.buildTemple = function(gameState, queues) { // at least one market (which have the same queue) should be build before any temple if (queues.economicBuilding.hasQueuedUnits() || @@ -1249,10 +1275,10 @@ PETRA.HQ.prototype.buildTemple = function(gameState, queues) templateName = "structures/{civ}/temple_vesta"; else if (!this.canBuild(gameState, templateName)) return; - queues.economicBuilding.addPlan(new PETRA.ConstructionPlan(gameState, templateName)); + queues.economicBuilding.addPlan(new ConstructionPlan(gameState, templateName)); }; -PETRA.HQ.prototype.buildMarket = function(gameState, queues) +Headquather.prototype.buildMarket = function(gameState, queues) { if (gameState.getOwnEntitiesByClass("Market", true).hasEntities() || !this.canBuild(gameState, "structures/{civ}/market")) @@ -1282,13 +1308,13 @@ PETRA.HQ.prototype.buildMarket = function(gameState, queues) } gameState.ai.queueManager.changePriority("economicBuilding", 3 * this.Config.priorities.economicBuilding); - const plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/market"); + const plan = new ConstructionPlan(gameState, "structures/{civ}/market"); plan.queueToReset = "economicBuilding"; queues.economicBuilding.addPlan(plan); }; /** Build a farmstead */ -PETRA.HQ.prototype.buildFarmstead = function(gameState, queues) +Headquather.prototype.buildFarmstead = function(gameState, queues) { // Only build one farmstead for the time being ("DropsiteFood" does not refer to CCs) if (gameState.getOwnEntitiesByClass("Farmstead", true).hasEntities()) @@ -1303,14 +1329,14 @@ PETRA.HQ.prototype.buildFarmstead = function(gameState, queues) if (!this.canBuild(gameState, "structures/{civ}/farmstead")) return; - queues.economicBuilding.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/farmstead")); + queues.economicBuilding.addPlan(new ConstructionPlan(gameState, "structures/{civ}/farmstead")); }; /** * Try to build a wonder when required * force = true when called from the victoryManager in case of Wonder victory condition. */ -PETRA.HQ.prototype.buildWonder = function(gameState, queues, force = false) +Headquather.prototype.buildWonder = function(gameState, queues, force = false) { if (queues.wonder && queues.wonder.hasQueuedUnits() || gameState.getOwnEntitiesByClass("Wonder", true).hasEntities() || @@ -1336,11 +1362,11 @@ PETRA.HQ.prototype.buildWonder = function(gameState, queues, force = false) return; } - queues.wonder.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/wonder")); + queues.wonder.addPlan(new ConstructionPlan(gameState, "structures/{civ}/wonder")); }; /** Build a corral, and train animals there */ -PETRA.HQ.prototype.manageCorral = function(gameState, queues) +Headquather.prototype.manageCorral = function(gameState, queues) { if (queues.corral.hasQueuedUnits()) return; @@ -1351,7 +1377,7 @@ PETRA.HQ.prototype.manageCorral = function(gameState, queues) { if (this.canBuild(gameState, "structures/{civ}/corral")) { - queues.corral.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/corral")); + queues.corral.addPlan(new ConstructionPlan(gameState, "structures/{civ}/corral")); return; } if (!nCorral) @@ -1377,7 +1403,7 @@ PETRA.HQ.prototype.manageCorral = function(gameState, queues) count += item.count; if (count > nCorral) continue; - queues.corral.addPlan(new PETRA.TrainingPlan(gameState, trainable, { "trainer": corral.id() })); + queues.corral.addPlan(new TrainingPlan(gameState, trainable, { "trainer": corral.id() })); return; } } @@ -1387,7 +1413,7 @@ PETRA.HQ.prototype.manageCorral = function(gameState, queues) * build more houses if needed. * kinda ugly, lots of special cases to both build enough houses but not tooo many… */ -PETRA.HQ.prototype.buildMoreHouses = function(gameState, queues) +Headquather.prototype.buildMoreHouses = function(gameState, queues) { let houseTemplateString = "structures/{civ}/apartment"; if (!gameState.isTemplateAvailable(gameState.applyCiv(houseTemplateString)) || @@ -1403,7 +1429,7 @@ PETRA.HQ.prototype.buildMoreHouses = function(gameState, queues) const numPlanned = queues.house.length(); if (numPlanned < 3 || numPlanned < 5 && gameState.getPopulation() > 80) { - const plan = new PETRA.ConstructionPlan(gameState, houseTemplateString); + const plan = new ConstructionPlan(gameState, houseTemplateString); // change the starting condition according to the situation. plan.goRequirement = "houseNeeded"; queues.house.addPlan(plan); @@ -1477,7 +1503,7 @@ PETRA.HQ.prototype.buildMoreHouses = function(gameState, queues) }; /** Checks the status of the territory expansion. If no new economic bases created, build some strategic ones. */ -PETRA.HQ.prototype.checkBaseExpansion = function(gameState, queues) +Headquather.prototype.checkBaseExpansion = function(gameState, queues) { if (queues.civilCentre.hasQueuedUnits()) return; @@ -1510,7 +1536,7 @@ PETRA.HQ.prototype.checkBaseExpansion = function(gameState, queues) } }; -PETRA.HQ.prototype.buildNewBase = function(gameState, queues, resource) +Headquather.prototype.buildNewBase = function(gameState, queues, resource) { if (this.hasPotentialBase() && this.currentPhase == 1 && !gameState.isResearching(gameState.getPhaseName(2))) return false; @@ -1539,12 +1565,12 @@ PETRA.HQ.prototype.buildNewBase = function(gameState, queues, resource) // base "-1" means new base. if (this.Config.debug > 1) API3.warn("new base " + gameState.applyCiv(template) + " planned with resource " + resource); - queues.civilCentre.addPlan(new PETRA.ConstructionPlan(gameState, template, { "base": -1, "resource": resource })); + queues.civilCentre.addPlan(new ConstructionPlan(gameState, template, { "base": -1, "resource": resource })); return true; }; /** Deals with building fortresses and towers along our border with enemies. */ -PETRA.HQ.prototype.buildDefenses = function(gameState, queues) +Headquather.prototype.buildDefenses = function(gameState, queues) { if (this.saveResources && !this.canBarter || queues.defenseBuilding.hasQueuedUnits()) return; @@ -1563,7 +1589,7 @@ PETRA.HQ.prototype.buildDefenses = function(gameState, queues) this.fortressStartTime = gameState.ai.elapsedTime; if (!numFortresses) gameState.ai.queueManager.changePriority("defenseBuilding", 2 * this.Config.priorities.defenseBuilding); - const plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/fortress"); + const plan = new ConstructionPlan(gameState, "structures/{civ}/fortress"); plan.queueToReset = "defenseBuilding"; queues.defenseBuilding.addPlan(plan); return; @@ -1579,7 +1605,7 @@ PETRA.HQ.prototype.buildDefenses = function(gameState, queues) if (numTowers < this.Config.Military.numSentryTowers && gameState.ai.elapsedTime > towerLapseTime + this.fortStartTime) { this.fortStartTime = gameState.ai.elapsedTime; - queues.defenseBuilding.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/sentry_tower")); + queues.defenseBuilding.addPlan(new ConstructionPlan(gameState, "structures/{civ}/sentry_tower")); } return; } @@ -1597,13 +1623,13 @@ PETRA.HQ.prototype.buildDefenses = function(gameState, queues) this.towerStartTime = gameState.ai.elapsedTime; if (numTowers > 2 * this.numActiveBases() + 3) gameState.ai.queueManager.changePriority("defenseBuilding", Math.round(0.7 * this.Config.priorities.defenseBuilding)); - const plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/defense_tower"); + const plan = new ConstructionPlan(gameState, "structures/{civ}/defense_tower"); plan.queueToReset = "defenseBuilding"; queues.defenseBuilding.addPlan(plan); } }; -PETRA.HQ.prototype.buildForge = function(gameState, queues) +Headquather.prototype.buildForge = function(gameState, queues) { if (this.getAccountedPopulation(gameState) < this.Config.Military.popForForge || queues.militaryBuilding.hasQueuedUnits() || gameState.getOwnEntitiesByClass("Forge", true).length) @@ -1613,14 +1639,14 @@ PETRA.HQ.prototype.buildForge = function(gameState, queues) return; if (this.canBuild(gameState, "structures/{civ}/forge")) - queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/forge")); + queues.militaryBuilding.addPlan(new ConstructionPlan(gameState, "structures/{civ}/forge")); }; /** * Deals with constructing military buildings (e.g. barracks, stable). * They are mostly defined by Config.js. This is unreliable since changes could be done easily. */ -PETRA.HQ.prototype.constructTrainingBuildings = function(gameState, queues) +Headquather.prototype.constructTrainingBuildings = function(gameState, queues) { if (this.saveResources && !this.canBarter || queues.militaryBuilding.hasQueuedUnits()) return; @@ -1647,7 +1673,7 @@ PETRA.HQ.prototype.constructTrainingBuildings = function(gameState, queues) if (template) { gameState.ai.queueManager.changePriority("militaryBuilding", 2 * this.Config.priorities.militaryBuilding); - const plan = new PETRA.ConstructionPlan(gameState, template, { "militaryBase": true }); + const plan = new ConstructionPlan(gameState, template, { "militaryBase": true }); plan.queueToReset = "militaryBuilding"; queues.militaryBuilding.addPlan(plan); return; @@ -1655,7 +1681,7 @@ PETRA.HQ.prototype.constructTrainingBuildings = function(gameState, queues) } if (numStables == 0 && stableTemplate) { - queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, stableTemplate, { "militaryBase": true })); + queues.militaryBuilding.addPlan(new ConstructionPlan(gameState, stableTemplate, { "militaryBase": true })); return; } @@ -1665,13 +1691,13 @@ PETRA.HQ.prototype.constructTrainingBuildings = function(gameState, queues) const template = numBarracks == 0 ? (barracksTemplate || rangeTemplate) : (rangeTemplate || barracksTemplate); if (template) { - queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, template, { "militaryBase": true })); + queues.militaryBuilding.addPlan(new ConstructionPlan(gameState, template, { "militaryBase": true })); return; } } if (numStables == 1 && stableTemplate && this.getAccountedPopulation(gameState) > this.Config.Military.popForBarracks2) { - queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, stableTemplate, { "militaryBase": true })); + queues.militaryBuilding.addPlan(new ConstructionPlan(gameState, stableTemplate, { "militaryBase": true })); return; } @@ -1681,7 +1707,7 @@ PETRA.HQ.prototype.constructTrainingBuildings = function(gameState, queues) const template = barracksTemplate || stableTemplate || rangeTemplate; if (template) { - queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, template, { "militaryBase": true })); + queues.militaryBuilding.addPlan(new ConstructionPlan(gameState, template, { "militaryBase": true })); return; } } @@ -1695,13 +1721,13 @@ PETRA.HQ.prototype.constructTrainingBuildings = function(gameState, queues) if (this.canBuild(gameState, "structures/{civ}/elephant_stable") && !gameState.getOwnEntitiesByClass("ElephantStable", true).hasEntities()) { - queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/elephant_stable", { "militaryBase": true })); + queues.militaryBuilding.addPlan(new ConstructionPlan(gameState, "structures/{civ}/elephant_stable", { "militaryBase": true })); return; } if (this.canBuild(gameState, "structures/{civ}/arsenal") && !gameState.getOwnEntitiesByClass("Arsenal", true).hasEntities()) { - queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/arsenal", { "militaryBase": true })); + queues.militaryBuilding.addPlan(new ConstructionPlan(gameState, "structures/{civ}/arsenal", { "militaryBase": true })); return; } @@ -1724,9 +1750,9 @@ PETRA.HQ.prototype.constructTrainingBuildings = function(gameState, queues) continue; const civ = gameState.getPlayerCiv(); if (template.hasDefensiveFire() || template.trainableEntities(civ)) - queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, advanced, { "militaryBase": true })); + queues.militaryBuilding.addPlan(new ConstructionPlan(gameState, advanced, { "militaryBase": true })); else // not a military building, but still use this queue - queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, advanced)); + queues.militaryBuilding.addPlan(new ConstructionPlan(gameState, advanced)); return; } } @@ -1735,7 +1761,7 @@ PETRA.HQ.prototype.constructTrainingBuildings = function(gameState, queues) /** * Find base nearest to ennemies for military buildings. */ -PETRA.HQ.prototype.findBestBaseForMilitary = function(gameState) +Headquather.prototype.findBestBaseForMilitary = function(gameState) { const ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")).toEntityArray(); let bestBase; @@ -1747,13 +1773,13 @@ PETRA.HQ.prototype.findBestBaseForMilitary = function(gameState) continue; if (enemyFound && !gameState.isPlayerEnemy(cce.owner())) continue; - const access = PETRA.getLandAccess(gameState, cce); + const access = getLandAccess(gameState, cce); const isEnemy = gameState.isPlayerEnemy(cce.owner()); for (const cc of ccEnts) { if (cc.owner() != PlayerID) continue; - if (PETRA.getLandAccess(gameState, cc) != access) + if (getLandAccess(gameState, cc) != access) continue; const dist = API3.SquareVectorDistance(cc.position(), cce.position()); if (!enemyFound && isEnemy) @@ -1771,7 +1797,7 @@ PETRA.HQ.prototype.findBestBaseForMilitary = function(gameState) * train with highest priority ranged infantry in the nearest civil center from a given set of positions * and garrison them there for defense */ -PETRA.HQ.prototype.trainEmergencyUnits = function(gameState, positions) +Headquather.prototype.trainEmergencyUnits = function(gameState, positions) { if (gameState.ai.queues.emergency.hasQueuedUnits()) return false; @@ -1789,7 +1815,7 @@ PETRA.HQ.prototype.trainEmergencyUnits = function(gameState, positions) { if (!base.anchor || !base.anchor.position()) continue; - if (PETRA.getLandAccess(gameState, base.anchor) != access) + if (getLandAccess(gameState, base.anchor) != access) continue; if (!base.anchor.trainableEntities(civ)) // base still in construction continue; @@ -1869,14 +1895,14 @@ PETRA.HQ.prototype.trainEmergencyUnits = function(gameState, positions) break; } } - const metadata = { "role": PETRA.Worker.ROLE_WORKER, "base": nearestAnchor.getMetadata(PlayerID, "base"), "plan": -1, "trainer": nearestAnchor.id() }; + const metadata = { "role": Worker.ROLE_WORKER, "base": nearestAnchor.getMetadata(PlayerID, "base"), "plan": -1, "trainer": nearestAnchor.id() }; if (autogarrison) - metadata.garrisonType = PETRA.GarrisonManager.TYPE_PROTECTION; - gameState.ai.queues.emergency.addPlan(new PETRA.TrainingPlan(gameState, templateFound[0], metadata, 1, 1)); + metadata.garrisonType = GarrisonManager.TYPE_PROTECTION; + gameState.ai.queues.emergency.addPlan(new TrainingPlan(gameState, templateFound[0], metadata, 1, 1)); return true; }; -PETRA.HQ.prototype.canBuild = function(gameState, structure) +Headquather.prototype.canBuild = function(gameState, structure) { const type = gameState.applyCiv(structure); if (this.buildManager.isUnbuildable(gameState, type)) @@ -1930,7 +1956,7 @@ PETRA.HQ.prototype.canBuild = function(gameState, structure) return true; }; -PETRA.HQ.prototype.updateTerritories = function(gameState) +Headquather.prototype.updateTerritories = function(gameState) { const around = [ [-0.7, 0.7], [0, 1], [0.7, 0.7], [1, 0], [0.7, -0.7], [0, -1], [-0.7, -0.7], [-1, 0] ]; const alliedVictory = gameState.getAlliedVictory(); @@ -1943,10 +1969,10 @@ PETRA.HQ.prototype.updateTerritories = function(gameState) for (let j = 0; j < this.territoryMap.length; ++j) { - if (this.borderMap.map[j] & PETRA.outside_Mask) + if (this.borderMap.map[j] & outside_Mask) continue; - if (this.borderMap.map[j] & PETRA.fullFrontier_Mask) - this.borderMap.map[j] &= ~PETRA.fullFrontier_Mask; // reset the frontier + if (this.borderMap.map[j] & fullFrontier_Mask) + this.borderMap.map[j] &= ~fullFrontier_Mask; // reset the frontier if (this.territoryMap.getOwnerIndex(j) != PlayerID) this.basesManager.removeBaseFromTerritoryIndex(j); @@ -1964,12 +1990,12 @@ PETRA.HQ.prototype.updateTerritories = function(gameState) let jz = iz + Math.round(insideSmall*a[1]); if (jz < 0 || jz >= width) continue; - if (this.borderMap.map[jx+width*jz] & PETRA.outside_Mask) + if (this.borderMap.map[jx+width*jz] & outside_Mask) continue; let territoryOwner = this.territoryMap.getOwnerIndex(jx+width*jz); if (territoryOwner != PlayerID && !(alliedVictory && gameState.isPlayerAlly(territoryOwner))) { - this.borderMap.map[j] |= PETRA.narrowFrontier_Mask; + this.borderMap.map[j] |= narrowFrontier_Mask; break; } jx = ix + Math.round(insideLarge*a[0]); @@ -1978,14 +2004,14 @@ PETRA.HQ.prototype.updateTerritories = function(gameState) jz = iz + Math.round(insideLarge*a[1]); if (jz < 0 || jz >= width) continue; - if (this.borderMap.map[jx+width*jz] & PETRA.outside_Mask) + if (this.borderMap.map[jx+width*jz] & outside_Mask) continue; territoryOwner = this.territoryMap.getOwnerIndex(jx+width*jz); if (territoryOwner != PlayerID && !(alliedVictory && gameState.isPlayerAlly(territoryOwner))) onFrontier = true; } - if (onFrontier && !(this.borderMap.map[j] & PETRA.narrowFrontier_Mask)) - this.borderMap.map[j] |= PETRA.largeFrontier_Mask; + if (onFrontier && !(this.borderMap.map[j] & narrowFrontier_Mask)) + this.borderMap.map[j] |= largeFrontier_Mask; if (this.basesManager.addTerritoryIndexToBase(gameState, j, passabilityMap)) expansion++; @@ -2005,7 +2031,7 @@ PETRA.HQ.prototype.updateTerritories = function(gameState) /** * returns the base corresponding to baseID */ -PETRA.HQ.prototype.getBaseByID = function(baseID) +Headquather.prototype.getBaseByID = function(baseID) { return this.basesManager.getBaseByID(baseID); }; @@ -2015,33 +2041,33 @@ PETRA.HQ.prototype.getBaseByID = function(baseID) * ActiveBases includes only those with a built cc * PotentialBases includes also those with a cc in construction */ -PETRA.HQ.prototype.numActiveBases = function() +Headquather.prototype.numActiveBases = function() { return this.basesManager.numActiveBases(); }; -PETRA.HQ.prototype.hasActiveBase = function() +Headquather.prototype.hasActiveBase = function() { return this.basesManager.hasActiveBase(); }; -PETRA.HQ.prototype.numPotentialBases = function() +Headquather.prototype.numPotentialBases = function() { return this.basesManager.numPotentialBases(); }; -PETRA.HQ.prototype.hasPotentialBase = function() +Headquather.prototype.hasPotentialBase = function() { return this.basesManager.hasPotentialBase(); }; -PETRA.HQ.prototype.isDangerousLocation = function(gameState, pos, radius) +Headquather.prototype.isDangerousLocation = function(gameState, pos, radius) { return this.isNearInvadingArmy(pos) || this.isUnderEnemyFire(gameState, pos, radius); }; /** Check that the chosen position is not too near from an invading army */ -PETRA.HQ.prototype.isNearInvadingArmy = function(pos) +Headquather.prototype.isNearInvadingArmy = function(pos) { for (const army of this.defenseManager.armies) if (army.foePosition && API3.SquareVectorDistance(army.foePosition, pos) < 12000) @@ -2049,7 +2075,7 @@ PETRA.HQ.prototype.isNearInvadingArmy = function(pos) return false; }; -PETRA.HQ.prototype.isUnderEnemyFire = function(gameState, pos, radius = 0) +Headquather.prototype.isUnderEnemyFire = function(gameState, pos, radius = 0) { if (!this.turnCache.firingStructures) this.turnCache.firingStructures = gameState.updatingCollection("diplo-FiringStructures", API3.Filters.hasDefensiveFire(), gameState.getEnemyStructures()); @@ -2063,7 +2089,7 @@ PETRA.HQ.prototype.isUnderEnemyFire = function(gameState, pos, radius = 0) }; /** Compute the capture strength of all units attacking a capturable target */ -PETRA.HQ.prototype.updateCaptureStrength = function(gameState) +Headquather.prototype.updateCaptureStrength = function(gameState) { this.capturableTargets.clear(); for (const ent of gameState.getOwnUnits().values()) @@ -2082,13 +2108,13 @@ PETRA.HQ.prototype.updateCaptureStrength = function(gameState) continue; if (!this.capturableTargets.has(targetId)) this.capturableTargets.set(targetId, { - "strength": ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture"), + "strength": ent.captureStrength() * getAttackBonus(ent, target, "Capture"), "ents": new Set([ent.id()]) }); else { const capturableTarget = this.capturableTargets.get(target.id()); - capturableTarget.strength += ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture"); + capturableTarget.strength += ent.captureStrength() * getAttackBonus(ent, target, "Capture"); capturableTarget.ents.add(ent.id()); } } @@ -2096,17 +2122,17 @@ PETRA.HQ.prototype.updateCaptureStrength = function(gameState) for (const [targetId, capturableTarget] of this.capturableTargets) { const target = gameState.getEntityById(targetId); - let allowCapture; + let shouldCapture; for (const entId of capturableTarget.ents) { const ent = gameState.getEntityById(entId); - if (allowCapture === undefined) - allowCapture = PETRA.allowCapture(gameState, ent, target); + if (shouldCapture === undefined) + shouldCapture = allowCapture(gameState, ent, target); const orderData = ent.unitAIOrderData(); if (!orderData || !orderData.length || !orderData[0].attackType) continue; - if ((orderData[0].attackType == "Capture") !== allowCapture) - ent.attack(targetId, allowCapture); + if ((orderData[0].attackType == "Capture") !== shouldCapture) + ent.attack(targetId, shouldCapture); } } @@ -2116,7 +2142,7 @@ PETRA.HQ.prototype.updateCaptureStrength = function(gameState) /** * Check if a structure in blinking territory should/can be defended (currently if it has some attacking armies around) */ -PETRA.HQ.prototype.isDefendable = function(ent) +Headquather.prototype.isDefendable = function(ent) { if (!this.turnCache.numAround) this.turnCache.numAround = {}; @@ -2128,7 +2154,7 @@ PETRA.HQ.prototype.isDefendable = function(ent) /** * Get the number of population already accounted for */ -PETRA.HQ.prototype.getAccountedPopulation = function(gameState) +Headquather.prototype.getAccountedPopulation = function(gameState) { if (this.turnCache.accountedPopulation == undefined) { @@ -2152,16 +2178,16 @@ PETRA.HQ.prototype.getAccountedPopulation = function(gameState) /** * Get the number of workers already accounted for */ -PETRA.HQ.prototype.getAccountedWorkers = function(gameState) +Headquather.prototype.getAccountedWorkers = function(gameState) { if (this.turnCache.accountedWorkers == undefined) { - let workers = gameState.getOwnEntitiesByRole(PETRA.Worker.ROLE_WORKER, true).length; + let workers = gameState.getOwnEntitiesByRole(Worker.ROLE_WORKER, true).length; for (const ent of gameState.getOwnTrainingFacilities().values()) { for (const item of ent.trainingQueue()) { - if (!item.metadata || !item.metadata.role || item.metadata.role !== PETRA.Worker.ROLE_WORKER) + if (!item.metadata || !item.metadata.role || item.metadata.role !== Worker.ROLE_WORKER) continue; workers += item.count; } @@ -2171,7 +2197,7 @@ PETRA.HQ.prototype.getAccountedWorkers = function(gameState) return this.turnCache.accountedWorkers; }; -PETRA.HQ.prototype.baseManagers = function() +Headquather.prototype.baseManagers = function() { return this.basesManager.baseManagers; }; @@ -2180,7 +2206,7 @@ PETRA.HQ.prototype.baseManagers = function() * @param {number} territoryIndex - The index to get the map for. * @return {number} - The ID of the base at the given territory index. */ -PETRA.HQ.prototype.baseAtIndex = function(territoryIndex) +Headquather.prototype.baseAtIndex = function(territoryIndex) { return this.basesManager.baseAtIndex(territoryIndex); }; @@ -2189,12 +2215,12 @@ PETRA.HQ.prototype.baseAtIndex = function(territoryIndex) * Some functions are run every turn * Others once in a while */ -PETRA.HQ.prototype.update = function(gameState, queues, events) +Headquather.prototype.update = function(gameState, queues, events) { Engine.ProfileStart("Headquarters update"); this.emergencyManager.update(gameState); this.turnCache = {}; - this.territoryMap = PETRA.createTerritoryMap(gameState); + this.territoryMap = createTerritoryMap(gameState); this.canBarter = gameState.getOwnEntitiesByClass("Market", true).filter(API3.Filters.isBuilt()).hasEntities(); // TODO find a better way to update if (this.currentPhase != gameState.currentPhase()) @@ -2217,7 +2243,7 @@ PETRA.HQ.prototype.update = function(gameState, queues, events) gameState.getOwnUnits().forEach (function (ent) { if (!ent.position()) return; - PETRA.dumpEntity(ent); + dumpEntity(ent); }); } */ @@ -2276,7 +2302,7 @@ PETRA.HQ.prototype.update = function(gameState, queues, events) if (gameState.ai.playedTurn % 3 == 0) { this.constructTrainingBuildings(gameState, queues); - if (this.Config.difficulty > PETRA.DIFFICULTY_SANDBOX) + if (this.Config.difficulty > DIFFICULTY_SANDBOX) this.buildDefenses(gameState, queues); } @@ -2284,7 +2310,7 @@ PETRA.HQ.prototype.update = function(gameState, queues, events) this.navalManager.update(gameState, queues, events); - if (this.Config.difficulty > PETRA.DIFFICULTY_SANDBOX && (this.hasActiveBase() || !this.canBuildUnits)) + if (this.Config.difficulty > DIFFICULTY_SANDBOX && (this.hasActiveBase() || !this.canBuildUnits)) this.attackManager.update(gameState, queues, events); this.diplomacyManager.update(gameState, events); @@ -2298,7 +2324,7 @@ PETRA.HQ.prototype.update = function(gameState, queues, events) Engine.ProfileStop(); }; -PETRA.HQ.prototype.Serialize = function() +Headquather.prototype.Serialize = function() { const properties = { "phasing": this.phasing, @@ -2360,47 +2386,47 @@ PETRA.HQ.prototype.Serialize = function() }; }; -PETRA.HQ.prototype.Deserialize = function(gameState, data) +Headquather.prototype.Deserialize = function(gameState, data) { for (const key in data.properties) this[key] = data.properties[key]; - this.basesManager = new PETRA.BasesManager(this.Config); + this.basesManager = new BasesManager(this.Config); this.basesManager.init(gameState); this.basesManager.Deserialize(gameState, data.basesManager); - this.navalManager = new PETRA.NavalManager(this.Config); + this.navalManager = new NavalManager(this.Config); this.navalManager.init(gameState, true); this.navalManager.Deserialize(gameState, data.navalManager); - this.attackManager = new PETRA.AttackManager(this.Config); + this.attackManager = new AttackManager(this.Config); this.attackManager.Deserialize(gameState, data.attackManager); this.attackManager.init(gameState); this.attackManager.Deserialize(gameState, data.attackManager); - this.buildManager = new PETRA.BuildManager(); + this.buildManager = new BuildManager(); this.buildManager.Deserialize(data.buildManager); - this.defenseManager = new PETRA.DefenseManager(this.Config); + this.defenseManager = new DefenseManager(this.Config); this.defenseManager.Deserialize(gameState, data.defenseManager); - this.tradeManager = new PETRA.TradeManager(this.Config); + this.tradeManager = new TradeManager(this.Config); this.tradeManager.init(gameState); this.tradeManager.Deserialize(gameState, data.tradeManager); - this.researchManager = new PETRA.ResearchManager(this.Config); + this.researchManager = new ResearchManager(this.Config); this.researchManager.Deserialize(data.researchManager); - this.diplomacyManager = new PETRA.DiplomacyManager(this.Config); + this.diplomacyManager = new DiplomacyManager(this.Config); this.diplomacyManager.Deserialize(data.diplomacyManager); - this.garrisonManager = new PETRA.GarrisonManager(this.Config); + this.garrisonManager = new GarrisonManager(this.Config); this.garrisonManager.Deserialize(data.garrisonManager); - this.victoryManager = new PETRA.VictoryManager(this.Config); + this.victoryManager = new VictoryManager(this.Config); this.victoryManager.Deserialize(data.victoryManager); - this.emergencyManager = new PETRA.EmergencyManager(this.Config); + this.emergencyManager = new EmergencyManager(this.Config); this.emergencyManager.Deserialize(data.emergencyManager); }; diff --git a/binaries/data/mods/public/simulation/ai/petra/mapModule.js b/binaries/data/mods/public/simulation/ai/petra/mapModule.js index f5edb3d8b2..25b9626b3e 100644 --- a/binaries/data/mods/public/simulation/ai/petra/mapModule.js +++ b/binaries/data/mods/public/simulation/ai/petra/mapModule.js @@ -1,9 +1,11 @@ /** map functions */ -PETRA.TERRITORY_PLAYER_MASK = 0x1F; -PETRA.TERRITORY_BLINKING_MASK = 0x40; +/* eslint-disable prefer-const -- Mods should be able to change them */ +let TERRITORY_PLAYER_MASK = 0x1F; +let TERRITORY_BLINKING_MASK = 0x40; +/* eslint-enable prefer-const */ -PETRA.createObstructionMap = function(gameState, accessIndex, template) +export function createObstructionMap(gameState, accessIndex, template) { const passabilityMap = gameState.getPassabilityMap(); const territoryMap = gameState.ai.territoryMap; @@ -41,8 +43,8 @@ PETRA.createObstructionMap = function(gameState, accessIndex, template) for (let k = 0; k < territoryMap.data.length; ++k) { - const tilePlayer = territoryMap.data[k] & PETRA.TERRITORY_PLAYER_MASK; - const isConnected = (territoryMap.data[k] & PETRA.TERRITORY_BLINKING_MASK) == 0; + const tilePlayer = territoryMap.data[k] & TERRITORY_PLAYER_MASK; + const isConnected = (territoryMap.data[k] & TERRITORY_BLINKING_MASK) == 0; if (tilePlayer === PlayerID) { if (!buildOwn || !buildNeutral && !isConnected) @@ -108,19 +110,19 @@ PETRA.createObstructionMap = function(gameState, accessIndex, template) } return map; -}; +} -PETRA.createTerritoryMap = function(gameState) +export function createTerritoryMap(gameState) { const map = gameState.ai.territoryMap; const ret = new API3.Map(gameState.sharedScript, "territory", map.data); - ret.getOwner = function(p) { return this.point(p) & PETRA.TERRITORY_PLAYER_MASK; }; - ret.getOwnerIndex = function(p) { return this.map[p] & PETRA.TERRITORY_PLAYER_MASK; }; - ret.isBlinking = function(p) { return (this.point(p) & PETRA.TERRITORY_BLINKING_MASK) != 0; }; + ret.getOwner = function(p) { return this.point(p) & TERRITORY_PLAYER_MASK; }; + ret.getOwnerIndex = function(p) { return this.map[p] & TERRITORY_PLAYER_MASK; }; + ret.isBlinking = function(p) { return (this.point(p) & TERRITORY_BLINKING_MASK) != 0; }; return ret; -}; +} /** * The borderMap contains some border and frontier information: @@ -132,14 +134,16 @@ PETRA.createTerritoryMap = function(gameState) * - large border (inside our territory, exclusive of narrow) => bit 3 */ -PETRA.outside_Mask = 1; -PETRA.border_Mask = 2; -PETRA.fullBorder_Mask = PETRA.outside_Mask | PETRA.border_Mask; -PETRA.narrowFrontier_Mask = 4; -PETRA.largeFrontier_Mask = 8; -PETRA.fullFrontier_Mask = PETRA.narrowFrontier_Mask | PETRA.largeFrontier_Mask; +/* eslint-disable prefer-const -- Mods should be able to change them */ +export let outside_Mask = 1; +export let border_Mask = 2; +export let fullBorder_Mask = outside_Mask | border_Mask; +export let narrowFrontier_Mask = 4; +export let largeFrontier_Mask = 8; +export let fullFrontier_Mask = narrowFrontier_Mask | largeFrontier_Mask; +/* eslint-enable prefer-const */ -PETRA.createBorderMap = function(gameState) +export function createBorderMap(gameState) { const map = new API3.Map(gameState.sharedScript, "territory"); const width = map.width; @@ -157,13 +161,13 @@ PETRA.createBorderMap = function(gameState) const radius = dx*dx + dy*dy; if (radius < radcut) continue; - map.map[j] = PETRA.outside_Mask; + map.map[j] = outside_Mask; const ind = API3.getMapIndices(j, map, passabilityMap); for (const k of ind) { if (passabilityMap.data[k] & obstructionMask) continue; - map.map[j] = PETRA.border_Mask; + map.map[j] = border_Mask; break; } } @@ -177,13 +181,13 @@ PETRA.createBorderMap = function(gameState) const iy = Math.floor(j/width); if (ix < border || ix >= borderCut || iy < border || iy >= borderCut) { - map.map[j] = PETRA.outside_Mask; + map.map[j] = outside_Mask; const ind = API3.getMapIndices(j, map, passabilityMap); for (const k of ind) { if (passabilityMap.data[k] & obstructionMask) continue; - map.map[j] = PETRA.border_Mask; + map.map[j] = border_Mask; break; } } @@ -192,9 +196,9 @@ PETRA.createBorderMap = function(gameState) // map.dumpIm("border.png", 5); return map; -}; +} -PETRA.debugMap = function(gameState, map) +function debugMap(gameState, map) { const width = map.width; const cell = map.cellSize; @@ -212,4 +216,4 @@ PETRA.debugMap = function(gameState, map) else if (map.map[id] == 3) Engine.PostCommand(PlayerID, { "type": "set-shading-color", "entities": [ent.id()], "rgb": [0, 0, 2] }); }); -}; +} diff --git a/binaries/data/mods/public/simulation/ai/petra/navalManager.js b/binaries/data/mods/public/simulation/ai/petra/navalManager.js index d203969965..cbcfcf187e 100644 --- a/binaries/data/mods/public/simulation/ai/petra/navalManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/navalManager.js @@ -1,3 +1,10 @@ +import { gatherTreasure, getLandAccess, getSeaAccess, isSiegeUnit, setSeaAccess } from + "simulation/ai/petra/entityExtend.js"; +import { ConstructionPlan } from "simulation/ai/petra/queueplanBuilding.js"; +import { TrainingPlan } from "simulation/ai/petra/queueplanTraining.js"; +import { TransportPlan } from "simulation/ai/petra/transportPlan.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + /** * Naval Manager * Will deal with anything ships. @@ -7,7 +14,7 @@ * -Scouting, ultimately. * Also deals with handling docks, making sure we have access and stuffs like that. */ -PETRA.NavalManager = function(Config) +export function NavalManager(Config) { this.Config = Config; @@ -29,16 +36,17 @@ PETRA.NavalManager = function(Config) // shore-line regions where we can load and unload units this.landingZones = {}; -}; +} /** More initialisation for stuff that needs the gameState */ -PETRA.NavalManager.prototype.init = function(gameState, deserializing) +NavalManager.prototype.init = function(gameState, deserializing) { // docks this.docks = gameState.getOwnStructures().filter(API3.Filters.byClasses(["Dock", "Shipyard"])); this.docks.registerUpdates(); - this.ships = gameState.getOwnUnits().filter(API3.Filters.and(API3.Filters.byClass("Ship"), API3.Filters.not(API3.Filters.byMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER)))); + this.ships = gameState.getOwnUnits().filter(API3.Filters.and(API3.Filters.byClass("Ship"), + API3.Filters.not(API3.Filters.byMetadata(PlayerID, "role", Worker.ROLE_TRADER)))); // note: those two can overlap (some transport ships are warships too and vice-versa). this.transportShips = this.ships.filter(API3.Filters.and(API3.Filters.byCanGarrison(), API3.Filters.not(API3.Filters.byClass("FishingBoat")))); this.warShips = this.ships.filter(API3.Filters.byClass("Warship")); @@ -152,18 +160,18 @@ PETRA.NavalManager.prototype.init = function(gameState, deserializing) // Assign our initial docks and ships for (const ship of this.ships.values()) - PETRA.setSeaAccess(gameState, ship); + setSeaAccess(gameState, ship); for (const dock of this.docks.values()) - PETRA.setSeaAccess(gameState, dock); + setSeaAccess(gameState, dock); }; -PETRA.NavalManager.prototype.updateFishingBoats = function(sea, num) +NavalManager.prototype.updateFishingBoats = function(sea, num) { if (this.wantedFishShips[sea]) this.wantedFishShips[sea] = num; }; -PETRA.NavalManager.prototype.resetFishingBoats = function(gameState, sea) +NavalManager.prototype.resetFishingBoats = function(gameState, sea) { if (sea !== undefined) this.wantedFishShips[sea] = 0; @@ -172,7 +180,7 @@ PETRA.NavalManager.prototype.resetFishingBoats = function(gameState, sea) }; /** Get the sea, cache it if not yet done and check if in opensea */ -PETRA.NavalManager.prototype.getFishSea = function(gameState, fish) +NavalManager.prototype.getFishSea = function(gameState, fish) { let sea = fish.getMetadata(PlayerID, "sea"); if (sea) @@ -209,7 +217,7 @@ PETRA.NavalManager.prototype.getFishSea = function(gameState, fish) }; /** check if we can safely fish at the fish position */ -PETRA.NavalManager.prototype.canFishSafely = function(gameState, fish) +NavalManager.prototype.canFishSafely = function(gameState, fish) { if (fish.getMetadata(PlayerID, "opensea")) return true; @@ -235,20 +243,20 @@ PETRA.NavalManager.prototype.canFishSafely = function(gameState, fish) }; /** get the list of seas (or lands) around this region not connected by a dock */ -PETRA.NavalManager.prototype.getUnconnectedSeas = function(gameState, region) +NavalManager.prototype.getUnconnectedSeas = function(gameState, region) { const seas = gameState.ai.accessibility.regionLinks[region].slice(); this.docks.forEach(dock => { - if (!dock.hasClass("Dock") || PETRA.getLandAccess(gameState, dock) != region) + if (!dock.hasClass("Dock") || getLandAccess(gameState, dock) != region) return; - const i = seas.indexOf(PETRA.getSeaAccess(gameState, dock)); + const i = seas.indexOf(getSeaAccess(gameState, dock)); if (i != -1) seas.splice(i, 1); }); return seas; }; -PETRA.NavalManager.prototype.checkEvents = function(gameState, queues, events) +NavalManager.prototype.checkEvents = function(gameState, queues, events) { for (const evt of events.Create) { @@ -256,7 +264,7 @@ PETRA.NavalManager.prototype.checkEvents = function(gameState, queues, events) continue; const ent = gameState.getEntityById(evt.entity); if (ent && ent.isOwn(PlayerID) && ent.foundationProgress() !== undefined && ent.hasClasses(["Dock", "Shipyard"])) - PETRA.setSeaAccess(gameState, ent); + setSeaAccess(gameState, ent); } for (const evt of events.TrainingFinished) @@ -268,7 +276,7 @@ PETRA.NavalManager.prototype.checkEvents = function(gameState, queues, events) const ent = gameState.getEntityById(entId); if (!ent || !ent.hasClass("Ship") || !ent.isOwn(PlayerID)) continue; - PETRA.setSeaAccess(gameState, ent); + setSeaAccess(gameState, ent); } } @@ -285,7 +293,7 @@ PETRA.NavalManager.prototype.checkEvents = function(gameState, queues, events) const shipId = evt.entityObj.id(); if (this.Config.debug > 1) API3.warn("one ship " + shipId + " from plan " + plan.ID + " destroyed during " + plan.state); - if (plan.state === PETRA.TransportPlan.BOARDING) + if (plan.state === TransportPlan.BOARDING) { // just reset the units onBoard metadata and wait for a new ship to be assigned to this plan plan.units.forEach(ent => { @@ -295,14 +303,14 @@ PETRA.NavalManager.prototype.checkEvents = function(gameState, queues, events) }); plan.needTransportShips = !plan.transportShips.hasEntities(); } - else if (plan.state === PETRA.TransportPlan.SAILING) + else if (plan.state === TransportPlan.SAILING) { const endIndex = plan.endIndex; for (const ent of plan.units.values()) { if (!ent.position()) // unit from another ship of this plan ... do nothing continue; - const access = PETRA.getLandAccess(gameState, ent); + const access = getLandAccess(gameState, ent); const endPos = ent.getMetadata(PlayerID, "endPos"); ent.setMetadata(PlayerID, "transport", undefined); ent.setMetadata(PlayerID, "onBoard", undefined); @@ -323,12 +331,12 @@ PETRA.NavalManager.prototype.checkEvents = function(gameState, queues, events) continue; const ent = gameState.getEntityById(evt.entity); if (ent && ent.hasClasses(["Dock", "Shipyard"])) - PETRA.setSeaAccess(gameState, ent); + setSeaAccess(gameState, ent); } }; -PETRA.NavalManager.prototype.getPlan = function(ID) +NavalManager.prototype.getPlan = function(ID) { for (const plan of this.transportPlans) if (plan.ID === ID) @@ -336,7 +344,7 @@ PETRA.NavalManager.prototype.getPlan = function(ID) return undefined; }; -PETRA.NavalManager.prototype.addPlan = function(plan) +NavalManager.prototype.addPlan = function(plan) { this.transportPlans.push(plan); }; @@ -346,7 +354,7 @@ PETRA.NavalManager.prototype.addPlan = function(plan) * (many units can then call this separately and end up in the same plan) * TODO check garrison classes */ -PETRA.NavalManager.prototype.requireTransport = function(gameState, ent, startIndex, endIndex, endPos) +NavalManager.prototype.requireTransport = function(gameState, ent, startIndex, endIndex, endPos) { if (!ent.canGarrison()) return false; @@ -361,10 +369,13 @@ PETRA.NavalManager.prototype.requireTransport = function(gameState, ent, startIn const plans = []; for (const plan of this.transportPlans) { - if (plan.startIndex != startIndex || plan.endIndex != endIndex || plan.state !== PETRA.TransportPlan.BOARDING) + if (plan.startIndex != startIndex || plan.endIndex != endIndex || + plan.state !== TransportPlan.BOARDING) + { continue; + } // Limit the number of siege units per transport to avoid problems when ungarrisoning - if (PETRA.isSiegeUnit(ent) && plan.units.filter(unit => PETRA.isSiegeUnit(unit)).length > 3) + if (isSiegeUnit(ent) && plan.units.filter(unit => isSiegeUnit(unit)).length > 3) continue; plans.push(plan); } @@ -376,7 +387,7 @@ PETRA.NavalManager.prototype.requireTransport = function(gameState, ent, startIn return true; } - const plan = new PETRA.TransportPlan(gameState, [ent], startIndex, endIndex, endPos); + const plan = new TransportPlan(gameState, [ent], startIndex, endIndex, endPos); if (plan.failed) { if (this.Config.debug > 1) @@ -389,11 +400,11 @@ PETRA.NavalManager.prototype.requireTransport = function(gameState, ent, startIn }; /** split a transport plan in two, moving all entities not yet affected to a ship in the new plan */ -PETRA.NavalManager.prototype.splitTransport = function(gameState, plan) +NavalManager.prototype.splitTransport = function(gameState, plan) { if (this.Config.debug > 1) API3.warn(">>>> split of transport plan started <<<<"); - const newplan = new PETRA.TransportPlan(gameState, [], plan.startIndex, plan.endIndex, plan.endPos); + const newplan = new TransportPlan(gameState, [], plan.startIndex, plan.endIndex, plan.endPos); if (newplan.failed) { if (this.Config.debug > 1) @@ -419,7 +430,7 @@ PETRA.NavalManager.prototype.splitTransport = function(gameState, plan) * create a transport from a garrisoned ship to a land location * needed at start game when starting with a garrisoned ship */ -PETRA.NavalManager.prototype.createTransportIfNeeded = function(gameState, fromPos, toPos, toAccess) +NavalManager.prototype.createTransportIfNeeded = function(gameState, fromPos, toPos, toAccess) { const fromAccess = gameState.ai.accessibility.getAccessValue(fromPos); if (fromAccess !== 1) @@ -437,7 +448,7 @@ PETRA.NavalManager.prototype.createTransportIfNeeded = function(gameState, fromP for (const entId of ship.garrisoned()) units.push(gameState.getEntityById(entId)); // TODO check that the garrisoned units have not another purpose - const plan = new PETRA.TransportPlan(gameState, units, fromAccess, toAccess, toPos, ship); + const plan = new TransportPlan(gameState, units, fromAccess, toAccess, toPos, ship); if (plan.failed) continue; plan.init(gameState); @@ -446,7 +457,7 @@ PETRA.NavalManager.prototype.createTransportIfNeeded = function(gameState, fromP }; // set minimal number of needed ships when a new event (new base or new attack plan) -PETRA.NavalManager.prototype.setMinimalTransportShips = function(gameState, sea, number) +NavalManager.prototype.setMinimalTransportShips = function(gameState, sea, number) { if (!sea) return; @@ -455,7 +466,7 @@ PETRA.NavalManager.prototype.setMinimalTransportShips = function(gameState, sea, }; // bumps up the number of ships we want if we need more. -PETRA.NavalManager.prototype.checkLevels = function(gameState, queues) +NavalManager.prototype.checkLevels = function(gameState, queues) { if (queues.ships.hasQueuedUnits()) return; @@ -484,7 +495,7 @@ PETRA.NavalManager.prototype.checkLevels = function(gameState, queues) ++this.wantedTransportShips[sea]; }; -PETRA.NavalManager.prototype.maintainFleet = function(gameState, queues) +NavalManager.prototype.maintainFleet = function(gameState, queues) { if (queues.ships.hasQueuedUnits()) return; @@ -503,7 +514,7 @@ PETRA.NavalManager.prototype.maintainFleet = function(gameState, queues) const template = this.getBestShip(gameState, sea, "transport"); if (template) { - queues.ships.addPlan(new PETRA.TrainingPlan(gameState, template, { "sea": sea }, 1, 1)); + queues.ships.addPlan(new TrainingPlan(gameState, template, { "sea": sea }, 1, 1)); continue; } } @@ -514,7 +525,8 @@ PETRA.NavalManager.prototype.maintainFleet = function(gameState, queues) const template = this.getBestShip(gameState, sea, "fishing"); if (template) { - queues.ships.addPlan(new PETRA.TrainingPlan(gameState, template, { "base": 0, "role": PETRA.Worker.ROLE_WORKER, "sea": sea }, 1, 1)); + queues.ships.addPlan(new TrainingPlan(gameState, template, + { "base": 0, "role": Worker.ROLE_WORKER, "sea": sea }, 1, 1)); continue; } } @@ -522,7 +534,7 @@ PETRA.NavalManager.prototype.maintainFleet = function(gameState, queues) }; /** assigns free ships to plans that need some */ -PETRA.NavalManager.prototype.assignShipsToPlans = function(gameState) +NavalManager.prototype.assignShipsToPlans = function(gameState) { for (const plan of this.transportPlans) if (plan.needTransportShips) @@ -530,7 +542,7 @@ PETRA.NavalManager.prototype.assignShipsToPlans = function(gameState) }; /** Return true if this ship is likeky (un)garrisoning units */ -PETRA.NavalManager.prototype.isShipBoarding = function(ship) +NavalManager.prototype.isShipBoarding = function(ship) { if (!ship.position()) return false; @@ -544,7 +556,7 @@ PETRA.NavalManager.prototype.isShipBoarding = function(ship) * TODO Ships entity collections are currently in two parts as the trader ships are dealt with * in the tradeManager. That should be modified to avoid dupplicating all the code here. */ -PETRA.NavalManager.prototype.moveApart = function(gameState) +NavalManager.prototype.moveApart = function(gameState) { const blockedShips = []; const blockedIds = []; @@ -591,13 +603,13 @@ PETRA.NavalManager.prototype.moveApart = function(gameState) continue; ship.setMetadata(PlayerID, "stationnary", true); // Check if there are some treasure around - if (PETRA.gatherTreasure(gameState, ship, true)) + if (gatherTreasure(gameState, ship, true)) continue; // Do not stay idle near a dock to not disturb other ships const sea = ship.getMetadata(PlayerID, "sea"); for (const dock of gameState.getAllyStructures().filter(API3.Filters.byClass("Dock")).values()) { - if (PETRA.getSeaAccess(gameState, dock) != sea) + if (getSeaAccess(gameState, dock) != sea) continue; if (API3.SquareVectorDistance(shipPosition, dock.position()) > 4900) continue; @@ -613,7 +625,7 @@ PETRA.NavalManager.prototype.moveApart = function(gameState) if (!shipPosition) continue; const role = ship.getMetadata(PlayerID, "role"); - if (role === undefined || role !== PETRA.Worker.ROLE_TRADER) // already accounted before + if (role === undefined || role !== Worker.ROLE_TRADER) // already accounted before continue; const unitAIState = ship.unitAIState(); @@ -648,13 +660,13 @@ PETRA.NavalManager.prototype.moveApart = function(gameState) continue; ship.setMetadata(PlayerID, "stationnary", true); // Check if there are some treasure around - if (PETRA.gatherTreasure(gameState, ship, true)) + if (gatherTreasure(gameState, ship, true)) continue; // Do not stay idle near a dock to not disturb other ships const sea = ship.getMetadata(PlayerID, "sea"); for (const dock of gameState.getAllyStructures().filter(API3.Filters.byClass("Dock")).values()) { - if (PETRA.getSeaAccess(gameState, dock) != sea) + if (getSeaAccess(gameState, dock) != sea) continue; if (API3.SquareVectorDistance(shipPosition, dock.position()) > 4900) continue; @@ -691,7 +703,7 @@ PETRA.NavalManager.prototype.moveApart = function(gameState) if (blockedIds.indexOf(blockingShip.id()) != -1 || !blockingShip.position()) continue; const role = blockingShip.getMetadata(PlayerID, "role"); - if (role === undefined || role !== PETRA.Worker.ROLE_TRADER) // already accounted before + if (role === undefined || role !== Worker.ROLE_TRADER) // already accounted before continue; const distSquare = API3.SquareVectorDistance(shipPosition, blockingShip.position()); const unitAIState = blockingShip.unitAIState(); @@ -706,7 +718,7 @@ PETRA.NavalManager.prototype.moveApart = function(gameState) } }; -PETRA.NavalManager.prototype.buildNavalStructures = function(gameState, queues) +NavalManager.prototype.buildNavalStructures = function(gameState, queues) { if (!gameState.ai.HQ.navalMap || !gameState.ai.HQ.hasPotentialBase()) return; @@ -731,7 +743,8 @@ PETRA.NavalManager.prototype.buildNavalStructures = function(gameState, queues) continue; const wantedLand = {}; wantedLand[base.accessIndex] = true; - queues.dock.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/dock", { "land": wantedLand, "sea": sea })); + queues.dock.addPlan(new ConstructionPlan(gameState, "structures/{civ}/dock", + { "land": wantedLand, "sea": sea })); dockStarted = true; break; } @@ -761,11 +774,12 @@ PETRA.NavalManager.prototype.buildNavalStructures = function(gameState, queues) if (base.anchor) wantedLand[base.accessIndex] = true; const sea = this.docks.toEntityArray()[0].getMetadata(PlayerID, "sea"); - queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, template, { "land": wantedLand, "sea": sea })); + queues.militaryBuilding.addPlan( + new ConstructionPlan(gameState, template, { "land": wantedLand, "sea": sea })); }; /** goal can be either attack (choose ship with best arrowCount) or transport (choose ship with best capacity) */ -PETRA.NavalManager.prototype.getBestShip = function(gameState, sea, goal) +NavalManager.prototype.getBestShip = function(gameState, sea, goal) { const civ = gameState.getPlayerCiv(); const trainableShips = []; @@ -822,7 +836,7 @@ PETRA.NavalManager.prototype.getBestShip = function(gameState, sea, goal) return bestShip; }; -PETRA.NavalManager.prototype.update = function(gameState, queues, events) +NavalManager.prototype.update = function(gameState, queues, events) { Engine.ProfileStart("Naval Manager update"); @@ -853,7 +867,7 @@ PETRA.NavalManager.prototype.update = function(gameState, queues, events) Engine.ProfileStop(); }; -PETRA.NavalManager.prototype.Serialize = function() +NavalManager.prototype.Serialize = function() { const properties = { "wantedTransportShips": this.wantedTransportShips, @@ -871,7 +885,7 @@ PETRA.NavalManager.prototype.Serialize = function() return { "properties": properties, "transports": transports }; }; -PETRA.NavalManager.prototype.Deserialize = function(gameState, data) +NavalManager.prototype.Deserialize = function(gameState, data) { for (const key in data.properties) this[key] = data.properties[key]; @@ -880,7 +894,7 @@ PETRA.NavalManager.prototype.Deserialize = function(gameState, data) for (const i in data.transports) { const dataPlan = data.transports[i]; - const plan = new PETRA.TransportPlan(gameState, [], dataPlan.startIndex, dataPlan.endIndex, dataPlan.endPos); + const plan = new TransportPlan(gameState, [], dataPlan.startIndex, dataPlan.endIndex, dataPlan.endPos); plan.Deserialize(dataPlan); plan.init(gameState); this.transportPlans.push(plan); diff --git a/binaries/data/mods/public/simulation/ai/petra/queue.js b/binaries/data/mods/public/simulation/ai/petra/queue.js index 0b01d21eda..cb3d34779c 100644 --- a/binaries/data/mods/public/simulation/ai/petra/queue.js +++ b/binaries/data/mods/public/simulation/ai/petra/queue.js @@ -1,19 +1,23 @@ +import { ConstructionPlan } from "simulation/ai/petra/queueplanBuilding.js"; +import { ResearchPlan } from "simulation/ai/petra/queueplanResearch.js"; +import { TrainingPlan } from "simulation/ai/petra/queueplanTraining.js"; + /** * Holds a list of wanted plans to train or construct */ -PETRA.Queue = function() +export function Queue() { this.plans = []; this.paused = false; this.switched = 0; -}; +} -PETRA.Queue.prototype.empty = function() +Queue.prototype.empty = function() { this.plans = []; }; -PETRA.Queue.prototype.addPlan = function(newPlan) +Queue.prototype.addPlan = function(newPlan) { if (!newPlan) return; @@ -30,7 +34,7 @@ PETRA.Queue.prototype.addPlan = function(newPlan) this.plans.push(newPlan); }; -PETRA.Queue.prototype.check= function(gameState) +Queue.prototype.check= function(gameState) { while (this.plans.length > 0) { @@ -42,14 +46,14 @@ PETRA.Queue.prototype.check= function(gameState) } }; -PETRA.Queue.prototype.getNext = function() +Queue.prototype.getNext = function() { if (this.plans.length > 0) return this.plans[0]; return null; }; -PETRA.Queue.prototype.startNext = function(gameState) +Queue.prototype.startNext = function(gameState) { if (this.plans.length > 0) { @@ -63,7 +67,7 @@ PETRA.Queue.prototype.startNext = function(gameState) * returns the maximal account we'll accept for this queue. * Currently all the cost of the first element and fraction of that of the second */ -PETRA.Queue.prototype.maxAccountWanted = function(gameState, fraction) +Queue.prototype.maxAccountWanted = function(gameState, fraction) { const cost = new API3.Resources(); if (this.plans.length > 0 && this.plans[0].isGo(gameState)) @@ -77,7 +81,7 @@ PETRA.Queue.prototype.maxAccountWanted = function(gameState, fraction) return cost; }; -PETRA.Queue.prototype.queueCost = function() +Queue.prototype.queueCost = function() { const cost = new API3.Resources(); for (const plan of this.plans) @@ -85,17 +89,17 @@ PETRA.Queue.prototype.queueCost = function() return cost; }; -PETRA.Queue.prototype.length = function() +Queue.prototype.length = function() { return this.plans.length; }; -PETRA.Queue.prototype.hasQueuedUnits = function() +Queue.prototype.hasQueuedUnits = function() { return this.plans.length > 0; }; -PETRA.Queue.prototype.countQueuedUnits = function() +Queue.prototype.countQueuedUnits = function() { let count = 0; for (const plan of this.plans) @@ -103,12 +107,12 @@ PETRA.Queue.prototype.countQueuedUnits = function() return count; }; -PETRA.Queue.prototype.hasQueuedUnitsWithClass = function(classe) +Queue.prototype.hasQueuedUnitsWithClass = function(classe) { return this.plans.some(plan => plan.template && plan.template.hasClass(classe)); }; -PETRA.Queue.prototype.countQueuedUnitsWithClass = function(classe) +Queue.prototype.countQueuedUnitsWithClass = function(classe) { let count = 0; for (const plan of this.plans) @@ -117,7 +121,7 @@ PETRA.Queue.prototype.countQueuedUnitsWithClass = function(classe) return count; }; -PETRA.Queue.prototype.countQueuedUnitsWithMetadata = function(data, value) +Queue.prototype.countQueuedUnitsWithMetadata = function(data, value) { let count = 0; for (const plan of this.plans) @@ -126,7 +130,7 @@ PETRA.Queue.prototype.countQueuedUnitsWithMetadata = function(data, value) return count; }; -PETRA.Queue.prototype.Serialize = function() +Queue.prototype.Serialize = function() { const plans = []; for (const plan of this.plans) @@ -135,7 +139,7 @@ PETRA.Queue.prototype.Serialize = function() return { "plans": plans, "paused": this.paused, "switched": this.switched }; }; -PETRA.Queue.prototype.Deserialize = function(gameState, data) +Queue.prototype.Deserialize = function(gameState, data) { this.paused = data.paused; this.switched = data.switched; @@ -144,11 +148,11 @@ PETRA.Queue.prototype.Deserialize = function(gameState, data) { let plan; if (dataPlan.category == "unit") - plan = new PETRA.TrainingPlan(gameState, dataPlan.type); + plan = new TrainingPlan(gameState, dataPlan.type); else if (dataPlan.category == "building") - plan = new PETRA.ConstructionPlan(gameState, dataPlan.type); + plan = new ConstructionPlan(gameState, dataPlan.type); else if (dataPlan.category == "technology") - plan = new PETRA.ResearchPlan(gameState, dataPlan.type); + plan = new ResearchPlan(gameState, dataPlan.type); else { API3.warn("Petra deserialization error: plan unknown " + uneval(dataPlan)); diff --git a/binaries/data/mods/public/simulation/ai/petra/queueManager.js b/binaries/data/mods/public/simulation/ai/petra/queueManager.js index f846145f45..b543a5ee29 100644 --- a/binaries/data/mods/public/simulation/ai/petra/queueManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/queueManager.js @@ -1,3 +1,6 @@ +import { Queue } from "simulation/ai/petra/queue.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + /** * This takes the input queues and picks which items to fund with resources until no more resources are left to distribute. * @@ -19,7 +22,7 @@ * This system should be improved. It's probably not flexible enough. */ -PETRA.QueueManager = function(Config, queues) +export function QueueManager(Config, queues) { this.Config = Config; this.queues = queues; @@ -37,9 +40,9 @@ PETRA.QueueManager = function(Config, queues) } const priorities = this.priorities; this.queueArrays.sort((a, b) => priorities[b[0]] - priorities[a[0]]); -}; +} -PETRA.QueueManager.prototype.getAvailableResources = function(gameState) +QueueManager.prototype.getAvailableResources = function(gameState) { const resources = gameState.getResources(); for (const key in this.queues) @@ -47,7 +50,7 @@ PETRA.QueueManager.prototype.getAvailableResources = function(gameState) return resources; }; -PETRA.QueueManager.prototype.getTotalAccountedResources = function() +QueueManager.prototype.getTotalAccountedResources = function() { const resources = new API3.Resources(); for (const key in this.queues) @@ -55,7 +58,7 @@ PETRA.QueueManager.prototype.getTotalAccountedResources = function() return resources; }; -PETRA.QueueManager.prototype.currentNeeds = function(gameState) +QueueManager.prototype.currentNeeds = function(gameState) { const needed = new API3.Resources(); // queueArrays because it's faster. @@ -77,7 +80,7 @@ PETRA.QueueManager.prototype.currentNeeds = function(gameState) // calculate the gather rates we'd want to be able to start all elements in our queues // TODO: many things. -PETRA.QueueManager.prototype.wantedGatherRates = function(gameState) +QueueManager.prototype.wantedGatherRates = function(gameState) { // default values for first turn when we have not yet set our queues. if (gameState.ai.playedTurn === 0) @@ -154,12 +157,15 @@ PETRA.QueueManager.prototype.wantedGatherRates = function(gameState) return rates; }; -PETRA.QueueManager.prototype.printQueues = function(gameState) +QueueManager.prototype.printQueues = function(gameState) { let numWorkers = 0; gameState.getOwnUnits().forEach(ent => { - if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_WORKER && ent.getMetadata(PlayerID, "plan") === undefined) + if (ent.getMetadata(PlayerID, "role") === Worker.ROLE_WORKER && + ent.getMetadata(PlayerID, "plan") === undefined) + { numWorkers++; + } }); API3.warn("---------- QUEUES ------------ with pop " + gameState.getPopulation() + " and workers " + numWorkers); for (const i in this.queues) @@ -190,7 +196,7 @@ PETRA.QueueManager.prototype.printQueues = function(gameState) API3.warn("------------------------------------"); }; -PETRA.QueueManager.prototype.clear = function() +QueueManager.prototype.clear = function() { for (const i in this.queues) this.queues[i].empty(); @@ -199,7 +205,7 @@ PETRA.QueueManager.prototype.clear = function() /** * set accounts of queue i from the unaccounted resources */ -PETRA.QueueManager.prototype.setAccounts = function(gameState, cost, i) +QueueManager.prototype.setAccounts = function(gameState, cost, i) { const available = this.getAvailableResources(gameState); for (const res of Resources.GetCodes()) @@ -213,7 +219,7 @@ PETRA.QueueManager.prototype.setAccounts = function(gameState, cost, i) /** * transfer accounts from queue i to queue j */ -PETRA.QueueManager.prototype.transferAccounts = function(cost, i, j) +QueueManager.prototype.transferAccounts = function(cost, i, j) { for (const res of Resources.GetCodes()) { @@ -228,7 +234,7 @@ PETRA.QueueManager.prototype.transferAccounts = function(cost, i, j) /** * distribute the resources between the different queues according to their priorities */ -PETRA.QueueManager.prototype.distributeResources = function(gameState) +QueueManager.prototype.distributeResources = function(gameState) { const availableRes = this.getAvailableResources(gameState); for (const res of Resources.GetCodes()) @@ -317,7 +323,7 @@ PETRA.QueueManager.prototype.distributeResources = function(gameState) } }; -PETRA.QueueManager.prototype.switchResource = function(gameState, res) +QueueManager.prototype.switchResource = function(gameState, res) { // We have no available resources, see if we can't "compact" them in one queue. // compare queues 2 by 2, and if one with a higher priority could be completed by our amount, give it. @@ -354,7 +360,7 @@ PETRA.QueueManager.prototype.switchResource = function(gameState, res) }; // Start the next item in the queue if we can afford it. -PETRA.QueueManager.prototype.startNextItems = function(gameState) +QueueManager.prototype.startNextItems = function(gameState) { for (const q of this.queueArrays) { @@ -383,7 +389,7 @@ PETRA.QueueManager.prototype.startNextItems = function(gameState) } }; -PETRA.QueueManager.prototype.update = function(gameState) +QueueManager.prototype.update = function(gameState) { Engine.ProfileStart("Queue Manager"); @@ -412,9 +418,9 @@ PETRA.QueueManager.prototype.update = function(gameState) }; // Recovery system: if short of workers after an attack, pause (and reset) some queues to favor worker training -PETRA.QueueManager.prototype.checkPausedQueues = function(gameState) +QueueManager.prototype.checkPausedQueues = function(gameState) { - const numWorkers = gameState.countOwnEntitiesAndQueuedWithRole(PETRA.Worker.ROLE_WORKER); + const numWorkers = gameState.countOwnEntitiesAndQueuedWithRole(Worker.ROLE_WORKER); const workersMin = Math.min(Math.max(12, 24 * this.Config.popScaling), this.Config.Economy.popPhase2); for (const q in this.queues) { @@ -466,14 +472,14 @@ PETRA.QueueManager.prototype.checkPausedQueues = function(gameState) } }; -PETRA.QueueManager.prototype.canAfford = function(queue, cost) +QueueManager.prototype.canAfford = function(queue, cost) { if (!this.accounts[queue]) return false; return this.accounts[queue].canAfford(cost); }; -PETRA.QueueManager.prototype.pauseQueue = function(queue, scrapAccounts) +QueueManager.prototype.pauseQueue = function(queue, scrapAccounts) { if (!this.queues[queue]) return; @@ -482,13 +488,13 @@ PETRA.QueueManager.prototype.pauseQueue = function(queue, scrapAccounts) this.accounts[queue].reset(); }; -PETRA.QueueManager.prototype.unpauseQueue = function(queue) +QueueManager.prototype.unpauseQueue = function(queue) { if (this.queues[queue]) this.queues[queue].paused = false; }; -PETRA.QueueManager.prototype.pauseAll = function(scrapAccounts, but) +QueueManager.prototype.pauseAll = function(scrapAccounts, but) { for (const q in this.queues) { @@ -500,7 +506,7 @@ PETRA.QueueManager.prototype.pauseAll = function(scrapAccounts, but) } }; -PETRA.QueueManager.prototype.unpauseAll = function(but) +QueueManager.prototype.unpauseAll = function(but) { for (const q in this.queues) if (q != but) @@ -508,12 +514,12 @@ PETRA.QueueManager.prototype.unpauseAll = function(but) }; -PETRA.QueueManager.prototype.addQueue = function(queueName, priority) +QueueManager.prototype.addQueue = function(queueName, priority) { if (this.queues[queueName] !== undefined) return; - this.queues[queueName] = new PETRA.Queue(); + this.queues[queueName] = new Queue(); this.priorities[queueName] = priority; this.accounts[queueName] = new API3.Resources(); @@ -524,7 +530,7 @@ PETRA.QueueManager.prototype.addQueue = function(queueName, priority) this.queueArrays.sort((a, b) => priorities[b[0]] - priorities[a[0]]); }; -PETRA.QueueManager.prototype.removeQueue = function(queueName) +QueueManager.prototype.removeQueue = function(queueName) { if (this.queues[queueName] === undefined) return; @@ -540,12 +546,12 @@ PETRA.QueueManager.prototype.removeQueue = function(queueName) this.queueArrays.sort((a, b) => priorities[b[0]] - priorities[a[0]]); }; -PETRA.QueueManager.prototype.getPriority = function(queueName) +QueueManager.prototype.getPriority = function(queueName) { return this.priorities[queueName]; }; -PETRA.QueueManager.prototype.changePriority = function(queueName, newPriority) +QueueManager.prototype.changePriority = function(queueName, newPriority) { if (this.Config.debug > 1) API3.warn(">>> Priority of queue " + queueName + " changed from " + this.priorities[queueName] + " to " + newPriority); @@ -555,7 +561,7 @@ PETRA.QueueManager.prototype.changePriority = function(queueName, newPriority) this.queueArrays.sort((a, b) => priorities[b[0]] - priorities[a[0]]); }; -PETRA.QueueManager.prototype.Serialize = function() +QueueManager.prototype.Serialize = function() { const accounts = {}; const queues = {}; @@ -575,7 +581,7 @@ PETRA.QueueManager.prototype.Serialize = function() }; }; -PETRA.QueueManager.prototype.Deserialize = function(gameState, data) +QueueManager.prototype.Deserialize = function(gameState, data) { this.priorities = data.priorities; this.queues = {}; @@ -585,7 +591,7 @@ PETRA.QueueManager.prototype.Deserialize = function(gameState, data) this.queueArrays = []; for (const q in data.queues) { - this.queues[q] = new PETRA.Queue(); + this.queues[q] = new Queue(); this.queues[q].Deserialize(gameState, data.queues[q]); this.accounts[q] = new API3.Resources(); this.accounts[q].Deserialize(data.accounts[q]); diff --git a/binaries/data/mods/public/simulation/ai/petra/queueplan.js b/binaries/data/mods/public/simulation/ai/petra/queueplan.js index bf21ab5901..c63aa4e428 100644 --- a/binaries/data/mods/public/simulation/ai/petra/queueplan.js +++ b/binaries/data/mods/public/simulation/ai/petra/queueplan.js @@ -2,7 +2,7 @@ * Common functions and variables to all queue plans. */ -PETRA.QueuePlan = function(gameState, type, metadata) +export function QueuePlan(gameState, type, metadata) { this.type = gameState.applyCiv(type); this.metadata = metadata; @@ -19,33 +19,33 @@ PETRA.QueuePlan = function(gameState, type, metadata) this.category = ""; return true; -}; +} /** Check the content of this queue */ -PETRA.QueuePlan.prototype.isInvalid = function(gameState) +QueuePlan.prototype.isInvalid = function(gameState) { return false; }; /** if true, the queue manager will begin increasing this plan's account. */ -PETRA.QueuePlan.prototype.isGo = function(gameState) +QueuePlan.prototype.isGo = function(gameState) { return true; }; /** can we start this plan immediately? */ -PETRA.QueuePlan.prototype.canStart = function(gameState) +QueuePlan.prototype.canStart = function(gameState) { return false; }; /** process the plan. */ -PETRA.QueuePlan.prototype.start = function(gameState) +QueuePlan.prototype.start = function(gameState) { // should call onStart. }; -PETRA.QueuePlan.prototype.getCost = function() +QueuePlan.prototype.getCost = function() { const costs = new API3.Resources(); costs.add(this.cost); @@ -60,6 +60,6 @@ PETRA.QueuePlan.prototype.getCost = function() * Need to be updated to actually do something if you want them to. * this is called by "Start" if it succeeds. */ -PETRA.QueuePlan.prototype.onStart = function(gameState) +QueuePlan.prototype.onStart = function(gameState) { }; diff --git a/binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js b/binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js index a89bafb443..719663b108 100644 --- a/binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js +++ b/binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js @@ -1,11 +1,16 @@ +import { getBuiltEntity, getLandAccess, getSeaAccess } from "simulation/ai/petra/entityExtend.js"; +import { border_Mask, createObstructionMap, fullBorder_Mask, outside_Mask } from + "simulation/ai/petra/mapModule.js"; +import { QueuePlan } from "simulation/ai/petra/queueplan.js"; + /** * Defines a construction plan, ie a building. * We'll try to fing a good position if non has been provided */ -PETRA.ConstructionPlan = function(gameState, type, metadata, position) +export function ConstructionPlan(gameState, type, metadata, position) { - if (!PETRA.QueuePlan.call(this, gameState, type, metadata)) + if (!QueuePlan.call(this, gameState, type, metadata)) return false; this.position = position ? position : 0; @@ -13,11 +18,11 @@ PETRA.ConstructionPlan = function(gameState, type, metadata, position) this.category = "building"; return true; -}; +} -PETRA.ConstructionPlan.prototype = Object.create(PETRA.QueuePlan.prototype); +ConstructionPlan.prototype = Object.create(QueuePlan.prototype); -PETRA.ConstructionPlan.prototype.canStart = function(gameState) +ConstructionPlan.prototype.canStart = function(gameState) { if (gameState.ai.HQ.turnCache.buildingBuilt) // do not start another building if already one this turn return false; @@ -31,7 +36,7 @@ PETRA.ConstructionPlan.prototype.canStart = function(gameState) return gameState.ai.HQ.buildManager.hasBuilder(this.type); }; -PETRA.ConstructionPlan.prototype.start = function(gameState) +ConstructionPlan.prototype.start = function(gameState) { Engine.ProfileStart("Building construction start"); @@ -106,7 +111,7 @@ PETRA.ConstructionPlan.prototype.start = function(gameState) gameState.ai.HQ.navalManager.createTransportIfNeeded(gameState, this.metadata.proximity, [pos.x, pos.z], this.metadata.access); }; -PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState) +ConstructionPlan.prototype.findGoodPosition = function(gameState) { let template = this.template; @@ -214,7 +219,7 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState) const x = Math.round(pos[0] / cellSize); const z = Math.round(pos[1] / cellSize); - const struct = PETRA.getBuiltEntity(gameState, ent); + const struct = getBuiltEntity(gameState, ent); if (struct.resourceDropsiteTypes() && struct.resourceDropsiteTypes().indexOf("food") != -1) { if (template.hasClasses(["Field", "Corral"])) @@ -248,7 +253,7 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState) for (let j = 0; j < placement.map.length; ++j) { let value = placement.map[j] - gameState.sharedScript.resourceMaps.wood.map[j]/3; - if (HQ.borderMap.map[j] & PETRA.fullBorder_Mask) + if (HQ.borderMap.map[j] & fullBorder_Mask) value /= 2; // we need space around farmstead, so disfavor map border placement.set(j, value); } @@ -270,9 +275,9 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState) placement.map[j] = 0; else if (placement.map[j] > 0) { - if (favorBorder && HQ.borderMap.map[j] & PETRA.border_Mask) + if (favorBorder && HQ.borderMap.map[j] & border_Mask) placement.set(j, placement.map[j] + 50); - else if (disfavorBorder && !(HQ.borderMap.map[j] & PETRA.fullBorder_Mask)) + else if (disfavorBorder && !(HQ.borderMap.map[j] & fullBorder_Mask)) placement.set(j, placement.map[j] + 10); const x = (j % placement.width + 0.5) * cellSize; @@ -290,9 +295,9 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState) placement.map[j] = 0; else if (placement.map[j] > 0) { - if (favorBorder && HQ.borderMap.map[j] & PETRA.border_Mask) + if (favorBorder && HQ.borderMap.map[j] & border_Mask) placement.set(j, placement.map[j] + 50); - else if (disfavorBorder && !(HQ.borderMap.map[j] & PETRA.fullBorder_Mask)) + else if (disfavorBorder && !(HQ.borderMap.map[j] & fullBorder_Mask)) placement.set(j, placement.map[j] + 10); const x = (j % placement.width + 0.5) * cellSize; @@ -311,7 +316,7 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState) // note: not for houses and dropsites who ought to be closer to either each other or a resource. // also not for fields who can be stacked quite a bit - const obstructions = PETRA.createObstructionMap(gameState, 0, template); + const obstructions = createObstructionMap(gameState, 0, template); // obstructions.dumpIm(template.buildPlacementType() + "_obstructions.png"); let radius; @@ -359,12 +364,12 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState) * => we try not to be too far from our territory * In all cases, we add a bonus for nearby resources, and when a large extend of water in front ot it. */ -PETRA.ConstructionPlan.prototype.findDockPosition = function(gameState) +ConstructionPlan.prototype.findDockPosition = function(gameState) { const template = this.template; const territoryMap = gameState.ai.HQ.territoryMap; - const obstructions = PETRA.createObstructionMap(gameState, 0, template); + const obstructions = createObstructionMap(gameState, 0, template); // obstructions.dumpIm(template.buildPlacementType() + "_obstructions.png"); let bestIdx; @@ -467,7 +472,7 @@ PETRA.ConstructionPlan.prototype.findDockPosition = function(gameState) let dockDist = 0; for (const dock of docks.values()) { - if (PETRA.getSeaAccess(gameState, dock) != navalPassMap[i]) + if (getSeaAccess(gameState, dock) != navalPassMap[i]) continue; const dist = API3.SquareVectorDistance(pos, dock.position()); if (dist > dockDist) @@ -483,7 +488,7 @@ PETRA.ConstructionPlan.prototype.findDockPosition = function(gameState) } // Add a penalty if on the map border as ship movement will be difficult - if (gameState.ai.HQ.borderMap.map[j] & PETRA.fullBorder_Mask) + if (gameState.ai.HQ.borderMap.map[j] & fullBorder_Mask) score += 20; // Do a pre-selection, supposing we will have the best possible water @@ -534,7 +539,7 @@ PETRA.ConstructionPlan.prototype.findDockPosition = function(gameState) /** * Find a good island to build a dock. */ -PETRA.ConstructionPlan.prototype.buildOverseaDock = function(gameState, template) +ConstructionPlan.prototype.buildOverseaDock = function(gameState, template) { const docks = gameState.getOwnStructures().filter(API3.Filters.byClass("Dock")); if (!docks.hasEntities()) @@ -554,7 +559,7 @@ PETRA.ConstructionPlan.prototype.buildOverseaDock = function(gameState, template let keep = true; for (const dock of docks.values()) { - if (PETRA.getLandAccess(gameState, dock) != i) + if (getLandAccess(gameState, dock) != i) continue; keep = false; break; @@ -564,7 +569,7 @@ PETRA.ConstructionPlan.prototype.buildOverseaDock = function(gameState, template let sea; for (const cc of ccEnts.values()) { - const ccAccess = PETRA.getLandAccess(gameState, cc); + const ccAccess = getLandAccess(gameState, cc); if (ccAccess != i) { if (cc.owner() == PlayerID && !sea) @@ -614,7 +619,7 @@ PETRA.ConstructionPlan.prototype.buildOverseaDock = function(gameState, template }; /** Algorithm taken from the function GetDockAngle in simulation/helpers/Commands.js */ -PETRA.ConstructionPlan.prototype.getDockAngle = function(gameState, x, z, size) +ConstructionPlan.prototype.getDockAngle = function(gameState, x, z, size) { let pos = gameState.ai.accessibility.gamePosToMapPos([x, z]); const k = pos[0] + pos[1]*gameState.ai.accessibility.width; @@ -676,7 +681,7 @@ PETRA.ConstructionPlan.prototype.getDockAngle = function(gameState, x, z, size) * to determine the special dock requirements * returns {"land": land index for this dock, "water": amount of water around this spot} */ -PETRA.ConstructionPlan.prototype.checkDockPlacement = function(gameState, x, z, halfDepth, halfWidth, angle) +ConstructionPlan.prototype.checkDockPlacement = function(gameState, x, z, halfDepth, halfWidth, angle) { const sz = halfDepth * Math.sin(angle); const cz = halfDepth * Math.cos(angle); @@ -738,10 +743,10 @@ PETRA.ConstructionPlan.prototype.checkDockPlacement = function(gameState, x, z, * if the (object) wantedLand is given, this nearest land should have one of these accessibility * if wantedSea is given, this tile should be inside this sea */ -PETRA.ConstructionPlan.prototype.around = [[ 1.0, 0.0], [ 0.87, 0.50], [ 0.50, 0.87], [ 0.0, 1.0], [-0.50, 0.87], [-0.87, 0.50], +ConstructionPlan.prototype.around = [[ 1.0, 0.0], [ 0.87, 0.50], [ 0.50, 0.87], [ 0.0, 1.0], [-0.50, 0.87], [-0.87, 0.50], [-1.0, 0.0], [-0.87, -0.50], [-0.50, -0.87], [ 0.0, -1.0], [ 0.50, -0.87], [ 0.87, -0.50]]; -PETRA.ConstructionPlan.prototype.isDockLocation = function(gameState, j, dimension, wantedLand, wantedSea) +ConstructionPlan.prototype.isDockLocation = function(gameState, j, dimension, wantedLand, wantedSea) { const width = gameState.ai.HQ.territoryMap.width; const cellSize = gameState.ai.HQ.territoryMap.cellSize; @@ -788,7 +793,7 @@ PETRA.ConstructionPlan.prototype.isDockLocation = function(gameState, j, dimensi * return a measure of the proximity to our frontier (including our allies) * 0=inside, 1=less than 24m, 2= less than 48m, 3= less than 72m, 4=less than 96m, 5=above 96m */ -PETRA.ConstructionPlan.prototype.getFrontierProximity = function(gameState, j) +ConstructionPlan.prototype.getFrontierProximity = function(gameState, j) { const alliedVictory = gameState.getAlliedVictory(); const territoryMap = gameState.ai.HQ.territoryMap; @@ -812,7 +817,7 @@ PETRA.ConstructionPlan.prototype.getFrontierProximity = function(gameState, j) const jz = iz + Math.round(i*step*a[1]); if (jz < 0 || jz >= width) continue; - if (borderMap.map[jx+width*jz] & PETRA.outside_Mask) + if (borderMap.map[jx+width*jz] & outside_Mask) continue; territoryOwner = territoryMap.getOwnerIndex(jx+width*jz); if (alliedVictory && gameState.isPlayerAlly(territoryOwner) || territoryOwner == PlayerID) @@ -832,7 +837,7 @@ PETRA.ConstructionPlan.prototype.getFrontierProximity = function(gameState, j) * get the sum of the resources (except food) around, inside a given radius * resources have a weight (1 if dist=0 and 0 if dist=size) doubled for wood */ -PETRA.ConstructionPlan.prototype.getResourcesAround = function(gameState, types, i, radius) +ConstructionPlan.prototype.getResourcesAround = function(gameState, types, i, radius) { const resourceMaps = gameState.sharedScript.resourceMaps; const w = resourceMaps.wood.width; @@ -885,7 +890,7 @@ PETRA.ConstructionPlan.prototype.getResourcesAround = function(gameState, types, return nbcell ? total / nbcell : 0; }; -PETRA.ConstructionPlan.prototype.isGo = function(gameState) +ConstructionPlan.prototype.isGo = function(gameState) { if (this.goRequirement && this.goRequirement == "houseNeeded") { @@ -913,13 +918,13 @@ PETRA.ConstructionPlan.prototype.isGo = function(gameState) return true; }; -PETRA.ConstructionPlan.prototype.onStart = function(gameState) +ConstructionPlan.prototype.onStart = function(gameState) { if (this.queueToReset) gameState.ai.queueManager.changePriority(this.queueToReset, gameState.ai.Config.priorities[this.queueToReset]); }; -PETRA.ConstructionPlan.prototype.Serialize = function() +ConstructionPlan.prototype.Serialize = function() { return { "category": this.category, @@ -934,7 +939,7 @@ PETRA.ConstructionPlan.prototype.Serialize = function() }; }; -PETRA.ConstructionPlan.prototype.Deserialize = function(gameState, data) +ConstructionPlan.prototype.Deserialize = function(gameState, data) { for (const key in data) this[key] = data[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/queueplanResearch.js b/binaries/data/mods/public/simulation/ai/petra/queueplanResearch.js index d81f0239cd..0079aa9d87 100644 --- a/binaries/data/mods/public/simulation/ai/petra/queueplanResearch.js +++ b/binaries/data/mods/public/simulation/ai/petra/queueplanResearch.js @@ -1,6 +1,8 @@ -PETRA.ResearchPlan = function(gameState, type, rush = false) +import { QueuePlan } from "simulation/ai/petra/queueplan.js"; + +export function ResearchPlan(gameState, type, rush = false) { - if (!PETRA.QueuePlan.call(this, gameState, type, {})) + if (!QueuePlan.call(this, gameState, type, {})) return false; if (this.template.researchTime === undefined) @@ -15,11 +17,11 @@ PETRA.ResearchPlan = function(gameState, type, rush = false) this.rush = rush; return true; -}; +} -PETRA.ResearchPlan.prototype = Object.create(PETRA.QueuePlan.prototype); +ResearchPlan.prototype = Object.create(QueuePlan.prototype); -PETRA.ResearchPlan.prototype.canStart = function(gameState) +ResearchPlan.prototype.canStart = function(gameState) { this.researchers = this.getBestResearchers(gameState); if (!this.researchers) @@ -28,7 +30,7 @@ PETRA.ResearchPlan.prototype.canStart = function(gameState) return true; }; -PETRA.ResearchPlan.prototype.getBestResearchers = function(gameState, noRequirementCheck = false) +ResearchPlan.prototype.getBestResearchers = function(gameState, noRequirementCheck = false) { const allResearchers = gameState.findResearchers(this.type, noRequirementCheck); if (!allResearchers || !allResearchers.hasEntities()) @@ -51,12 +53,12 @@ PETRA.ResearchPlan.prototype.getBestResearchers = function(gameState, noRequirem return researchers; }; -PETRA.ResearchPlan.prototype.isInvalid = function(gameState) +ResearchPlan.prototype.isInvalid = function(gameState) { return gameState.isResearched(this.type) || gameState.isResearching(this.type); }; -PETRA.ResearchPlan.prototype.start = function(gameState) +ResearchPlan.prototype.start = function(gameState) { // Prefer researcher with shortest queues (no need to serialize this.researchers // as the functions canStart and start are always called on the same turn) @@ -68,7 +70,7 @@ PETRA.ResearchPlan.prototype.start = function(gameState) this.onStart(gameState); }; -PETRA.ResearchPlan.prototype.onStart = function(gameState) +ResearchPlan.prototype.onStart = function(gameState) { if (this.queueToReset) gameState.ai.queueManager.changePriority(this.queueToReset, gameState.ai.Config.priorities[this.queueToReset]); @@ -83,7 +85,7 @@ PETRA.ResearchPlan.prototype.onStart = function(gameState) } }; -PETRA.ResearchPlan.prototype.Serialize = function() +ResearchPlan.prototype.Serialize = function() { return { "category": this.category, @@ -97,7 +99,7 @@ PETRA.ResearchPlan.prototype.Serialize = function() }; }; -PETRA.ResearchPlan.prototype.Deserialize = function(gameState, data) +ResearchPlan.prototype.Deserialize = function(gameState, data) { for (const key in data) this[key] = data[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/queueplanTraining.js b/binaries/data/mods/public/simulation/ai/petra/queueplanTraining.js index 30937977ed..0c26a88f50 100644 --- a/binaries/data/mods/public/simulation/ai/petra/queueplanTraining.js +++ b/binaries/data/mods/public/simulation/ai/petra/queueplanTraining.js @@ -1,6 +1,9 @@ -PETRA.TrainingPlan = function(gameState, type, metadata, number = 1, maxMerge = 5) +import { QueuePlan } from "simulation/ai/petra/queueplan.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + +export function TrainingPlan(gameState, type, metadata, number = 1, maxMerge = 5) { - if (!PETRA.QueuePlan.call(this, gameState, type, metadata)) + if (!QueuePlan.call(this, gameState, type, metadata)) { API3.warn(" Plan training " + type + " canceled"); return false; @@ -16,11 +19,11 @@ PETRA.TrainingPlan = function(gameState, type, metadata, number = 1, maxMerge = this.maxMerge = maxMerge; return true; -}; +} -PETRA.TrainingPlan.prototype = Object.create(PETRA.QueuePlan.prototype); +TrainingPlan.prototype = Object.create(QueuePlan.prototype); -PETRA.TrainingPlan.prototype.canStart = function(gameState) +TrainingPlan.prototype.canStart = function(gameState) { this.trainers = this.getBestTrainers(gameState); if (!this.trainers) @@ -29,7 +32,7 @@ PETRA.TrainingPlan.prototype.canStart = function(gameState) return true; }; -PETRA.TrainingPlan.prototype.getBestTrainers = function(gameState) +TrainingPlan.prototype.getBestTrainers = function(gameState) { if (this.metadata && this.metadata.trainer) { @@ -63,7 +66,7 @@ PETRA.TrainingPlan.prototype.getBestTrainers = function(gameState) return trainers; }; -PETRA.TrainingPlan.prototype.start = function(gameState) +TrainingPlan.prototype.start = function(gameState) { if (this.metadata && this.metadata.trainer) { @@ -79,7 +82,8 @@ PETRA.TrainingPlan.prototype.start = function(gameState) let wantedIndex; if (this.metadata && this.metadata.index) wantedIndex = this.metadata.index; - const workerUnit = this.metadata && this.metadata.role && this.metadata.role === PETRA.Worker.ROLE_WORKER; + const workerUnit = this.metadata && this.metadata.role && + this.metadata.role === Worker.ROLE_WORKER; const supportUnit = this.template.hasClass("Support"); this.trainers.sort(function(a, b) { // Prefer training buildings with short queues @@ -129,12 +133,12 @@ PETRA.TrainingPlan.prototype.start = function(gameState) this.onStart(gameState); }; -PETRA.TrainingPlan.prototype.addItem = function(amount = 1) +TrainingPlan.prototype.addItem = function(amount = 1) { this.number += amount; }; -PETRA.TrainingPlan.prototype.Serialize = function() +TrainingPlan.prototype.Serialize = function() { return { "category": this.category, @@ -147,7 +151,7 @@ PETRA.TrainingPlan.prototype.Serialize = function() }; }; -PETRA.TrainingPlan.prototype.Deserialize = function(gameState, data) +TrainingPlan.prototype.Deserialize = function(gameState, data) { for (const key in data) this[key] = data[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/researchManager.js b/binaries/data/mods/public/simulation/ai/petra/researchManager.js index 27d0756584..a0e1cb343a 100644 --- a/binaries/data/mods/public/simulation/ai/petra/researchManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/researchManager.js @@ -1,15 +1,18 @@ +import { ResearchPlan } from "simulation/ai/petra/queueplanResearch.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + /** * Manage the research */ -PETRA.ResearchManager = function(Config) +export function ResearchManager(Config) { this.Config = Config; -}; +} /** * Check if we can go to the next phase */ -PETRA.ResearchManager.prototype.checkPhase = function(gameState, queues) +ResearchManager.prototype.checkPhase = function(gameState, queues) { if (queues.majorTech.hasQueuedUnits()) return; @@ -31,11 +34,11 @@ PETRA.ResearchManager.prototype.checkPhase = function(gameState, queues) gameState.ai.HQ.phasing = currentPhaseIndex + 1; // Reset the queue priority in case it was changed during a previous phase update gameState.ai.queueManager.changePriority("majorTech", gameState.ai.Config.priorities.majorTech); - queues.majorTech.addPlan(new PETRA.ResearchPlan(gameState, nextPhaseName, true)); + queues.majorTech.addPlan(new ResearchPlan(gameState, nextPhaseName, true)); } }; -PETRA.ResearchManager.prototype.researchPopulationBonus = function(gameState, queues) +ResearchManager.prototype.researchPopulationBonus = function(gameState, queues) { if (queues.minorTech.hasQueuedUnits()) return; @@ -48,12 +51,12 @@ PETRA.ResearchManager.prototype.researchPopulationBonus = function(gameState, qu // TODO may-be loop on all modifs and check if the effect if positive ? if (tech[1]._template.modifications[0].value !== "Population/Bonus") continue; - queues.minorTech.addPlan(new PETRA.ResearchPlan(gameState, tech[0])); + queues.minorTech.addPlan(new ResearchPlan(gameState, tech[0])); break; } }; -PETRA.ResearchManager.prototype.researchTradeBonus = function(gameState, queues) +ResearchManager.prototype.researchTradeBonus = function(gameState, queues) { if (queues.minorTech.hasQueuedUnits()) return; @@ -69,17 +72,17 @@ PETRA.ResearchManager.prototype.researchTradeBonus = function(gameState, queues) if (tech[1]._template.modifications[0].value !== "UnitMotion/WalkSpeed" && tech[1]._template.modifications[0].value !== "Trader/GainMultiplier") continue; - queues.minorTech.addPlan(new PETRA.ResearchPlan(gameState, tech[0])); + queues.minorTech.addPlan(new ResearchPlan(gameState, tech[0])); break; } }; /** Techs to be searched for as soon as they are available */ -PETRA.ResearchManager.prototype.researchWantedTechs = function(gameState, techs) +ResearchManager.prototype.researchWantedTechs = function(gameState, techs) { const phase1 = gameState.currentPhase() === 1; const available = phase1 ? gameState.ai.queueManager.getAvailableResources(gameState) : null; - const numWorkers = phase1 ? gameState.getOwnEntitiesByRole(PETRA.Worker.ROLE_WORKER, true).length : 0; + const numWorkers = phase1 ? gameState.getOwnEntitiesByRole(Worker.ROLE_WORKER, true).length : 0; for (const tech of techs) { if (tech[0].indexOf("unlock_champion") == 0) @@ -119,11 +122,11 @@ PETRA.ResearchManager.prototype.researchWantedTechs = function(gameState, techs) }; /** Techs to be searched for as soon as they are available, but only after phase 2 */ -PETRA.ResearchManager.prototype.researchPreferredTechs = function(gameState, techs) +ResearchManager.prototype.researchPreferredTechs = function(gameState, techs) { const phase2 = gameState.currentPhase() === 2; const available = phase2 ? gameState.ai.queueManager.getAvailableResources(gameState) : null; - const numWorkers = phase2 ? gameState.getOwnEntitiesByRole(PETRA.Worker.ROLE_WORKER, true).length : 0; + const numWorkers = phase2 ? gameState.getOwnEntitiesByRole(Worker.ROLE_WORKER, true).length : 0; for (const tech of techs) { if (!tech[1]._template.modifications) @@ -155,7 +158,7 @@ PETRA.ResearchManager.prototype.researchPreferredTechs = function(gameState, tec return null; }; -PETRA.ResearchManager.prototype.update = function(gameState, queues) +ResearchManager.prototype.update = function(gameState, queues) { if (queues.minorTech.hasQueuedUnits() || queues.majorTech.hasQueuedUnits()) return; @@ -168,12 +171,12 @@ PETRA.ResearchManager.prototype.update = function(gameState, queues) if (techName.increasePriority) { gameState.ai.queueManager.changePriority("minorTech", 2*this.Config.priorities.minorTech); - const plan = new PETRA.ResearchPlan(gameState, techName.name); + const plan = new ResearchPlan(gameState, techName.name); plan.queueToReset = "minorTech"; queues.minorTech.addPlan(plan); } else - queues.minorTech.addPlan(new PETRA.ResearchPlan(gameState, techName.name)); + queues.minorTech.addPlan(new ResearchPlan(gameState, techName.name)); return; } @@ -186,12 +189,12 @@ PETRA.ResearchManager.prototype.update = function(gameState, queues) if (techName.increasePriority) { gameState.ai.queueManager.changePriority("minorTech", 2*this.Config.priorities.minorTech); - const plan = new PETRA.ResearchPlan(gameState, techName.name); + const plan = new ResearchPlan(gameState, techName.name); plan.queueToReset = "minorTech"; queues.minorTech.addPlan(plan); } else - queues.minorTech.addPlan(new PETRA.ResearchPlan(gameState, techName.name)); + queues.minorTech.addPlan(new ResearchPlan(gameState, techName.name)); return; } @@ -221,10 +224,10 @@ PETRA.ResearchManager.prototype.update = function(gameState, queues) return; // randomly pick one. No worries about pairs in that case. - queues.minorTech.addPlan(new PETRA.ResearchPlan(gameState, pickRandom(techs)[0])); + queues.minorTech.addPlan(new ResearchPlan(gameState, pickRandom(techs)[0])); }; -PETRA.ResearchManager.prototype.CostSum = function(cost) +ResearchManager.prototype.CostSum = function(cost) { let costSum = 0; for (const res in cost) @@ -232,11 +235,11 @@ PETRA.ResearchManager.prototype.CostSum = function(cost) return costSum; }; -PETRA.ResearchManager.prototype.Serialize = function() +ResearchManager.prototype.Serialize = function() { return {}; }; -PETRA.ResearchManager.prototype.Deserialize = function(data) +ResearchManager.prototype.Deserialize = function(data) { }; diff --git a/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js b/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js index 07766bad87..0e1ebbdcf9 100644 --- a/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js +++ b/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js @@ -1,9 +1,14 @@ +import { Config, DIFFICULTY_SANDBOX } from "simulation/ai/petra/config.js"; +import { gatherTreasure, getLandAccess, isFastMoving } from "simulation/ai/petra/entityExtend.js"; +import { Headquather } from "simulation/ai/petra/headquarters.js"; +import { ConstructionPlan } from "simulation/ai/petra/queueplanBuilding.js"; + /** * Determines the strategy to adopt when starting a new game, * depending on the initial conditions */ -PETRA.HQ.prototype.gameAnalysis = function(gameState) +Headquather.prototype.gameAnalysis = function(gameState) { // Analysis of the terrain and the different access regions if (!this.regionAnalysis(gameState)) @@ -27,7 +32,7 @@ PETRA.HQ.prototype.gameAnalysis = function(gameState) // Sandbox difficulty should not try to expand - this.canExpand = this.Config.difficulty != PETRA.DIFFICULTY_SANDBOX; + this.canExpand = this.Config.difficulty != DIFFICULTY_SANDBOX; // If no base yet, check if we can construct one. If not, dispatch our units to possible tasks/attacks this.canBuildUnits = true; if (!gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).hasEntities()) @@ -52,7 +57,7 @@ PETRA.HQ.prototype.gameAnalysis = function(gameState) /** * Assign the starting entities to the different bases */ -PETRA.HQ.prototype.assignStartingEntities = function(gameState) +Headquather.prototype.assignStartingEntities = function(gameState) { for (const ent of gameState.getOwnEntities().values()) { @@ -96,7 +101,7 @@ PETRA.HQ.prototype.assignStartingEntities = function(gameState) * determine the main land Index (or water index if none) * as well as the list of allowed (land andf water) regions */ -PETRA.HQ.prototype.regionAnalysis = function(gameState) +Headquather.prototype.regionAnalysis = function(gameState) { const accessibility = gameState.ai.accessibility; let landIndex; @@ -190,7 +195,7 @@ PETRA.HQ.prototype.regionAnalysis = function(gameState) * load units and buildings from the config files * TODO: change that to something dynamic */ -PETRA.HQ.prototype.structureAnalysis = function(gameState) +Headquather.prototype.structureAnalysis = function(gameState) { const civref = gameState.playerData.civ; const civ = civref in this.Config.buildings ? civref : 'default'; @@ -204,7 +209,7 @@ PETRA.HQ.prototype.structureAnalysis = function(gameState) * build our first base * if not enough resource, try first to do a dock */ -PETRA.HQ.prototype.buildFirstBase = function(gameState) +Headquather.prototype.buildFirstBase = function(gameState) { if (gameState.ai.queues.civilCentre.hasQueuedUnits()) return; @@ -226,7 +231,7 @@ PETRA.HQ.prototype.buildFirstBase = function(gameState) continue; // If we can get a treasure around, just do it if (ent.isIdle()) - PETRA.gatherTreasure(gameState, ent); + gatherTreasure(gameState, ent); // Then count the resources from the treasures being collected const treasureId = ent.getMetadata(PlayerID, "treasure"); if (!treasureId) @@ -266,12 +271,12 @@ PETRA.HQ.prototype.buildFirstBase = function(gameState) { if (!ent.hasClass("Worker")) continue; - if (PETRA.isFastMoving(ent)) + if (isFastMoving(ent)) continue; let pos = ent.position(); if (!pos) { - const holder = PETRA.getHolder(gameState, ent); + const holder = getHolder(gameState, ent); if (!holder || !holder.position()) continue; pos = holder.position(); @@ -305,10 +310,15 @@ PETRA.HQ.prototype.buildFirstBase = function(gameState) if (goal == "dock") { const sea = startingPoint[imax].sea > 1 ? startingPoint[imax].sea : undefined; - gameState.ai.queues.dock.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/dock", { "sea": sea, "proximity": startingPoint[imax].pos })); + gameState.ai.queues.dock.addPlan(new ConstructionPlan(gameState, "structures/{civ}/dock", + { "sea": sea, "proximity": startingPoint[imax].pos })); } else - gameState.ai.queues.civilCentre.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/civil_centre", { "base": -1, "resource": "wood", "proximity": startingPoint[imax].pos })); + { + gameState.ai.queues.civilCentre.addPlan(new ConstructionPlan(gameState, + "structures/{civ}/civil_centre", + { "base": -1, "resource": "wood", "proximity": startingPoint[imax].pos })); + } }; /** @@ -316,7 +326,7 @@ PETRA.HQ.prototype.buildFirstBase = function(gameState) * - if one of our allies has a cc, affect a small fraction of our army for his defense, the rest will attack * - otherwise all units will attack */ -PETRA.HQ.prototype.dispatchUnits = function(gameState) +Headquather.prototype.dispatchUnits = function(gameState) { const allycc = gameState.getExclusiveAllyEntities().filter(API3.Filters.byClass("CivCentre")).toEntityArray(); if (allycc.length) @@ -333,10 +343,10 @@ PETRA.HQ.prototype.dispatchUnits = function(gameState) return; if (ent.getMetadata(PlayerID, "allied")) return; - const access = PETRA.getLandAccess(gameState, ent); + const access = getLandAccess(gameState, ent); for (const cc of allycc) { - if (!cc.position() || PETRA.getLandAccess(gameState, cc) != access) + if (!cc.position() || getLandAccess(gameState, cc) != access) continue; --num; --num1; @@ -352,10 +362,10 @@ PETRA.HQ.prototype.dispatchUnits = function(gameState) return; if (ent.getMetadata(PlayerID, "allied")) return; - const access = PETRA.getLandAccess(gameState, ent); + const access = getLandAccess(gameState, ent); for (const cc of allycc) { - if (!cc.position() || PETRA.getLandAccess(gameState, cc) != access) + if (!cc.position() || getLandAccess(gameState, cc) != access) continue; --num; --num2; @@ -371,10 +381,10 @@ PETRA.HQ.prototype.dispatchUnits = function(gameState) return; if (ent.getMetadata(PlayerID, "allied")) return; - const access = PETRA.getLandAccess(gameState, ent); + const access = getLandAccess(gameState, ent); for (const cc of allycc) { - if (!cc.position() || PETRA.getLandAccess(gameState, cc) != access) + if (!cc.position() || getLandAccess(gameState, cc) != access) continue; if (!ent.hasClass("Support")) --num; @@ -392,7 +402,7 @@ PETRA.HQ.prototype.dispatchUnits = function(gameState) * - if on a small island, favor fishing * - count the available wood resource, and allow rushes only if enough (we should otherwise favor expansion) */ -PETRA.HQ.prototype.configFirstBase = function(gameState) +Headquather.prototype.configFirstBase = function(gameState) { if (!this.hasPotentialBase()) return; @@ -495,14 +505,19 @@ PETRA.HQ.prototype.configFirstBase = function(gameState) const cost = new API3.Resources(gameState.getTemplate(newDP.templateName).cost()); gameState.ai.queueManager.setAccounts(gameState, cost, "dropsites"); } - gameState.ai.queues.dropsites.addPlan(new PETRA.ConstructionPlan(gameState, newDP.templateName, { "base": this.baseManagers()[0].ID }, newDP.pos)); + gameState.ai.queues.dropsites.addPlan(new ConstructionPlan(gameState, newDP.templateName, + { "base": this.baseManagers()[0].ID }, newDP.pos)); } } // and build immediately a corral if needed if (this.needCorral) { const template = gameState.applyCiv("structures/{civ}/corral"); - if (!gameState.getOwnEntitiesByClass("Corral", true).hasEntities() && this.canBuild(gameState, template)) - gameState.ai.queues.corral.addPlan(new PETRA.ConstructionPlan(gameState, template, { "base": this.baseManagers()[0].ID })); + if (!gameState.getOwnEntitiesByClass("Corral", true).hasEntities() && + this.canBuild(gameState, template)) + { + gameState.ai.queues.corral.addPlan( + new ConstructionPlan(gameState, template, { "base": this.baseManagers()[0].ID })); + } } }; diff --git a/binaries/data/mods/public/simulation/ai/petra/tradeManager.js b/binaries/data/mods/public/simulation/ai/petra/tradeManager.js index 071cb758ef..0662740d20 100644 --- a/binaries/data/mods/public/simulation/ai/petra/tradeManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/tradeManager.js @@ -1,35 +1,44 @@ +import { newTradeRoute as chatNewTradeRoute } from "simulation/ai/petra/chatHelper.js"; +import { Config, DIFFICULTY_VERY_EASY } from "simulation/ai/petra/config.js"; +import { gatherTreasure, getBestBase, getLandAccess, getSeaAccess, isLineInsideEnemyTerritory } from + "simulation/ai/petra/entityExtend.js"; +import { ConstructionPlan } from "simulation/ai/petra/queueplanBuilding.js"; +import { TrainingPlan } from "simulation/ai/petra/queueplanTraining.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + /** * Manage the trade */ -PETRA.TradeManager = function(Config) +export function TradeManager(config) { - this.Config = Config; + this.Config = config; this.tradeRoute = undefined; this.potentialTradeRoute = undefined; this.routeProspection = false; this.targetNumTraders = this.Config.Economy.targetNumTraders; this.warnedAllies = {}; -}; +} -PETRA.TradeManager.prototype.init = function(gameState) +TradeManager.prototype.init = function(gameState) { - this.traders = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER)); + this.traders = gameState.getOwnUnits().filter( + API3.Filters.byMetadata(PlayerID, "role", Worker.ROLE_TRADER)); this.traders.registerUpdates(); this.minimalGain = gameState.ai.HQ.navalMap ? 3 : 5; }; -PETRA.TradeManager.prototype.hasTradeRoute = function() +TradeManager.prototype.hasTradeRoute = function() { return this.tradeRoute !== undefined; }; -PETRA.TradeManager.prototype.assignTrader = function(ent) +TradeManager.prototype.assignTrader = function(ent) { - ent.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER); + ent.setMetadata(PlayerID, "role", Worker.ROLE_TRADER); this.traders.updateEnt(ent); }; -PETRA.TradeManager.prototype.trainMoreTraders = function(gameState, queues) +TradeManager.prototype.trainMoreTraders = function(gameState, queues) { if (!this.hasTradeRoute() || queues.trader.hasQueuedUnits()) return; @@ -41,7 +50,7 @@ PETRA.TradeManager.prototype.trainMoreTraders = function(gameState, queues) gameState.getOwnTrainingFacilities().forEach(function(ent) { for (const item of ent.trainingQueue()) { - if (!item.metadata || !item.metadata.role || item.metadata.role !== PETRA.Worker.ROLE_TRADER) + if (!item.metadata || !item.metadata.role || item.metadata.role !== Worker.ROLE_TRADER) continue; numTraders += item.count; if (item.metadata.sea !== undefined) @@ -56,7 +65,7 @@ PETRA.TradeManager.prototype.trainMoreTraders = function(gameState, queues) return; let template; - const metadata = { "role": PETRA.Worker.ROLE_TRADER }; + const metadata = { "role": Worker.ROLE_TRADER }; if (this.tradeRoute.sea) { // if we have some merchand ships assigned to transport, try first to reassign them @@ -67,7 +76,7 @@ PETRA.TradeManager.prototype.trainMoreTraders = function(gameState, queues) gameState.ai.HQ.navalManager.seaTransportShips[this.tradeRoute.sea].forEach(function(ship) { if (already || !ship.hasClass("Trader")) return; - if (ship.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_SWITCH_TO_TRADER) + if (ship.getMetadata(PlayerID, "role") === Worker.ROLE_SWITCH_TO_TRADER) { already = true; return; @@ -79,9 +88,9 @@ PETRA.TradeManager.prototype.trainMoreTraders = function(gameState, queues) if (shipToSwitch) { if (shipToSwitch.getMetadata(PlayerID, "transporter") === undefined) - shipToSwitch.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER); + shipToSwitch.setMetadata(PlayerID, "role", Worker.ROLE_TRADER); else - shipToSwitch.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_SWITCH_TO_TRADER); + shipToSwitch.setMetadata(PlayerID, "role", Worker.ROLE_SWITCH_TO_TRADER); return; } @@ -104,15 +113,17 @@ PETRA.TradeManager.prototype.trainMoreTraders = function(gameState, queues) gameState.getPlayerCiv() + " but no template found."); return; } - queues.trader.addPlan(new PETRA.TrainingPlan(gameState, template, metadata, 1, 1)); + queues.trader.addPlan(new TrainingPlan(gameState, template, metadata, 1, 1)); }; -PETRA.TradeManager.prototype.updateTrader = function(gameState, ent) +TradeManager.prototype.updateTrader = function(gameState, ent) { if (ent.hasClass("Ship") && gameState.ai.playedTurn % 5 == 0 && - !ent.unitAIState().startsWith("INDIVIDUAL.COLLECTTREASURE") && - PETRA.gatherTreasure(gameState, ent, true)) + !ent.unitAIState().startsWith("INDIVIDUAL.COLLECTTREASURE") && + gatherTreasure(gameState, ent, true)) + { return; + } if (!this.hasTradeRoute() || !ent.isIdle() || !ent.position()) return; @@ -122,7 +133,7 @@ PETRA.TradeManager.prototype.updateTrader = function(gameState, ent) // TODO if the trader is idle and has workOrders, restore them to avoid losing the current gain Engine.ProfileStart("Trade Manager"); - const access = ent.hasClass("Ship") ? PETRA.getSeaAccess(gameState, ent) : PETRA.getLandAccess(gameState, ent); + const access = ent.hasClass("Ship") ? getSeaAccess(gameState, ent) : getLandAccess(gameState, ent); const route = this.checkRoutes(gameState, access); if (!route) { @@ -155,7 +166,7 @@ PETRA.TradeManager.prototype.updateTrader = function(gameState, ent) Engine.ProfileStop(); }; -PETRA.TradeManager.prototype.setTradingGoods = function(gameState) +TradeManager.prototype.setTradingGoods = function(gameState) { const resTradeCodes = Resources.GetTradableCodes(); if (!resTradeCodes.length) @@ -212,7 +223,7 @@ PETRA.TradeManager.prototype.setTradingGoods = function(gameState) * Try to barter unneeded resources for needed resources. * only once per turn because the info is not updated within a turn */ -PETRA.TradeManager.prototype.performBarter = function(gameState) +TradeManager.prototype.performBarter = function(gameState) { const barterers = gameState.getOwnEntitiesByClass("Barter", true).filter(API3.Filters.isBuilt()).toEntityArray(); if (barterers.length == 0) @@ -329,7 +340,7 @@ PETRA.TradeManager.prototype.performBarter = function(gameState) return false; }; -PETRA.TradeManager.prototype.checkEvents = function(gameState, events) +TradeManager.prototype.checkEvents = function(gameState, events) { // check if one market from a traderoute is renamed, change the route accordingly for (const evt of events.EntityRenamed) @@ -398,7 +409,7 @@ PETRA.TradeManager.prototype.checkEvents = function(gameState, events) return false; }; -PETRA.TradeManager.prototype.activateProspection = function(gameState) +TradeManager.prototype.activateProspection = function(gameState) { this.routeProspection = true; gameState.ai.HQ.buildManager.setBuildable(gameState.applyCiv("structures/{civ}/market")); @@ -409,7 +420,7 @@ PETRA.TradeManager.prototype.activateProspection = function(gameState) * fills the best trade route in this.tradeRoute and the best potential route in this.potentialTradeRoute * If an index is given, it returns the best route with this index or the best land route if index is a land index */ -PETRA.TradeManager.prototype.checkRoutes = function(gameState, accessIndex) +TradeManager.prototype.checkRoutes = function(gameState, accessIndex) { // If we cannot trade, do not bother checking routes. if (!Resources.GetTradableCodes().length) @@ -443,21 +454,21 @@ PETRA.TradeManager.prototype.checkRoutes = function(gameState, accessIndex) { if (!m1.position()) continue; - const access1 = PETRA.getLandAccess(gameState, m1); - const sea1 = m1.hasClass("Naval") ? PETRA.getSeaAccess(gameState, m1) : undefined; + const access1 = getLandAccess(gameState, m1); + const sea1 = m1.hasClass("Naval") ? getSeaAccess(gameState, m1) : undefined; for (const m2 of market2.values()) { if (onlyOurs && m1.id() >= m2.id()) continue; if (!m2.position()) continue; - const access2 = PETRA.getLandAccess(gameState, m2); - const sea2 = m2.hasClass("Naval") ? PETRA.getSeaAccess(gameState, m2) : undefined; + const access2 = getLandAccess(gameState, m2); + const sea2 = m2.hasClass("Naval") ? getSeaAccess(gameState, m2) : undefined; const land = access1 == access2 ? access1 : undefined; const sea = sea1 && sea1 == sea2 ? sea1 : undefined; if (!land && !sea) continue; - if (land && PETRA.isLineInsideEnemyTerritory(gameState, m1.position(), m2.position())) + if (land && isLineInsideEnemyTerritory(gameState, m1.position(), m2.position())) continue; let gainMultiplier; if (land && traderTemplatesGains.landGainMultiplier) @@ -531,7 +542,7 @@ PETRA.TradeManager.prototype.checkRoutes = function(gameState, accessIndex) owner = this.tradeRoute.target.owner(); if (owner != PlayerID && !this.warnedAllies[owner]) { // Warn an ally that we have a trade route with him - PETRA.chatNewTradeRoute(gameState, owner); + chatNewTradeRoute(gameState, owner); this.warnedAllies[owner] = true; } } @@ -548,7 +559,7 @@ PETRA.TradeManager.prototype.checkRoutes = function(gameState, accessIndex) }; /** Called when a market was built or destroyed, and checks if trader orders should be changed */ -PETRA.TradeManager.prototype.checkTrader = function(gameState, ent) +TradeManager.prototype.checkTrader = function(gameState, ent) { const presentRoute = ent.getMetadata(PlayerID, "route"); if (!presentRoute) @@ -561,7 +572,7 @@ PETRA.TradeManager.prototype.checkTrader = function(gameState, ent) return; } - const access = ent.hasClass("Ship") ? PETRA.getSeaAccess(gameState, ent) : PETRA.getLandAccess(gameState, ent); + const access = ent.hasClass("Ship") ? getSeaAccess(gameState, ent) : getLandAccess(gameState, ent); const possibleRoute = this.checkRoutes(gameState, access); // Warning: presentRoute is from metadata, so contains entity ids if (!possibleRoute || @@ -572,7 +583,7 @@ PETRA.TradeManager.prototype.checkTrader = function(gameState, ent) ent.setMetadata(PlayerID, "route", undefined); if (!possibleRoute && !ent.hasClass("Ship")) { - const closestBase = PETRA.getBestBase(gameState, ent, true); + const closestBase = getBestBase(gameState, ent, true); if (closestBase.accessIndex == access) { const closestBasePos = closestBase.anchor.position(); @@ -584,7 +595,7 @@ PETRA.TradeManager.prototype.checkTrader = function(gameState, ent) } }; -PETRA.TradeManager.prototype.prospectForNewMarket = function(gameState, queues) +TradeManager.prototype.prospectForNewMarket = function(gameState, queues) { if (queues.economicBuilding.hasQueuedUnitsWithClass("Trade") || queues.dock.hasQueuedUnitsWithClass("Trade")) return; @@ -622,13 +633,13 @@ PETRA.TradeManager.prototype.prospectForNewMarket = function(gameState, queues) if (!this.tradeRoute) gameState.ai.queueManager.changePriority("economicBuilding", 2 * this.Config.priorities.economicBuilding); - const plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/market"); + const plan = new ConstructionPlan(gameState, "structures/{civ}/market"); if (!this.tradeRoute) plan.queueToReset = "economicBuilding"; queues.economicBuilding.addPlan(plan); }; -PETRA.TradeManager.prototype.isNewMarketWorth = function(expectedGain) +TradeManager.prototype.isNewMarketWorth = function(expectedGain) { if (!Resources.GetTradableCodes().length) return false; @@ -640,12 +651,12 @@ PETRA.TradeManager.prototype.isNewMarketWorth = function(expectedGain) return true; }; -PETRA.TradeManager.prototype.update = function(gameState, events, queues) +TradeManager.prototype.update = function(gameState, events, queues) { if (gameState.ai.HQ.canBarter && Resources.GetBarterableCodes().length) this.performBarter(gameState); - if (this.Config.difficulty <= PETRA.DIFFICULTY_VERY_EASY) + if (this.Config.difficulty <= DIFFICULTY_VERY_EASY) return; if (this.checkEvents(gameState, events)) // true if one market was built or destroyed @@ -669,7 +680,7 @@ PETRA.TradeManager.prototype.update = function(gameState, events, queues) this.prospectForNewMarket(gameState, queues); }; -PETRA.TradeManager.prototype.routeEntToId = function(route) +TradeManager.prototype.routeEntToId = function(route) { if (!route) return undefined; @@ -689,7 +700,7 @@ PETRA.TradeManager.prototype.routeEntToId = function(route) return ret; }; -PETRA.TradeManager.prototype.routeIdToEnt = function(gameState, route) +TradeManager.prototype.routeIdToEnt = function(gameState, route) { if (!route) return undefined; @@ -709,7 +720,7 @@ PETRA.TradeManager.prototype.routeIdToEnt = function(gameState, route) return ret; }; -PETRA.TradeManager.prototype.Serialize = function() +TradeManager.prototype.Serialize = function() { return { "tradeRoute": this.routeEntToId(this.tradeRoute), @@ -720,7 +731,7 @@ PETRA.TradeManager.prototype.Serialize = function() }; }; -PETRA.TradeManager.prototype.Deserialize = function(gameState, data) +TradeManager.prototype.Deserialize = function(gameState, data) { for (const key in data) { diff --git a/binaries/data/mods/public/simulation/ai/petra/transportPlan.js b/binaries/data/mods/public/simulation/ai/petra/transportPlan.js index 99f2ee0513..b1bddcfa27 100644 --- a/binaries/data/mods/public/simulation/ai/petra/transportPlan.js +++ b/binaries/data/mods/public/simulation/ai/petra/transportPlan.js @@ -1,3 +1,6 @@ +import { getLandAccess } from "simulation/ai/petra/entityExtend.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + /** * Describes a transport plan * Constructor assign units (units is an ID array), a destination (position). @@ -20,7 +23,7 @@ * transporter = this.ID */ -PETRA.TransportPlan = function(gameState, units, startIndex, endIndex, endPos, ship) +export function TransportPlan(gameState, units, startIndex, endIndex, endPos, ship) { this.ID = gameState.ai.uniqueIDs.transports++; this.debug = gameState.ai.Config.debug; @@ -67,23 +70,23 @@ PETRA.TransportPlan = function(gameState, units, startIndex, endIndex, endPos, s API3.warn("Starting a new transport plan with ID " + this.ID + " to index " + endIndex + " with units length " + units.length); - this.state = PETRA.TransportPlan.BOARDING; + this.state = TransportPlan.BOARDING; this.boardingPos = {}; this.needTransportShips = ship === undefined; this.nTry = {}; return true; -}; +} /** * We're trying to board units onto our ships. */ -PETRA.TransportPlan.BOARDING = "boarding"; +TransportPlan.BOARDING = "boarding"; /** * We're moving ships and eventually unload units. */ -PETRA.TransportPlan.SAILING = "sailing"; +TransportPlan.SAILING = "sailing"; -PETRA.TransportPlan.prototype.init = function(gameState) +TransportPlan.prototype.init = function(gameState) { this.units = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "transport", this.ID)); this.ships = gameState.ai.HQ.navalManager.ships.filter(API3.Filters.byMetadata(PlayerID, "transporter", this.ID)); @@ -97,7 +100,7 @@ PETRA.TransportPlan.prototype.init = function(gameState) }; /** count available slots */ -PETRA.TransportPlan.prototype.countFreeSlots = function() +TransportPlan.prototype.countFreeSlots = function() { let slots = 0; for (const ship of this.transportShips.values()) @@ -105,7 +108,7 @@ PETRA.TransportPlan.prototype.countFreeSlots = function() return slots; }; -PETRA.TransportPlan.prototype.countFreeSlotsOnShip = function(ship) +TransportPlan.prototype.countFreeSlotsOnShip = function(ship) { if (ship.hitpoints() < ship.garrisonEjectHealth() * ship.maxHitpoints()) return 0; @@ -114,7 +117,7 @@ PETRA.TransportPlan.prototype.countFreeSlotsOnShip = function(ship) return Math.max(ship.garrisonMax() - occupied, 0); }; -PETRA.TransportPlan.prototype.assignUnitToShip = function(gameState, ent) +TransportPlan.prototype.assignUnitToShip = function(gameState, ent) { if (this.needTransportShips) return; @@ -126,7 +129,7 @@ PETRA.TransportPlan.prototype.assignUnitToShip = function(gameState, ent) ent.setMetadata(PlayerID, "onBoard", ship.id()); if (this.debug > 1) { - if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_ATTACK) + if (ent.getMetadata(PlayerID, "role") === Worker.ROLE_ATTACK) Engine.PostCommand(PlayerID, { "type": "set-shading-color", "entities": [ent.id()], "rgb": [2, 0, 0] }); else Engine.PostCommand(PlayerID, { "type": "set-shading-color", "entities": [ent.id()], "rgb": [0, 2, 0] }); @@ -146,7 +149,7 @@ PETRA.TransportPlan.prototype.assignUnitToShip = function(gameState, ent) this.needSplit.push(ent); }; -PETRA.TransportPlan.prototype.assignShip = function(gameState) +TransportPlan.prototype.assignShip = function(gameState) { let pos; // choose a unit of this plan not yet assigned to a ship @@ -186,7 +189,7 @@ PETRA.TransportPlan.prototype.assignShip = function(gameState) }; /** add a unit to this plan */ -PETRA.TransportPlan.prototype.addUnit = function(unit, endPos) +TransportPlan.prototype.addUnit = function(unit, endPos) { unit.setMetadata(PlayerID, "transport", this.ID); unit.setMetadata(PlayerID, "endPos", endPos); @@ -194,7 +197,7 @@ PETRA.TransportPlan.prototype.addUnit = function(unit, endPos) }; /** remove a unit from this plan, if not yet on board */ -PETRA.TransportPlan.prototype.removeUnit = function(gameState, unit) +TransportPlan.prototype.removeUnit = function(gameState, unit) { const shipId = unit.getMetadata(PlayerID, "onBoard"); if (shipId == "onBoard") @@ -218,7 +221,7 @@ PETRA.TransportPlan.prototype.removeUnit = function(gameState, unit) } }; -PETRA.TransportPlan.prototype.releaseShip = function(ship) +TransportPlan.prototype.releaseShip = function(ship) { if (ship.getMetadata(PlayerID, "transporter") != this.ID) { @@ -232,11 +235,11 @@ PETRA.TransportPlan.prototype.releaseShip = function(ship) ship.setStance(defaultStance); ship.setMetadata(PlayerID, "transporter", undefined); - if (ship.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_SWITCH_TO_TRADER) - ship.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER); + if (ship.getMetadata(PlayerID, "role") === Worker.ROLE_SWITCH_TO_TRADER) + ship.setMetadata(PlayerID, "role", Worker.ROLE_TRADER); }; -PETRA.TransportPlan.prototype.releaseAll = function() +TransportPlan.prototype.releaseAll = function() { for (const ship of this.ships.values()) this.releaseShip(ship); @@ -256,7 +259,7 @@ PETRA.TransportPlan.prototype.releaseAll = function() }; /** TODO not currently used ... to be fixed */ -PETRA.TransportPlan.prototype.cancelTransport = function(gameState) +TransportPlan.prototype.cancelTransport = function(gameState) { const ent = this.units.toEntityArray()[0]; let base = gameState.ai.HQ.getBaseByID(ent.getMetadata(PlayerID, "base")); @@ -284,17 +287,17 @@ PETRA.TransportPlan.prototype.cancelTransport = function(gameState) /** * Try to move on and then clear the plan. */ -PETRA.TransportPlan.prototype.update = function(gameState) +TransportPlan.prototype.update = function(gameState) { - if (this.state === PETRA.TransportPlan.BOARDING) + if (this.state === TransportPlan.BOARDING) this.onBoarding(gameState); - else if (this.state === PETRA.TransportPlan.SAILING) + else if (this.state === TransportPlan.SAILING) this.onSailing(gameState); return this.units.length; }; -PETRA.TransportPlan.prototype.onBoarding = function(gameState) +TransportPlan.prototype.onBoarding = function(gameState) { let ready = true; const time = gameState.ai.elapsedTime; @@ -427,14 +430,14 @@ PETRA.TransportPlan.prototype.onBoarding = function(gameState) this.boardingPos[ship.id()] = this.getBoardingPos(gameState, ship, this.endIndex, this.sea, this.endPos, true); ship.move(this.boardingPos[ship.id()][0], this.boardingPos[ship.id()][1]); } - this.state = PETRA.TransportPlan.SAILING; + this.state = TransportPlan.SAILING; this.nTry = {}; this.unloaded = []; this.recovered = []; }; /** tell if a unit is garrisoned in one of the ships of this plan, and update its metadata if yes */ -PETRA.TransportPlan.prototype.isOnBoard = function(ent) +TransportPlan.prototype.isOnBoard = function(ent) { for (const ship of this.transportShips.values()) { @@ -447,7 +450,8 @@ PETRA.TransportPlan.prototype.isOnBoard = function(ent) }; /** when avoidEnnemy is true, we try to not board/unboard in ennemy territory */ -PETRA.TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex, seaIndex, destination, avoidEnnemy) +TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex, seaIndex, destination, + avoidEnnemy) { if (!gameState.ai.HQ.navalManager.landingZones[landIndex]) { @@ -510,7 +514,7 @@ PETRA.TransportPlan.prototype.getBoardingPos = function(gameState, ship, landInd return posmin; }; -PETRA.TransportPlan.prototype.onSailing = function(gameState) +TransportPlan.prototype.onSailing = function(gameState) { // Check that the units recovered on the previous turn have been reloaded for (const recov of this.recovered) @@ -528,7 +532,7 @@ PETRA.TransportPlan.prototype.onSailing = function(gameState) if (this.debug > 1) API3.warn(">>> transport " + this.ID + " reloading failed ... <<<"); // destroy the unit if inaccessible otherwise leave it there - const index = PETRA.getLandAccess(gameState, ent); + const index = getLandAccess(gameState, ent); if (gameState.ai.HQ.landRegions[index]) { if (this.debug > 1) @@ -576,7 +580,7 @@ PETRA.TransportPlan.prototype.onSailing = function(gameState) ent.destroy(); } } - else if (PETRA.getLandAccess(gameState, ent) != this.endIndex) + else if (getLandAccess(gameState, ent) != this.endIndex) { // unit unloaded on a wrong region - try to regarrison it and move a bit the ship if (this.debug > 1) @@ -680,7 +684,7 @@ PETRA.TransportPlan.prototype.onSailing = function(gameState) } }; -PETRA.TransportPlan.prototype.resetUnit = function(gameState, ent) +TransportPlan.prototype.resetUnit = function(gameState, ent) { ent.setMetadata(PlayerID, "transport", undefined); ent.setMetadata(PlayerID, "onBoard", undefined); @@ -700,7 +704,7 @@ PETRA.TransportPlan.prototype.resetUnit = function(gameState, ent) } }; -PETRA.TransportPlan.prototype.Serialize = function() +TransportPlan.prototype.Serialize = function() { return { "ID": this.ID, @@ -719,7 +723,7 @@ PETRA.TransportPlan.prototype.Serialize = function() }; }; -PETRA.TransportPlan.prototype.Deserialize = function(data) +TransportPlan.prototype.Deserialize = function(data) { for (const key in data) this[key] = data[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/victoryManager.js b/binaries/data/mods/public/simulation/ai/petra/victoryManager.js index 327d42aa76..facb6e80f5 100644 --- a/binaries/data/mods/public/simulation/ai/petra/victoryManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/victoryManager.js @@ -1,3 +1,9 @@ +import { AttackPlan } from "simulation/ai/petra/attackPlan.js"; +import { getAttackBonus, getBestBase, getLandAccess, returnResources } from + "simulation/ai/petra/entityExtend.js"; +import { TrainingPlan } from "simulation/ai/petra/queueplanTraining.js"; +import { Worker } from "simulation/ai/petra/worker.js"; + /** * Handle events that are important to specific victory conditions: * in capture_the_relic, capture gaia relics and train military guards. @@ -5,7 +11,7 @@ * in wonder, train military guards. */ -PETRA.VictoryManager = function(Config) +export function VictoryManager(Config) { this.Config = Config; this.criticalEnts = new Map(); @@ -16,12 +22,12 @@ PETRA.VictoryManager = function(Config) this.tryCaptureGaiaRelicLapseTime = -1; // Gaia relics which we are targeting currently and have not captured yet this.targetedGaiaRelics = new Map(); -}; +} /** * Cache the ids of any inital victory-critical entities. */ -PETRA.VictoryManager.prototype.init = function(gameState) +VictoryManager.prototype.init = function(gameState) { if (gameState.getVictoryConditions().has("wonder")) { @@ -60,7 +66,7 @@ PETRA.VictoryManager.prototype.init = function(gameState) * If it is less than 40%, try to garrison in the closest possible structure * If the hero cannot garrison, retreat it to the closest base */ -PETRA.VictoryManager.prototype.checkEvents = function(gameState, events) +VictoryManager.prototype.checkEvents = function(gameState, events) { if (gameState.getVictoryConditions().has("wonder")) { @@ -78,7 +84,7 @@ PETRA.VictoryManager.prototype.checkEvents = function(gameState, events) for (const worker of builders.values()) { worker.setMetadata(PlayerID, "base", base.ID); - worker.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER); + worker.setMetadata(PlayerID, "subrole", Worker.SUBROLE_BUILDER); worker.setMetadata(PlayerID, "target-foundation", ent.id()); } } @@ -141,7 +147,7 @@ PETRA.VictoryManager.prototype.checkEvents = function(gameState, events) for (const entId of evt.entities) { const ent = gameState.getEntityById(entId); - if (ent && ent.isOwn(PlayerID) && ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_CRITICAL_ENT_HEALER) + if (ent && ent.isOwn(PlayerID) && ent.getMetadata(PlayerID, "role") === Worker.ROLE_CRITICAL_ENT_HEALER) this.assignGuardToCriticalEnt(gameState, ent); } @@ -253,8 +259,8 @@ PETRA.VictoryManager.prototype.checkEvents = function(gameState, events) continue; // If this ent travelled to a criticalEnt's accessValue, try again to assign as a guard - if ((ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_CRITICAL_ENT_HEALER || - ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_CRITICAL_ENT_GUARD) && !this.guardEnts.get(evt.entity)) + if ((ent.getMetadata(PlayerID, "role") === Worker.ROLE_CRITICAL_ENT_HEALER || + ent.getMetadata(PlayerID, "role") === Worker.ROLE_CRITICAL_ENT_GUARD) && !this.guardEnts.get(evt.entity)) { this.assignGuardToCriticalEnt(gameState, ent, ent.getMetadata(PlayerID, "guardedEnt")); continue; @@ -304,7 +310,7 @@ PETRA.VictoryManager.prototype.checkEvents = function(gameState, events) } }; -PETRA.VictoryManager.prototype.removeCriticalEnt = function(gameState, criticalEntId) +VictoryManager.prototype.removeCriticalEnt = function(gameState, criticalEntId) { for (const [guardId, role] of this.criticalEnts.get(criticalEntId).guards) { @@ -330,7 +336,7 @@ PETRA.VictoryManager.prototype.removeCriticalEnt = function(gameState, criticalE /** * Train more healers to be later affected to critical entities if needed */ -PETRA.VictoryManager.prototype.manageCriticalEntHealers = function(gameState, queues) +VictoryManager.prototype.manageCriticalEntHealers = function(gameState, queues) { if (gameState.ai.HQ.saveResources || queues.healer.hasQueuedUnits() || !gameState.getOwnEntitiesByClass("Temple", true).hasEntities() || @@ -342,7 +348,8 @@ PETRA.VictoryManager.prototype.manageCriticalEntHealers = function(gameState, qu if (data.healersAssigned === undefined || data.healersAssigned >= this.healersPerCriticalEnt) continue; const template = gameState.applyCiv("units/{civ}/support_healer_b"); - queues.healer.addPlan(new PETRA.TrainingPlan(gameState, template, { "role": PETRA.Worker.ROLE_CRITICAL_ENT_HEALER, "base": 0 }, 1, 1)); + queues.healer.addPlan(new TrainingPlan(gameState, template, + { "role": Worker.ROLE_CRITICAL_ENT_HEALER, "base": 0 }, 1, 1)); return; } }; @@ -352,9 +359,9 @@ PETRA.VictoryManager.prototype.manageCriticalEntHealers = function(gameState, qu * If we have too low a population and require units for other needs, remove guards so they can be reassigned. * TODO: Swap citizen soldier guards with champions if they become available. */ -PETRA.VictoryManager.prototype.manageCriticalEntGuards = function(gameState) +VictoryManager.prototype.manageCriticalEntGuards = function(gameState) { - let numWorkers = gameState.getOwnEntitiesByRole(PETRA.Worker.ROLE_WORKER, true).length; + let numWorkers = gameState.getOwnEntitiesByRole(Worker.ROLE_WORKER, true).length; if (numWorkers < 20) { for (const data of this.criticalEnts.values()) @@ -363,7 +370,7 @@ PETRA.VictoryManager.prototype.manageCriticalEntGuards = function(gameState) { const guardEnt = gameState.getEntityById(guardId); if (!guardEnt || !guardEnt.hasClass("CitizenSoldier") || - guardEnt.getMetadata(PlayerID, "role") !== PETRA.Worker.ROLE_CRITICAL_ENT_GUARD) + guardEnt.getMetadata(PlayerID, "role") !== Worker.ROLE_CRITICAL_ENT_GUARD) continue; guardEnt.removeGuard(); @@ -439,23 +446,23 @@ PETRA.VictoryManager.prototype.manageCriticalEntGuards = function(gameState) } }; -PETRA.VictoryManager.prototype.tryAssignMilitaryGuard = function(gameState, guardEnt, criticalEnt, checkForSameAccess) +VictoryManager.prototype.tryAssignMilitaryGuard = function(gameState, guardEnt, criticalEnt, checkForSameAccess) { if (guardEnt.getMetadata(PlayerID, "plan") !== undefined || guardEnt.getMetadata(PlayerID, "transport") !== undefined || this.criticalEnts.has(guardEnt.id()) || checkForSameAccess && (!guardEnt.position() || !criticalEnt.position() || - PETRA.getLandAccess(gameState, criticalEnt) != PETRA.getLandAccess(gameState, guardEnt))) + getLandAccess(gameState, criticalEnt) != getLandAccess(gameState, guardEnt))) return false; if (!this.assignGuardToCriticalEnt(gameState, guardEnt, criticalEnt.id())) return false; guardEnt.setMetadata(PlayerID, "plan", -2); - guardEnt.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_CRITICAL_ENT_GUARD); + guardEnt.setMetadata(PlayerID, "role", Worker.ROLE_CRITICAL_ENT_GUARD); return true; }; -PETRA.VictoryManager.prototype.pickCriticalEntRetreatLocation = function(gameState, criticalEnt, emergency) +VictoryManager.prototype.pickCriticalEntRetreatLocation = function(gameState, criticalEnt, emergency) { gameState.ai.HQ.defenseManager.garrisonAttackedUnit(gameState, criticalEnt, emergency); const plan = criticalEnt.getMetadata(PlayerID, "plan"); @@ -469,8 +476,8 @@ PETRA.VictoryManager.prototype.pickCriticalEntRetreatLocation = function(gameSta // Couldn't find a place to garrison, so the ent will flee from attacks if (!criticalEnt.hasClass("Relic") && criticalEnt.getStance() != "passive") criticalEnt.setStance("passive"); - const accessIndex = PETRA.getLandAccess(gameState, criticalEnt); - const bestBase = PETRA.getBestBase(gameState, criticalEnt, true); + const accessIndex = getLandAccess(gameState, criticalEnt); + const bestBase = getBestBase(gameState, criticalEnt, true); if (bestBase.accessIndex == accessIndex) { const bestBasePos = bestBase.anchor.position(); @@ -487,7 +494,7 @@ PETRA.VictoryManager.prototype.pickCriticalEntRetreatLocation = function(gameSta * which will be used once its transport has finished. * Return false if the guardEnt is not a valid guard unit (i.e. cannot guard or is being transported). */ -PETRA.VictoryManager.prototype.assignGuardToCriticalEnt = function(gameState, guardEnt, criticalEntId) +VictoryManager.prototype.assignGuardToCriticalEnt = function(gameState, guardEnt, criticalEntId) { if (guardEnt.getMetadata(PlayerID, "transport") !== undefined || !guardEnt.canGuard()) return false; @@ -542,13 +549,13 @@ PETRA.VictoryManager.prototype.assignGuardToCriticalEnt = function(gameState, gu if (guardEnt.getMetadata(PlayerID, "guardedEnt") != criticalEntId) guardEnt.setMetadata(PlayerID, "guardedEnt", criticalEntId); - const guardEntAccess = PETRA.getLandAccess(gameState, guardEnt); - const criticalEntAccess = PETRA.getLandAccess(gameState, criticalEnt); + const guardEntAccess = getLandAccess(gameState, guardEnt); + const criticalEntAccess = getLandAccess(gameState, criticalEnt); if (guardEntAccess == criticalEntAccess) { - const queued = PETRA.returnResources(gameState, guardEnt); + const queued = returnResources(gameState, guardEnt); guardEnt.guard(criticalEnt, queued); - const guardRole = guardEnt.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_CRITICAL_ENT_HEALER ? "healer" : "guard"; + const guardRole = guardEnt.getMetadata(PlayerID, "role") === Worker.ROLE_CRITICAL_ENT_HEALER ? "healer" : "guard"; this.criticalEnts.get(criticalEntId).guards.set(guardEnt.id(), guardRole); // Switch this guard ent to the criticalEnt's base @@ -562,14 +569,14 @@ PETRA.VictoryManager.prototype.assignGuardToCriticalEnt = function(gameState, gu return true; }; -PETRA.VictoryManager.prototype.resetCaptureGaiaRelic = function(gameState) +VictoryManager.prototype.resetCaptureGaiaRelic = function(gameState) { // Do not capture gaia relics too frequently as the ai has access to the entire map this.tryCaptureGaiaRelicLapseTime = gameState.ai.elapsedTime + 240 - 30 * (this.Config.difficulty - 3); this.tryCaptureGaiaRelic = false; }; -PETRA.VictoryManager.prototype.update = function(gameState, events, queues) +VictoryManager.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) @@ -649,7 +656,7 @@ PETRA.VictoryManager.prototype.update = function(gameState, events, queues) /** * Send an expedition to capture a gaia relic, or reinforce an existing one. */ -PETRA.VictoryManager.prototype.captureGaiaRelic = function(gameState, relic) +VictoryManager.prototype.captureGaiaRelic = function(gameState, relic) { let capture = -relic.defaultRegenRate(); const sumCapturePoints = relic.capturePoints().reduce((a, b) => a + b); @@ -660,13 +667,13 @@ PETRA.VictoryManager.prototype.captureGaiaRelic = function(gameState, relic) if (!attack) continue; for (const ent of attack.unitCollection.values()) - capture += ent.captureStrength() * PETRA.getAttackBonus(ent, relic, "Capture"); + capture += ent.captureStrength() * getAttackBonus(ent, relic, "Capture"); } // No need to make a new attack if already enough units if (capture > sumCapturePoints / 50) return; const relicPosition = relic.position(); - const access = PETRA.getLandAccess(gameState, relic); + const access = getLandAccess(gameState, relic); const units = gameState.getOwnUnits().filter(ent => { if (!ent.position() || !ent.canCapture(relic)) return false; @@ -680,17 +687,20 @@ PETRA.VictoryManager.prototype.captureGaiaRelic = function(gameState, relic) if (plan !== undefined && plan >= 0) { const attack = gameState.ai.HQ.attackManager.getPlan(plan); - if (attack && (attack.state !== PETRA.AttackPlan.STATE_UNEXECUTED || attack.type === PETRA.AttackPlan.TYPE_RAID)) + if (attack && (attack.state !== AttackPlan.STATE_UNEXECUTED || + attack.type === AttackPlan.TYPE_RAID)) + { return false; + } } - if (PETRA.getLandAccess(gameState, ent) != access) + if (getLandAccess(gameState, ent) != access) return false; return true; }).filterNearest(relicPosition); const expedition = []; for (const ent of units.values()) { - capture += ent.captureStrength() * PETRA.getAttackBonus(ent, relic, "Capture"); + capture += ent.captureStrength() * getAttackBonus(ent, relic, "Capture"); expedition.push(ent); if (capture > sumCapturePoints / 25) break; @@ -713,7 +723,7 @@ PETRA.VictoryManager.prototype.captureGaiaRelic = function(gameState, relic) this.targetedGaiaRelics.get(relic.id()).push(plan); }; -PETRA.VictoryManager.prototype.abortCaptureGaiaRelic = function(gameState, relicId) +VictoryManager.prototype.abortCaptureGaiaRelic = function(gameState, relicId) { for (const plan of this.targetedGaiaRelics.get(relicId)) { @@ -724,7 +734,7 @@ PETRA.VictoryManager.prototype.abortCaptureGaiaRelic = function(gameState, relic this.targetedGaiaRelics.delete(relicId); }; -PETRA.VictoryManager.prototype.Serialize = function() +VictoryManager.prototype.Serialize = function() { return { "criticalEnts": this.criticalEnts, @@ -736,7 +746,7 @@ PETRA.VictoryManager.prototype.Serialize = function() }; }; -PETRA.VictoryManager.prototype.Deserialize = function(data) +VictoryManager.prototype.Deserialize = function(data) { for (const key in data) this[key] = data[key]; diff --git a/binaries/data/mods/public/simulation/ai/petra/worker.js b/binaries/data/mods/public/simulation/ai/petra/worker.js index 239d844167..22550b9daa 100644 --- a/binaries/data/mods/public/simulation/ai/petra/worker.js +++ b/binaries/data/mods/public/simulation/ai/petra/worker.js @@ -1,32 +1,36 @@ +import { allowCapture, gatherTreasure, getBuiltEntity, getLandAccess, getSeaAccess, isFastMoving, + isSupplyFull, returnResources } from "simulation/ai/petra/entityExtend.js"; +import { TransportPlan } from "simulation/ai/petra/transportPlan.js"; + /** * This class makes a worker do as instructed by the economy manager */ -PETRA.Worker = function(base) +export function Worker(base) { this.ent = undefined; this.base = base; this.baseID = base.ID; -}; +} -PETRA.Worker.ROLE_ATTACK = "attack"; -PETRA.Worker.ROLE_TRADER = "trader"; -PETRA.Worker.ROLE_SWITCH_TO_TRADER = "switchToTrader"; -PETRA.Worker.ROLE_WORKER = "worker"; -PETRA.Worker.ROLE_CRITICAL_ENT_GUARD = "criticalEntGuard"; -PETRA.Worker.ROLE_CRITICAL_ENT_HEALER = "criticalEntHealer"; +Worker.ROLE_ATTACK = "attack"; +Worker.ROLE_TRADER = "trader"; +Worker.ROLE_SWITCH_TO_TRADER = "switchToTrader"; +Worker.ROLE_WORKER = "worker"; +Worker.ROLE_CRITICAL_ENT_GUARD = "criticalEntGuard"; +Worker.ROLE_CRITICAL_ENT_HEALER = "criticalEntHealer"; -PETRA.Worker.SUBROLE_DEFENDER = "defender"; -PETRA.Worker.SUBROLE_IDLE = "idle"; -PETRA.Worker.SUBROLE_BUILDER = "builder"; -PETRA.Worker.SUBROLE_COMPLETING = "completing"; -PETRA.Worker.SUBROLE_WALKING = "walking"; -PETRA.Worker.SUBROLE_ATTACKING = "attacking"; -PETRA.Worker.SUBROLE_GATHERER = "gatherer"; -PETRA.Worker.SUBROLE_HUNTER = "hunter"; -PETRA.Worker.SUBROLE_FISHER = "fisher"; -PETRA.Worker.SUBROLE_GARRISONING = "garrisoning"; +Worker.SUBROLE_DEFENDER = "defender"; +Worker.SUBROLE_IDLE = "idle"; +Worker.SUBROLE_BUILDER = "builder"; +Worker.SUBROLE_COMPLETING = "completing"; +Worker.SUBROLE_WALKING = "walking"; +Worker.SUBROLE_ATTACKING = "attacking"; +Worker.SUBROLE_GATHERER = "gatherer"; +Worker.SUBROLE_HUNTER = "hunter"; +Worker.SUBROLE_FISHER = "fisher"; +Worker.SUBROLE_GARRISONING = "garrisoning"; -PETRA.Worker.prototype.update = function(gameState, ent) +Worker.prototype.update = function(gameState, ent) { if (!ent.position() || ent.getMetadata(PlayerID, "plan") == -2 || ent.getMetadata(PlayerID, "plan") == -3) return; @@ -37,26 +41,26 @@ PETRA.Worker.prototype.update = function(gameState, ent) if (ent.getMetadata(PlayerID, "transport") !== undefined) { // Except if builder with their foundation destroyed, in which case cancel the transport if not yet on board - if (subrole === PETRA.Worker.SUBROLE_BUILDER && ent.getMetadata(PlayerID, "target-foundation") !== undefined) + if (subrole === Worker.SUBROLE_BUILDER && ent.getMetadata(PlayerID, "target-foundation") !== undefined) { const plan = gameState.ai.HQ.navalManager.getPlan(ent.getMetadata(PlayerID, "transport")); const target = gameState.getEntityById(ent.getMetadata(PlayerID, "target-foundation")); - if (!target && plan && plan.state === PETRA.TransportPlan.BOARDING && ent.position()) + if (!target && plan && plan.state === TransportPlan.BOARDING && ent.position()) plan.removeUnit(gameState, ent); } // and gatherer if there are no more dropsite accessible in the base the ent is going to - if (subrole === PETRA.Worker.SUBROLE_GATHERER || subrole === PETRA.Worker.SUBROLE_HUNTER) + if (subrole === Worker.SUBROLE_GATHERER || subrole === Worker.SUBROLE_HUNTER) { const plan = gameState.ai.HQ.navalManager.getPlan(ent.getMetadata(PlayerID, "transport")); - if (plan.state === PETRA.TransportPlan.BOARDING && ent.position()) + if (plan.state === TransportPlan.BOARDING && ent.position()) { let hasDropsite = false; const gatherType = ent.getMetadata(PlayerID, "gather-type") || "food"; for (const structure of gameState.getOwnStructures().values()) { - if (PETRA.getLandAccess(gameState, structure) != plan.endIndex) + if (getLandAccess(gameState, structure) != plan.endIndex) continue; - const resourceDropsiteTypes = PETRA.getBuiltEntity(gameState, structure).resourceDropsiteTypes(); + const resourceDropsiteTypes = getBuiltEntity(gameState, structure).resourceDropsiteTypes(); if (!resourceDropsiteTypes || resourceDropsiteTypes.indexOf(gatherType) == -1) continue; hasDropsite = true; @@ -66,7 +70,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) { for (const unit of gameState.getOwnUnits().filter(API3.Filters.byClass("Support")).values()) { - if (!unit.position() || PETRA.getLandAccess(gameState, unit) != plan.endIndex) + if (!unit.position() || getLandAccess(gameState, unit) != plan.endIndex) continue; const resourceDropsiteTypes = unit.resourceDropsiteTypes(); if (!resourceDropsiteTypes || resourceDropsiteTypes.indexOf(gatherType) == -1) @@ -83,7 +87,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) return; } - this.entAccess = PETRA.getLandAccess(gameState, ent); + this.entAccess = getLandAccess(gameState, ent); // Base for unassigned entities has no accessIndex, so take the one from the entity. if (this.baseID == gameState.ai.HQ.basesManager.baselessBase().ID) this.baseAccess = this.entAccess; @@ -92,7 +96,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) if (subrole == undefined) // subrole may-be undefined after a transport, garrisoning, army, ... { - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_IDLE); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE); this.base.reassignIdleWorkers(gameState, [ent]); this.update(gameState, ent); return; @@ -101,7 +105,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) this.ent = ent; const unitAIState = ent.unitAIState(); - if ((subrole === PETRA.Worker.SUBROLE_HUNTER || subrole === PETRA.Worker.SUBROLE_GATHERER) && + if ((subrole === Worker.SUBROLE_HUNTER || subrole === Worker.SUBROLE_GATHERER) && (unitAIState == "INDIVIDUAL.GATHER.GATHERING" || unitAIState == "INDIVIDUAL.GATHER.APPROACHING" || unitAIState == "INDIVIDUAL.COMBAT.APPROACHING")) { @@ -130,8 +134,8 @@ PETRA.Worker.prototype.update = function(gameState, ent) } else if (!gameState.isPlayerAlly(territoryOwner)) { - const distanceSquare = PETRA.isFastMoving(ent) ? 90000 : 30000; - const targetAccess = PETRA.getLandAccess(gameState, target); + const distanceSquare = isFastMoving(ent) ? 90000 : 30000; + const targetAccess = getLandAccess(gameState, target); const foodDropsites = gameState.playerData.hasSharedDropsites ? gameState.getAnyDropsites("food") : gameState.getOwnDropsites("food"); let hasFoodDropsiteWithinDistance = false; @@ -143,7 +147,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) // owner != PlayerID can only happen when hasSharedDropsites == true, so no need to test it again if (owner != PlayerID && (!dropsite.isSharedDropsite() || !gameState.isPlayerMutualAlly(owner))) continue; - if (targetAccess != PETRA.getLandAccess(gameState, dropsite)) + if (targetAccess != getLandAccess(gameState, dropsite)) continue; if (API3.SquareVectorDistance(target.position(), dropsite.position()) < distanceSquare) { @@ -174,7 +178,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) // Also, if we are attacking, do not capture if (unitAIStateOrder == "COMBAT") { - if (subrole === PETRA.Worker.SUBROLE_FISHER) + if (subrole === Worker.SUBROLE_FISHER) this.startFishing(gameState); else if (unitAIState == "INDIVIDUAL.COMBAT.APPROACHING" && ent.unitAIOrderData().length && !ent.getMetadata(PlayerID, "PartOfArmy")) @@ -183,7 +187,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) if (orderData && orderData.target) { const target = gameState.getEntityById(orderData.target); - if (target && (!target.position() || PETRA.getLandAccess(gameState, target) != this.entAccess)) + if (target && (!target.position() || getLandAccess(gameState, target) != this.entAccess)) { if (this.retryWorking(gameState, subrole)) return; @@ -201,7 +205,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) // and UnitAI sent it fight back with allowCapture=true const target = gameState.getEntityById(orderData.target); if (target && target.owner() > 0 && !gameState.isPlayerAlly(target.owner())) - ent.attack(orderData.target, PETRA.allowCapture(gameState, ent, target)); + ent.attack(orderData.target, allowCapture(gameState, ent, target)); } } return; @@ -211,7 +215,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) // If we're gathering, we'll check that we haven't run idle. // And we'll also check that we're gathering a resource we want to gather. - if (subrole === PETRA.Worker.SUBROLE_GATHERER) + if (subrole === Worker.SUBROLE_GATHERER) { if (ent.isIdle()) { @@ -223,7 +227,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) { this.startGathering(gameState); } - else if (!PETRA.returnResources(gameState, ent)) // try to deposit resources + else if (!returnResources(gameState, ent)) // try to deposit resources { // no dropsite, abandon old resources and start gathering new ones this.startGathering(gameState); @@ -277,8 +281,11 @@ PETRA.Worker.prototype.update = function(gameState, ent) { // Check from time to time that UnitAI does not send us to an inaccessible dropsite const dropsite = gameState.getEntityById(ent.unitAIOrderData()[0].target); - if (dropsite && dropsite.position() && this.entAccess != PETRA.getLandAccess(gameState, dropsite)) - PETRA.returnResources(gameState, this.ent); + if (dropsite && dropsite.position() && + this.entAccess != getLandAccess(gameState, dropsite)) + { + returnResources(gameState, this.ent); + } } // If gathering a sparse resource, we may have been sent to a faraway resource if the one nearby was full. @@ -308,7 +315,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) } } } - else if (subrole === PETRA.Worker.SUBROLE_BUILDER) + else if (subrole === Worker.SUBROLE_BUILDER) { if (unitAIStateOrder == "REPAIR") { @@ -325,7 +332,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) return; } ent.setMetadata(PlayerID, "target-foundation", undefined); - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_IDLE); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE); ent.stopMoving(); if (this.baseID != gameState.ai.HQ.basesManager.baselessBase().ID) { @@ -346,7 +353,7 @@ PETRA.Worker.prototype.update = function(gameState, ent) const target = gameState.getEntityById(ent.getMetadata(PlayerID, "target-foundation")); if (!target || target.foundationProgress() === undefined && target.needsRepair() === false) { - ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_IDLE); + ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE); ent.setMetadata(PlayerID, "target-foundation", undefined); // If worker elephant, move away to avoid being trapped in between constructions if (ent.hasClass("Elephant")) @@ -361,15 +368,15 @@ PETRA.Worker.prototype.update = function(gameState, ent) } else { - const goalAccess = PETRA.getLandAccess(gameState, target); - const queued = PETRA.returnResources(gameState, ent); + const goalAccess = getLandAccess(gameState, target); + const queued = returnResources(gameState, ent); if (this.entAccess == goalAccess) ent.repair(target, target.hasClass("House"), queued); // autocontinue=true for houses else gameState.ai.HQ.navalManager.requireTransport(gameState, ent, this.entAccess, goalAccess, target.position()); } } - else if (subrole === PETRA.Worker.SUBROLE_HUNTER) + else if (subrole === Worker.SUBROLE_HUNTER) { const lastHuntSearch = ent.getMetadata(PlayerID, "lastHuntSearch"); if (ent.isIdle() && (!lastHuntSearch || gameState.ai.elapsedTime - lastHuntSearch > 20)) @@ -410,13 +417,16 @@ PETRA.Worker.prototype.update = function(gameState, ent) { // Check that UnitAI does not send us to an inaccessible dropsite const dropsite = gameState.getEntityById(ent.unitAIOrderData()[0].target); - if (dropsite && dropsite.position() && this.entAccess != PETRA.getLandAccess(gameState, dropsite)) - PETRA.returnResources(gameState, ent); + if (dropsite && dropsite.position() && + this.entAccess != getLandAccess(gameState, dropsite)) + { + returnResources(gameState, ent); + } } } } } - else if (subrole === PETRA.Worker.SUBROLE_FISHER) + else if (subrole === Worker.SUBROLE_FISHER) { if (ent.isIdle()) this.startFishing(gameState); @@ -429,38 +439,38 @@ PETRA.Worker.prototype.update = function(gameState, ent) } }; -PETRA.Worker.prototype.retryWorking = function(gameState, subrole) +Worker.prototype.retryWorking = function(gameState, subrole) { switch (subrole) { - case PETRA.Worker.SUBROLE_GATHERER: + case Worker.SUBROLE_GATHERER: return this.startGathering(gameState); - case PETRA.Worker.SUBROLE_HUNTER: + case Worker.SUBROLE_HUNTER: return this.startHunting(gameState); - case PETRA.Worker.SUBROLE_FISHER: + case Worker.SUBROLE_FISHER: return this.startFishing(gameState); - case PETRA.Worker.SUBROLE_BUILDER: + case Worker.SUBROLE_BUILDER: return this.startBuilding(gameState); default: return false; } }; -PETRA.Worker.prototype.startBuilding = function(gameState) +Worker.prototype.startBuilding = function(gameState) { const target = gameState.getEntityById(this.ent.getMetadata(PlayerID, "target-foundation")); if (!target || target.foundationProgress() === undefined && target.needsRepair() == false) return false; - if (PETRA.getLandAccess(gameState, target) != this.entAccess) + if (getLandAccess(gameState, target) != this.entAccess) return false; this.ent.repair(target, target.hasClass("House")); // autocontinue=true for houses return true; }; -PETRA.Worker.prototype.startGathering = function(gameState) +Worker.prototype.startGathering = function(gameState) { // First look for possible treasure if any - if (PETRA.gatherTreasure(gameState, this.ent)) + if (gatherTreasure(gameState, this.ent)) return true; const resource = this.ent.getMetadata(PlayerID, "gather-type"); @@ -481,7 +491,7 @@ PETRA.Worker.prototype.startGathering = function(gameState) supplies.splice(i--, 1); continue; } - if (PETRA.IsSupplyFull(gameState, supplies[i].ent)) + if (isSupplyFull(gameState, supplies[i].ent)) continue; const inaccessibleTime = supplies[i].ent.getMetadata(PlayerID, "inaccessibleTime"); if (inaccessibleTime && gameState.ai.elapsedTime < inaccessibleTime) @@ -600,7 +610,7 @@ PETRA.Worker.prototype.startGathering = function(gameState) // Try to help building one if any accessible foundation available const foundations = gameState.getOwnFoundations().toEntityArray(); let shouldBuild = this.ent.isBuilder() && foundations.some(function(foundation) { - if (!foundation || PETRA.getLandAccess(gameState, foundation) != this.entAccess) + if (!foundation || getLandAccess(gameState, foundation) != this.entAccess) return false; const structure = gameState.getBuiltTemplate(foundation.templateName()); if (structure.resourceDropsiteTypes() && structure.resourceDropsiteTypes().indexOf(resource) != -1) @@ -608,7 +618,7 @@ PETRA.Worker.prototype.startGathering = function(gameState) if (foundation.getMetadata(PlayerID, "base") != this.baseID) this.ent.setMetadata(PlayerID, "base", foundation.getMetadata(PlayerID, "base")); this.ent.setMetadata(PlayerID, "target-foundation", foundation.id()); - this.ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER); + this.ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_BUILDER); this.ent.repair(foundation); return true; } @@ -667,18 +677,18 @@ PETRA.Worker.prototype.startGathering = function(gameState) // Okay so we haven't found any appropriate dropsite anywhere. // Try to help building one if any non-accessible foundation available shouldBuild = this.ent.isBuilder() && foundations.some(function(foundation) { - if (!foundation || PETRA.getLandAccess(gameState, foundation) == this.entAccess) + if (!foundation || getLandAccess(gameState, foundation) == this.entAccess) return false; const structure = gameState.getBuiltTemplate(foundation.templateName()); if (structure.resourceDropsiteTypes() && structure.resourceDropsiteTypes().indexOf(resource) != -1) { - const foundationAccess = PETRA.getLandAccess(gameState, foundation); + const foundationAccess = getLandAccess(gameState, foundation); if (navalManager.requireTransport(gameState, this.ent, this.entAccess, foundationAccess, foundation.position())) { if (foundation.getMetadata(PlayerID, "base") != this.baseID) this.ent.setMetadata(PlayerID, "base", foundation.getMetadata(PlayerID, "base")); this.ent.setMetadata(PlayerID, "target-foundation", foundation.id()); - this.ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER); + this.ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_BUILDER); return true; } } @@ -741,7 +751,7 @@ PETRA.Worker.prototype.startGathering = function(gameState) gameState.ai.HQ.lastFailedGather[resource] = gameState.ai.elapsedTime; if (gameState.ai.Config.debug > 2) API3.warn(" >>>>> worker with gather-type " + resource + " with nothing to gather "); - this.ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_IDLE); + this.ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE); return false; }; @@ -749,10 +759,10 @@ PETRA.Worker.prototype.startGathering = function(gameState) * if position is given, we only check if we could hunt from this position but do nothing * otherwise the position of the entity is taken, and if something is found, we directly start the hunt */ -PETRA.Worker.prototype.startHunting = function(gameState, position) +Worker.prototype.startHunting = function(gameState, position) { // First look for possible treasure if any - if (!position && PETRA.gatherTreasure(gameState, this.ent)) + if (!position && gatherTreasure(gameState, this.ent)) return true; const resources = gameState.getHuntableSupplies(); @@ -762,7 +772,7 @@ PETRA.Worker.prototype.startHunting = function(gameState, position) let nearestSupplyDist = Math.min(); let nearestSupply; - const isFastMoving = PETRA.isFastMoving(this.ent); + const entIsFastMoving = isFastMoving(this.ent); const isRanged = this.ent.hasClass("Ranged"); const entPosition = position ? position : this.ent.position(); const foodDropsites = gameState.playerData.hasSharedDropsites ? @@ -778,7 +788,7 @@ PETRA.Worker.prototype.startHunting = function(gameState, position) // owner != PlayerID can only happen when hasSharedDropsites == true, so no need to test it again if (owner != PlayerID && (!dropsite.isSharedDropsite() || !gameState.isPlayerMutualAlly(owner))) continue; - if (supplyAccess != PETRA.getLandAccess(gameState, dropsite)) + if (supplyAccess != getLandAccess(gameState, dropsite)) continue; if (API3.SquareVectorDistance(supplyPosition, dropsite.position()) < distSquare) return true; @@ -800,7 +810,7 @@ PETRA.Worker.prototype.startHunting = function(gameState, position) if (!gatherRates[supplyType]) continue; - if (PETRA.IsSupplyFull(gameState, supply)) + if (isSupplyFull(gameState, supply)) continue; // Check if available resource is worth one additionnal gatherer (except for farms). const nbGatherers = supply.resourceSupplyNumGatherers() + this.base.GetTCGatherer(supply.id()); @@ -809,10 +819,10 @@ PETRA.Worker.prototype.startHunting = function(gameState, position) const canFlee = !supply.hasClass("Domestic") && supply.templateName().indexOf("resource|") == -1; // Only FastMoving and Ranged units should hunt fleeing animals. - if (canFlee && !isFastMoving && !isRanged) + if (canFlee && !entIsFastMoving && !isRanged) continue; - const supplyAccess = PETRA.getLandAccess(gameState, supply); + const supplyAccess = getLandAccess(gameState, supply); if (supplyAccess != this.entAccess) continue; @@ -822,7 +832,7 @@ PETRA.Worker.prototype.startHunting = function(gameState, position) continue; // Only FastMoving should hunt faraway. - if (!isFastMoving && dist > 25000) + if (!entIsFastMoving && dist > 25000) continue; // Avoid enemy territory. @@ -834,9 +844,9 @@ PETRA.Worker.prototype.startHunting = function(gameState, position) continue; // Only FastMoving should hunt far from dropsite (specially for non-Domestic animals which flee). - if (!isFastMoving && canFlee && territoryOwner == 0) + if (!entIsFastMoving && canFlee && territoryOwner == 0) continue; - const distanceSquare = isFastMoving ? 35000 : (canFlee ? 7000 : 12000); + const distanceSquare = entIsFastMoving ? 35000 : (canFlee ? 7000 : 12000); if (!hasFoodDropsiteWithinDistance(supply.position(), supplyAccess, distanceSquare)) continue; @@ -857,7 +867,7 @@ PETRA.Worker.prototype.startHunting = function(gameState, position) return false; }; -PETRA.Worker.prototype.startFishing = function(gameState) +Worker.prototype.startFishing = function(gameState) { if (!this.ent.position()) return false; @@ -873,7 +883,7 @@ PETRA.Worker.prototype.startFishing = function(gameState) let nearestSupplyDist = Math.min(); let nearestSupply; - const fisherSea = PETRA.getSeaAccess(gameState, this.ent); + const fisherSea = getSeaAccess(gameState, this.ent); const fishDropsites = (gameState.playerData.hasSharedDropsites ? gameState.getAnyDropsites("food") : gameState.getOwnDropsites("food")). filter(API3.Filters.byClass("Dock")).toEntityArray(); @@ -888,7 +898,7 @@ PETRA.Worker.prototype.startFishing = function(gameState) // owner != PlayerID can only happen when hasSharedDropsites == true, so no need to test it again if (owner != PlayerID && (!dropsite.isSharedDropsite() || !gameState.isPlayerMutualAlly(owner))) continue; - if (fisherSea != PETRA.getSeaAccess(gameState, dropsite)) + if (fisherSea != getSeaAccess(gameState, dropsite)) continue; distMin = Math.min(distMin, API3.SquareVectorDistance(pos, dropsite.position())); } @@ -911,7 +921,7 @@ PETRA.Worker.prototype.startFishing = function(gameState) if (!gatherRates[supplyType]) return; - if (PETRA.IsSupplyFull(gameState, supply)) + if (isSupplyFull(gameState, supply)) return; // check if available resource is worth one additionnal gatherer (except for farms) const nbGatherers = supply.resourceSupplyNumGatherers() + this.base.GetTCGatherer(supply.id()); @@ -946,12 +956,12 @@ PETRA.Worker.prototype.startFishing = function(gameState) this.ent.setMetadata(PlayerID, "target-foundation", undefined); return true; } - if (this.ent.getMetadata(PlayerID, "subrole") === PETRA.Worker.SUBROLE_FISHER) - this.ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_IDLE); + if (this.ent.getMetadata(PlayerID, "subrole") === Worker.SUBROLE_FISHER) + this.ent.setMetadata(PlayerID, "subrole", Worker.SUBROLE_IDLE); return false; }; -PETRA.Worker.prototype.gatherNearestField = function(gameState, baseID) +Worker.prototype.gatherNearestField = function(gameState, baseID) { const ownFields = gameState.getOwnEntitiesByClass("Field", true).filter(API3.Filters.isBuilt()).filter(API3.Filters.byMetadata(PlayerID, "base", baseID)); let bestFarm; @@ -959,7 +969,7 @@ PETRA.Worker.prototype.gatherNearestField = function(gameState, baseID) const gatherRates = this.ent.resourceGatherRates(); for (const field of ownFields.values()) { - if (PETRA.IsSupplyFull(gameState, field)) + if (isSupplyFull(gameState, field)) continue; const supplyType = field.get("ResourceSupply/Type"); if (!gatherRates[supplyType]) @@ -991,7 +1001,7 @@ PETRA.Worker.prototype.gatherNearestField = function(gameState, baseID) * WARNING with the present options of AI orders, the unit will not gather after building the farm. * This is done by calling the gatherNearestField function when construction is completed. */ -PETRA.Worker.prototype.buildAnyField = function(gameState, baseID) +Worker.prototype.buildAnyField = function(gameState, baseID) { if (!this.ent.isBuilder()) return false; @@ -1020,17 +1030,17 @@ PETRA.Worker.prototype.buildAnyField = function(gameState, baseID) * For the time being, we move towards the nearest gatherer (providing him a dropsite). * BaseManager does also use that function to deal with its mobile dropsites. */ -PETRA.Worker.prototype.moveToGatherer = function(gameState, ent, forced) +Worker.prototype.moveToGatherer = function(gameState, ent, forced) { const pos = ent.position(); if (!pos || ent.getMetadata(PlayerID, "target-foundation") !== undefined) return; if (!forced && gameState.ai.elapsedTime < (ent.getMetadata(PlayerID, "nextMoveToGatherer") || 5)) return; - const gatherers = this.base.workersBySubrole(gameState, PETRA.Worker.SUBROLE_GATHERER); + const gatherers = this.base.workersBySubrole(gameState, Worker.SUBROLE_GATHERER); let dist = Math.min(); let destination; - const access = PETRA.getLandAccess(gameState, ent); + const access = getLandAccess(gameState, ent); const types = ent.resourceDropsiteTypes(); for (const gatherer of gatherers.values()) { @@ -1038,8 +1048,10 @@ PETRA.Worker.prototype.moveToGatherer = function(gameState, ent, forced) if (!gathererType || types.indexOf(gathererType) == -1) continue; if (!gatherer.position() || gatherer.getMetadata(PlayerID, "transport") !== undefined || - PETRA.getLandAccess(gameState, gatherer) != access || gatherer.isIdle()) + getLandAccess(gameState, gatherer) != access || gatherer.isIdle()) + { continue; + } const distance = API3.SquareVectorDistance(pos, gatherer.position()); if (distance > dist) continue; @@ -1056,7 +1068,7 @@ PETRA.Worker.prototype.moveToGatherer = function(gameState, ent, forced) * inside obstruction of other entities). The resource will be flagged as inaccessible during 10 mn (in case * it will be cleared later). */ -PETRA.Worker.prototype.isInaccessibleSupply = function(gameState) +Worker.prototype.isInaccessibleSupply = function(gameState) { if (!this.ent.unitAIOrderData()[0] || !this.ent.unitAIOrderData()[0].target) return false; diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp index ce169994ef..8421fbfcf9 100644 --- a/source/simulation2/components/CCmpAIManager.cpp +++ b/source/simulation2/components/CCmpAIManager.cpp @@ -41,7 +41,9 @@ #include "ps/scripting/JSInterface_VFS.h" #include "scriptinterface/FunctionWrapper.h" #include "scriptinterface/JSON.h" +#include "scriptinterface/ModuleLoader.h" #include "scriptinterface/Object.h" +#include "scriptinterface/ScriptContext.h" #include "scriptinterface/ScriptConversions.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRequest.h" @@ -128,10 +130,6 @@ private: bool Initialise() { - // LoadScripts will only load each script once even though we call it for each player - if (!m_Worker.LoadScripts(m_AIName)) - return false; - ScriptRequest rq(m_ScriptInterface); OsPath path = L"simulation/ai/" + m_AIName + L"/data.json"; @@ -144,24 +142,23 @@ private: } // Get the constructor name from the metadata - std::string moduleName; + std::string filename; std::string constructor; - JS::RootedValue objectWithConstructor(rq.cx); // object that should contain the constructor function - JS::RootedValue global(rq.cx, rq.globalValue()); JS::RootedValue ctor(rq.cx); - if (!Script::HasProperty(rq, metadata, "moduleName")) + if (!Script::HasProperty(rq, metadata, "filename")) { - LOGERROR("Failed to create AI player: %s: missing 'moduleName'", path.string8()); + LOGERROR("Failed to create AI player: %s: missing 'filename'", path.string8()); return false; } - Script::GetProperty(rq, metadata, "moduleName", moduleName); - if (!Script::GetProperty(rq, global, moduleName.c_str(), &objectWithConstructor) - || objectWithConstructor.isUndefined()) - { - LOGERROR("Failed to create AI player: %s: can't find the module that should contain the constructor: '%s'", path.string8(), moduleName); - return false; - } + Script::GetProperty(rq, metadata, "filename", filename); + + + auto result = m_ScriptInterface->GetModuleLoader().LoadModule(rq, + "simulation/ai/" + utf8_from_wstring(m_AIName) + "/" + filename); + + g_ScriptContext->RunJobs(); + JS::RootedValue objectWithConstructor(rq.cx, JS::ObjectValue(*result.begin()->Get())); if (!Script::GetProperty(rq, metadata, "constructor", constructor)) { @@ -267,7 +264,11 @@ public: { // Create the script interface in the same compartment as the simulation interface. // This will allow us to directly share data from the sim to the AI (and vice versa, should the need arise). - m_ScriptInterface = std::make_shared("Engine", "AI", simInterface); + m_ScriptInterface = std::make_shared("Engine", "AI", simInterface, + [](const VfsPath& path) + { + return path.string().find(L"simulation/ai/") == 0; + }); ScriptRequest rq(m_ScriptInterface);