fix(ai): guarding pets never target their owner or the owner's pets (#2665)
Supersedes #2657 (rebased onto #2662, which the original branch conflicted with). Credit to @MithrilHammer for the diagnosis, the fix and the regression tests; their three commits are kept as authored. Fixes #2656. ## Root cause When a guarding pet's combatant hides or becomes invalid, its AI calls `AcquireFocusMob` to find a new target. A controlled creature with no `ControlTarget` (Guard normally has none) fell out of `HandleControlled` and into the wild-creature scan, where `IsEnemy` treats any player as an enemy. With `FightMode.Closest`, the owner won. The fall-through dates to #2232. RunUO never let a controlled creature reach the wild scan. ## Changes - `AcquireFocusMob`: a controlled creature returns whatever `HandleControlled` decides. Guard uses `FindGuardTarget`, Attack uses its `ControlTarget`, and every other order acquires nothing. - `FindGuardTarget` skips guard allies: the master and creatures the master controls, including controlled summons. Uncontrolled summons (energy vortex, blade spirits) attack their caster, so a guard still defends against them, as in RunUO. - Friendly fire: `BaseAI.OnAggressiveAction` stands a guard down when an ally is the aggressor. `BaseCreature.Combatant` refuses an ally while guarding, because `Mobile.AggressiveAction` assigns the first aggressor as `Combatant` before any retaliation policy runs. The veto sits on `Combatant` rather than `CanBeHarmful`, so what a pet can harm doesn't depend on its current order. An explicit Attack order and bard provocation can still target an ally. ## Tests 16 regression cases in `GuardFriendlyFireTests` (skipped without client map data), including a real `WhiteWyrm` reproduction. `UOContent.Tests`: 1142 passed, 2 skipped, 0 failed on Windows.
This commit is contained in:
parent
28c38168bc
commit
6b910ff089
4 changed files with 287 additions and 5 deletions
|
|
@ -137,6 +137,12 @@ public abstract partial class BaseAI
|
|||
/// </summary>
|
||||
public virtual bool OnAggressiveAction(Mobile aggressor)
|
||||
{
|
||||
if (Mobile.RefusesGuardTarget(aggressor))
|
||||
{
|
||||
DebugSay("Friendly fire; I keep guarding.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only a creature somebody can command has been told anything; wild creatures rest on None.
|
||||
var toldToStandDown = Mobile.Controlled && Mobile.ControlMaster != null && Mobile.Commandable &&
|
||||
IsStandDownOrder(Mobile.ControlOrder);
|
||||
|
|
@ -856,7 +862,19 @@ public abstract partial class BaseAI
|
|||
return false;
|
||||
}
|
||||
|
||||
if (HandleBardProvoked() || HandleControlled() || HandleConstantFocus())
|
||||
if (HandleBardProvoked())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// A controlled creature must not fall through to wild target selection when
|
||||
// its order has no target. Guard in particular normally has no ControlTarget.
|
||||
if (Mobile.Controlled)
|
||||
{
|
||||
return HandleControlled();
|
||||
}
|
||||
|
||||
if (HandleConstantFocus())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -911,8 +929,17 @@ public abstract partial class BaseAI
|
|||
|
||||
private bool HandleControlled()
|
||||
{
|
||||
if (!Mobile.Controlled)
|
||||
if (Mobile.ControlOrder == OrderType.Guard)
|
||||
{
|
||||
Mobile.FocusMob = FindGuardTarget();
|
||||
return Mobile.FocusMob != null;
|
||||
}
|
||||
|
||||
// Follow and other non-combat orders may target the owner or a friend.
|
||||
// Only an explicit attack order supplies a combat target.
|
||||
if (Mobile.ControlOrder != OrderType.Attack)
|
||||
{
|
||||
Mobile.FocusMob = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue