Index: binaries/data/mods/public/simulation/ai/common-api/entity.js =================================================================== --- binaries/data/mods/public/simulation/ai/common-api/entity.js +++ binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -346,6 +346,13 @@ return templates.replace(/\{native\}/g, this.civ()).replace(/\{civ\}/g, civ).split(/\s+/); }, + "upgradableEntities": function(civ) { + let choices = this.get("Upgrade"); + if (!choices) + return undefined; + return choices; + }, + "researchableTechs": function(gameState, civ) { const templates = this.get("Researcher/Technologies/_string"); if (!templates) Index: binaries/data/mods/public/simulation/ai/petra/headquarters.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/headquarters.js +++ binaries/data/mods/public/simulation/ai/petra/headquarters.js @@ -130,9 +130,14 @@ { if (evt.to != PlayerID) continue; + let ent = gameState.getEntityById(evt.entity); if (!ent) continue; + + if (evt.from > 0 && ent.getMetadata(evt.from, "upgrading") !== undefined) + ent.setMetadata(evt.from, "upgrading", undefined); + if (!ent.hasClass("Unit")) { if (ent.decaying()) @@ -1569,6 +1574,39 @@ } } + // Check for possible upgrades + if (!this.saveResource) + { + let upgradableDefenses = gameState.getOwnEntitiesByClass("Defensive", true).filter(ent => ent.upgradableEntities() && !ent.getMetadata(PlayerID, "upgrading")); + for (let ent of upgradableDefenses.values()) + { + let upgradableEnts = ent.upgradableEntities(); + let bestAttack = ent.attackStrengths("Ranged"); + let chosenUpgrade; + + for (let choice in upgradableEnts) + { + // It is safe to just pick the choice with the highest attack rate here + // Also safe to only track Pierce damage + let template = gameState.getTemplate(gameState.applyCiv(upgradableEnts[choice].Entity)); + if (!template) + continue; + + let templateAttackStrength = template.attackStrengths("Ranged"); + + // Dont want a weaker upgrade here since this concerns defense + if (!templateAttackStrength || templateAttackStrength.Pierce <= bestAttack.Pierce) + continue; + + bestAttack = templateAttackStrength; + chosenUpgrade = upgradableEnts[choice]; + } + + if (chosenUpgrade) + queues.defenseBuilding.addPlan(new PETRA.UpgradePlan(gameState, ent.id(), chosenUpgrade)); + } + } + if (this.Config.Military.numSentryTowers && this.currentPhase < 2 && this.canBuild(gameState, "structures/{civ}/sentry_tower")) { // Count all towers + wall towers. Index: binaries/data/mods/public/simulation/ai/petra/queue.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/queue.js +++ binaries/data/mods/public/simulation/ai/petra/queue.js @@ -149,6 +149,8 @@ plan = new PETRA.ConstructionPlan(gameState, dataPlan.type); else if (dataPlan.category == "technology") plan = new PETRA.ResearchPlan(gameState, dataPlan.type); + else if (dataPlan.category == "upgrading") + plan = new PETRA.UpgradePlan(gameState, dataPlan.entityID, dataPlan.upgradeData); else { API3.warn("Petra deserialization error: plan unknown " + uneval(dataPlan)); Index: binaries/data/mods/public/simulation/ai/petra/queueplanUpgrading.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/ai/petra/queueplanUpgrading.js @@ -0,0 +1,77 @@ +PETRA.UpgradePlan = function(gameState, entityID, upgradeData) +{ + let ent = gameState.getEntityById(entityID); + if (!ent) + return false; + ent.setMetadata(PlayerID, "upgrading", true); + + this.entityID = entityID; + + this.upgradeData = upgradeData; + this.cost = new API3.Resources(upgradeData.Cost); + + this.category = "upgrading"; + this.type = "upgrading"; + this.number = 1; + + return true; +}; + +PETRA.UpgradePlan.prototype = Object.create(PETRA.QueuePlan.prototype); + +PETRA.UpgradePlan.prototype.canStart = function(gameState) +{ + let requiredTech = this.upgradeData.RequiredTechnology; + if (requiredTech && !gameState.isResearched(requiredTech)) + { + if (!requiredTech.startsWith("phase") && gameState.canResearch(requiredTech)) + gameState.ai.HQ.researchManager.addTechToQueue(requiredTech); + return false; + } + + return true; +}; + +PETRA.UpgradePlan.prototype.isInvalid = function(gameState) +{ + let ent = gameState.getEntityById(this.entityID); + return !ent || !ent.getMetadata(PlayerID, "upgrading"); +}; + +PETRA.UpgradePlan.prototype.start = function(gameState) +{ + Engine.PostCommand(PlayerID, { + "type": "upgrade", + "entities": [this.entityID], + "template": gameState.applyCiv(this.upgradeData.Entity), + "queued": false + }); + + this.onStart(gameState); +}; + +PETRA.UpgradePlan.prototype.onStart = function(gameState) +{ + if (this.queueToReset) + gameState.ai.queueManager.changePriority(this.queueToReset, gameState.ai.Config.priorities[this.queueToReset]); +}; + +PETRA.UpgradePlan.prototype.Serialize = function() +{ + return { + "entityID": this.entityID, + "upgradeData": this.upgradeData, + "category": this.category, + "cost": this.cost.Serialize(), + "queueToReset": this.queueToReset || undefined + }; +}; + +PETRA.UpgradePlan.prototype.Deserialize = function(gameState, data) +{ + for (let key in data) + this[key] = data[key]; + + this.cost = new API3.Resources(); + this.cost.Deserialize(data.cost); +};