mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-14 15:10:39 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b81f672ad7 |
+25
-17
@@ -5484,10 +5484,13 @@ go to a different resolver and won't match).
|
||||
|
||||
### `GameTooltip:SetUnitAura(unit, index, [filter])`
|
||||
|
||||
Unified-aura method. The engine splits this into `SetUnitBuff` and
|
||||
`SetUnitDebuff`; we dispatch to the right one based on the `filter`
|
||||
string (`"HARMFUL"` → `SetUnitDebuff`, anything else → `SetUnitBuff`).
|
||||
`filter` defaults to helpful when omitted.
|
||||
Unified-aura method. `index` is in the same `HELPFUL` / `HARMFUL` index
|
||||
space as `C_UnitAuras.GetAuraDataByIndex`, so an index you got from
|
||||
`C_UnitAuras` always opens the matching tooltip. That includes a debuff
|
||||
that the server parked in a buff slot. The tooltip itself comes from the
|
||||
engine's own `SetUnitBuff` or `SetUnitDebuff`. `filter` defaults to
|
||||
helpful when omitted. Only its `HELPFUL` or `HARMFUL` token is read, so
|
||||
pass an index from the same plain list.
|
||||
|
||||
```lua
|
||||
GameTooltip:SetOwner(UIParent, "ANCHOR_CURSOR")
|
||||
@@ -5497,9 +5500,9 @@ GameTooltip:SetUnitAura("player", 1) -- defaults to HELPFUL
|
||||
GameTooltip:Show()
|
||||
```
|
||||
|
||||
Pure dispatcher — no engine changes; the underlying logic is whatever
|
||||
the engine's `SetUnitBuff` / `SetUnitDebuff` already does. Just lets you use
|
||||
the unified call shape (which most aura libraries backport from)
|
||||
The engine methods index their own slot ranges, which differ from the
|
||||
`C_UnitAuras` index space once a debuff sits in a buff slot. This method
|
||||
translates between the two, so you never need to
|
||||
without conditionally splitting on filter.
|
||||
|
||||
### `GameTooltip:SetSpellByID(spellID)`
|
||||
@@ -15269,10 +15272,14 @@ Backport of the `C_UnitAuras` namespace. Returns
|
||||
|
||||
Reads primarily off the unit's `m_objectFields` descriptor — same
|
||||
data source `UnitBuff` / `UnitDebuff` use. The descriptor has 48 aura
|
||||
slots total: 32 helpful (buffs) at indices 0..31, 16 harmful
|
||||
(debuffs) at indices 32..47. Functions in this namespace take a
|
||||
1-based Lua index that translates onto whichever range the filter
|
||||
selects.
|
||||
slots. The server seats buffs in slots 0 to 31 and debuffs in slots 32
|
||||
to 47 while it can. When the 16 debuff slots are full, it puts further
|
||||
debuffs into free buff slots. Each slot also carries a flag that records
|
||||
the aura's real polarity, and `HELPFUL` / `HARMFUL` select on that flag.
|
||||
So a debuff that sits in a buff slot is still a debuff here. The native
|
||||
`UnitBuff` / `UnitDebuff` split by slot range and report such a debuff as
|
||||
a buff. Indices are stable in the normal case: a `HARMFUL` walk visits
|
||||
slots 32 to 47 first, then any spilled debuffs in 0 to 31.
|
||||
|
||||
When a party/raid member has **no live unit object at all** (a
|
||||
different map, far out of range), there is no descriptor to read — but
|
||||
@@ -15289,8 +15296,8 @@ these functions surface them, exactly as the built-in `UnitBuff` /
|
||||
| `applications` | number | stack count (engine stores `stacks-1`, we display `+1`) |
|
||||
| `spellId` | number | spell ID from the descriptor's aura array |
|
||||
| `dispelName` | string | `"Magic"` / `"Curse"` / `"Disease"` / `"Poison"` (from `SpellDispelType.dbc`), or `""` if non-dispellable |
|
||||
| `isHelpful` | boolean | true for slot < 32 |
|
||||
| `isHarmful` | boolean | true for slot >= 32 |
|
||||
| `isHelpful` | boolean | true when the slot's polarity flag marks the aura positive |
|
||||
| `isHarmful` | boolean | true when the slot's polarity flag marks the aura negative. Read from the flag, not the slot number, so a debuff parked in a buff slot reads harmful |
|
||||
| `duration` | number | applied duration in seconds. When the aura's cast was observed (in the `Aura::Source` cache), this is the caster-modified duration — talent/glyph extensions like Improved Shadow Word: Pain included — so it stays consistent with `expirationTime` (`remaining ≤ duration`). On a cache miss it falls back to the base `Spell.dbc → SpellDuration.dbc` value with level scaling. Returns 0 for spells flagged "no duration" (passives, paladin auras, infinite buffs) |
|
||||
| `expirationTime` | number | for `unit == "player"`, read from the engine's player-buff table at `0x00BC6040` (same data `GetPlayerBuffTimeLeft` returns). For any other unit, taken from the `Aura::Source` cache (cast time + duration captured from `SMSG_SPELL_GO`; see below). `0` when neither source has it. `expirationTime - GetTime()` gives the true remaining time |
|
||||
| `sourceUnit` | string | unit token of the caster (`"player"`, `"raid7"`, `"nameplate1"`, …), resolved from the `Aura::Source` cache. `nil` if the cast wasn't observed or the caster maps to no current token |
|
||||
@@ -15307,8 +15314,8 @@ The optional `filter` string is a pipe-separated set of upper-case
|
||||
tokens (`"HELPFUL"`, `"HARMFUL"`,
|
||||
`"HELPFUL|PLAYER"`, etc.). Honored:
|
||||
|
||||
- **`HELPFUL`** (default) / **`HARMFUL`** — pick the buff or debuff range.
|
||||
In `GetUnitAuras`, supplying neither returns both ranges.
|
||||
- **`HELPFUL`** (default) / **`HARMFUL`** — pick buffs or debuffs by each
|
||||
aura's polarity flag. In `GetUnitAuras`, supplying neither returns both.
|
||||
- **`PLAYER`** — restrict to auras the local player cast, via the
|
||||
`Aura::Source` caster cache (`sourceGUID == ` player GUID). Combines with
|
||||
the range tokens (`"HARMFUL|PLAYER"` = your debuffs only). Because the
|
||||
@@ -15414,8 +15421,9 @@ end
|
||||
separated by `|` or spaces, each token optionally negated with a
|
||||
leading `!`. This build honors:
|
||||
|
||||
- `HELPFUL` / `HARMFUL` — buffs / debuffs. With neither token the
|
||||
query returns both (the indexed getter defaults to helpful).
|
||||
- `HELPFUL` / `HARMFUL` — buffs / debuffs, by each aura's polarity flag
|
||||
rather than its slot number. With neither token the query returns both
|
||||
(the indexed getter defaults to helpful).
|
||||
- `PLAYER` / `!PLAYER` — only auras the local player cast, or only
|
||||
auras the player did not cast. Caster data comes from casts this
|
||||
session, so an aura present before you saw it cast has no caster and
|
||||
|
||||
@@ -1497,6 +1497,16 @@ enum Offsets {
|
||||
UNIT_AURA_DEBUFF_COUNT = 16, // slot range 32..47 (harmful)
|
||||
UNIT_AURA_TOTAL = 48,
|
||||
UNIT_AURA_VISIBLE_MASK = 0x0E, // nibble mask used by the engine's visibility gate
|
||||
// Per-slot flag nibble bits as the server writes them (tortoise-wow
|
||||
// SpellAuraHolder::SetAuraFlag, verified): a positive aura gets HELPFUL
|
||||
// (+ CANCELABLE unless SPELL_ATTR_CANT_CANCEL), a negative aura gets
|
||||
// HARMFUL. This is the aura's real polarity. The slot range is only where
|
||||
// the server preferred to seat it — once the 16 debuff slots are full it
|
||||
// parks further debuffs in 0..31 (and sets UNIT_FLAG_AURAS_VISIBLE so the
|
||||
// client renders them), so a slot number alone misreads those.
|
||||
UNIT_AURA_FLAG_CANCELABLE = 0x01,
|
||||
UNIT_AURA_FLAG_HELPFUL = 0x04,
|
||||
UNIT_AURA_FLAG_HARMFUL = 0x08,
|
||||
|
||||
// PLAYER_FIELD_MOD_DAMAGE_DONE_POS/NEG — the player's per-school spell
|
||||
// damage bonus. Offsets from the engine's own UpdateField name/index
|
||||
@@ -1528,6 +1538,15 @@ enum Offsets {
|
||||
// `+0x2B0`. Both `Script_UnitBuff` and `Script_UnitDebuff` call
|
||||
// it to filter their aura iteration; we call it the same way.
|
||||
FUN_SPELL_IS_VISIBLE_AURA = 0x00519860,
|
||||
// The TOOLTIP variant of the gate: what `Script_GameTooltip_SetUnitBuff`
|
||||
// (0x00534AC0) / `SetUnitDebuff` (0x00534E30) apply per slot while
|
||||
// turning a Lua index into a slot. `__fastcall(spellRecord) -> bool`:
|
||||
// Attributes & 0x80 (HIDDEN_CLIENTSIDE) clear, AttributesEx & 0x10000000
|
||||
// (NO_AURA_ICON) clear, and no EffectApplyAuraName in {44, 45, 151}
|
||||
// (the tracking auras). Verified by decompile; differs from the UnitBuff
|
||||
// gate above, so an index into one space is not an index into the other
|
||||
// — `GameTooltip:SetUnitAura` counts with THIS one when it translates.
|
||||
FUN_GAMETOOLTIP_AURA_VISIBLE = 0x00534DF0,
|
||||
|
||||
// Player-only aura timing tables. Unlike UNIT_FIELD_AURA (which is
|
||||
// populated for any unit but has no timing info), these are the
|
||||
|
||||
+10
-12
@@ -21,7 +21,9 @@
|
||||
// tokens separated by `|` and/or whitespace, each optionally negated with a
|
||||
// leading `!`. Whole-token matching, so `RAID_PLAYER_DISPELLABLE` is not
|
||||
// mistaken for `PLAYER` and `!PLAYER` is a negation rather than a match.
|
||||
// Honored: `HELPFUL` / `HARMFUL` (descriptor slot ranges), `PLAYER` /
|
||||
// Honored: `HELPFUL` / `HARMFUL` (the aura's flag-nibble polarity — see
|
||||
// Data.h; NOT the slot range, which misreads debuffs spilled into buff
|
||||
// slots), `PLAYER` /
|
||||
// `!PLAYER` (caster == / != the local player, from the Aura::Source cache),
|
||||
// `DISPELLABLE` / `!DISPELLABLE` (dispel type is / isn't one a
|
||||
// dispel/purge/steal can remove — Spell.dbc Dispel ∈ Magic/Curse/Disease/
|
||||
@@ -472,19 +474,15 @@ int __fastcall Script_UnitAuraBySlot(void *L) {
|
||||
return PushAuraBySlot(L, Data::Emit::Positional);
|
||||
}
|
||||
|
||||
// Iterates one slot range and pushes AuraData tables into `outer` at
|
||||
// sequential keys starting from `nextKey`. Updates `nextKey` so a
|
||||
// follow-up call can append to the same outer table.
|
||||
// Iterates the auras of one polarity (in `SlotInFilterOrder`, the same order
|
||||
// the by-index getters use) and pushes AuraData tables into `outer` at
|
||||
// sequential keys starting from `nextKey`. Updates `nextKey` so a follow-up
|
||||
// call can append to the same outer table.
|
||||
void AppendRangeToArray(void *L, const uint8_t *unit, int outerIdx,
|
||||
Data::Filter filter, int &nextKey, Data::Match match) {
|
||||
const int start = (filter == Data::Filter::Harmful)
|
||||
? Offsets::UNIT_AURA_BUFF_COUNT
|
||||
: 0;
|
||||
const int end = (filter == Data::Filter::Harmful)
|
||||
? Offsets::UNIT_AURA_TOTAL
|
||||
: Offsets::UNIT_AURA_BUFF_COUNT;
|
||||
for (int slot = start; slot < end; ++slot) {
|
||||
if (!Data::SlotMatches(unit, slot, match))
|
||||
for (int i = 0; i < Offsets::UNIT_AURA_TOTAL; ++i) {
|
||||
const int slot = Data::SlotInFilterOrder(filter, i);
|
||||
if (!Data::SlotMatchesFilter(unit, slot, filter, match))
|
||||
continue;
|
||||
Game::Lua::PushNumber(L, static_cast<double>(nextKey++));
|
||||
Data::Push(L, unit, slot);
|
||||
|
||||
+75
-29
@@ -272,6 +272,49 @@ bool IsSlotPopulated(const uint8_t *unit, int slot) {
|
||||
return IsVisible(SpellRecord(spellID));
|
||||
}
|
||||
|
||||
bool IsSlotHarmful(const uint8_t *unit, int slot) {
|
||||
if (slot < 0 || slot >= Offsets::UNIT_AURA_TOTAL)
|
||||
return false;
|
||||
auto *desc = Descriptor(unit);
|
||||
if (desc == nullptr)
|
||||
return false;
|
||||
const uint8_t byte = *(desc + Offsets::OFF_UNIT_FIELD_AURAFLAGS + slot / 2);
|
||||
const uint8_t nibble = (byte >> ((slot & 1) * 4)) & 0xF;
|
||||
return (nibble & Offsets::UNIT_AURA_FLAG_HARMFUL) != 0;
|
||||
}
|
||||
|
||||
bool IsSlotTooltipVisible(const uint8_t *unit, int slot) {
|
||||
if (slot < 0 || slot >= Offsets::UNIT_AURA_TOTAL)
|
||||
return false;
|
||||
auto *desc = Descriptor(unit);
|
||||
if (desc == nullptr)
|
||||
return false;
|
||||
const int spellID = *reinterpret_cast<const int *>(
|
||||
desc + Offsets::OFF_UNIT_FIELD_AURA + slot * 4);
|
||||
if (spellID <= 0) // the engine's own `0 < id` test (signed)
|
||||
return false;
|
||||
const uint8_t byte = *(desc + Offsets::OFF_UNIT_FIELD_AURAFLAGS + slot / 2);
|
||||
const uint8_t nibble = (byte >> ((slot & 1) * 4)) & 0xF;
|
||||
if ((nibble & Offsets::UNIT_AURA_VISIBLE_MASK) == 0)
|
||||
return false;
|
||||
const uint8_t *rec = SpellRecord(static_cast<uint32_t>(spellID));
|
||||
if (rec == nullptr)
|
||||
return false;
|
||||
using Fn = char(__fastcall *)(const uint8_t *);
|
||||
auto fn = reinterpret_cast<Fn>(
|
||||
static_cast<uintptr_t>(Offsets::FUN_GAMETOOLTIP_AURA_VISIBLE));
|
||||
return fn(rec) != 0;
|
||||
}
|
||||
|
||||
int SlotInFilterOrder(Filter filter, int i) {
|
||||
// Harmful: 32..47 first, then 0..31 (where the server parks debuffs once
|
||||
// the 16 are full). Helpful: 0..31 then 32..47 — positive auras never
|
||||
// spill downward in practice, so that tail is a no-op safety net.
|
||||
if (filter == Filter::Harmful)
|
||||
return (i + Offsets::UNIT_AURA_BUFF_COUNT) % Offsets::UNIT_AURA_TOTAL;
|
||||
return i;
|
||||
}
|
||||
|
||||
bool IsPlayerCast(const uint8_t *unit, int slot) {
|
||||
const uint32_t spellID = ReadSpellID(unit, slot);
|
||||
if (spellID == 0)
|
||||
@@ -342,6 +385,15 @@ bool SlotMatches(const uint8_t *unit, int slot, const Match &match) {
|
||||
return IsSlotPopulated(unit, slot) && PopulatedSlotMatches(unit, slot, match);
|
||||
}
|
||||
|
||||
bool SlotMatchesFilter(const uint8_t *unit, int slot, Filter filter,
|
||||
const Match &match) {
|
||||
if (!IsSlotPopulated(unit, slot))
|
||||
return false;
|
||||
if (IsSlotHarmful(unit, slot) != (filter == Filter::Harmful))
|
||||
return false;
|
||||
return PopulatedSlotMatches(unit, slot, match);
|
||||
}
|
||||
|
||||
// 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).
|
||||
@@ -355,15 +407,10 @@ int FindNthSlot(const uint8_t *unit, int oneBasedIndex, Filter filter,
|
||||
Match match) {
|
||||
if (unit == nullptr || oneBasedIndex < 1)
|
||||
return -1;
|
||||
const int start = (filter == Filter::Harmful)
|
||||
? Offsets::UNIT_AURA_BUFF_COUNT
|
||||
: 0;
|
||||
const int end = (filter == Filter::Harmful)
|
||||
? Offsets::UNIT_AURA_TOTAL
|
||||
: Offsets::UNIT_AURA_BUFF_COUNT;
|
||||
int matches = 0;
|
||||
for (int slot = start; slot < end; ++slot) {
|
||||
if (!SlotMatches(unit, slot, match))
|
||||
for (int i = 0; i < Offsets::UNIT_AURA_TOTAL; ++i) {
|
||||
const int slot = SlotInFilterOrder(filter, i);
|
||||
if (!SlotMatchesFilter(unit, slot, filter, match))
|
||||
continue;
|
||||
if (++matches == oneBasedIndex)
|
||||
return slot;
|
||||
@@ -375,17 +422,15 @@ int FindSlotBySpellID(const uint8_t *unit, uint32_t spellID,
|
||||
const Filter *filter, Match match) {
|
||||
if (unit == nullptr || spellID == 0)
|
||||
return -1;
|
||||
const int start = (filter != nullptr && *filter == Filter::Harmful)
|
||||
? Offsets::UNIT_AURA_BUFF_COUNT
|
||||
: 0;
|
||||
const int end = (filter != nullptr && *filter == Filter::Helpful)
|
||||
? Offsets::UNIT_AURA_BUFF_COUNT
|
||||
: Offsets::UNIT_AURA_TOTAL;
|
||||
for (int slot = start; slot < end; ++slot) {
|
||||
for (int i = 0; i < Offsets::UNIT_AURA_TOTAL; ++i) {
|
||||
const int slot = (filter != nullptr) ? SlotInFilterOrder(*filter, i) : i;
|
||||
if (!IsSlotPopulated(unit, slot))
|
||||
continue;
|
||||
if (ReadSpellID(unit, slot) != spellID)
|
||||
continue;
|
||||
if (filter != nullptr &&
|
||||
IsSlotHarmful(unit, slot) != (*filter == Filter::Harmful))
|
||||
continue;
|
||||
if (!PopulatedSlotMatches(unit, slot, match))
|
||||
continue;
|
||||
return slot;
|
||||
@@ -397,18 +442,16 @@ int FindSlotBySpellName(const uint8_t *unit, const char *spellName,
|
||||
const Filter *filter, Match match) {
|
||||
if (unit == nullptr || spellName == nullptr || *spellName == '\0')
|
||||
return -1;
|
||||
const int start = (filter != nullptr && *filter == Filter::Harmful)
|
||||
? Offsets::UNIT_AURA_BUFF_COUNT
|
||||
: 0;
|
||||
const int end = (filter != nullptr && *filter == Filter::Helpful)
|
||||
? Offsets::UNIT_AURA_BUFF_COUNT
|
||||
: Offsets::UNIT_AURA_TOTAL;
|
||||
for (int slot = start; slot < end; ++slot) {
|
||||
for (int i = 0; i < Offsets::UNIT_AURA_TOTAL; ++i) {
|
||||
const int slot = (filter != nullptr) ? SlotInFilterOrder(*filter, i) : i;
|
||||
if (!IsSlotPopulated(unit, slot))
|
||||
continue;
|
||||
const char *name = LocalizedSpellName(SpellRecord(ReadSpellID(unit, slot)));
|
||||
if (name == nullptr || std::strcmp(name, spellName) != 0)
|
||||
continue;
|
||||
if (filter != nullptr &&
|
||||
IsSlotHarmful(unit, slot) != (*filter == Filter::Harmful))
|
||||
continue;
|
||||
if (!PopulatedSlotMatches(unit, slot, match))
|
||||
continue;
|
||||
return slot;
|
||||
@@ -670,7 +713,7 @@ static Attribution CachedAttribution(const Aura::Source::CachedAura &c) {
|
||||
|
||||
void Push(void *L, const uint8_t *unit, int slot, Emit emit) {
|
||||
const uint32_t spellID = ReadSpellID(unit, slot);
|
||||
const bool isHelpful = slot < Offsets::UNIT_AURA_BUFF_COUNT;
|
||||
const bool isHelpful = !IsSlotHarmful(unit, slot); // nibble, not slot range
|
||||
const int unitLevel = (unit != nullptr) ? PlayerLevel(unit) : 0;
|
||||
PushEnriched(L, UnitGuid(unit), spellID, isHelpful, ReadStacks(unit, slot),
|
||||
unitLevel, unit != nullptr && unit == LocalPlayer(), slot,
|
||||
@@ -828,8 +871,10 @@ void AppendCacheFallbacks(void *L, const uint8_t *unit, Filter filter,
|
||||
|
||||
namespace {
|
||||
|
||||
// Absolute slot range [start, end) for a filter, matching the descriptor
|
||||
// convention (helpful 0..31, harmful 32..47).
|
||||
// Absolute slot range [start, end) for a filter in the GROUP ARRAY (helpful
|
||||
// 0..31, harmful 32..47). The array carries spell IDs only — no flag nibble —
|
||||
// so the seating range is the only polarity the out-of-range path has. (The
|
||||
// descriptor path reads the nibble instead; see SlotInFilterOrder.)
|
||||
void GroupRange(Filter filter, int &start, int &end) {
|
||||
start = (filter == Filter::Harmful) ? Offsets::UNIT_AURA_BUFF_COUNT : 0;
|
||||
end = (filter == Filter::Harmful) ? Offsets::UNIT_AURA_TOTAL
|
||||
@@ -1125,11 +1170,10 @@ void AppendGroupAuras(void *L, uint64_t guid, Filter filter, Match match,
|
||||
int CollectSlots(const uint8_t *unit, uint64_t guid, Filter filter, Match match,
|
||||
int *out, int cap) {
|
||||
int n = 0;
|
||||
int start, end;
|
||||
GroupRange(filter, start, end); // same [start, end) for the descriptor
|
||||
if (unit != nullptr) {
|
||||
for (int slot = start; slot < end && n < cap; ++slot) {
|
||||
if (SlotMatches(unit, slot, match))
|
||||
for (int i = 0; i < Offsets::UNIT_AURA_TOTAL && n < cap; ++i) {
|
||||
const int slot = SlotInFilterOrder(filter, i);
|
||||
if (SlotMatchesFilter(unit, slot, filter, match))
|
||||
out[n++] = slot;
|
||||
}
|
||||
// The cache fallback yields only while the descriptor shows no visible
|
||||
@@ -1152,6 +1196,8 @@ int CollectSlots(const uint8_t *unit, uint64_t guid, Filter filter, Match match,
|
||||
if (arr == nullptr)
|
||||
return 0;
|
||||
Aura::Source::ObserveGroupAuras(guid, arr);
|
||||
int start, end;
|
||||
GroupRange(filter, start, end);
|
||||
for (int slot = start; slot < end && n < cap; ++slot) {
|
||||
if (GroupSlotMatches(guid, arr, slot, match))
|
||||
out[n++] = OPAQUE_GROUP + slot;
|
||||
|
||||
+43
-14
@@ -20,19 +20,27 @@
|
||||
// `unit + OFF_CGUNIT_OBJECT_FIELDS`; the layout is documented in
|
||||
// `Offsets.h` under `OFF_UNIT_FIELD_AURA*`.
|
||||
//
|
||||
// Slot indexing convention used here matches the engine's:
|
||||
// Slots are absolute 0..47. The server SEATS positive auras in 0..31 and
|
||||
// negative ones in 32..47, but that is a preference, not a rule: once the 16
|
||||
// debuff slots are full it parks further debuffs in 0..31 (and sets
|
||||
// UNIT_FLAG_AURAS_VISIBLE so the client renders them). An aura's real polarity
|
||||
// is the per-slot flag nibble (UNIT_AURA_FLAG_HELPFUL / _HARMFUL), which the
|
||||
// server writes for every visible aura — that is what `Filter` selects on
|
||||
// here. The engine's own UnitBuff/UnitDebuff split by slot range and so report
|
||||
// a spilled debuff as a buff; that is a 1.12 UI limitation we deliberately do
|
||||
// not mirror (retail's contract is the aura's actual polarity). A polarity
|
||||
// walk visits its home range first, then the other range, so indices stay
|
||||
// exactly what they were in the common no-spill case.
|
||||
//
|
||||
// slot 0..31 → buffs (helpful)
|
||||
// slot 32..47 → debuffs (harmful)
|
||||
//
|
||||
// Callers that take a 1-based Lua index into "buffs" or "debuffs"
|
||||
// translate to the absolute 0..47 slot before calling in.
|
||||
// Callers that take a 1-based Lua index into "buffs" or "debuffs" translate
|
||||
// to the absolute 0..47 slot before calling in.
|
||||
|
||||
namespace Aura::Data {
|
||||
|
||||
// Filter for slot iteration. The engine's `UnitBuff` / `UnitDebuff`
|
||||
// pick one direction; modern `C_UnitAuras.GetAuraDataByIndex` defaults
|
||||
// to helpful when no filter is specified.
|
||||
// Filter for slot iteration: the aura's polarity per its flag nibble (see the
|
||||
// file header). The engine's `UnitBuff` / `UnitDebuff` pick one direction;
|
||||
// modern `C_UnitAuras.GetAuraDataByIndex` defaults to helpful when no filter
|
||||
// is specified.
|
||||
enum class Filter { Helpful, Harmful };
|
||||
|
||||
// How a resolved aura is emitted onto the Lua stack by the single-aura push
|
||||
@@ -85,11 +93,32 @@ uint32_t ReadSpellID(const uint8_t *unit, int slot);
|
||||
// to decide whether to surface an aura through Lua.
|
||||
bool IsSlotPopulated(const uint8_t *unit, int slot);
|
||||
|
||||
// Finds the absolute slot of the `oneBasedIndex`-th populated aura
|
||||
// matching `filter`. Returns -1 if no such aura. Walks
|
||||
// 0..UNIT_AURA_BUFF_COUNT-1 for Helpful, UNIT_AURA_BUFF_COUNT..
|
||||
// UNIT_AURA_TOTAL-1 for Harmful. When `playerOnly` is true, only auras
|
||||
// the local player cast (per `IsPlayerCast`) are counted/returned.
|
||||
// The aura's polarity from its flag nibble: true iff the slot carries
|
||||
// UNIT_AURA_FLAG_HARMFUL. False for an empty slot or a null unit. This, not
|
||||
// the slot range, is what `Filter` selects on (see the file header).
|
||||
bool IsSlotHarmful(const uint8_t *unit, int slot);
|
||||
|
||||
// The engine's TOOLTIP visibility gate for a slot: occupied, visible nibble,
|
||||
// and the spell passes `FUN_GAMETOOLTIP_AURA_VISIBLE` — exactly what
|
||||
// `SetUnitBuff` / `SetUnitDebuff` count while turning an index into a slot.
|
||||
// Differs from `IsSlotPopulated` (the UnitBuff gate), so use THIS to compute
|
||||
// the index the engine's tooltip methods expect for a given slot.
|
||||
bool IsSlotTooltipVisible(const uint8_t *unit, int slot);
|
||||
|
||||
// The i-th slot (0..UNIT_AURA_TOTAL-1) in the order a `filter` walk visits the
|
||||
// descriptor: the polarity's home range first (helpful 0..31, harmful 32..47),
|
||||
// then the other range, where spilled debuffs live. Every enumeration that
|
||||
// hands out indices or slot lists uses this so they agree on order.
|
||||
int SlotInFilterOrder(Filter filter, int i);
|
||||
|
||||
// `SlotMatches` plus the polarity test: populated, of `filter`'s polarity, and
|
||||
// passing `match`.
|
||||
bool SlotMatchesFilter(const uint8_t *unit, int slot, Filter filter,
|
||||
const Match &match);
|
||||
|
||||
// Finds the absolute slot of the `oneBasedIndex`-th populated aura of
|
||||
// `filter`'s polarity that passes `match`. Returns -1 if no such aura. Walks
|
||||
// all 48 slots in `SlotInFilterOrder`.
|
||||
int FindNthSlot(const uint8_t *unit, int oneBasedIndex, Filter filter,
|
||||
Match match = {});
|
||||
|
||||
|
||||
+9
-5
@@ -1515,10 +1515,14 @@ const Net::PacketDispatch::AutoSubscribe _spellGoSub{&SpellGoSub};
|
||||
|
||||
// ---- Aura-application co-hooks (timing for proc / triggered auras) -------
|
||||
|
||||
// Classify by the absolute aura slot: 0..BUFF_COUNT-1 = buff (helpful),
|
||||
// BUFF_COUNT..TOTAL-1 = debuff (harmful).
|
||||
int8_t KindForSlot(int slot) {
|
||||
return slot >= Offsets::UNIT_AURA_BUFF_COUNT ? KIND_HARMFUL : KIND_HELPFUL;
|
||||
// Classify by the slot's flag nibble (UNIT_AURA_FLAG_HARMFUL), not its range:
|
||||
// the server parks debuffs in buff slots once the 16 are full, and the nibble
|
||||
// is what records their real polarity. The descriptor write pass has already
|
||||
// landed the flags when the application hooks fire (see Aura::Data).
|
||||
int8_t KindForSlot(const void *unit, int slot) {
|
||||
return Aura::Data::IsSlotHarmful(static_cast<const uint8_t *>(unit), slot)
|
||||
? KIND_HARMFUL
|
||||
: KIND_HELPFUL;
|
||||
}
|
||||
|
||||
// Stamp expiration for an aura that just landed/refreshed in `slot` on `unit`.
|
||||
@@ -1574,7 +1578,7 @@ void StampApplication(void *unit, uint32_t spellId, int slot) {
|
||||
}
|
||||
const uint32_t expirationMs = durationMs > 0 ? NowMs() + durationMs : 0;
|
||||
StoreFromApplication(unitGuid, spellId, caster, expirationMs, durationMs,
|
||||
slot, KindForSlot(slot));
|
||||
slot, KindForSlot(unit, slot));
|
||||
}
|
||||
|
||||
// Bump the player-stat-inputs signal when an aura change hits the LOCAL
|
||||
|
||||
+53
-10
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "aura/Data.h"
|
||||
#include "guid/Guid.h"
|
||||
#include "object/Resolve.h"
|
||||
|
||||
@@ -20,13 +21,38 @@
|
||||
|
||||
namespace Unit::Tooltip {
|
||||
|
||||
// The 1-based index the engine's `SetUnitBuff` / `SetUnitDebuff` would need to
|
||||
// land on `slot`: one plus the slots before it in ITS range (0..31 or 32..47)
|
||||
// that pass the engine's tooltip visibility gate — the exact walk
|
||||
// `FUN_00534AC0` / `FUN_00534E30` perform (verified by decompile).
|
||||
static int EngineIndexForSlot(const uint8_t *unit, int slot) {
|
||||
const int home =
|
||||
slot < Offsets::UNIT_AURA_BUFF_COUNT ? 0 : Offsets::UNIT_AURA_BUFF_COUNT;
|
||||
int index = 1;
|
||||
for (int s = home; s < slot; ++s) {
|
||||
if (Aura::Data::IsSlotTooltipVisible(unit, s))
|
||||
++index;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
// `GameTooltip:SetUnitAura(unit, index, [filter])` — modern unified-aura
|
||||
// method. 1.12 splits this into `SetUnitBuff` (slot 32) and
|
||||
// `SetUnitDebuff` (slot 33); we dispatch to the right one based on the
|
||||
// `filter` string ("HELPFUL" → SetUnitBuff, "HARMFUL" → SetUnitDebuff).
|
||||
// method. 1.12 has `SetUnitBuff` (slot 32) and `SetUnitDebuff` (slot 33), each
|
||||
// indexing its own slot RANGE (0..31 / 32..47) under the engine's tooltip
|
||||
// gate. `C_UnitAuras` indexes by the aura's polarity nibble instead (see
|
||||
// Aura::Data), so the two index spaces diverge once the server has parked a
|
||||
// debuff in a buff slot — and an index an addon got from `C_UnitAuras` must
|
||||
// still open the right tooltip. So: resolve (index, filter) to the absolute
|
||||
// slot in OUR space, then hand the engine the index THAT slot has within its
|
||||
// own range under ITS gate, choosing SetUnitBuff or SetUnitDebuff by where the
|
||||
// slot lives. The aura tooltip builder only ever receives spellId / level /
|
||||
// stacks, so a debuff in a buff slot renders identically through SetUnitBuff.
|
||||
//
|
||||
// `filter` defaults to "HELPFUL" when omitted, matching modern
|
||||
// behavior. Anything other than a HARMFUL match goes to SetUnitBuff.
|
||||
// `filter` defaults to "HELPFUL" when omitted, matching modern behavior; only
|
||||
// its HELPFUL/HARMFUL half is read (a PLAYER-filtered index is not an index
|
||||
// into the plain list — pass an index from the same plain list, as FrameXML
|
||||
// does). A unit with no live CGUnit (out-of-range groupmate) passes straight
|
||||
// through: the engine reads the group array by range there, exactly as we do.
|
||||
static int __fastcall Script_GameTooltipSetUnitAura(void *L) {
|
||||
// Args: self (table), unit (string), index (number), filter (optional string)
|
||||
if (Game::Lua::Type(L, 1) != Game::Lua::TYPE_TABLE) {
|
||||
@@ -51,14 +77,31 @@ static int __fastcall Script_GameTooltipSetUnitAura(void *L) {
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the filter arg before calling — the existing
|
||||
// SetUnitBuff/SetUnitDebuff take (self, unit, index) only.
|
||||
Game::Lua::SetTop(L, 3);
|
||||
int index = static_cast<int>(Game::Lua::ToNumber(L, 3));
|
||||
bool useDebuffMethod = isHarmful;
|
||||
const auto *unit = static_cast<const uint8_t *>(
|
||||
Game::ResolveUnitToken(Game::Lua::ToString(L, 2)));
|
||||
if (unit != nullptr) {
|
||||
const int slot = Aura::Data::FindNthSlot(
|
||||
unit, index,
|
||||
isHarmful ? Aura::Data::Filter::Harmful : Aura::Data::Filter::Helpful);
|
||||
if (slot >= 0) {
|
||||
index = EngineIndexForSlot(unit, slot);
|
||||
useDebuffMethod = slot >= Offsets::UNIT_AURA_BUFF_COUNT;
|
||||
}
|
||||
// slot < 0: nothing at that index in our space; the raw index goes
|
||||
// through and the engine's own walk shows nothing either.
|
||||
}
|
||||
|
||||
// Rebuild the args as (self, unit, engineIndex) — the engine methods take
|
||||
// exactly those three; the filter string is consumed here.
|
||||
Game::Lua::SetTop(L, 2);
|
||||
Game::Lua::PushNumber(L, static_cast<double>(index));
|
||||
|
||||
using Script_t = int(__fastcall *)(void *L);
|
||||
auto fn = reinterpret_cast<Script_t>(
|
||||
isHarmful ? Offsets::FUN_SCRIPT_GAMETOOLTIP_SET_UNIT_DEBUFF
|
||||
: Offsets::FUN_SCRIPT_GAMETOOLTIP_SET_UNIT_BUFF);
|
||||
useDebuffMethod ? Offsets::FUN_SCRIPT_GAMETOOLTIP_SET_UNIT_DEBUFF
|
||||
: Offsets::FUN_SCRIPT_GAMETOOLTIP_SET_UNIT_BUFF);
|
||||
return fn(L);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user