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) { + const 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/config.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/config.js +++ binaries/data/mods/public/simulation/ai/petra/config.js @@ -36,6 +36,13 @@ "Fire": 0.095 }; + this.DefensiveStructureDamageTypeImportance = { + "Hack": 0.075, + "Pierce": 0.095, + "Crush": 0.065, + "Fire": 0.085 + }; + this.Economy = { "popPhase2": 38, // How many units we want before aging to phase2. "workPhase3": 65, // How many workers we want before aging to phase3. 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 === PlayerID && ent.getMetadata(evt.from, "upgrading") !== undefined) + ent.setMetadata(evt.from, "upgrading", undefined); + if (!ent.hasClass("Unit")) { if (ent.decaying()) @@ -1569,6 +1574,43 @@ } } + // Check for possible upgrades + if (!this.saveResource) + { + // TODO: Sort by most active defences, tracked via by how recently they last attacked an enemy. + const upgradableDefenses = gameState.getOwnEntitiesByClass("Defensive", true).filter(ent => ent.upgradableEntities() && !ent.getMetadata(PlayerID, "upgrading")); + for (const ent of upgradableDefenses.values()) + { + const upgradableEnts = ent.upgradableEntities(); + let bestAttack = PETRA.getMaxStrength(ent, this.Config.debug, this.Config.DefensiveStructureDamageTypeImportance); + let chosenUpgrade; + + for (const choice in upgradableEnts) + { + const template = gameState.getTemplate(gameState.applyCiv(upgradableEnts[choice].Entity)); + if (!template) + continue; + + // Notice that AI entities are derived from templates and can be passed to most entityExtend functions. + const templateAttackStrength = PETRA.getMaxStrength(template, this.Config.debug, this.Config.DefensiveStructureDamageTypeImportance); + + // Dont want a weaker upgrade here since this concerns defense. + if (templateAttackStrength < bestAttack) + continue; + + // If the upgraded entity's strength is the same but has less health, we don't want it. + if (templateAttackStrength == bestAttack && template.maxHitpoints() < ent.maxHitpoints()) + 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,72 @@ +PETRA.UpgradePlan = function(gameState, entityID, upgradeData) +{ + const ent = gameState.getEntityById(entityID); + if (!ent || ent.upgradableEntities().every(upgrade => upgradeData.Entity !== upgrade.Entity)) + return false; + ent.setMetadata(PlayerID, "upgrading", true); + + this.entityID = entityID; + + this.upgradeData = upgradeData; + this.cost = new API3.Resources(upgradeData.Cost); + + // We do not call the base constructor, so set the ID here. + this.ID = gameState.ai.uniqueIDs.plans++; + 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) +{ + const requiredTech = this.upgradeData.RequiredTechnology; + return !requiredTech || gameState.isResearched(requiredTech); +}; + +PETRA.UpgradePlan.prototype.isInvalid = function(gameState) +{ + const 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 (const key in data) + this[key] = data[key]; + + this.cost = new API3.Resources(); + this.cost.Deserialize(data.cost); +};