Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 69 additions & 64 deletions src/aura/Data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,23 +218,33 @@ int PlayerLevel(const uint8_t *player) {
desc + Offsets::OFF_UNIT_FIELD_LEVEL);
}

// The caster GUID attributed to `(guid, spellID)`: the observed caster from
// the Aura::Source cache, else — when we never saw the cast — the unit itself
// if the spell is a self-only-target buff (self-buffs can't be cross-cast).
// 0 when genuinely unknown. Single source of truth for both display
// (sourceGUID/sourceUnit) and the PLAYER caster filter, so a self-buff shows a
// source AND matches HELPFUL|PLAYER instead of the two paths disagreeing.
uint64_t EffectiveCaster(uint64_t guid, uint32_t spellID) {
// Caster + timing attributed to ONE aura instance — the aura `spellID` sitting
// in absolute descriptor `slot` on `guid` (`Aura::Source::SLOT_UNBOUND` when
// the caller has no slot: the out-of-range group array, the cache fallback).
// The slot is what tells two casters' copies of one spell apart; without it
// they are indistinguishable from a spell ID alone.
//
// The caster is the observed one from the Aura::Source cache, else — when we
// never saw the cast — the unit itself if the spell is a self-only-target buff
// (self-buffs can't be cross-cast), else 0. Single source of truth for both
// display (sourceGUID/sourceUnit) and the PLAYER caster filter, so a self-buff
// shows a source AND matches HELPFUL|PLAYER instead of the two paths
// disagreeing.
struct Attribution {
uint64_t caster;
uint32_t expirationMs;
uint32_t durationMs;
};

Attribution Attribute(uint64_t guid, uint32_t spellID, int slot) {
Attribution a = {0, 0, 0};
if (guid == 0 || spellID == 0)
return 0;
uint64_t c = 0;
uint32_t expMs = 0;
uint32_t durMs = 0;
if (Aura::Source::Get(guid, spellID, &c, &expMs, &durMs) && c != 0)
return c; // observed caster
if (Spell::IsSelfBuff::IsSelfBuff(spellID))
return guid; // self-only-target aura → cast by the unit itself
return 0;
return a;
Aura::Source::Get(guid, spellID, slot, &a.caster, &a.expirationMs,
&a.durationMs);
if (a.caster == 0 && Spell::IsSelfBuff::IsSelfBuff(spellID))
a.caster = guid; // self-only-target aura → cast by the unit itself
return a;
}

} // namespace
Expand Down Expand Up @@ -271,7 +281,7 @@ bool IsPlayerCast(const uint8_t *unit, int slot) {
if (spellID == 0)
return false;
const uint64_t player = Unit::Identity::PlayerGuid();
return player != 0 && EffectiveCaster(UnitGuid(unit), spellID) == player;
return player != 0 && Attribute(UnitGuid(unit), spellID, slot).caster == player;
}

// Applies the PLAYER / !PLAYER caster restriction. `isPlayerCast` is the
Expand Down Expand Up @@ -321,12 +331,13 @@ bool MatchesAura(const Match &match, bool isPlayerCast, uint32_t spellID) {
CcMatches(match.cc, spellID);
}

// Group-array analog of IsPlayerCast: the group aura carries no caster, so we
// consult the Aura::Source cache by (guid, spellID). A miss counts as "not the
// player" (same as IsPlayerCast).
// Group-array analog of IsPlayerCast: the member has no descriptor, so there is
// no slot to attribute by and the cache is consulted by (guid, spellID) alone.
// A miss counts as "not the player" (same as IsPlayerCast).
bool GroupIsPlayerCast(uint64_t guid, uint32_t spellID) {
const uint64_t player = Unit::Identity::PlayerGuid();
return player != 0 && EffectiveCaster(guid, spellID) == player;
return player != 0 &&
Attribute(guid, spellID, Aura::Source::SLOT_UNBOUND).caster == player;
}

