petra: adapt garrisoning to the attackers

Summary: When a structure with arrow was attacked, it was garrisoned
independently of the attacker, which was not the best answer when
atacked by a ram for example. This patch tries to adapt the garrisoning
to the attackers.

Reviewed By: Sandarac
Differential Revision: https://code.wildfiregames.com/D446
This was SVN commit r19547.
This commit is contained in:
mimo
2017-05-09 17:53:59 +00:00
parent 0517bbb83f
commit d1108516af
4 changed files with 91 additions and 37 deletions
@@ -1030,7 +1030,7 @@ m.AttackPlan.prototype.checkTargetObstruction = function(gameState, target, posi
if (blocker && blocker.hasClass("StoneWall"))
{
/* if (this.hasSiegeUnits(gameState))
/* if (this.hasSiegeUnits())
{ */
this.isBlocked = true;
return blocker;
@@ -1216,18 +1216,18 @@ m.AttackPlan.prototype.update = function(gameState, events)
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 @@ m.AttackPlan.prototype.update = function(gameState, events)
// 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 @@ m.AttackPlan.prototype.update = function(gameState, events)
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 @@ m.AttackPlan.prototype.update = function(gameState, events)
// 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 @@ m.AttackPlan.prototype.UpdateWalking = function(gameState, events)
}
}
// 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 @@ m.AttackPlan.prototype.waitingForTransport = function()
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 @@ m.AttackPlan.prototype.hasForceOrder = function(data, value)
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);
@@ -476,7 +476,7 @@ m.DefenseManager.prototype.checkEvents = function(gameState, events)
{
// 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 @@ m.DefenseManager.prototype.checkEvents = function(gameState, events)
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 @@ m.DefenseManager.prototype.garrisonRangedUnitsInside = function(gameState, targe
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;
@@ -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)
{
@@ -24,9 +24,9 @@ m.GarrisonManager.prototype.update = function(gameState, events)
{
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 @@ m.GarrisonManager.prototype.update = function(gameState, events)
}
}
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 @@ m.GarrisonManager.prototype.update = function(gameState, events)
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 @@ m.GarrisonManager.prototype.update = function(gameState, events)
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 @@ m.GarrisonManager.prototype.update = function(gameState, events)
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 @@ m.GarrisonManager.prototype.numberOfGarrisonedUnits = function(holder)
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 @@ m.GarrisonManager.prototype.garrison = function(gameState, ent, holder, type)
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 @@ m.GarrisonManager.prototype.cancelGarrison = function(ent)
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 @@ m.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, enemiesAround
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 @@ m.GarrisonManager.prototype.registerHolder = function(gameState, holder)
{
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);
};