fix: Fixes criminals having guards called on them (#2348)
## Summary Fixes three bugs in `GuardedRegion.CallGuards`: - **Operator precedence bug**: The condition `!m.Region.IsPartOf(this) && !m_GuardCandidates.ContainsKey(m)` was inverted from intent. Replaced with explicit split: dictionary members are targeted regardless of region; permanent candidates (reds/AlwaysMurderer) must be inside the region. - **Premature `break`**: Only the first guard candidate was ever processed per "guards" call. Removed the `break` so all valid candidates in range get a guard spawned. - **Misleading message for permanent reds**: "Guards can no longer be called on you." (502276) was sent to permanent reds, but guards can *always* be called on them. Now only sent to temporary criminals whose guard window is actually consumed. Also extracts `IsAlwaysGuardCandidate()` helper and simplifies `IsGuardCandidate()`. ## Edge cases verified (by analysis) - **Multiple players call guards on same red**: `BaseGuard.Spawn` dedup (scans 15 tiles for existing guard with same `Focus`) prevents duplicate guards. Message suppression eliminates spam. - **Red fights spawned guard → criminal → guards called again**: Same dedup prevents infinite guard spawns. Existing guard already has `Focus == red`. ## Test plan - [ ] Temporary criminal in guarded region → guard spawns, receives "Guards can no longer be called on you." - [ ] Permanent red (5+ kills) in guarded region → guard spawns, does NOT receive the message - [ ] Multiple players call "guards" on same red → only 1 guard spawns - [ ] Criminal outside region but in dictionary → still targeted by guards call from inside region - [ ] Red outside guarded region → NOT targeted by guards call (must be inside region)
This commit is contained in:
parent
4f9bc1d9f6
commit
1e4cdc4ab6
1 changed files with 23 additions and 16 deletions
|
|
@ -282,7 +282,11 @@ public class GuardedRegion : BaseRegion
|
|||
BaseGuard.Spawn(fakeCall, m);
|
||||
timer.Stop();
|
||||
m_GuardCandidates.Remove(m);
|
||||
m.SendLocalizedMessage(502276); // Guards can no longer be called on you.
|
||||
|
||||
if (!IsAlwaysGuardCandidate(m))
|
||||
{
|
||||
m.SendLocalizedMessage(502276); // Guards can no longer be called on you.
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -298,28 +302,38 @@ public class GuardedRegion : BaseRegion
|
|||
|
||||
foreach (var m in Map.GetMobilesInRange(p, 14))
|
||||
{
|
||||
if (!IsGuardCandidate(m) || !m.Region.IsPartOf(this) && !m_GuardCandidates.ContainsKey(m))
|
||||
if (!IsGuardCandidate(m))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_GuardCandidates.Remove(m, out var timer))
|
||||
// Dictionary members can be targeted regardless of region (guards reach outside).
|
||||
// Non-dictionary permanent candidates (reds) must be in the region.
|
||||
if (m_GuardCandidates.ContainsKey(m) || m.Region.IsPartOf(this) && IsAlwaysGuardCandidate(m))
|
||||
{
|
||||
timer.Stop();
|
||||
}
|
||||
if (m_GuardCandidates.Remove(m, out var timer))
|
||||
{
|
||||
timer.Stop();
|
||||
}
|
||||
|
||||
queue.Enqueue(m);
|
||||
break;
|
||||
queue.Enqueue(m);
|
||||
}
|
||||
}
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var m = queue.Dequeue();
|
||||
BaseGuard.Spawn(this, m);
|
||||
m.SendLocalizedMessage(502276); // Guards can no longer be called on you.
|
||||
|
||||
if (!IsAlwaysGuardCandidate(m))
|
||||
{
|
||||
m.SendLocalizedMessage(502276); // Guards can no longer be called on you.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsAlwaysGuardCandidate(Mobile m) => !AllowReds && m.Murderer;
|
||||
|
||||
public bool IsGuardCandidate(Mobile m)
|
||||
{
|
||||
if (m is BaseGuard || !m.Alive || m.AccessLevel > AccessLevel.Player || m.Blessed)
|
||||
|
|
@ -327,14 +341,7 @@ public class GuardedRegion : BaseRegion
|
|||
return false;
|
||||
}
|
||||
|
||||
var bc = m as BaseCreature;
|
||||
|
||||
if (bc?.IsInvulnerable == true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !AllowReds && m.Murderer || m.Criminal;
|
||||
return (m as BaseCreature)?.IsInvulnerable != true && (IsAlwaysGuardCandidate(m) || m.Criminal);
|
||||
}
|
||||
|
||||
private class GuardTimer : Timer
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue