Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/attackPlan.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/attackPlan.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/attackPlan.js @@ -1030,7 +1030,7 @@ if (blocker && blocker.hasClass("StoneWall")) { -/* if (this.hasSiegeUnits(gameState)) +/* if (this.hasSiegeUnits()) { */ this.isBlocked = true; return blocker; @@ -1216,18 +1216,18 @@ if (!attacker || !attacker.position() || !attacker.hasClass("Unit")) continue; let ourUnit = gameState.getEntityById(evt.target); - if (this.isSiegeUnit(gameState, ourUnit)) + if (m.isSiegeUnit(ourUnit)) { // if our siege units are attacked, we'll send some units to deal with enemies. let collec = this.unitCollection.filter(API3.Filters.not(API3.Filters.byClass("Siege"))).filterNearest(ourUnit.position(), 5); for (let ent of collec.values()) { - if (this.isSiegeUnit(gameState, ent)) // needed as mauryan elephants are not filtered out + if (m.isSiegeUnit(ent)) // needed as mauryan elephants are not filtered out continue; ent.attack(attacker.id(), m.allowCapture(gameState, ent, attacker)); ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); } // And if this attacker is a non-ranged siege unit and our unit also, attack it - if (this.isSiegeUnit(gameState, attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee")) + if (m.isSiegeUnit(attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee")) { ourUnit.attack(attacker.id(), m.allowCapture(gameState, ourUnit, attacker)); ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); @@ -1241,7 +1241,7 @@ // TODO check that the attacker is from behind the wall continue; } - else if (this.isSiegeUnit(gameState, attacker)) + else if (m.isSiegeUnit(attacker)) { // if our unit is attacked by a siege unit, we'll send some melee units to help it. let collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5); for (let ent of collec.values()) @@ -1289,7 +1289,7 @@ continue; if (!(targetId in unitTargets)) { - if (this.isSiegeUnit(gameState, target) || target.hasClass("Hero")) + if (m.isSiegeUnit(target) || target.hasClass("Hero")) unitTargets[targetId] = -8; else if (target.hasClass("Champion") || target.hasClass("Ship")) unitTargets[targetId] = -5; @@ -1347,7 +1347,7 @@ // update the order if needed let needsUpdate = false; let maybeUpdate = false; - let siegeUnit = this.isSiegeUnit(gameState, ent); + let siegeUnit = m.isSiegeUnit(ent); if (ent.isIdle()) needsUpdate = true; else if (siegeUnit && targetId) @@ -1658,7 +1658,7 @@ } } // Are we arrived at destination ? - if (attackedNB > 1 && (attackedUnitNB || this.hasSiegeUnits(gameState))) + if (attackedNB > 1 && (attackedUnitNB || this.hasSiegeUnits())) { if (gameState.ai.HQ.territoryMap.getOwner(this.position) === this.targetPlayer || attackedNB > 3) { @@ -1918,10 +1918,10 @@ return false; }; -m.AttackPlan.prototype.hasSiegeUnits = function(gameState) +m.AttackPlan.prototype.hasSiegeUnits = function() { for (let ent of this.unitCollection.values()) - if (this.isSiegeUnit(gameState, ent)) + if (m.isSiegeUnit(ent)) return true; return false; }; @@ -1940,11 +1940,6 @@ return false; }; -m.AttackPlan.prototype.isSiegeUnit = function(gameState, ent) -{ - return ent.hasClass("Siege") || (ent.hasClass("Elephant") && ent.hasClass("Champion")); -}; - m.AttackPlan.prototype.debugAttack = function() { API3.warn("---------- attack " + this.name); Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/defenseManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/defenseManager.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/defenseManager.js @@ -476,7 +476,7 @@ { // If enemies are in range of one of our defensive structures, garrison it for arrow multiplier if (attacker.position() && attacker.isGarrisonHolder() && attacker.getArrowMultiplier()) - this.garrisonRangedUnitsInside(gameState, attacker, {"attacker": target}); + this.garrisonUnitsInside(gameState, attacker, {"attacker": target}); } if (!gameState.isEntityOwn(target)) @@ -557,11 +557,11 @@ continue; if (target.isGarrisonHolder() && target.getArrowMultiplier()) - this.garrisonRangedUnitsInside(gameState, target, {"attacker": attacker}); + this.garrisonUnitsInside(gameState, target, {"attacker": attacker}); } }; -m.DefenseManager.prototype.garrisonRangedUnitsInside = function(gameState, target, data) +m.DefenseManager.prototype.garrisonUnitsInside = function(gameState, target, data) { let minGarrison = data.min ? data.min : target.garrisonMax(); let typeGarrison = data.type ? data.type : "protection"; @@ -583,12 +583,23 @@ let garrisonManager = gameState.ai.HQ.garrisonManager; let garrisonArrowClasses = target.getGarrisonArrowClasses(); let units = gameState.getOwnUnits().filter(ent => MatchesClassList(ent.classes(), garrisonArrowClasses)).filterNearest(target.position()); + let allowMelee = gameState.ai.HQ.garrisonManager.allowMelee(target); + if (allowMelee === undefined) + { + // Should be kept in sync with garrisonManager to avoid garrisoning-ungarrisoning some units + if (data.attacker) + allowMelee = data.attacker.hasClass("Structure") ? data.attacker.attackRange("Ranged") : !m.isSiegeUnit(data.attacker); + else + allowMelee = true; + } for (let ent of units.values()) { if (garrisonManager.numberOfGarrisonedUnits(target) >= minGarrison) break; if (!ent.position()) continue; + if (typeGarrison !== "decay" && !allowMelee && ent.attackTypes().indexOf("Melee") !== -1) + continue; if (ent.getMetadata(PlayerID, "transport") !== undefined) continue; let army = ent.getMetadata(PlayerID, "PartOfArmy") ? this.getArmy(ent.getMetadata(PlayerID, "PartOfArmy")) : undefined; Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/entityExtend.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/entityExtend.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/entityExtend.js @@ -1,6 +1,12 @@ var PETRA = function(m) { +/** returns true if this unit should be considered as a siege unit */ +m.isSiegeUnit = function(ent) +{ + return ent.hasClass("Siege") || (ent.hasClass("Elephant") && ent.hasClass("Champion")); +}; + /** returns some sort of DPS * health factor. If you specify a class, it'll use the modifiers against that class too. */ m.getMaxStrength = function(ent, againstClass) { Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/garrisonManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/garrisonManager.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/garrisonManager.js @@ -24,9 +24,9 @@ { if (id !== evt.entity) continue; - let list = this.holders.get(id); + let data = this.holders.get(id); this.holders.delete(id); - this.holders.set(evt.newentity, list); + this.holders.set(evt.newentity, data); } for (let id of this.decayingStructures.keys()) { @@ -43,8 +43,9 @@ } } - for (let [id, list] of this.holders.entries()) + for (let [id, data] of this.holders.entries()) { + let list = data.list; let holder = gameState.getEntityById(id); if (!holder || !gameState.isPlayerAlly(holder.owner())) { @@ -108,7 +109,7 @@ if (gameState.ai.elapsedTime - holder.getMetadata(PlayerID, "holderTimeUpdate") > 3) { let range = holder.attackRange("Ranged") ? holder.attackRange("Ranged").max : 80; - let enemiesAround = false; + let around = { "defenseStructure": false, "inertStructure": false, "meleeSiege": false, "rangeSiege": false, "unit": false }; for (let ent of gameState.getEnemyEntities().values()) { if (!ent.position()) @@ -118,20 +119,39 @@ let dist = API3.SquareVectorDistance(ent.position(), holder.position()); if (dist > range*range) continue; - enemiesAround = true; - break; + if (ent.hasClass("Structure")) + { + if (ent.attackRange("Ranged")) // TODO units on wall are not taken into account + around.defenseStructure = true; + else + around.inertStructure = true; + } + else if (m.isSiegeUnit(ent)) + { + if (ent.attackTypes().indexOf("Melee") !== -1) + around.meleeSiege = true; + else + around.rangeSiege = true; + } + else + { + around.unit = true; + break; + } } + // Keep defenseManager.garrisonUnitsInside in sync to avoid garrisoning-ungarrisoning some units + data.allowMelee = around.defenseStructure || around.unit; for (let entId of holder.garrisoned()) { let ent = gameState.getEntityById(entId); - if (ent.owner() === PlayerID && !this.keepGarrisoned(ent, holder, enemiesAround)) + if (ent.owner() === PlayerID && !this.keepGarrisoned(ent, holder, around)) holder.unload(entId); } for (let j = 0; j < list.length; ++j) { let ent = gameState.getEntityById(list[j]); - if (this.keepGarrisoned(ent, holder, enemiesAround)) + if (this.keepGarrisoned(ent, holder, around)) continue; if (ent.getMetadata(PlayerID, "garrisonHolder") == id) { @@ -155,7 +175,7 @@ if (!ent || ent.owner() !== PlayerID) this.decayingStructures.delete(id); else if (this.numberOfGarrisonedUnits(ent) < gmin) - gameState.ai.HQ.defenseManager.garrisonRangedUnitsInside(gameState, ent, {"min": gmin, "type": "decay"}); + gameState.ai.HQ.defenseManager.garrisonUnitsInside(gameState, ent, {"min": gmin, "type": "decay"}); } }; @@ -165,7 +185,15 @@ if (!this.holders.has(holder.id())) return holder.garrisoned().length; - return holder.garrisoned().length + this.holders.get(holder.id()).length; + return holder.garrisoned().length + this.holders.get(holder.id()).list.length; +}; + +m.GarrisonManager.prototype.allowMelee = function(holder) +{ + if (!this.holders.has(holder.id())) + return undefined; + + return this.holders.get(holder.id()).allowMelee; }; /** This is just a pre-garrison state, while the entity walk to the garrison holder */ @@ -175,7 +203,7 @@ return; this.registerHolder(gameState, holder); - this.holders.get(holder.id()).push(ent.id()); + this.holders.get(holder.id()).list.push(ent.id()); if (gameState.ai.Config.debug > 2) { @@ -218,13 +246,13 @@ let holderId = ent.getMetadata(PlayerID, "garrisonHolder"); if (!holderId || !this.holders.has(holderId)) return; - let list = this.holders.get(holderId); + let list = this.holders.get(holderId).list; let index = list.indexOf(ent.id()); if (index !== -1) list.splice(index, 1); }; -m.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, enemiesAround) +m.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around) { switch (ent.getMetadata(PlayerID, "garrisonType")) { @@ -233,14 +261,28 @@ case 'trade': // trader garrisoned in ship return true; case 'protection': // hurt unit for healing or infantry for defense - return ent.needsHeal() && holder.buffHeal() || - enemiesAround && (ent.hasClass("Support") || - MatchesClassList(ent.classes(), holder.getGarrisonArrowClasses()) || - MatchesClassList(ent.classes(), "Siege+!Melee")); + if (ent.needsHeal() && holder.buffHeal()) + return true; + if (MatchesClassList(ent.classes(), holder.getGarrisonArrowClasses())) + { + if (around.unit || around.defenseStructure) + return true; + else if (around.meleeSiege || around.rangeSiege) + return ent.attackTypes().indexOf("Melee") === -1; + else + return false; + } + if (ent.attackTypes() && ent.attackTypes().indexOf("Melee") !== -1) + return false; + if (around.unit) + return ent.hasClass("Support") || m.isSiegeUnit(ent); // only ranged siege here and below as melee siege already released above + if (m.isSiegeUnit(ent)) + return around.meleeSiege; + return false; case 'decay': return this.decayingStructures.has(holder.id()); case 'emergency': // f.e. hero in regicide mode - return enemiesAround; + return around.unit || around.defenseStructure || around.meleeSiege; default: if (ent.getMetadata(PlayerID, "onBoard") === "onBoard") // transport is not (yet ?) managed by garrisonManager return true; @@ -256,7 +298,7 @@ { if (this.holders.has(holder.id())) // already registered return; - this.holders.set(holder.id(), []); + this.holders.set(holder.id(), { "list": [], "allowMelee": true }); holder.setMetadata(PlayerID, "holderTimeUpdate", gameState.ai.elapsedTime); };