mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-06-21 06:04:22 +00:00
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:
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user