diff --git a/binaries/data/mods/public/simulation/ai/common-api/entity.js b/binaries/data/mods/public/simulation/ai/common-api/entity.js index b5aa0376d6..cac775d773 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/entity.js +++ b/binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -578,7 +578,7 @@ m.Entity = m.Class({ return this._entity.idle; }, - unitAIState: function() { return this._entity.unitAIState; }, + unitAIState: function() { if (this._entity.unitAIState) return this._entity.unitAIState; return undefined; }, unitAIOrderData: function() { return this._entity.unitAIOrderData; }, // TODO understand why we have sometimes rounding problems with maxHitpoints ? making wrongly isHurt=true diff --git a/binaries/data/mods/public/simulation/ai/petra/_petrabot.js b/binaries/data/mods/public/simulation/ai/petra/_petrabot.js index cb4e952916..290ca20f88 100644 --- a/binaries/data/mods/public/simulation/ai/petra/_petrabot.js +++ b/binaries/data/mods/public/simulation/ai/petra/_petrabot.js @@ -36,7 +36,6 @@ m.PetraBot.prototype.CustomInit = function(gameState, sharedScript) this.turn = this.data.turn; this.playedTurn = this.data.playedTurn; this.elapsedTime = this.data.elapsedTime; - this.myIndex = this.data.myIndex; this.savedEvents = this.data.savedEvents; for (let key in this.savedEvents) { @@ -84,11 +83,6 @@ m.PetraBot.prototype.CustomInit = function(gameState, sharedScript) this.HQ = new m.HQ(this.Config); - var myKeyEntities = gameState.getOwnEntities().filter(API3.Filters.byClass("CivCentre")); - if (myKeyEntities.length == 0) - myKeyEntities = gameState.getOwnEntities(); - this.myIndex = this.accessibility.getAccessValue(myKeyEntities.toEntityArray()[0].position()); - this.HQ.init(gameState, this.queues); this.HQ.start(gameState); @@ -161,7 +155,6 @@ m.PetraBot.prototype.Serialize = function() "turn": this.turn, "playedTurn": this.playedTurn, "elapsedTime": this.elapsedTime, - "myIndex": this.myIndex, "savedEvents": savedEvents, "config": this.Config.Serialize(), "queueManager": this.queueManager.Serialize(), diff --git a/binaries/data/mods/public/simulation/ai/petra/attackPlan.js b/binaries/data/mods/public/simulation/ai/petra/attackPlan.js index eef36e8ed2..b5626505fc 100644 --- a/binaries/data/mods/public/simulation/ai/petra/attackPlan.js +++ b/binaries/data/mods/public/simulation/ai/petra/attackPlan.js @@ -37,7 +37,7 @@ m.AttackPlan = function(gameState, Config, uniqueID, type, data) // get a starting rallyPoint ... will be improved later var rallyPoint = undefined; - for each (var base in gameState.ai.HQ.baseManagers) + for (var base of gameState.ai.HQ.baseManagers) { if (!base.anchor || !base.anchor.position()) continue; @@ -413,7 +413,7 @@ m.AttackPlan.prototype.updatePreparation = function(gameState, events) var rallySame = undefined; var distminDiff = Math.min(); var rallyDiff = undefined; - for each (var base in gameState.ai.HQ.baseManagers) + for (var base of gameState.ai.HQ.baseManagers) { var anchor = base.anchor; if (!anchor || !anchor.position()) diff --git a/binaries/data/mods/public/simulation/ai/petra/baseManager.js b/binaries/data/mods/public/simulation/ai/petra/baseManager.js index 350a65b7ac..796480f1be 100644 --- a/binaries/data/mods/public/simulation/ai/petra/baseManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/baseManager.js @@ -78,12 +78,12 @@ m.BaseManager.prototype.setAnchor = function(gameState, anchorEntity) this.anchor.setMetadata(PlayerID, "baseAnchor", true); this.buildings.updateEnt(this.anchor); this.accessIndex = gameState.ai.accessibility.getAccessValue(this.anchor.position()); - // in case all our other bases were destroyed, reaffect these destroyed bases to this base - for each (var base in gameState.ai.HQ.baseManagers) + // in case some of our other bases were destroyed, reaffect these destroyed bases to this base + for (var base of gameState.ai.HQ.baseManagers) { - if (base.anchor || base.newbase) + if (base.anchor || base.newbaseID) continue; - base.newbase = this.ID; + base.newbaseID = this.ID; } return true; }; @@ -108,31 +108,13 @@ m.BaseManager.prototype.checkEvents = function (gameState, events, queues) // sounds like we lost our anchor. Let's reaffect our units and buildings this.anchor = undefined; this.anchorId = undefined; - var distmin = Math.min(); - var basemin = undefined; - for each (var base in gameState.ai.HQ.baseManagers) - { - if (!base.anchor) - continue; - var dist = API3.SquareVectorDistance(base.anchor.position(), ent.position()); - if (base.accessIndex != this.accessIndex) - dist += 100000000; - if (dist > distmin) - continue; - distmin = dist; - basemin = base; - } - if (!basemin) - { - if (this.Config.debug > 1) - API3.warn(" base " + this.ID + " destroyed and no other bases found"); - continue; - } - this.newbase = basemin.ID; - this.units.forEach( function (ent) { ent.setMetadata(PlayerID, "base", basemin.ID); }); - this.buildings.forEach( function (ent) { ent.setMetadata(PlayerID, "base", basemin.ID); }); - } - + let bestbase = m.getBestBase(ent, gameState); + this.newbaseID = bestbase.ID; + for (let entity of this.units.values()) + bestbase.assignEntity(entity); + for (let entity of this.buildings.values()) + bestbase.assignEntity(entity); + } } } @@ -538,14 +520,14 @@ m.BaseManager.prototype.getGatherRates = function(gameState, currentRates) m.BaseManager.prototype.assignRolelessUnits = function(gameState) { // TODO: make this cleverer. - var roleless = this.units.filter(API3.Filters.not(API3.Filters.byHasMetadata(PlayerID, "role"))); - var self = this; - roleless.forEach(function(ent) { + var roleless = this.units.filter(API3.Filters.not(API3.Filters.byHasMetadata(PlayerID, "role"))).values(); + for (var ent of roleless) + { if (ent.hasClass("Worker") || ent.hasClass("CitizenSoldier") || ent.hasClass("FishingBoat")) ent.setMetadata(PlayerID, "role", "worker"); else if (ent.hasClass("Support") && ent.hasClass("Elephant")) ent.setMetadata(PlayerID, "role", "worker"); - }); + } }; // If the numbers of workers on the resources is unbalanced then set some of workers to idle so @@ -754,7 +736,7 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) if (workers.length < 2) { - var noobs = gameState.ai.HQ.bulkPickWorkers(gameState, this.ID, 2); + var noobs = gameState.ai.HQ.bulkPickWorkers(gameState, this, 2); if(noobs) { noobs.forEach(function (worker) { @@ -919,19 +901,42 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) m.BaseManager.prototype.update = function(gameState, queues, events) { - if (!this.anchor) // this base has been destroyed + if (this.ID === gameState.ai.HQ.baseManagers[0].ID) // base for unaffected units { - // transfer possible remaining units (probably they were in training during previous transfers) - if (this.newbase) + // if some active base, reassigns the workers/buildings + // otherwise look for anything useful to do, i.e. treasures to gather + if (gameState.ai.HQ.numActiveBase() > 0) { - var newbase = this.newbase; - this.units.forEach( function (ent) { ent.setMetadata(PlayerID, "base", newbase); }); - this.buildings.forEach( function (ent) { ent.setMetadata(PlayerID, "base", newbase); }); + for (var ent of this.units.values()) + m.getBestBase(ent, gameState).assignEntity(ent, gameState); + for (var ent of this.buildings.values()) + m.getBestBase(ent, gameState).assignEntity(ent, gameState); + } + else + { + this.assignRolelessUnits(gameState); + this.reassignIdleWorkers(gameState); + for (var ent of this.workers.values()) + this.workerObject.update(ent, gameState); } return; } - if (this.anchor && this.anchor.getMetadata(PlayerID, "access") != this.accessIndex) + if (!this.anchor) // this base has been destroyed + { + // transfer possible remaining units (probably they were in training during previous transfers) + if (this.newbaseID) + { + var newbaseID = this.newbaseID; + for (let ent of this.units.values()) + ent.setMetadata(PlayerID, "base", newbaseID); + for (let ent of this.buildings.values()) + ent.setMetadata(PlayerID, "base", newbaseID); + } + return; + } + + if (this.anchor.getMetadata(PlayerID, "access") != this.accessIndex) API3.warn("Petra baseManager " + this.ID + " problem with accessIndex " + this.accessIndex + " while metadata access is " + this.anchor.getMetadata(PlayerID, "access")); @@ -940,7 +945,7 @@ m.BaseManager.prototype.update = function(gameState, queues, events) this.checkResourceLevels(gameState, queues); this.assignToFoundations(gameState); - if (this.constructing && this.anchor) + if (this.constructing) { var owner = gameState.ai.HQ.territoryMap.getOwner(this.anchor.position()); if(owner !== 0 && !gameState.isPlayerAlly(owner)) @@ -963,13 +968,10 @@ m.BaseManager.prototype.update = function(gameState, queues, events) this.setWorkersIdleByPriority(gameState); this.assignRolelessUnits(gameState); - - // should probably be last to avoid reallocations of units that would have done stuffs otherwise. this.reassignIdleWorkers(gameState); - - // TODO: do this incrementally a la defense.js - var self = this; - this.workers.forEach(function(ent) { self.workerObject.update(ent, gameState); }); + // check if workers can find something useful to do + for (var ent of this.workers.values()) + this.workerObject.update(ent, gameState); Engine.ProfileStop(); }; diff --git a/binaries/data/mods/public/simulation/ai/petra/config.js b/binaries/data/mods/public/simulation/ai/petra/config.js index 2e8fd6b680..be18755bc8 100644 --- a/binaries/data/mods/public/simulation/ai/petra/config.js +++ b/binaries/data/mods/public/simulation/ai/petra/config.js @@ -28,7 +28,7 @@ m.Config = function(difficulty) "targetNumTraders" : 4, // Target number of traders "targetNumFishers" : 1, // Target number of fishers per sea "femaleRatio" : 0.5, // fraction of females among the workforce - "provisionFields" : 3 + "provisionFields" : 2 }; this.distUnitGain = 110*110; // TODO take it directly from helpers/TraderGain.js @@ -136,7 +136,7 @@ m.Config.prototype.setConfig = function(gameState) this.Economy.cityPhase = 1800; this.Economy.popForMarket = 80; this.Economy.femaleRatio = 0.6; - this.Economy.provisionFields = 2; + this.Economy.provisionFields = 1; } else { diff --git a/binaries/data/mods/public/simulation/ai/petra/entity-extend.js b/binaries/data/mods/public/simulation/ai/petra/entity-extend.js index 9c29f62e8f..a92d38b6e5 100644 --- a/binaries/data/mods/public/simulation/ai/petra/entity-extend.js +++ b/binaries/data/mods/public/simulation/ai/petra/entity-extend.js @@ -94,6 +94,43 @@ m.returnResources = function(ent, gameState) return true; }; +/** + * get the best base (in terms of distance and accessIndex) for an entity + */ +m.getBestBase = function(ent, gameState) +{ + var pos = ent.position(); + if (!pos) + { + var holder = m.getHolder(ent, gameState); + if (!holder || !holder.position()) + { + API3.warn("Petra error: entity without position, but not garrisoned"); + m.dumpEntity(ent); + return gameState.ai.HQ.baseManagers[0]; + } + pos = holder.position(); + } + var distmin = Math.min(); + var bestbase = undefined; + var accessIndex = gameState.ai.accessibility.getAccessValue(pos); + for (var base of gameState.ai.HQ.baseManagers) + { + if (!base.anchor) + continue; + var dist = API3.SquareVectorDistance(base.anchor.position(), pos); + if (base.accessIndex !== accessIndex) + dist += 100000000; + if (dist > distmin) + continue; + distmin = dist; + bestbase = base; + } + if (!bestbase) + bestbase = gameState.ai.HQ.baseManagers[0]; + return bestbase; +}; + m.getHolder = function(ent, gameState) { var found = undefined; diff --git a/binaries/data/mods/public/simulation/ai/petra/headquarters.js b/binaries/data/mods/public/simulation/ai/petra/headquarters.js index dcbd8ec963..5759c37e19 100644 --- a/binaries/data/mods/public/simulation/ai/petra/headquarters.js +++ b/binaries/data/mods/public/simulation/ai/petra/headquarters.js @@ -37,7 +37,7 @@ m.HQ = function(Config) this.fortressStartTime = 0; this.fortressLapseTime = this.Config.Military.fortressLapseTime; - this.baseManagers = {}; + this.baseManagers = []; this.attackManager = new m.AttackManager(this.Config); this.defenseManager = new m.DefenseManager(this.Config); this.tradeManager = new m.TradeManager(this.Config); @@ -58,46 +58,10 @@ m.HQ.prototype.init = function(gameState, queues, deserializing) // initialize frontier map. Each cell is 2 if on the near frontier, 1 on the frontier and 0 otherwise this.frontierMap = m.createFrontierMap(gameState); // list of allowed regions - this.allowedRegions = {}; - + this.landRegions = {}; // try to determine if we have a water map this.navalMap = false; - this.navalRegions = []; - - var passabilityMap = gameState.getMap(); - var totalSize = passabilityMap.width * passabilityMap.width; - var minLandSize = Math.floor(0.2*totalSize); - var minWaterSize = Math.floor(0.3*totalSize); - var cellArea = passabilityMap.cellSize * passabilityMap.cellSize; - for (var i = 0; i < gameState.ai.accessibility.regionSize.length; ++i) - { - if (i == gameState.ai.myIndex) - this.allowedRegions[i] = true; - else if (gameState.ai.accessibility.regionType[i] === "land" && cellArea*gameState.ai.accessibility.regionSize[i] > 320) - { - var seaIndex = this.getSeaIndex(gameState, gameState.ai.myIndex, i); - if (!seaIndex) - continue; - this.allowedRegions[i] = true; - if (gameState.ai.accessibility.regionSize[i] > minLandSize) - { - this.navalMap = true; - if (this.navalRegions.indexOf(seaIndex) == -1) - this.navalRegions.push(seaIndex); - } - } - else if (gameState.ai.accessibility.regionType[i] === "water" && gameState.ai.accessibility.regionSize[i] > minWaterSize) - { - this.navalMap = true; - if (this.navalRegions.indexOf(i) == -1) - this.navalRegions.push(i); - } - } - if (this.Config.debug > 2) - { - for (var region in this.allowedRegions) - API3.warn(" >>> zone " + region + " taille " + cellArea*gameState.ai.accessibility.regionSize[region]); - } + this.navalRegions = {}; if (this.Config.difficulty < 2) this.targetNumWorkers = Math.max(1, Math.min(40, Math.floor(gameState.getPopulationMax()))); @@ -117,6 +81,86 @@ m.HQ.prototype.init = function(gameState, queues, deserializing) if (deserializing) return; + // determine the main land Index (or water index if none) + let landIndex = undefined; + let seaIndex = undefined; + var ccEnts = gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")); + for (let cc of ccEnts.values()) + { + let land = gameState.ai.accessibility.getAccessValue(cc.position()); + if (land > 1) + { + landIndex = land; + break; + } + } + if (!landIndex) + { + for (let ent of gameState.getOwnEntities().values()) + { + if (!ent.position() || (!ent.hasClass("Unit") && !ent.trainableEntities())) + continue; + let land = gameState.ai.accessibility.getAccessValue(ent.position()); + if (land > 1) + { + landIndex = land; + break; + } + let sea = gameState.ai.accessibility.getAccessValue(ent.position(), true); + if (!seaIndex && sea > 1) + seaIndex = sea; + } + } + if (!landIndex && !seaIndex) + API3.warn("Petra error: it does not know how to interpret this map"); + + var passabilityMap = gameState.getMap(); + var totalSize = passabilityMap.width * passabilityMap.width; + var minLandSize = Math.floor(0.1*totalSize); + var minWaterSize = Math.floor(0.3*totalSize); + var cellArea = passabilityMap.cellSize * passabilityMap.cellSize; + for (var i = 0; i < gameState.ai.accessibility.regionSize.length; ++i) + { + if (landIndex && i == landIndex) + this.landRegions[i] = true; + else if (gameState.ai.accessibility.regionType[i] === "land" && cellArea*gameState.ai.accessibility.regionSize[i] > 320) + { + if (landIndex) + { + var sea = this.getSeaIndex(gameState, landIndex, i); + if (sea && (gameState.ai.accessibility.regionSize[i] > minLandSize || gameState.ai.accessibility.regionSize[sea] > minWaterSize)) + { + this.navalMap = true; + this.landRegions[i] = true; + this.navalRegions[sea] = true; + } + } + else + { + var traject = gameState.ai.accessibility.getTrajectToIndex(seaIndex, i); + if (traject && traject.length === 2) + { + this.navalMap = true; + this.landRegions[i] = true; + this.navalRegions[seaIndex] = true; + } + } + } + else if (gameState.ai.accessibility.regionType[i] === "water" && gameState.ai.accessibility.regionSize[i] > minWaterSize) + { + this.navalMap = true; + this.navalRegions[i] = true; + } + } + if (this.Config.debug > 2) + { + for (var region in this.landRegions) + API3.warn(" >>> zone " + region + " taille " + cellArea*gameState.ai.accessibility.regionSize[region]); + API3.warn(" navalMap " + this.navalMap); + API3.warn(" landRegions " + uneval(this.landRegions)); + API3.warn(" navalRegions " + uneval(this.navalRegions)); + } + // TODO: change that to something dynamic. var civ = gameState.playerData.civ; // load units and buildings from the config files @@ -135,67 +179,104 @@ m.HQ.prototype.init = function(gameState, queues, deserializing) this.bAdvanced[i] = gameState.applyCiv(this.bAdvanced[i]); // Let's get our initial situation here. - var b0 = gameState.ai.uniqueIDs.bases; - var ccEnts = gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).toEntityArray(); - for (let i = 0; i < ccEnts.length; ++i) + let nobase = new m.BaseManager(gameState, this.Config); + nobase.init(gameState); + nobase.accessIndex = 0; + this.baseManagers.push(nobase); // baseManager[0] will deal with unit/structure witout bases + var ccEnts = gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")); + for (let cc of ccEnts.values()) { - this.baseManagers[i+b0] = new m.BaseManager(gameState, this.Config); - this.baseManagers[i+b0].init(gameState); - this.baseManagers[i+b0].setAnchor(gameState, ccEnts[i]); + let newbase = new m.BaseManager(gameState, this.Config); + newbase.init(gameState); + newbase.setAnchor(gameState, cc); + this.baseManagers.push(newbase); } this.updateTerritories(gameState); - if (this.baseManagers[b0]) // Assign entities in the different bases + // Assign entities in the different bases + var defaultbase = (this.numActiveBase() > 0) ? 1 : 0; + var width = gameState.getMap().width; + for (var ent of gameState.getOwnEntities().values()) { - var self = this; - var width = gameState.getMap().width; - gameState.getOwnEntities().forEach( function (ent) { - // do not affect merchant ship immediately to trade as they may-be useful for transport - if (ent.hasClass("Trader") && !ent.hasClass("Ship")) - self.tradeManager.assignTrader(ent); - var pos = ent.position(); - if (!pos) - { - // TODO temporarily assigned to base 1. Certainly a garrisoned unit, - // should assign it to the base of the garrison holder - self.baseManagers[b0].assignEntity(ent); - return; - } - ent.setMetadata(PlayerID, "access", gameState.ai.accessibility.getAccessValue(ent.position())); - var x = Math.round(pos[0] / gameState.cellSize); - var z = Math.round(pos[1] / gameState.cellSize); - var id = x + width*z; - for each (var base in self.baseManagers) - { - if (base.territoryIndices.indexOf(id) == -1) - continue; - base.assignEntity(ent); - if (ent.resourceDropsiteTypes() && !ent.hasClass("Elephant")) - base.assignResourceToDropsite(gameState, ent); - return; - } - // entity outside our territory, assign it to base b0 - self.baseManagers[b0].assignEntity(ent); + // make sure we have not rejected small regions with units (TODO should probably also check with other non-gaia units) + if (ent.position()) + { + let pos = gameState.ai.accessibility.gamePosToMapPos(ent.position()); + let index = pos[0] + pos[1]*gameState.ai.accessibility.width; + let land = gameState.ai.accessibility.landPassMap[index]; + if (land > 1 && !this.landRegions[land]) + API3.warn("entity " + ent.genericName() + " in a not allowed land region " + land); + if (land > 1 && !this.landRegions[land]) + this.landRegions[land] = true; + let sea = gameState.ai.accessibility.navalPassMap[index]; + if (sea > 1 && !this.navalRegions[sea]) + API3.warn("entity " + ent.genericName() + " in a not allowed sea region " + sea); + if (sea > 1 && !this.navalRegions[sea]) + this.navalRegions[sea] = true; + } + // if garrisoned units inside, ungarrison them except if a ship in which case we make a transport + if (ent.isGarrisonHolder() && ent.garrisoned().length) + { +API3.warn(">>>>>>>>>>>>>>>> ent " + ent.id() + " name " + ent.genericName()); +m.dumpEntity(ent); +API3.warn(" garrisoned " + uneval(ent.garrisoned())); + if (!ent.hasClass("Ship")) + for (let id of ent.garrisoned()) + ent.unload(id); + else +warn(" ship garrisoned >>> create a transport"); + } + // do not affect merchant ship immediately to trade as they may-be useful for transport + if (ent.hasClass("Trader") && !ent.hasClass("Ship")) + this.tradeManager.assignTrader(ent); + var pos = ent.position(); + if (!pos) + continue; + ent.setMetadata(PlayerID, "access", gameState.ai.accessibility.getAccessValue(pos)); + var x = Math.round(pos[0] / gameState.cellSize); + var z = Math.round(pos[1] / gameState.cellSize); + var id = x + width*z; + var bestbase = undefined; + for (var i = 1; i < this.baseManagers.length; ++i) + { + var base = this.baseManagers[i]; + if (base.territoryIndices.indexOf(id) == -1) + continue; + base.assignEntity(ent); if (ent.resourceDropsiteTypes() && !ent.hasClass("Elephant")) - self.baseManagers[b0].assignResourceToDropsite(gameState, ent); - }); + base.assignResourceToDropsite(gameState, ent); + bestbase = base; + break; + } + if (!bestbase) + { + // entity outside our territory + var bestbase = m.getBestBase(ent, gameState); + bestbase.assignEntity(ent); + if (bestbase.ID !== this.baseManagers[0].ID && ent.resourceDropsiteTypes() && !ent.hasClass("Elephant")) + bestbase.assignResourceToDropsite(gameState, ent); + } + // now assign entities garrisoned inside this entity + if (ent.isGarrisonHolder() && ent.garrisoned().length) + for (let id of ent.garrisoned()) + bestBase.assignEntity(gameState.getEntityByID(id)); } // we now have enough data to decide on a few things. // immediatly build a wood dropsite if possible. - if (this.baseManagers[1] && gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_storehouse"), true) == 0) + if (this.baseManagers.length > 1 && gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_storehouse"), true) == 0) { var newDP = this.baseManagers[1].findBestDropsiteLocation(gameState, "wood"); if (newDP.quality > 40 && this.canBuild(gameState, "structures/{civ}_storehouse")) - queues.dropsites.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_storehouse", { "base": 1 }, newDP.pos)); + queues.dropsites.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_storehouse", { "base": this.baseManagers[1].ID }, newDP.pos)); } // Check if we will ever be able to produce units this.canBuildUnits = true; if (!gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).length) { - var template = gameState.getTemplate(this.bBase[0]); + var template = gameState.getTemplate(gameState.applyCiv("structures/{civ}_civil_centre")); if (!template.available(gameState)) { if (this.Config.debug > 1) @@ -290,26 +371,26 @@ m.HQ.prototype.start = function(gameState, deserializing) { // Rebuild the base maps from the territory indices of each base this.basesMap = new API3.Map(gameState.sharedScript, "territory"); - for each (let base in this.baseManagers) + for (let base of this.baseManagers) for (let j of base.territoryIndices) this.basesMap.map[j] = base.ID; - var self = this; - gameState.getOwnEntities().forEach( function (ent) { + for (let ent of gameState.getOwnEntities().values()) + { if (!ent.resourceDropsiteTypes() || ent.hasClass("Elephant")) - return; - let base = self.baseManagers[ent.getMetadata(PlayerID, "base")]; + continue; + let base = this.getBaseByID(ent.getMetadata(PlayerID, "base")); base.assignResourceToDropsite(gameState, ent); - }); + } return; } // adapt our starting strategy to the available resources // - if on a small island, favor fishing and require less fields to save room for buildings var startingSize = 0; - for (var region in this.allowedRegions) + for (let region in this.landRegions) { - for each (var base in this.baseManagers) + for (let base of this.baseManagers) { if (!base.anchor || base.accessIndex != +region) continue; @@ -332,7 +413,7 @@ m.HQ.prototype.start = function(gameState, deserializing) var check = {}; for (var proxim of ["nearby", "medium", "faraway"]) { - for each (var base in this.baseManagers) + for (let base of this.baseManagers) { for (var supply of base.dropsiteSupplies["wood"][proxim]) { @@ -385,17 +466,16 @@ m.HQ.prototype.checkEvents = function (gameState, events, queues) if (ent.getMetadata(PlayerID, "base") == -1) { // Okay so let's try to create a new base around this. - var bID = gameState.ai.uniqueIDs.bases; - this.baseManagers[bID] = new m.BaseManager(gameState, this.Config); - this.baseManagers[bID].init(gameState, true); - this.baseManagers[bID].setAnchor(gameState, ent); - - // Let's get a few units out there to build this. - var builders = this.bulkPickWorkers(gameState, bID, 10); + let newbase = new m.BaseManager(gameState, this.Config); + newbase.init(gameState, true); + newbase.setAnchor(gameState, ent); + this.baseManagers.push(newbase); + // Let's get a few units from other bases there to build this. + var builders = this.bulkPickWorkers(gameState, newbase, 10); if (builders !== false) { builders.forEach(function (worker) { - worker.setMetadata(PlayerID, "base", bID); + worker.setMetadata(PlayerID, "base", newbase.ID); worker.setMetadata(PlayerID, "subrole", "builder"); worker.setMetadata(PlayerID, "target-foundation", ent.id()); }); @@ -403,15 +483,13 @@ m.HQ.prototype.checkEvents = function (gameState, events, queues) } else if (ent.hasClass("Wonder") && gameState.getGameType() === "wonder") { - var base = ent.getMetadata(PlayerID, "base"); - if (!base) - API3.warn("Petra: wonder foundation without base ???"); - // Let's get a few units out there to build this. + // Let's get a few units from other bases there to build this. + var base = this.getBaseByID(ent.getMetadata(PlayerID, "base")); var builders = this.bulkPickWorkers(gameState, base, 10); if (builders !== false) { builders.forEach(function (worker) { - worker.setMetadata(PlayerID, "base", base); + worker.setMetadata(PlayerID, "base", baseID); worker.setMetadata(PlayerID, "subrole", "builder"); worker.setMetadata(PlayerID, "target-foundation", ent.id()); }); @@ -432,12 +510,12 @@ m.HQ.prototype.checkEvents = function (gameState, events, queues) if (ent.getMetadata(PlayerID, "baseAnchor") == true) { - var base = ent.getMetadata(PlayerID, "base"); - if (this.baseManagers[base].constructing) - this.baseManagers[base].constructing = false; - this.baseManagers[base].anchor = ent; - this.baseManagers[base].anchorId = evt.newentity; - this.baseManagers[base].buildings.updateEnt(ent); + let base = this.getBaseByID(ent.getMetadata(PlayerID, "base")); + if (base.constructing) + base.constructing = false; + base.anchor = ent; + base.anchorId = evt.newentity; + base.buildings.updateEnt(ent); this.updateTerritories(gameState); // let us hope this new base will fix our resource shortage this.saveResources = undefined; @@ -691,13 +769,13 @@ m.HQ.prototype.findBestTrainableUnit = function(gameState, classes, requirements // returns an entity collection of workers through BaseManager.pickBuilders // TODO: when same accessIndex, sort by distance -m.HQ.prototype.bulkPickWorkers = function(gameState, newBaseID, number) +m.HQ.prototype.bulkPickWorkers = function(gameState, baseRef, number) { - var accessIndex = this.baseManagers[newBaseID].accessIndex; + var accessIndex = baseRef.accessIndex; if (!accessIndex) return false; // sorting bases by whether they are on the same accessindex or not. - var baseBest = API3.AssocArraytoArray(this.baseManagers).sort(function (a,b) { + var baseBest = this.baseManagers.slice().sort(function (a,b) { if (a.accessIndex == accessIndex && b.accessIndex != accessIndex) return -1; else if (b.accessIndex == accessIndex && a.accessIndex != accessIndex) @@ -709,7 +787,7 @@ m.HQ.prototype.bulkPickWorkers = function(gameState, newBaseID, number) var workers = new API3.EntityCollection(gameState.sharedScript); for (let base of baseBest) { - if (base.ID === newBaseID) + if (base.ID === baseRef.ID) continue; base.pickBuilders(gameState, workers, needed); if (workers.length < number) @@ -725,7 +803,7 @@ m.HQ.prototype.bulkPickWorkers = function(gameState, newBaseID, number) m.HQ.prototype.getTotalResourceLevel = function(gameState) { var total = { "food": 0, "wood": 0, "stone": 0, "metal": 0 }; - for each (var base in this.baseManagers) + for (var base of this.baseManagers) for (var type in total) total[type] += base.getResourceLevel(gameState, type); @@ -739,7 +817,7 @@ m.HQ.prototype.GetCurrentGatherRates = function(gameState) for (var type in this.wantedRates) this.currentRates[type] = 0; - for each (var base in this.baseManagers) + for (var base of this.baseManagers) base.getGatherRates(gameState, this.currentRates); return this.currentRates; @@ -755,8 +833,6 @@ m.HQ.prototype.GetCurrentGatherRates = function(gameState) */ m.HQ.prototype.pickMostNeededResources = function(gameState) { - var self = this; - this.wantedRates = gameState.ai.queueManager.wantedGatherRates(gameState); var currentRates = this.GetCurrentGatherRates(gameState); @@ -813,7 +889,7 @@ m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource, continue; // We require that it is accessible var index = gameState.ai.accessibility.landPassMap[j]; - if (!this.allowedRegions[index]) + if (!this.landRegions[index]) continue; // and with enough room around to build the cc var i = API3.getMaxMapIndex(j, this.territoryMap, obstructions); @@ -847,7 +923,7 @@ m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource, } if (norm == 0) continue; - if (minDist > 170000 && !this.navalMap) // Reject if too far from any allied cc (-> not connected) +/* rototo if (minDist > 170000 && !this.navalMap) // Reject if too far from any allied cc (-> not connected) { norm = 0; continue; @@ -863,7 +939,7 @@ m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource, } else norm *= 0.5; - } + } */ for (var dp of dpList) { @@ -911,7 +987,7 @@ m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource, // Define a minimal number of wanted ships in the seas reaching this new base var index = gameState.ai.accessibility.landPassMap[i]; - for each (var base in this.baseManagers) + for (var base of this.baseManagers) { if (!base.anchor || base.accessIndex === index) continue; @@ -966,7 +1042,7 @@ m.HQ.prototype.findStrategicCCLocation = function(gameState, template) continue; // We require that it is accessible var index = gameState.ai.accessibility.landPassMap[j]; - if (!this.allowedRegions[index]) + if (!this.landRegions[index]) continue; // and with enough room around to build the cc var i = API3.getMaxMapIndex(j, this.territoryMap, obstructions); @@ -1046,7 +1122,7 @@ m.HQ.prototype.findStrategicCCLocation = function(gameState, template) // Define a minimal number of wanted ships in the seas reaching this new base var index = gameState.ai.accessibility.landPassMap[i]; - for each (var base in this.baseManagers) + for (var base of this.baseManagers) { if (!base.anchor || base.accessIndex === index) continue; @@ -1095,7 +1171,7 @@ m.HQ.prototype.findMarketLocation = function(gameState, template) if (obstructions.map[i] <= radius) continue; var index = gameState.ai.accessibility.landPassMap[i]; - if (!this.allowedRegions[index]) + if (!this.landRegions[index]) continue; var pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)]; @@ -1397,7 +1473,13 @@ m.HQ.prototype.checkBaseExpansion = function(gameState, queues) { if (queues.civilCentre.length() > 0) return; - // first expand if we have not enough room available for buildings + // first build one cc if none already available + if (this.numActiveBase() < 1) + { + this.buildNewBase(gameState, queues); + return; + } + // then expand if we have not enough room available for buildings if (this.stopBuilding.length > 1) { if (this.Config.debug > 2) @@ -1413,24 +1495,25 @@ m.HQ.prototype.checkBaseExpansion = function(gameState, queues) if (Math.floor(numUnits/popForBase) >= gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).length) { if (this.Config.debug > 2) - API3.warn("try to build a new base because of population " + numUnits + " for " + numCCs + " CCs"); + API3.warn("try to build a new base because of population " + numUnits + " for " + this.numActiveBase() + " CCs"); this.buildNewBase(gameState, queues); } }; m.HQ.prototype.buildNewBase = function(gameState, queues, type) { - if (gameState.currentPhase() == 1 && !gameState.isResearching(gameState.townPhase())) + if (this.numActiveBase() > 0 && gameState.currentPhase() == 1 && !gameState.isResearching(gameState.townPhase())) return false; - if (gameState.countFoundationsByType(this.bBase[0], true) > 0 || queues.civilCentre.length() > 0) + var template = (this.numActiveBase() > 0) ? this.bBase[0] : gameState.applyCiv("structures/{civ}_civil_centre"); + if (gameState.countFoundationsByType(template, true) > 0 || queues.civilCentre.length() > 0) return false; - if (!this.canBuild(gameState, this.bBase[0])) + if (!this.canBuild(gameState, template)) return false; // base "-1" means new base. if (this.Config.debug > 1) API3.warn("new base planned with type " + type); - queues.civilCentre.addItem(new m.ConstructionPlan(gameState, this.bBase[0], { "base": -1, "type": type })); + queues.civilCentre.addItem(new m.ConstructionPlan(gameState, template, { "base": -1, "type": type })); return true; }; @@ -1634,7 +1717,7 @@ m.HQ.prototype.trainEmergencyUnits = function(gameState, positions) { var access = gameState.ai.accessibility.getAccessValue(pos); // check nearest base anchor - for each (var base in this.baseManagers) + for (var base of this.baseManagers) { if (!base.anchor || !base.anchor.position()) continue; @@ -1801,14 +1884,14 @@ m.HQ.prototype.updateTerritories = function(gameState) { if (this.basesMap.map[j] == 0) continue; - var baseID = this.basesMap.map[j]; - var index = this.baseManagers[baseID].territoryIndices.indexOf(j); + var base = this.getBaseByID(this.basesMap.map[j]); + var index = base.territoryIndices.indexOf(j); if (index == -1) { - API3.warn(" problem in headquarters::updateTerritories for base " + baseID); + API3.warn(" problem in headquarters::updateTerritories for base " + this.basesMap.map[j]); continue; } - this.baseManagers[baseID].territoryIndices.splice(index, 1); + base.territoryIndices.splice(index, 1); this.basesMap.map[j] = 0; } else if (this.basesMap.map[j] == 0) @@ -1818,7 +1901,7 @@ m.HQ.prototype.updateTerritories = function(gameState) var access; for (var k of ind) { - if (!this.allowedRegions[gameState.ai.accessibility.landPassMap[k]]) + if (!this.landRegions[gameState.ai.accessibility.landPassMap[k]]) continue; landPassable = true; access = gameState.ai.accessibility.landPassMap[k]; @@ -1829,7 +1912,7 @@ m.HQ.prototype.updateTerritories = function(gameState) var distmin = Math.min(); var baseID = undefined; var pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)]; - for each (var base in this.baseManagers) + for (var base of this.baseManagers) { if (!base.anchor || !base.anchor.position()) continue; @@ -1843,7 +1926,7 @@ m.HQ.prototype.updateTerritories = function(gameState) } if (!baseID) continue; - this.baseManagers[baseID].territoryIndices.push(j); + this.getBaseByID(baseID).territoryIndices.push(j); this.basesMap.map[j] = baseID; expansion++; } @@ -1861,11 +1944,37 @@ m.HQ.prototype.updateTerritories = function(gameState) this.tradeManager.routeProspection = true; }; +/** + * returns the base corresponding to baseID + */ +m.HQ.prototype.getBaseByID = function(baseID) +{ + for (let base of this.baseManagers) + if (base.ID === baseID) + return base; + + API3.warn("Petra error: no base found with ID " + baseID); + return undefined; +}; + +/** + * returns the number of active (i.e. with one cc) bases + * TODO should be cached + */ +m.HQ.prototype.numActiveBase = function() +{ + let num = 0; + for (let base of this.baseManagers) + if (base.anchor) + ++num; + return num; +}; + // Count gatherers returning resources in the number of gatherers of resourceSupplies // to prevent the AI always reaffecting idle workers to these resourceSupplies (specially in naval maps). m.HQ.prototype.assignGatherers = function(gameState) { - for each (var base in this.baseManagers) + for (var base of this.baseManagers) { base.workers.forEach( function (worker) { if (worker.unitAIState().split(".")[1] !== "RETURNRESOURCE") @@ -1933,7 +2042,7 @@ m.HQ.prototype.update = function(gameState, queues, events) if (gameState.getGameType() === "wonder") this.buildWonder(gameState, queues); - if (this.baseManagers[1]) + if (this.numActiveBase() > 0) { this.trainMoreWorkers(gameState, queues); @@ -1947,12 +2056,12 @@ m.HQ.prototype.update = function(gameState, queues, events) this.researchManager.update(gameState, queues); } + if (this.numActiveBase() < 1 || + (this.Config.difficulty > 0 && gameState.ai.playedTurn % 10 == 7 && gameState.currentPhase() > 1)) + this.checkBaseExpansion(gameState, queues); + if (gameState.currentPhase() > 1) { - // sandbox doesn't expand - if (this.Config.difficulty > 0 && gameState.ai.playedTurn % 10 == 7) - this.checkBaseExpansion(gameState, queues); - if (!this.saveResources) { this.buildMarket(gameState, queues); @@ -1974,15 +2083,17 @@ m.HQ.prototype.update = function(gameState, queues, events) this.buildDefenses(gameState, queues); this.assignGatherers(gameState); - for (var i in this.baseManagers) + for (let i = 0; i < this.baseManagers.length; ++i) { this.baseManagers[i].checkEvents(gameState, events, queues); - if (((+i + gameState.ai.playedTurn)%(gameState.ai.uniqueIDs.bases - 1)) == 0) + if (((i + gameState.ai.playedTurn)%this.baseManagers.length) === 0) this.baseManagers[i].update(gameState, queues, events); } this.navalManager.update(gameState, queues, events); - + +var rototo = false; +if (rototo) if (this.Config.difficulty > 0) this.attackManager.update(gameState, queues, events); @@ -2011,12 +2122,15 @@ m.HQ.prototype.Serialize = function() "bAdvanced": this.bAdvanced, "saveResources": this.saveResources, "saveSpace": this.saveSpace, - "canBuildUnits": this.canBuildUnits + "canBuildUnits": this.canBuildUnits, + "navalMap": this.navalMap, + "landRegions": this.landRegions, + "navalRegions": this.navalRegions }; - let baseManagers = {}; - for (let base in this.baseManagers) - baseManagers[base] = this.baseManagers[base].Serialize(); + let baseManagers = []; + for (let base of this.baseManagers) + baseManagers.push(base.Serialize()); if (this.Config.debug == -100) { @@ -2051,14 +2165,15 @@ m.HQ.prototype.Deserialize = function(gameState, data) for (let key in data.properties) this[key] = data.properties[key]; - this.baseManagers = {}; - for (let base in data.baseManagers) + this.baseManagers = []; + for (let base of data.baseManagers) { // the first call to deserialize set the ID base needed by entitycollections - this.baseManagers[base] = new m.BaseManager(gameState, this.Config); - this.baseManagers[base].Deserialize(gameState, data.baseManagers[base]); - this.baseManagers[base].init(gameState); - this.baseManagers[base].Deserialize(gameState, data.baseManagers[base]); + var newbase = new m.BaseManager(gameState, this.Config); + newbase.Deserialize(gameState, base); + newbase.init(gameState); + newbase.Deserialize(gameState, base); + this.baseManagers.push(newbase); } this.attackManager = new m.AttackManager(this.Config); diff --git a/binaries/data/mods/public/simulation/ai/petra/map-module.js b/binaries/data/mods/public/simulation/ai/petra/map-module.js index 788b991cbb..c42e7e6baa 100644 --- a/binaries/data/mods/public/simulation/ai/petra/map-module.js +++ b/binaries/data/mods/public/simulation/ai/petra/map-module.js @@ -43,11 +43,6 @@ m.createObstructionMap = function(gameState, accessIndex, template) var iter = xter + yter*territoryMap.width; var tilePlayer = (territoryMap.data[iter] & m.TERRITORY_PLAYER_MASK); - //if (gameState.ai.myIndex !== gameState.ai.accessibility.landPassMap[i]) - //{ - // obstructionTiles[i] = 0; - // continue; - //} if (gameState.isPlayerEnemy(tilePlayer) && tilePlayer !== 0) { obstructionTiles[i] = 0; diff --git a/binaries/data/mods/public/simulation/ai/petra/navalManager.js b/binaries/data/mods/public/simulation/ai/petra/navalManager.js index 2a98eb1374..dac1cbb21c 100644 --- a/binaries/data/mods/public/simulation/ai/petra/navalManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/navalManager.js @@ -57,7 +57,7 @@ m.NavalManager.prototype.init = function(gameState, deserializing) for (var i = 0; i < gameState.ai.accessibility.regionSize.length; ++i) { - if (gameState.ai.HQ.navalRegions.indexOf(i) === -1) + if (!gameState.ai.HQ.navalRegions[i]) { // push dummies this.seaShips.push(undefined); @@ -162,9 +162,11 @@ m.NavalManager.prototype.init = function(gameState, deserializing) if (deserializing) return; - // Assign our docks - var self = this; - this.docks.forEach(function(dock) { self.assignDock(gameState, dock); }); + // Assign our initial docks and ships + for (let ship of this.ships.values()) + this.setShipIndex(gameState, ship); + for (let dock of this.docks.values()) + this.setDockIndex(gameState, dock); }; m.NavalManager.prototype.resetFishingBoats = function(gameState) @@ -173,7 +175,13 @@ m.NavalManager.prototype.resetFishingBoats = function(gameState) this.wantedFishShips[i] = 0; }; -m.NavalManager.prototype.assignDock = function(gameState, dock) +m.NavalManager.prototype.setShipIndex = function(gameState, ship) +{ + let sea = gameState.ai.accessibility.getAccessValue(ship.position(), true); + ship.setMetadata(PlayerID, "sea", sea); +}; + +m.NavalManager.prototype.setDockIndex = function(gameState, dock) { var land = dock.getMetadata(PlayerID, "access"); if (land === undefined) @@ -238,7 +246,7 @@ m.NavalManager.prototype.checkEvents = function(gameState, queues, events) continue; var entity = gameState.getEntityById(evt.newentity); if (entity && entity.hasClass("Dock") && entity.isOwn(PlayerID)) - this.assignDock(gameState, entity); + this.setDockIndex(gameState, entity); } var evts = events["TrainingFinished"]; @@ -251,10 +259,7 @@ m.NavalManager.prototype.checkEvents = function(gameState, queues, events) var entity = gameState.getEntityById(entId); if (!entity || !entity.hasClass("Ship") || !entity.isOwn(PlayerID)) continue; - var pos = gameState.ai.accessibility.gamePosToMapPos(entity.position()); - var index = pos[0] + pos[1]*gameState.ai.accessibility.width; - var sea = gameState.ai.accessibility.navalPassMap[index]; - entity.setMetadata(PlayerID, "sea", sea); + this.setShipIndex(gameState, entity); } } @@ -533,7 +538,7 @@ m.NavalManager.prototype.buildNavalStructures = function(gameState, queues) && gameState.ai.HQ.canBuild(gameState, "structures/{civ}_dock")) { var dockStarted = false; - for each (var base in gameState.ai.HQ.baseManagers) + for (var base of gameState.ai.HQ.baseManagers) { if (dockStarted) break; @@ -542,7 +547,7 @@ m.NavalManager.prototype.buildNavalStructures = function(gameState, queues) var remaining = this.getUnconnectedSeas(gameState, base.accessIndex); for (var sea of remaining) { - if (gameState.ai.HQ.navalRegions.indexOf(sea) === -1) + if (!gameState.ai.HQ.navalRegions[sea]) continue; queues.economicBuilding.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_dock", { "land": base.accessIndex, "sea": sea })); dockStarted = true; diff --git a/binaries/data/mods/public/simulation/ai/petra/queueplan-building.js b/binaries/data/mods/public/simulation/ai/petra/queueplan-building.js index 21431ca995..7122f7200d 100644 --- a/binaries/data/mods/public/simulation/ai/petra/queueplan-building.js +++ b/binaries/data/mods/public/simulation/ai/petra/queueplan-building.js @@ -374,14 +374,13 @@ m.ConstructionPlan.prototype.findDockPosition = function(gameState) var baseIndex = gameState.ai.HQ.basesMap.map[bestIdx]; if (!baseIndex) { - for (let i in gameState.ai.HQ.baseManagers) + for (let base of gameState.ai.HQ.baseManagers) { - let base = gameState.ai.HQ.baseManagers[i]; if (!base.anchor || !base.anchor.position()) continue; if (base.accessIndex !== access) continue; - baseIndex = i; + baseIndex = base.ID; break; } if (!baseIndex) diff --git a/binaries/data/mods/public/simulation/ai/petra/queueplan-training.js b/binaries/data/mods/public/simulation/ai/petra/queueplan-training.js index 69d21ba076..db7cd86750 100644 --- a/binaries/data/mods/public/simulation/ai/petra/queueplan-training.js +++ b/binaries/data/mods/public/simulation/ai/petra/queueplan-training.js @@ -85,16 +85,16 @@ m.TrainingPlan.prototype.start = function(gameState) let bBase = b.getMetadata(PlayerID, "base"); if (wantedIndex) { - if (!aBase || gameState.ai.HQ.baseManagers[aBase].accessIndex != wantedIndex) + if (!aBase || gameState.ai.HQ.getBaseByID(aBase).accessIndex != wantedIndex) aa += 30; - if (!bBase || gameState.ai.HQ.baseManagers[bBase].accessIndex != wantedIndex) + if (!bBase || gameState.ai.HQ.getBaseByID(bBase).accessIndex != wantedIndex) bb += 30; } // then, if worker, small preference for bases with less workers if (workerUnit && aBase && bBase && aBase != bBase) { - let apop = gameState.ai.HQ.baseManagers[aBase].workers.length; - let bpop = gameState.ai.HQ.baseManagers[bBase].workers.length; + let apop = gameState.ai.HQ.getBaseByID(aBase).workers.length; + let bpop = gameState.ai.HQ.getBaseByID(bBase).workers.length; if (apop > bpop) aa++; else if (bpop > apop) diff --git a/binaries/data/mods/public/simulation/ai/petra/transportPlan.js b/binaries/data/mods/public/simulation/ai/petra/transportPlan.js index 70201fce26..cbfc43b4e2 100644 --- a/binaries/data/mods/public/simulation/ai/petra/transportPlan.js +++ b/binaries/data/mods/public/simulation/ai/petra/transportPlan.js @@ -184,22 +184,21 @@ m.TransportPlan.prototype.releaseAll = function() m.TransportPlan.prototype.cancelTransport = function(gameState) { var ent = this.units.toEntityArray()[0]; - var base = gameState.ai.HQ.baseManagers[ent.getMetadata(PlayerID, "base")]; + var base = gameState.ai.HQ.getBaseByID(ent.getMetadata(PlayerID, "base")); if (!base.anchor || !base.anchor.position()) { - for (var i in gameState.ai.HQ.baseManagers) + for (let newbase of gameState.ai.HQ.baseManagers) { - base = gameState.ai.HQ.baseManagers[i]; - if (base.anchor && base.anchor.position()) - { - ent.setMetadata(PlayerID, "base", +i); - break; - } + if (!newbase.anchor || !newbase.anchor.position()) + continue; + ent.setMetadata(PlayerID, "base", newbase.ID); + base = newbase; + break; } if (!base.anchor || !base.anchor.position()) return false; this.units.forEach(function (ent) { - ent.setMetadata(PlayerID, "base", base); + ent.setMetadata(PlayerID, "base", base.ID); }); } this.endIndex = this.startIndex; @@ -401,7 +400,7 @@ m.TransportPlan.prototype.onSailing = function(gameState) API3.warn(">>> transport " + this.ID + " reloading failed ... <<<"); // destroy the unit if inaccessible otherwise leave it there var index = gameState.ai.accessibility.getAccessValue(ent.position()); - if (gameState.ai.HQ.allowedRegions[index]) + if (gameState.ai.HQ.landRegions[index]) { if (this.debug > 1) API3.warn(" recovered entity kept " + ent.id()); diff --git a/binaries/data/mods/public/simulation/ai/petra/worker.js b/binaries/data/mods/public/simulation/ai/petra/worker.js index cd08fa3b38..1ebb125bfb 100644 --- a/binaries/data/mods/public/simulation/ai/petra/worker.js +++ b/binaries/data/mods/public/simulation/ai/petra/worker.js @@ -5,11 +5,11 @@ var PETRA = function(m) * This class makes a worker do as instructed by the economy manager */ -m.Worker = function(baseManager) +m.Worker = function(base) { this.ent = undefined; - this.baseManager = baseManager - this.baseID = baseManager.ID; + this.base = base + this.baseID = base.ID; }; m.Worker.prototype.update = function(ent, gameState) @@ -22,6 +22,12 @@ m.Worker.prototype.update = function(ent, gameState) return; this.ent = ent; + // base 0 for unassigned entities has no accessIndex, so take the one from the entity + if (this.baseID === gameState.ai.HQ.baseManagers[0].ID) + this.accessIndex = gameState.ai.accessibility.getAccessValue(ent.position()); + else + this.accessIndex = this.base.accessIndex; + var subrole = this.ent.getMetadata(PlayerID, "subrole"); // Check for inaccessible targets (in RMS maps, we quite often have chicken or bushes inside obstruction of other entities). @@ -77,7 +83,7 @@ m.Worker.prototype.update = function(ent, gameState) else { var gatherType = this.ent.getMetadata(PlayerID, "gather-type"); - var nearby = this.baseManager.dropsiteSupplies[gatherType]["nearby"]; + var nearby = this.base.dropsiteSupplies[gatherType]["nearby"]; var isNearby = nearby.some(function(sup) { if (sup.id === supplyId) return true; @@ -152,9 +158,8 @@ m.Worker.prototype.update = function(ent, gameState) { // nothing to hunt around. Try another region if any var nowhereToHunt = true; - for (var i in gameState.ai.HQ.baseManagers) + for (var base of gameState.ai.HQ.baseManagers) { - var base = gameState.ai.HQ.baseManagers[i]; if (!base.anchor || !base.anchor.position()) continue; var basePos = base.anchor.position(); @@ -222,40 +227,8 @@ m.Worker.prototype.startGathering = function(gameState) var access = gameState.ai.accessibility.getAccessValue(this.ent.position()); // First look for possible treasure if any - var treasureFound = undefined; - var distmin = Math.min(); - gameState.ai.HQ.treasures.forEach(function (treasure) { - var treasureAccess = treasure.getMetadata(PlayerID, "access"); - if (!treasureAccess) - { - treasureAccess = gameState.ai.accessibility.getAccessValue(treasure.position()); - treasure.setMetadata(PlayerID, "access", treasureAccess); - } - if (treasureAccess !== access) - return; - var territoryOwner = gameState.ai.HQ.territoryMap.getOwner(treasure.position()); - if (territoryOwner !== 0 && !gameState.isPlayerAlly(territoryOwner)) - return; - var lastGathered = treasure.getMetadata(PlayerID, "lastGathered"); - // let some time for the previous gatherer to reach the treasure - if (lastGathered && gameState.ai.elapsedTime - lastGathered < 20) - return; - var dist = API3.SquareVectorDistance(self.ent.position(), treasure.position()); - if (territoryOwner !== PlayerID && dist > 14000) // AI has no LOS, so restrict it a bit - return; - if (dist > distmin) - return; - distmin = dist; - treasureFound = treasure; - }); - if (treasureFound) - { - treasureFound.setMetadata(PlayerID, "lastGathered", gameState.ai.elapsedTime); - this.ent.gather(treasureFound); - m.AddTCGatherer(gameState, treasureFound.id()); - this.ent.setMetadata(PlayerID, "supply", treasureFound.id()); + if (this.gatherTreasure(gameState)) return true; - } var resource = this.ent.getMetadata(PlayerID, "gather-type"); @@ -300,9 +273,9 @@ m.Worker.prototype.startGathering = function(gameState) var supply; // first look in our own base if accessible from our present position - if (this.baseManager.accessIndex === access) + if (this.base.accessIndex === access) { - if ((supply = findSupply(this.ent, this.baseManager.dropsiteSupplies[resource]["nearby"]))) + if ((supply = findSupply(this.ent, this.base.dropsiteSupplies[resource]["nearby"]))) { this.ent.gather(supply); return true; @@ -321,7 +294,7 @@ m.Worker.prototype.startGathering = function(gameState) return true; } } - if ((supply = findSupply(this.ent, this.baseManager.dropsiteSupplies[resource]["medium"]))) + if ((supply = findSupply(this.ent, this.base.dropsiteSupplies[resource]["medium"]))) { this.ent.gather(supply); return true; @@ -329,7 +302,7 @@ m.Worker.prototype.startGathering = function(gameState) } // So if we're here we have checked our whole base for a proper resource (or it was not accessible) // --> check other bases directly accessible - for each (var base in gameState.ai.HQ.baseManagers) + for (var base of gameState.ai.HQ.baseManagers) { if (base.ID === this.baseID) continue; @@ -344,7 +317,7 @@ m.Worker.prototype.startGathering = function(gameState) } if (resource === "food") // --> for food, try to gather from fields if any, otherwise build one if any { - for each (var base in gameState.ai.HQ.baseManagers) + for (var base of gameState.ai.HQ.baseManagers) { if (base.ID === this.baseID) continue; @@ -364,7 +337,7 @@ m.Worker.prototype.startGathering = function(gameState) } } } - for each (var base in gameState.ai.HQ.baseManagers) + for (var base of gameState.ai.HQ.baseManagers) { if (base.ID === this.baseID) continue; @@ -398,7 +371,7 @@ m.Worker.prototype.startGathering = function(gameState) return true; // Still nothing ... try bases which need a transport - for each (var base in gameState.ai.HQ.baseManagers) + for (var base of gameState.ai.HQ.baseManagers) { if (base.accessIndex === access) continue; @@ -412,7 +385,7 @@ m.Worker.prototype.startGathering = function(gameState) } if (resource === "food") // --> for food, try to gather from fields if any, otherwise build one if any { - for each (var base in gameState.ai.HQ.baseManagers) + for (var base of gameState.ai.HQ.baseManagers) { if (base.accessIndex === access) continue; @@ -432,7 +405,7 @@ m.Worker.prototype.startGathering = function(gameState) } } } - for each (var base in gameState.ai.HQ.baseManagers) + for (var base of gameState.ai.HQ.baseManagers) { if (base.accessIndex === access) continue; @@ -463,15 +436,15 @@ m.Worker.prototype.startGathering = function(gameState) return true; // Still nothing, we look now for faraway resources, first in the accessible ones, then in the others - if (this.baseManager.accessIndex === access) + if (this.accessIndex === access) { - if ((supply = findSupply(this.ent, this.baseManager.dropsiteSupplies[resource]["faraway"]))) + if ((supply = findSupply(this.ent, this.base.dropsiteSupplies[resource]["faraway"]))) { this.ent.gather(supply); return true; } } - for each (var base in gameState.ai.HQ.baseManagers) + for (var base of gameState.ai.HQ.baseManagers) { if (base.ID === this.baseID) continue; @@ -484,7 +457,7 @@ m.Worker.prototype.startGathering = function(gameState) return true; } } - for each (var base in gameState.ai.HQ.baseManagers) + for (var base of gameState.ai.HQ.baseManagers) { if (base.accessIndex === access) continue; @@ -505,10 +478,16 @@ m.Worker.prototype.startGathering = function(gameState) return false; }; -// 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 +/** + * 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 + */ m.Worker.prototype.startHunting = function(gameState, position) { + // First look for possible treasure if any + if (!position && this.gatherTreasure(gameState)) + return true; + var resources = gameState.getHuntableSupplies(); if (resources.length === 0) return false; @@ -748,11 +727,58 @@ m.Worker.prototype.buildAnyField = function(gameState, baseID) return bestFarmEnt; }; -// Workers elephant should move away from the buildings they've built to avoid being trapped in between constructions -// For the time being, we move towards the nearest gatherer (providing him a dropsite) +/** + * Look for some treasure to gather + */ +m.Worker.prototype.gatherTreasure = function(gameState) +{ + var rates = this.ent.resourceGatherRates(); + if (!rates || !rates["treasure"] || rates["treasure"] <= 0) + return false; + var treasureFound = undefined; + var distmin = Math.min(); + var access = gameState.ai.accessibility.getAccessValue(this.ent.position()); + for (var treasure of gameState.ai.HQ.treasures.values()) + { + // let some time for the previous gatherer to reach the treasure befor trying again + var lastGathered = treasure.getMetadata(PlayerID, "lastGathered"); + if (lastGathered && gameState.ai.elapsedTime - lastGathered < 20) + continue; + var treasureAccess = treasure.getMetadata(PlayerID, "access"); + if (!treasureAccess) + { + treasureAccess = gameState.ai.accessibility.getAccessValue(treasure.position()); + treasure.setMetadata(PlayerID, "access", treasureAccess); + } + if (treasureAccess !== access) + continue; + var territoryOwner = gameState.ai.HQ.territoryMap.getOwner(treasure.position()); + if (territoryOwner !== 0 && !gameState.isPlayerAlly(territoryOwner)) + continue; + var dist = API3.SquareVectorDistance(this.ent.position(), treasure.position()); + if (territoryOwner !== PlayerID && dist > 14000) // AI has no LOS, so restrict it a bit + continue; + if (dist > distmin) + continue; + distmin = dist; + treasureFound = treasure; + } + if (!treasureFound) + return false; + treasureFound.setMetadata(PlayerID, "lastGathered", gameState.ai.elapsedTime); + this.ent.gather(treasureFound); + m.AddTCGatherer(gameState, treasureFound.id()); + this.ent.setMetadata(PlayerID, "supply", treasureFound.id()); + return true; +}; + +/** + * Workers elephant should move away from the buildings they've built to avoid being trapped in between constructions + * For the time being, we move towards the nearest gatherer (providing him a dropsite) + */ m.Worker.prototype.moveAway = function(gameState) { - var gatherers = this.baseManager.workersBySubrole(gameState, "gatherer"); + var gatherers = this.base.workersBySubrole(gameState, "gatherer"); var pos = this.ent.position(); var dist = Math.min(); var destination = pos;