Skip to content

Commit bbefdb8

Browse files
committed
fix(aura): include the player's pet in the PLAYER filter
Blizzard defines the token as "Include only auras that were cast by the player, or by the player's pet or vehicle" (AuraUtil.AuraFilters.Player). There are no vehicles here, so the caster set is player + pet -- but all three comparisons tested `casterGuid == PlayerGuid()` and nothing else. A pet's cast carries the PET's guid in the Aura::Source cache, since that is who SMSG_SPELL_GO names, so "HARMFUL|PLAYER" silently dropped every aura the pet applied. `!PLAYER` had the mirror bug: it returned the pet's auras as though a third party cast them. Centralize on one IsPlayerOrPetCaster predicate rather than fixing each site -- IsPlayerCast, GroupIsPlayerCast and CachedMatches had already grown three separate copies of the comparison, which is how they would drift again. The pet guid comes from VAR_PET_GUID, a live engine global, so the predicate stays two compares with no object resolve; it runs per aura slot. A caster of 0 (cache miss) still matches nothing. Unrelated and untouched: the `isFromPlayerOrPlayerPet` aura field, which means "applied by ANY player or a player's pet" and is answered from the caster's HIGHGUID prefix. Verified in-game: a Hunter pet's Scorpid Poison now returns from C_UnitAuras.GetAuraDataByIndex("target", 1, "HARMFUL|PLAYER").
1 parent 56d2dbb commit bbefdb8

4 files changed

Lines changed: 50 additions & 29 deletions

File tree

