From 0dfebc8d2ba6b434c66f5f8bf2ad4db58cb2a2fb Mon Sep 17 00:00:00 2001 From: Freagarach Date: Sat, 22 May 2021 07:53:47 +0000 Subject: [PATCH] Let PetraAI check for different templates when finding the best dropsite. Loops buildable templates to find the best. Differential revision: https://code.wildfiregames.com/D3876 Comments by: @Angen, @Stan, @wraitii This was SVN commit r25514. --- .../public/simulation/ai/petra/baseManager.js | 41 +++++++++++++++---- .../simulation/ai/petra/buildManager.js | 31 +++++++++----- .../simulation/ai/petra/queueplanBuilding.js | 2 +- .../simulation/ai/petra/startingStrategy.js | 13 +++--- 4 files changed, 60 insertions(+), 27 deletions(-) diff --git a/binaries/data/mods/public/simulation/ai/petra/baseManager.js b/binaries/data/mods/public/simulation/ai/petra/baseManager.js index 76fcc12299..bc27f8d764 100644 --- a/binaries/data/mods/public/simulation/ai/petra/baseManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/baseManager.js @@ -245,12 +245,37 @@ PETRA.BaseManager.prototype.removeDropsite = function(gameState, ent) }; /** - * Returns the position of the best place to build a new dropsite for the specified resource + * @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.findBestDropsiteLocation = function(gameState, resource) +PETRA.BaseManager.prototype.findBestDropsiteAndLocation = function(gameState, resource) { + let bestResult = { + "quality": 0, + "pos": [0, 0] + }; + for (const templateName of gameState.ai.HQ.buildManager.findStructuresByFilter(gameState, API3.Filters.isDropsite(resource))) + { + const dp = this.findBestDropsiteLocation(gameState, resource, templateName); + if (dp.quality < bestResult.quality) + continue; + bestResult = dp; + bestResult.templateName = templateName; + } + return bestResult; +}; + +/** + * 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) +{ + const template = gameState.getTemplate(gameState.applyCiv(templateName)); + + // CCs and Docks are handled elsewhere. + if (template.hasClass("CivCentre") || template.hasClass("Dock")) + return { "quality": 0, "pos": [0, 0] }; - let template = gameState.getTemplate(gameState.applyCiv("structures/{civ}/storehouse")); let halfSize = 0; if (template.get("Footprint/Square")) halfSize = Math.max(+template.get("Footprint/Square/@depth"), +template.get("Footprint/Square/@width")) / 2; @@ -430,17 +455,17 @@ PETRA.BaseManager.prototype.checkResourceLevels = function(gameState, queues) let ratio = this.gatherers[type].lost / total; if (ratio > 0.15) { - let newDP = this.findBestDropsiteLocation(gameState, type); - if (newDP.quality > 50 && gameState.ai.HQ.canBuild(gameState, "structures/{civ}/storehouse")) - queues.dropsites.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/storehouse", { "base": this.ID, "type": type }, newDP.pos)); + 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)); 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, "structures/{civ}/storehouse")) - queues.dropsites.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/storehouse", { "base": this.ID, "type": type }, newDP.pos)); + gameState.ai.HQ.canBuild(gameState, newDP.templateName)) + queues.dropsites.addPlan(new PETRA.ConstructionPlan(gameState, newDP.templateName, { "base": this.ID, "type": type }, newDP.pos)); } } this.gatherers[type].nextCheck = gameState.ai.playedTurn + 20; diff --git a/binaries/data/mods/public/simulation/ai/petra/buildManager.js b/binaries/data/mods/public/simulation/ai/petra/buildManager.js index 90774096fe..3f2f5da5b3 100644 --- a/binaries/data/mods/public/simulation/ai/petra/buildManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/buildManager.js @@ -97,23 +97,32 @@ PETRA.BuildManager.prototype.checkEvents = function(gameState, events) }; +/** + * Get the buildable structures passing a filter. + */ +PETRA.BuildManager.prototype.findStructuresByFilter = function(gameState, filter) +{ + const result = []; + for (let [templateName, count] of this.builderCounters) + { + if (!count || gameState.isTemplateDisabled(templateName)) + continue; + let template = gameState.getTemplate(templateName); + if (!template || !template.available(gameState)) + continue; + if (filter.func(template)) + result.push(templateName); + } + return result; +}; + /** * Get the first buildable structure with a given class * TODO when several available, choose the best one */ PETRA.BuildManager.prototype.findStructureWithClass = function(gameState, classes) { - for (let [templateName, count] of this.builderCounters) - { - if (count == 0 || gameState.isTemplateDisabled(templateName)) - continue; - let template = gameState.getTemplate(templateName); - if (!template || !template.available(gameState)) - continue; - if (MatchesClassList(template.classes(), classes)) - return templateName; - } - return undefined; + return this.findStructuresByFilter(gameState, API3.Filters.byClassesOr(classes))[0]; }; PETRA.BuildManager.prototype.hasBuilder = function(template) diff --git a/binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js b/binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js index fead675be6..e1f2ca3d6b 100644 --- a/binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js +++ b/binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js @@ -119,7 +119,7 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState) // recompute the best dropsite location in case some conditions have changed let base = HQ.getBaseByID(this.metadata.base); let type = this.metadata.type ? this.metadata.type : "wood"; - let newpos = base.findBestDropsiteLocation(gameState, type); + const newpos = base.findBestDropsiteLocation(gameState, type, template._templateName); if (newpos && newpos.quality > 0) { let pos = newpos.pos; diff --git a/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js b/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js index c00b790473..78ab8acf50 100644 --- a/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js +++ b/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js @@ -545,11 +545,10 @@ PETRA.HQ.prototype.configFirstBase = function(gameState) } // immediatly build a wood dropsite if possible. - let template = gameState.applyCiv("structures/{civ}/storehouse"); - if (!gameState.getOwnEntitiesByClass("Storehouse", true).hasEntities() && this.canBuild(gameState, template)) + if (!gameState.getOwnEntitiesByClass("DropsiteWood", true).hasEntities()) { - let newDP = this.baseManagers[1].findBestDropsiteLocation(gameState, "wood"); - if (newDP.quality > 40) + const newDP = this.baseManagers[1].findBestDropsiteAndLocation(gameState, "wood"); + if (newDP.quality > 40 && this.canBuild(gameState, newDP.templateName)) { // if we start with enough workers, put our available resources in this first dropsite // same thing if our pop exceed the allowed one, as we will need several houses @@ -557,16 +556,16 @@ PETRA.HQ.prototype.configFirstBase = function(gameState) if (numWorkers > 12 && newDP.quality > 60 || gameState.getPopulation() > gameState.getPopulationLimit() + 20) { - let cost = new API3.Resources(gameState.getTemplate(template).cost()); + 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, template, { "base": this.baseManagers[1].ID }, newDP.pos)); + gameState.ai.queues.dropsites.addPlan(new PETRA.ConstructionPlan(gameState, newDP.templateName, { "base": this.baseManagers[1].ID }, newDP.pos)); } } // and build immediately a corral if needed if (this.needCorral) { - template = gameState.applyCiv("structures/{civ}/corral"); + 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[1].ID })); }