int FindNthSlot(const uint8_t *unit, int oneBasedIndex, Filter filter,
Expand Down Expand Up @@ -521,10 +532,13 @@ static void BuildTable(void *L, uint32_t spellID, int applications,
// buff table (gated on `isPlayer`); for everyone else it, the caster, and the
// applied (caster-modified) duration come from the `Aura::Source` SMSG_SPELL_GO
// cache when it observed the cast — a miss leaves the modern-truthful defaults
// (expiration 0, no sourceUnit/GUID).
// (expiration 0, no sourceUnit/GUID). `slot` is the absolute descriptor slot
// the aura occupies, which is what attributes it to the right caster when two
// of them hold the same spell on the unit; `Aura::Source::SLOT_UNBOUND` for the
// paths with no descriptor to read one from.
static void PushEnriched(void *L, uint64_t guid, uint32_t spellID,
bool isHelpful, int applications, int unitLevel,
bool isPlayer) {
bool isPlayer, int slot) {
double duration = 0.0;
double expirationTime = 0.0;
uint64_t casterGuid = 0;
Expand All @@ -536,25 +550,20 @@ static void PushEnriched(void *L, uint64_t guid, uint32_t spellID,
expirationTime = PlayerBuffExpirationSeconds(entry);
}
if (spellID != 0 && guid != 0) {
uint64_t c = 0;
uint32_t expMs = 0;
uint32_t durMs = 0;
if (Aura::Source::Get(guid, spellID, &c, &expMs, &durMs)) {
// A cached expiration that already elapsed while the aura is still
// present (non-player casters get an underestimated base duration)
// is not meaningful — report unknown (0) rather than a negative
// remaining time.
if (expirationTime == 0.0 && expMs != 0 && !ExpirationElapsed(expMs))
expirationTime = static_cast<double>(expMs) * 0.001;
if (durMs != 0)
duration = static_cast<double>(durMs) * 0.001;
}
// Caster (sourceGUID/sourceUnit): observed caster, else the self-buff
// inference — same resolution the PLAYER filter uses, so a self-buff
// both shows a source and matches HELPFUL|PLAYER. Timing stays unknown
// when we never saw the cast; only the caster is inferred, and it's
// exact.
casterGuid = EffectiveCaster(guid, spellID);
// Caster (sourceGUID/sourceUnit) and timing resolved together, so a
// self-buff both shows a source and matches HELPFUL|PLAYER instead of
// the two paths disagreeing.
const Attribution a = Attribute(guid, spellID, slot);
// A cached expiration that already elapsed while the aura is still
// present (non-player casters get an underestimated base duration)
// is not meaningful — report unknown (0) rather than a negative
// remaining time.
if (expirationTime == 0.0 && a.expirationMs != 0 &&
!ExpirationElapsed(a.expirationMs))
expirationTime = static_cast<double>(a.expirationMs) * 0.001;
if (a.durationMs != 0)
duration = static_cast<double>(a.durationMs) * 0.001;
casterGuid = a.caster;
}
BuildTable(L, spellID, applications, isHelpful, duration, expirationTime,
casterGuid);
Expand All @@ -565,7 +574,7 @@ void Push(void *L, const uint8_t *unit, int slot) {
const bool isHelpful = slot < Offsets::UNIT_AURA_BUFF_COUNT;
const int unitLevel = (unit != nullptr) ? PlayerLevel(unit) : 0;
PushEnriched(L, UnitGuid(unit), spellID, isHelpful, ReadStacks(unit, slot),
unitLevel, unit != nullptr && unit == LocalPlayer());
unitLevel, unit != nullptr && unit == LocalPlayer(), slot);
}

namespace {
Expand All @@ -576,12 +585,13 @@ namespace {
// applied (caster-modified) ms when known, else the Spell.dbc base.
void PushFromCache(void *L, const uint8_t *unit,
const Aura::Source::CachedAura &c, bool isHelpful) {
// `PushEnriched` re-reads the same cache entry `c` came from (by
// guid+spellID) to fill caster / expiration / applied duration, so the
// result is identical to reading `c`'s fields directly. Stacks aren't in
// SMSG_SPELL_GO, so `applications` is 1.
// `PushEnriched` re-reads the same cache entry `c` came from — passing
// `c.slot` back is what makes it land on that exact entry rather than on
// another caster's copy of the spell. Stacks aren't in SMSG_SPELL_GO, so
// `applications` is 1.
const int unitLevel = (unit != nullptr) ? PlayerLevel(unit) : 0;
PushEnriched(L, UnitGuid(unit), c.spellId, isHelpful, 1, unitLevel, false);
PushEnriched(L, UnitGuid(unit), c.spellId, isHelpful, 1, unitLevel, false,
c.slot);
}

// Reconciles the `Aura::Source` cache against `unit`'s descriptor: when the
Expand All @@ -598,15 +608,9 @@ void ReconcileCache(const uint8_t *unit) {
if (unit == nullptr)
return;
uint32_t present[Offsets::UNIT_AURA_TOTAL];
int n = 0;
for (int slot = 0; slot < Offsets::UNIT_AURA_TOTAL; ++slot) {
const uint32_t id = ReadSpellID(unit, slot);
if (id != 0)
present[n++] = id;
}
if (n == 0)
return;
Aura::Source::EvictAbsent(UnitGuid(unit), present, n);
for (int slot = 0; slot < Offsets::UNIT_AURA_TOTAL; ++slot)
present[slot] = ReadSpellID(unit, slot);
Aura::Source::EvictAbsent(UnitGuid(unit), present);
}

// True if `unit`'s descriptor currently exposes any visible aura in either
Expand Down Expand Up @@ -813,7 +817,7 @@ bool PushNthGroupCacheFallback(void *L, uint64_t guid, const uint16_t *arr,
continue;
if (++seen == oneBasedIndex) {
PushEnriched(L, guid, buf[i].spellId, filter == Filter::Helpful, 1,
GroupMemberLevel(guid), false);
GroupMemberLevel(guid), false, buf[i].slot);
return true;
}
}
Expand All @@ -833,7 +837,7 @@ void AppendGroupCacheFallbacks(void *L, uint64_t guid, const uint16_t *arr,
continue;
Game::Lua::PushNumber(L, static_cast<double>(nextKey++));
PushEnriched(L, guid, buf[i].spellId, filter == Filter::Helpful, 1, level,
false);
false, buf[i].slot);
Game::Lua::SetTable(L, outerIdx);
}
}
Expand All @@ -856,7 +860,7 @@ bool PushGroupCacheFallbackMatch(void *L, uint64_t guid, const uint16_t *arr,
if (!GroupFallbackEligible(arr, buf[i], match))
continue;
PushEnriched(L, guid, buf[i].spellId, !harmful, 1, GroupMemberLevel(guid),
false);
false, buf[i].slot);
return true;
}
return false;
Expand All @@ -881,7 +885,7 @@ bool PushNthGroupAura(void *L, uint64_t guid, int oneBasedIndex, Filter filter,
if (++matches == oneBasedIndex) {
PushEnriched(L, guid, GroupSpellID(arr, slot),
filter == Filter::Helpful, 1, GroupMemberLevel(guid),
false);
false, Aura::Source::SLOT_UNBOUND);
return true;
}
}
Expand Down Expand Up @@ -912,7 +916,7 @@ bool PushGroupAuraBySpellID(void *L, uint64_t guid, uint32_t spellID,
if (!GroupSlotEligible(guid, arr, slot, match))
continue;
PushEnriched(L, guid, spellID, slot < Offsets::UNIT_AURA_BUFF_COUNT, 1,
GroupMemberLevel(guid), false);
GroupMemberLevel(guid), false, Aura::Source::SLOT_UNBOUND);
return true;
}
if (!GroupArrayHasVisibleAura(arr)) {
Expand Down Expand Up @@ -953,7 +957,7 @@ bool PushGroupAuraBySpellName(void *L, uint64_t guid, const char *spellName,
if (!GroupSlotEligible(guid, arr, slot, match))
continue;
PushEnriched(L, guid, id, slot < Offsets::UNIT_AURA_BUFF_COUNT, 1,
GroupMemberLevel(guid), false);
GroupMemberLevel(guid), false, Aura::Source::SLOT_UNBOUND);
return true;
}
if (!GroupArrayHasVisibleAura(arr)) {
Expand Down Expand Up @@ -986,7 +990,8 @@ void AppendGroupAuras(void *L, uint64_t guid, Filter filter, Match match,
continue;
Game::Lua::PushNumber(L, static_cast<double>(nextKey++));
PushEnriched(L, guid, GroupSpellID(arr, slot),
filter == Filter::Helpful, 1, level, false);
filter == Filter::Helpful, 1, level, false,
Aura::Source::SLOT_UNBOUND);
Game::Lua::SetTable(L, outerIdx);
}
// Empty array (server delta-resend gap): supplement from the cache.
Expand Down
Loading