docs/API.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18563,12 +18563,10 @@ tokens (`"HELPFUL"`, `"HARMFUL"`,
1856318563

1856418564
- **`HELPFUL`** (default) / **`HARMFUL`** — pick buffs or debuffs by each
1856518565
aura's polarity flag. In `GetUnitAuras`, supplying neither returns both.
18566-
- **`PLAYER`** — restrict to auras the local player cast, via the
18567-
`Aura::Source` caster cache (`sourceGUID == ` player GUID). Combines with
18568-
the range tokens (`"HARMFUL|PLAYER"` = your debuffs only). Because the
18569-
caster is best-effort, an aura whose cast we didn't observe is treated as
18570-
not-player-cast and excluded — so `PLAYER` can under-report auras that
18571-
predate login.
18566+
- **`PLAYER`** — restrict to auras that the player or the player's pet
18567+
cast. Combines with the range tokens (`"HARMFUL|PLAYER"` = your debuffs
18568+
only). The caster is known only for casts seen this session, so an aura
18569+
that predates login counts as not-player-cast and is excluded.
1857218570

1857318571
Other tokens (`RAID` / `CANCELABLE` / `INCLUDE_NAME_PLATE_ONLY`) are
1857418572
accepted but no-op — they need engine systems (raid-dispel relevance,
@@ -18671,10 +18669,10 @@ leading `!`. This build honors:
1867118669
- `HELPFUL` / `HARMFUL` — buffs / debuffs, by each aura's polarity flag
1867218670
rather than its slot number. With neither token the query returns both
1867318671
(the indexed getter defaults to helpful).
18674-
- `PLAYER` / `!PLAYER` — only auras the local player cast, or only
18675-
auras the player did not cast. Caster data comes from casts this
18676-
session, so an aura present before you saw it cast has no caster and
18677-
counts as not-player.
18672+
- `PLAYER` / `!PLAYER` — only auras the player or the player's pet cast,
18673+
or only the auras neither of them cast. Caster data comes from casts
18674+
this session, so an aura present before you saw it cast has no caster
18675+
and counts as not-player.
1867818676
- `DISPELLABLE` / `!DISPELLABLE` — only auras that can be dispelled,
1867918677
purged, or stolen (dispel type Magic, Curse, Disease, or Poison), or
1868018678
only auras that cannot. This is "can it be removed at all", not "can

src/aura/Api.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
// mistaken for `PLAYER` and `!PLAYER` is a negation rather than a match.
2424
// Honored: `HELPFUL` / `HARMFUL` (the aura's real polarity, read however this
2525
// server encodes it — see `Aura::Data::IsSlotHarmful`), `PLAYER` /
26-
// `!PLAYER` (caster == / != the local player, from the Aura::Source cache),
26+
// `!PLAYER` (caster is / isn't the local player or their pet, from the
27+
// Aura::Source cache),
2728
// `DISPELLABLE` / `!DISPELLABLE` (dispel type is / isn't one a
2829
// dispel/purge/steal can remove — Spell.dbc Dispel ∈ Magic/Curse/Disease/
2930
// Poison, matching the server's DISPEL_ALL_MASK), and `CROWD_CONTROL` /

src/aura/Data.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -361,16 +361,36 @@ int SlotInFilterOrder(Filter filter, int i) {
361361
return i;
362362
}
363363

364+
// The caster set the `PLAYER` filter selects: the player AND the player's
365+
// pet. Modern defines the token as "auras that were cast by the player, or
366+
// by the player's pet or vehicle" (`AuraUtil.AuraFilters.Player`) — there
367+
// are no vehicles here, so the pair is player + pet. A pet's cast carries
368+
// the PET's guid in the Aura::Source cache (that is who SMSG_SPELL_GO
369+
// names), so a plain player compare drops e.g. a Hunter's own Serpent
370+
// Sting off a `HARMFUL|PLAYER` query. The pet guid is a live engine global,
371+
// so this stays two compares with no object resolve — it runs per slot.
372+
// A caster of 0 is a cache miss, never a match (see CasterMode).
373+
bool IsPlayerOrPetCaster(uint64_t casterGuid) {
374+
if (casterGuid == 0)
375+
return false;
376+
if (casterGuid == Unit::Identity::PlayerGuid())
377+
return true;
378+
const uint64_t pet =
379+
Game::Read<uint64_t>(static_cast<uintptr_t>(Offsets::VAR_PET_GUID));
380+
return pet != 0 && casterGuid == pet;
381+
}
382+
364383
bool IsPlayerCast(const uint8_t *unit, int slot) {
365384
const uint32_t spellID = ReadSpellID(unit, slot);
366385
if (spellID == 0)
367386
return false;
368-
const uint64_t player = Unit::Identity::PlayerGuid();
369-
return player != 0 && Attribute(UnitGuid(unit), spellID, slot).caster == player;
387+
return IsPlayerOrPetCaster(
388+
Attribute(UnitGuid(unit), spellID, slot).caster);
370389
}
371390

372391
// Applies the PLAYER / !PLAYER caster restriction. `isPlayerCast` is the
373-
// per-aura "was this cast by the local player" answer (false on a cache miss).
392+
// per-aura "was this cast by the player or their pet" answer (false on a
393+
// cache miss).
374394
bool CasterMatches(CasterMode caster, bool isPlayerCast) {
375395
switch (caster) {
376396
case CasterMode::PlayerOnly: return isPlayerCast;
@@ -444,9 +464,8 @@ bool SlotMatchesFilter(const uint8_t *unit, int slot, Filter filter,
444464
// no slot to attribute by and the cache is consulted by (guid, spellID) alone.
445465
// A miss counts as "not the player" (same as IsPlayerCast).
446466
bool GroupIsPlayerCast(uint64_t guid, uint32_t spellID) {
447-
const uint64_t player = Unit::Identity::PlayerGuid();
448-
return player != 0 &&
449-
Attribute(guid, spellID, Aura::Source::SLOT_UNBOUND).caster == player;
467+
return IsPlayerOrPetCaster(
468+
Attribute(guid, spellID, Aura::Source::SLOT_UNBOUND).caster);
450469
}
451470

452471
int FindNthSlot(const uint8_t *unit, int oneBasedIndex, Filter filter,
@@ -847,8 +866,7 @@ bool FallbackEligible(const uint8_t *unit, const Aura::Source::CachedAura &c,
847866
// Applies `match` to a cache entry. The entry carries its caster, so the caster
848867
// test is a plain GUID compare — no scan, nothing to defer.
849868
bool CachedMatches(const Aura::Source::CachedAura &c, const Match &match) {
850-
return MatchesAura(match, c.casterGuid == Unit::Identity::PlayerGuid(),
851-
c.spellId);
869+
return MatchesAura(match, IsPlayerOrPetCaster(c.casterGuid), c.spellId);
852870
}
853871

854872
// The eligible cache-fallback entries for `unit` under `filter`, in `Enumerate`

src/aura/Data.h

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,13 @@ enum class Filter { Helpful, Harmful };
6363
enum class Emit { Table, Positional };
6464

6565
// Caster restriction from the `PLAYER` / `!PLAYER` aura filter tokens.
66-
// `Any` = no restriction; `PlayerOnly` = only auras the local player cast
67-
// (`PLAYER`); `NotPlayer` = only auras NOT cast by the local player
68-
// (`!PLAYER`). Caster attribution comes from the `Aura::Source` cache; a
69-
// cache miss counts as "not the player" (so `PlayerOnly` excludes it and
70-
// `NotPlayer` includes it — matching `IsPlayerCast`'s miss semantics).
66+
// `Any` = no restriction; `PlayerOnly` = only auras the local player OR the
67+
// player's pet cast (`PLAYER`); `NotPlayer` = the complement (`!PLAYER`).
68+
// The pet is part of the set by definition — modern documents the token as
69+
// "cast by the player, or by the player's pet or vehicle". Caster
70+
// attribution comes from the `Aura::Source` cache; a cache miss counts as
71+
// "not the player" (so `PlayerOnly` excludes it and `NotPlayer` includes
72+
// it — matching `IsPlayerCast`'s miss semantics).
7173
enum class CasterMode { Any, PlayerOnly, NotPlayer };
7274

7375
// Restriction from the `DISPELLABLE` / `!DISPELLABLE` aura filter tokens.
@@ -148,13 +150,15 @@ int FindSlotBySpellID(const uint8_t *unit, uint32_t spellID,
148150
int FindSlotBySpellName(const uint8_t *unit, const char *spellName,
149151
const Filter *filter, Match match = {});
150152

151-
// True iff the aura at `slot` was cast by the local player, per the
152-
// `Aura::Source` cache. False on a cache miss (caster unknown) — so a
153-
// PLAYER-filtered query excludes auras whose cast we didn't observe.
153+
// True iff the aura at `slot` was cast by the local player or by the
154+
// player's pet, per the `Aura::Source` cache. False on a cache miss (caster
155+
// unknown) — so a PLAYER-filtered query excludes auras whose cast we didn't
156+
// observe.
154157
bool IsPlayerCast(const uint8_t *unit, int slot);
155158

156-
// Applies a `CasterMode` to a per-aura "was cast by the local player" answer.
157-
// `Any` → always true; `PlayerOnly` → the answer; `NotPlayer` → its negation.
159+
// Applies a `CasterMode` to a per-aura "was cast by the player or their pet"
160+
// answer. `Any` → always true; `PlayerOnly` → the answer; `NotPlayer` → its
161+
// negation.
158162
bool CasterMatches(CasterMode caster, bool isPlayerCast);
159163

160164
// True iff the spell's dispel type is one a dispel/purge/steal can remove —

0 commit comments

Comments
 (0)