From 3efa4be02c66a51727780c9580cd163325a86e31 Mon Sep 17 00:00:00 2001 From: mimo Date: Sat, 27 Jun 2015 13:30:44 +0000 Subject: [PATCH] AI completes the removal of the JS pathfinder and fixes naval transport This was SVN commit r16819. --- .../simulation/ai/petra/attackManager.js | 17 +- .../public/simulation/ai/petra/attackPlan.js | 436 ++++++++++-------- .../simulation/ai/petra/headquarters.js | 8 +- .../simulation/ai/petra/navalManager.js | 24 +- .../simulation/ai/petra/queueplan-building.js | 6 +- .../simulation/ai/petra/transportPlan.js | 10 +- 6 files changed, 260 insertions(+), 241 deletions(-) diff --git a/binaries/data/mods/public/simulation/ai/petra/attackManager.js b/binaries/data/mods/public/simulation/ai/petra/attackManager.js index e2acff037c..210683ac5a 100644 --- a/binaries/data/mods/public/simulation/ai/petra/attackManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/attackManager.js @@ -73,21 +73,10 @@ m.AttackManager.prototype.checkEvents = function(gameState, events) other = attack.targetPlayer; continue; } + if (!attack.targetPlayer || attack.targetPlayer !== targetPlayer) - { - let oldTargetPlayer = attack.targetPlayer; - let oldTarget = attack.target; attack.targetPlayer = targetPlayer; - attack.target = attack.getNearestTarget(gameState, attack.rallyPoint); - if (!attack.target) - { - attack.targetPlayer = oldTargetPlayer; - attack.target = oldTarget; - continue; - } - attack.targetPos = attack.target.position(); - attack.resetPath(); - } + if (attack.targetPlayer && attack.targetPlayer === targetPlayer) available += attack.unitCollection.length; } @@ -153,7 +142,7 @@ m.AttackManager.prototype.update = function(gameState, queues, events) if (attack.state === "unexecuted") ++unexecutedAttacks[attackType]; } - else if (updateStep === 0 || updateStep === 3) + else if (updateStep === 0) { if (this.Config.debug > 1) API3.warn("Attack Manager: " + attack.getType() + " plan " + attack.getName() + " aborted."); diff --git a/binaries/data/mods/public/simulation/ai/petra/attackPlan.js b/binaries/data/mods/public/simulation/ai/petra/attackPlan.js index 98eab32f5f..a5fbc90fdc 100644 --- a/binaries/data/mods/public/simulation/ai/petra/attackPlan.js +++ b/binaries/data/mods/public/simulation/ai/petra/attackPlan.js @@ -24,23 +24,25 @@ m.AttackPlan = function(gameState, Config, uniqueID, type, data) { this.target = undefined; this.targetPos = undefined; - this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); + this.targetPlayer = undefined; } - if (this.targetPlayer === undefined) - { - this.failed = true; - return false; - } - // get a starting rallyPoint ... will be improved later - var rallyPoint; + let rallyPoint; + let rallyAccess; + let allAccesses = {}; for (let base of gameState.ai.HQ.baseManagers) { if (!base.anchor || !base.anchor.position()) continue; - rallyPoint = base.anchor.position(); - break; + let access = gameState.ai.accessibility.getAccessValue(base.anchor.position()); + if (!rallyPoint) + { + rallyPoint = base.anchor.position(); + rallyAccess = access; + } + if (!allAccesses[access]) + allAccesses[access] = base.anchor.position(); } if (!rallyPoint) // no base ? take the position of any of our entities { @@ -48,7 +50,10 @@ m.AttackPlan = function(gameState, Config, uniqueID, type, data) { if (!ent.position()) continue; + let access = gameState.ai.accessibility.getAccessValue(ent.position()); rallyPoint = ent.position(); + rallyAccess = access; + allAccesses[access] = rallyPoint; break; } if (!rallyPoint) @@ -58,8 +63,30 @@ m.AttackPlan = function(gameState, Config, uniqueID, type, data) } } this.rallyPoint = rallyPoint; - - this.overseas = undefined; + this.overseas = 0; + if (gameState.ai.HQ.navalMap) + { + for (let structure of gameState.getEnemyStructures().values()) + { + if (!structure.position()) + continue; + let access = gameState.ai.accessibility.getAccessValue(structure.position()); + if (access in allAccesses) + { + this.overseas = 0; + this.rallyPoint = allAccesses[access]; + break; + } + else if (!this.overseas) + { + let sea = gameState.ai.HQ.getSeaIndex(gameState, rallyAccess, access); + if (!sea) + continue; + this.overseas = sea; + gameState.ai.HQ.navalManager.setMinimalTransportShips(gameState, sea, 1); + } + } + } this.paused = false; this.maxCompletingTurn = 0; @@ -182,9 +209,9 @@ m.AttackPlan.prototype.init = function(gameState) // defining the entity collections. Will look for units I own, that are part of this plan. // Also defining the buildOrders. - for (var cat in this.unitStat) + for (let cat in this.unitStat) { - var Unit = this.unitStat[cat]; + let Unit = this.unitStat[cat]; this.unit[cat] = this.unitCollection.filter(API3.Filters.byClassesAnd(Unit["classes"])); this.unit[cat].registerUpdates(); if (this.canBuildUnits) @@ -224,9 +251,9 @@ m.AttackPlan.prototype.canStart = function() if (!this.canBuildUnits) return true; - for (var unitCat in this.unitStat) + for (let unitCat in this.unitStat) { - var Unit = this.unitStat[unitCat]; + let Unit = this.unitStat[unitCat]; if (this.unit[unitCat].length < Unit["minSize"]) return false; } @@ -235,7 +262,7 @@ m.AttackPlan.prototype.canStart = function() m.AttackPlan.prototype.mustStart = function() { - if (this.isPaused() || this.path === undefined) + if (this.isPaused()) return false; if (!this.canBuildUnits) @@ -243,9 +270,9 @@ m.AttackPlan.prototype.mustStart = function() var MaxReachedEverywhere = true; var MinReachedEverywhere = true; - for (var unitCat in this.unitStat) + for (let unitCat in this.unitStat) { - var Unit = this.unitStat[unitCat]; + let Unit = this.unitStat[unitCat]; if (this.unit[unitCat].length < Unit["targetSize"]) MaxReachedEverywhere = false; if (this.unit[unitCat].length < Unit["minSize"]) @@ -282,9 +309,8 @@ m.AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats, rese { // no minsize as we don't want the plan to fail at the last minute though. this.unitStat[name] = unitStats; - var Unit = this.unitStat[name]; - var filter = API3.Filters.and(API3.Filters.byClassesAnd(Unit["classes"]), API3.Filters.byMetadata(PlayerID, "plan", this.name)); - this.unit[name] = gameState.getOwnUnits().filter(filter); + let Unit = this.unitStat[name]; + this.unit[name] = this.unitCollection.filter(API3.Filters.byClassesAnd(Unit["classes"])); this.unit[name].registerUpdates(); this.buildOrder.push([0, Unit["classes"], this.unit[name], Unit, name]); if (resetQueue) @@ -315,105 +341,33 @@ m.AttackPlan.prototype.addSiegeUnits = function(gameState) }; // Three returns possible: 1 is "keep going", 0 is "failed plan", 2 is "start" -// 3 is a special case: no valid path returned. Right now I stop attacking alltogether. m.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 === "completing") { - // check that all units have finished with their transport if needed - if (this.waitingForTransport()) - return 1; - // bloqued units which cannot finish their order should not stop the attack - if (gameState.ai.playedTurn < this.maxCompletingTurn && this.hasForceOrder()) - return 1; - return 2; + // if our target was destroyed, go back to "unexecuted" state + if (!this.targetPlayer || !this.target || !gameState.getEntityById(this.target.id())) + { + this.state === "unexecuted"; + this.target = undefined; + } + else + { + // check that all units have finished with their transport if needed + if (this.waitingForTransport()) + return 1; + // bloqued units which cannot finish their order should not stop the attack + if (gameState.ai.playedTurn < this.maxCompletingTurn && this.hasForceOrder()) + return 1; + return 2; + } } if (this.Config.debug > 3 && gameState.ai.playedTurn % 50 == 0) this.debugAttack(); - // find our target (if not yet done or because our previous one was destroyed) - if (!this.targetPlayer) - { - this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); - if (!this.targetPlayer) - return 0; - if (this.target && this.target.owner() !== this.targetPlayer) - this.target = undefined; - } - if (!this.target || !gameState.getEntityById(this.target.id())) - { - this.target = this.getNearestTarget(gameState, this.rallyPoint); - if (!this.target) - { - // may-be all our previous enemey targets have been destroyed ? - this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); - if (this.targetPlayer !== undefined) - this.target = this.getNearestTarget(gameState, this.rallyPoint); - if (!this.target) - return 0; - } - 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) - var targetIndex = gameState.ai.accessibility.getAccessValue(this.targetPos); - var rallyIndex = gameState.ai.accessibility.getAccessValue(this.rallyPoint); - if (targetIndex !== rallyIndex) - { - var distminSame = Math.min(); - var rallySame = undefined; - var distminDiff = Math.min(); - var rallyDiff = undefined; - for (var base of gameState.ai.HQ.baseManagers) - { - var anchor = base.anchor; - if (!anchor || !anchor.position()) - continue; - var dist = API3.SquareVectorDistance(anchor.position(), this.targetPos); - if (base.accessIndex === targetIndex) - { - if (dist < distminSame) - { - distminSame = dist; - rallySame = anchor.position(); - } - } - else - { - dist = dist / Math.sqrt(gameState.ai.accessibility.regionSize[base.accessIndex]); - if (dist < distminDiff) - { - distminDiff = dist; - rallyDiff = anchor.position(); - } - } - } - if (rallySame) - this.rallyPoint = rallySame; - else if (rallyDiff) - { - this.rallyPoint = rallyDiff; - this.overseas = gameState.ai.HQ.getSeaIndex(gameState, rallyIndex, targetIndex); - if (this.overseas) - gameState.ai.HQ.navalManager.setMinimalTransportShips(gameState, this.overseas, this.neededShips); - else - return 0; - } - } - // reset the path so that we recompute it for this new target - this.resetPath(); - } - - // when we have a target, we path to it. - if (!this.path || this.path === "toBeContinued") - { - let ret = this.getPathToTarget(gameState); - if (ret >= 0) - return ret; - } - // if we need a transport, wait for some transport ships if (this.overseas && !gameState.ai.HQ.navalManager.seaTransportShips[this.overseas].length) return 1; @@ -484,6 +438,12 @@ m.AttackPlan.prototype.updatePreparation = function(gameState) // if we're here, it means we must start this.state = "completing"; + + if (!this.chooseTarget(gameState)) + return 0; + if (!this.overseas) + this.getPathToTarget(gameState); + if (this.type === "Raid") this.maxCompletingTurn = gameState.ai.playedTurn + 20; else @@ -496,28 +456,28 @@ m.AttackPlan.prototype.updatePreparation = function(gameState) var rallyPoint = this.rallyPoint; var rallyIndex = gameState.ai.accessibility.getAccessValue(rallyPoint); - for (var entity of this.unitCollection.values()) + for (let ent of this.unitCollection.values()) { // For the time being, if occupied in a transport, remove the unit from this plan TODO improve that - if (entity.getMetadata(PlayerID, "transport") !== undefined || entity.getMetadata(PlayerID, "transporter") !== undefined) + if (ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined) { - entity.setMetadata(PlayerID, "plan", -1); + ent.setMetadata(PlayerID, "plan", -1); continue; } - entity.setMetadata(PlayerID, "role", "attack"); - entity.setMetadata(PlayerID, "subrole", "completing"); - var queued = false; - if (entity.resourceCarrying() && entity.resourceCarrying().length) - queued = m.returnResources(gameState, entity); - var index = gameState.ai.accessibility.getAccessValue(entity.position()); + ent.setMetadata(PlayerID, "role", "attack"); + ent.setMetadata(PlayerID, "subrole", "completing"); + let queued = false; + if (ent.resourceCarrying() && ent.resourceCarrying().length) + queued = m.returnResources(gameState, ent); + let index = gameState.ai.accessibility.getAccessValue(ent.position()); if (index === rallyIndex) - entity.moveToRange(rallyPoint[0], rallyPoint[1], 0, 15, queued); + ent.moveToRange(rallyPoint[0], rallyPoint[1], 0, 15, queued); else - gameState.ai.HQ.navalManager.requireTransport(gameState, entity, index, rallyIndex, rallyPoint); + gameState.ai.HQ.navalManager.requireTransport(gameState, ent, index, rallyIndex, rallyPoint); } // reset all queued units - var plan = this.name; + let plan = this.name; gameState.ai.queueManager.removeQueue("plan_" + plan); gameState.ai.queueManager.removeQueue("plan_" + plan + "_champ"); gameState.ai.queueManager.removeQueue("plan_" + plan + "_siege"); @@ -540,10 +500,10 @@ m.AttackPlan.prototype.trainMoreUnits = function(gameState) this.buildOrder[i][0] = this.buildOrder[i][2].length + aQueued; } this.buildOrder.sort(function (a,b) { - var va = a[0]/a[3]["targetSize"] - a[3]["priority"]; + let va = a[0]/a[3]["targetSize"] - a[3]["priority"]; if (a[0] >= a[3]["targetSize"]) va += 1000; - var vb = b[0]/b[3]["targetSize"] - b[3]["priority"]; + let vb = b[0]/b[3]["targetSize"] - b[3]["priority"]; if (b[0] >= b[3]["targetSize"]) vb += 1000; return va - vb; @@ -743,6 +703,75 @@ m.AttackPlan.prototype.reassignCavUnit = function(gameState) raid.unitCollection.updateEnt(found); }; +m.AttackPlan.prototype.chooseTarget = function(gameState) +{ + if (!this.targetPlayer) + { + this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); + if (!this.targetPlayer) + return false; + } + + this.target = this.getNearestTarget(gameState, this.rallyPoint); + if (!this.target) + { + // may-be all our previous enemey target (if not recomputed here) have been destroyed ? + this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); + if (this.targetPlayer !== undefined) + this.target = this.getNearestTarget(gameState, this.rallyPoint); + if (!this.target) + return false; + } + 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) + let targetIndex = gameState.ai.accessibility.getAccessValue(this.targetPos); + let rallyIndex = gameState.ai.accessibility.getAccessValue(this.rallyPoint); + if (targetIndex !== rallyIndex) + { + let distminSame = Math.min(); + let rallySame; + let distminDiff = Math.min(); + let rallyDiff; + for (let base of gameState.ai.HQ.baseManagers) + { + let anchor = base.anchor; + if (!anchor || !anchor.position()) + continue; + let dist = API3.SquareVectorDistance(anchor.position(), this.targetPos); + if (base.accessIndex === targetIndex) + { + if (dist >= distminSame) + continue; + distminSame = dist; + rallySame = anchor.position(); + } + else + { + dist = dist / Math.sqrt(gameState.ai.accessibility.regionSize[base.accessIndex]); + if (dist >= distminDiff) + continue; + distminDiff = dist; + rallyDiff = anchor.position(); + } + } + + if (rallySame) + this.rallyPoint = rallySame; + else if (rallyDiff) + { + rallyIndex = gameState.ai.accessibility.getAccessValue(rallyDiff); + this.rallyPoint = rallyDiff; + this.overseas = gameState.ai.HQ.getSeaIndex(gameState, rallyIndex, targetIndex); + if (this.overseas) + gameState.ai.HQ.navalManager.setMinimalTransportShips(gameState, this.overseas, this.neededShips); + else + return false; + } + } + return true; +}; + // sameLand true means that we look for a target for which we do not need to take a transport m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand) { @@ -759,7 +788,7 @@ m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand // picking the nearest target var minDist = -1; - var target = undefined; + var target; for (let ent of targets.values()) { if (!ent.position()) @@ -783,7 +812,7 @@ m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand // Default target finder aims for conquest critical targets m.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy) { - var targets = undefined; + var targets; if (gameState.getGameType() === "wonder") { targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("Wonder")); @@ -791,7 +820,7 @@ m.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy) return targets; } - var targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("CivCentre")); + targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("CivCentre")); if (!targets.length) targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("ConquestCritical")); // If there's nothing, attack anything else that's less critical @@ -878,46 +907,49 @@ m.AttackPlan.prototype.raidTargetFinder = function(gameState) m.AttackPlan.prototype.getPathToTarget = function(gameState) { - if (!this.path) - { - Engine.ProfileStart("AI Compute path"); - let startPos = { "x": this.rallyPoint[0], "y": this.rallyPoint[1] }; - let endPos = { "x": this.targetPos[0], "y": this.targetPos[1] }; - let path = Engine.ComputePath(startPos, endPos, gameState.getPassabilityClassMask("siege-large")); - this.path = []; - this.path.push(this.targetPos); - for (let p in path) - this.path.push([path[p].x, path[p].y]) - this.path.push(this.rallyPoint); - this.path.reverse(); - // Change the rally point to something useful - this.setRallyPoint(gameState); - Engine.ProfileStop(); - } - else if (this.path === "toBeContinued") - return 1; // carry on + let startAccess = gameState.ai.accessibility.getAccessValue(this.rallyPoint); + let endAccess = gameState.ai.accessibility.getAccessValue(this.targetPos); + if (startAccess != endAccess) + return false; - return -1; + Engine.ProfileStart("AI Compute path"); + let startPos = { "x": this.rallyPoint[0], "y": this.rallyPoint[1] }; + let endPos = { "x": this.targetPos[0], "y": this.targetPos[1] }; + let path = Engine.ComputePath(startPos, endPos, gameState.getPassabilityClassMask("siege-large")); + this.path = []; + this.path.push(this.targetPos); + for (let p in path) + this.path.push([path[p].x, path[p].y]) + this.path.push(this.rallyPoint); + this.path.reverse(); + // Change the rally point to something useful + this.setRallyPoint(gameState); + Engine.ProfileStop(); + + return true; }; +// Set rally point at the border of our territory m.AttackPlan.prototype.setRallyPoint = function(gameState) { for (let i = 0; i < this.path.length; ++i) { - let waypointPos = this.path[i]; - if (gameState.ai.HQ.territoryMap.getOwner(waypointPos) !== PlayerID) - { - // Set rally point at the border of our territory - // or where we need to change transportation method. - if (i !== 0) - this.rallyPoint = this.path[i-1]; - else - this.rallyPoint = this.path[0]; + if (gameState.ai.HQ.territoryMap.getOwner(this.path[i]) === PlayerID) + continue; - if (i >= 2) - this.path.splice(0, i-1); - break; + if (i == 0) + this.rallyPoint = this.path[0]; + else if (i > 1 && gameState.ai.HQ.isDangerousLocation(gameState, this.path[i-1], 20)) + { + this.rallyPoint = this.path[i-2]; + this.path.splice(0, i-2); } + else + { + this.rallyPoint = this.path[i-1]; + this.path.splice(0, i-1); + } + break; } }; @@ -928,46 +960,15 @@ m.AttackPlan.prototype.StartAttack = function(gameState) if (this.Config.debug > 1) API3.warn("start attack " + this.name + " with type " + this.type); - if (!this.targetPlayer) + // if our target was destroyed during preparation, choose a new one + if (!this.targetPlayer || !this.target || !gameState.getEntityById(this.target.id())) { - this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); - if (!this.targetPlayer) + if (!this.chooseTarget(gameState)) return false; - if (this.target && this.target.owner() !== this.targetPlayer) - this.target = undefined; - } - if (!this.target || !gameState.getEntityById(this.target.id())) // our target was destroyed during our preparation - { - if (!this.targetPos) // should not happen - return false; - var targetIndex = gameState.ai.accessibility.getAccessValue(this.targetPos); - var rallyIndex = gameState.ai.accessibility.getAccessValue(this.rallyPoint); - if (targetIndex === rallyIndex) - { - // If on the same index: if we are doing a raid, look for a better target, - // otherwise proceed with the previous target position - // and we will look for a better target there - if (this.type === "Raid") - { - this.target = this.getNearestTarget(gameState, this.rallyPoint); - if (!this.target) - return false; - this.targetPos = this.target.position(); - } - } - else - { - // Not on the same index, do not loose time to go to previous targetPos if nothing there - // so directly look for a new target right now - this.target = this.getNearestTarget(gameState, this.rallyPoint); - if (!this.target) - return false; - this.targetPos = this.target.position(); - } } // check we have a target and a path. - if (this.targetPos && this.path !== undefined) + if (this.targetPos && (this.overseas || this.path)) { // erase our queue. This will stop any leftover unit from being trained. gameState.ai.queueManager.removeQueue("plan_" + this.name); @@ -1203,7 +1204,23 @@ m.AttackPlan.prototype.update = function(gameState, events) // basic state of attacking. if (this.state === "") { - // First update the target if needed: + // First update the target position in case it's a unit (and check if it has garrisoned) + if (this.target && this.target.hasClass("Unit")) + { + this.targetPos = this.target.position(); + if (!this.targetPos) + { + let holder = m.getHolder(gameState, this.target); + if (holder && gameState.isPlayerEnemy(holder.owner())) + { + this.target = holder; + this.targetPos = holder.position(); + } + else + this.target = undefined; + } + } + // Then update the target if needed: if (!this.targetPlayer) { this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); @@ -1266,9 +1283,6 @@ m.AttackPlan.prototype.update = function(gameState, events) } this.targetPos = this.target.position(); } - // and regularly update the target position in case it's a unit. - if (this.target.hasClass("Unit")) - this.targetPos = this.target.position(); var time = gameState.ai.elapsedTime; for (var evt of events["Attacked"]) @@ -1678,12 +1692,6 @@ m.AttackPlan.prototype.removeUnit = function(ent, update) this.unitCollection.updateEnt(ent); }; -// Reset the path so that it can be recomputed for a new target -m.AttackPlan.prototype.resetPath = function() -{ - this.path = undefined; -}; - m.AttackPlan.prototype.checkEvents = function(gameState, events) { for (let evt of events["EntityRenamed"]) @@ -1706,6 +1714,28 @@ m.AttackPlan.prototype.checkEvents = function(gameState, events) this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); this.target = undefined; } + + if (!this.overseas || this.state !== "unexecuted") + return; + // let's check if an enemy has built a structure at our access + for (let evt of events["Create"]) + { + let ent = gameState.getEntityById(evt.entity); + if (!ent || !ent.position() || !ent.hasClass("Structure")) + continue; + if (!gameState.isPlayerEnemy(ent.owner())) + continue; + let access = gameState.ai.accessibility.getAccessValue(ent.position()); + for (let base of gameState.ai.HQ.baseManagers) + { + if (!base.anchor || !base.anchor.position()) + continue; + if (base.accessIndex !== access) + continue; + this.overseas = 0; + this.rallyPoint = base.anchor.position(); + } + } }; m.AttackPlan.prototype.waitingForTransport = function() diff --git a/binaries/data/mods/public/simulation/ai/petra/headquarters.js b/binaries/data/mods/public/simulation/ai/petra/headquarters.js index 0dc6355adb..bbfeb12dc9 100644 --- a/binaries/data/mods/public/simulation/ai/petra/headquarters.js +++ b/binaries/data/mods/public/simulation/ai/petra/headquarters.js @@ -2088,6 +2088,10 @@ m.HQ.prototype.Deserialize = function(gameState, data) this.baseManagers.push(newbase); } + this.navalManager = new m.NavalManager(this.Config); + this.navalManager.init(gameState, true); + this.navalManager.Deserialize(gameState, data.navalManager); + this.attackManager = new m.AttackManager(this.Config); this.attackManager.Deserialize(gameState, data.attackManager); this.attackManager.init(gameState); @@ -2100,10 +2104,6 @@ m.HQ.prototype.Deserialize = function(gameState, data) this.tradeManager.init(gameState); this.tradeManager.Deserialize(gameState, data.tradeManager); - this.navalManager = new m.NavalManager(this.Config); - this.navalManager.init(gameState, true); - this.navalManager.Deserialize(gameState, data.navalManager); - this.researchManager = new m.ResearchManager(this.Config); this.researchManager.Deserialize(data.researchManager); diff --git a/binaries/data/mods/public/simulation/ai/petra/navalManager.js b/binaries/data/mods/public/simulation/ai/petra/navalManager.js index 4a31db9681..f417864eac 100644 --- a/binaries/data/mods/public/simulation/ai/petra/navalManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/navalManager.js @@ -134,8 +134,8 @@ m.NavalManager.prototype.init = function(gameState, deserializing) if (!this.landingZones[land]) this.landingZones[land] = {}; if (!this.landingZones[land][naval]) - this.landingZones[land][naval] = []; - this.landingZones[land][naval].push(i); + this.landingZones[land][naval] = new Set(); + this.landingZones[land][naval].add(i); } // and keep only thoses with enough room around when possible for (let land in this.landingZones) @@ -145,27 +145,25 @@ m.NavalManager.prototype.init = function(gameState, deserializing) let landing = this.landingZones[land][sea]; let nbaround = {}; let nbcut = 0; - for (let i = 0; i < landing.length; i++) + for (let i of landing) { - let j = landing[i]; let nb = 0; - if (i > 0 && landing[i-1] == j-1) + if (landing.has(i-1)) nb++; - if (i < landing.length-1 && landing[i+1] == j+1) + if (landing.has(i+1)) nb++; - if (landing.indexOf(j+width) != -1) + if (landing.has(i+width)) nb++; - if (landing.indexOf(j-width) != -1) + if (landing.has(i-width)) nb++; - nbaround[j] = nb; + nbaround[i] = nb; nbcut = Math.max(nb, nbcut); } nbcut = Math.min(2, nbcut); - for (let i = 0; i < landing.length; i++) + for (let i of landing) { - let j = landing[i]; - if (nbaround[j] < nbcut) - landing.splice(i--, 1); + if (nbaround[i] < nbcut) + landing.delete(i); } } } 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 67f8845201..eb72b2f241 100644 --- a/binaries/data/mods/public/simulation/ai/petra/queueplan-building.js +++ b/binaries/data/mods/public/simulation/ai/petra/queueplan-building.js @@ -144,17 +144,17 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState) else return false; } - else if (template.hasClass("Tower") || template.hasClass("Fortress")) + else if (template.hasClass("Tower") || template.hasClass("Fortress") || template.hasClass("ArmyCamp")) { var pos = gameState.ai.HQ.findDefensiveLocation(gameState, template); if (pos) return { "x": pos[0], "z": pos[1], "angle": 3*Math.PI/4, "base": pos[2] }; - else if (!template.hasClass("Fortress") || gameState.civ() === "mace" || gameState.civ() === "maur" || + else if (template.hasClass("Tower") || gameState.civ() === "mace" || gameState.civ() === "maur" || gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_fortress"), true) + gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_army_camp"), true) > 0) - // if this fortress is our first siege unit builder, just try the standard placement as we want siege units return false; + // if this fortress is our first siege unit builder, just try the standard placement as we want siege units } else if (template.hasClass("Market")) // Docks (i.e. NavalMarket) are done before { diff --git a/binaries/data/mods/public/simulation/ai/petra/transportPlan.js b/binaries/data/mods/public/simulation/ai/petra/transportPlan.js index 1b0ea1095c..bb2c97e1a6 100644 --- a/binaries/data/mods/public/simulation/ai/petra/transportPlan.js +++ b/binaries/data/mods/public/simulation/ai/petra/transportPlan.js @@ -85,6 +85,8 @@ m.TransportPlan.prototype.init = function(gameState) this.units.registerUpdates(); this.ships.registerUpdates(); this.transportShips.registerUpdates(); + + this.boardingRange = 18*18; // TODO compute it from the ship clearance and garrison range }; // count available slots @@ -274,7 +276,7 @@ m.TransportPlan.prototype.onBoarding = function(gameState) else { let distShip = API3.SquareVectorDistance(self.boardingPos[shipId], ship.position()); - if (time - ship.getMetadata(PlayerID, "timeGarrison") > 8 && distShip > 225) + if (time - ship.getMetadata(PlayerID, "timeGarrison") > 8 && distShip > self.boardingRange) { if (!self.nTry[shipId]) self.nTry[shipId] = 1; @@ -296,7 +298,7 @@ m.TransportPlan.prototype.onBoarding = function(gameState) let newPos = ent.position(); if (oldPos[0] === newPos[0] && oldPos[1] === newPos[1]) { - if (distShip < 225) // looks like we are blocked ... try to go out of this trap + if (distShip < self.boardingRange) // looks like we are blocked ... try to go out of this trap { if (!self.nTry[ent.id()]) self.nTry[ent.id()] = 1; @@ -388,7 +390,7 @@ m.TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex, // require a small distance between all ships of the transport plan to avoid path finder problems // this is also used when the ship is blocked and we want to find a new boarding point for (let shipId in this.boardingPos) - if (this.boardingPos[shipId] !== undefined && API3.SquareVectorDistance(this.boardingPos[shipId], pos) < 225) + if (this.boardingPos[shipId] !== undefined && API3.SquareVectorDistance(this.boardingPos[shipId], pos) < this.boardingRange) dist += 1000000; if (dist > distmin) continue; @@ -549,7 +551,7 @@ m.TransportPlan.prototype.onSailing = function(gameState) continue; } - if (dist > 225) + if (dist > this.boardingRange) { if (!this.nTry[shipId]) this.nTry[shipId] = 1;