Skip to content

Commit 492c70d

Browse files
committed
Use applied (caster-modified) duration for AuraData.duration
duration came from a base Spell.dbc/SpellDuration.dbc lookup while expirationTime came from the Aura::Source cache computed with the caster's duration modifiers. For a talented DoT (e.g. 2/2 Improved Shadow Word: Pain, 18s -> 24s) the two disagreed, so remaining (expirationTime - GetTime()) exceeded duration -- impossible for a single aura. Cache the applied durationMs alongside expirationMs in Aura::Source and have Push prefer it for the duration field, keeping the two consistent (remaining <= duration). Falls back to the base DBC value on a cache miss.
1 parent c68ec17 commit 492c70d

4 files changed

Lines changed: 30 additions & 16 deletions

File tree

‎docs/API.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9270,7 +9270,7 @@ selects.
92709270
| `dispelName` | string | `"Magic"` / `"Curse"` / `"Disease"` / `"Poison"` (from `SpellDispelType.dbc`), or `""` if non-dispellable |
92719271
| `isHelpful` | boolean | true for slot < 32 |
92729272
| `isHarmful` | boolean | true for slot >= 32 |
9273-
| `duration` | number | base applied duration in seconds, looked up via `Spell.dbc → SpellDuration.dbc` with level scaling. Talent / glyph duration extensions (Improved PW:F, etc.) aren't reflected here — those are baked into `expirationTime` on the caster's side. Returns 0 for spells flagged "no duration" (passives, paladin auras, infinite buffs) |
9273+
| `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) |
92749274
| `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 |
92759275
| `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 |
92769276
| `sourceGUID` | string | caster's `"0x…"` GUID string from the same cache. **ClassicAPI extension — not a retail `AuraData` field.** Set whenever a caster is known, including when `sourceUnit` is `nil` (caster left token range). Stable for the session, unlike the volatile nameplate token; doubles as a unit token under SuperWoW. `nil` on a cache miss |

‎src/aura/Data.cpp‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ bool IsPlayerCast(const uint8_t *unit, int slot) {
242242
return false;
243243
uint64_t casterGuid = 0;
244244
uint32_t expMs = 0;
245-
if (!Aura::Source::Get(UnitGuid(unit), spellID, &casterGuid, &expMs))
245+
uint32_t durMs = 0;
246+
if (!Aura::Source::Get(UnitGuid(unit), spellID, &casterGuid, &expMs, &durMs))
246247
return false;
247248
return casterGuid != 0 && casterGuid == Unit::Identity::PlayerGuid();
248249
}
@@ -426,9 +427,17 @@ void Push(void *L, const uint8_t *unit, int slot) {
426427
if (spellID != 0) {
427428
uint64_t casterGuid = 0;
428429
uint32_t expMs = 0;
429-
if (Aura::Source::Get(UnitGuid(unit), spellID, &casterGuid, &expMs)) {
430+
uint32_t durMs = 0;
431+
if (Aura::Source::Get(UnitGuid(unit), spellID, &casterGuid, &expMs,
432+
&durMs)) {
430433
if (expirationTime == 0.0 && expMs != 0)
431434
expirationTime = static_cast<double>(expMs) * 0.001;
435+
// Prefer the applied (caster-modified) duration so `duration`
436+
// stays consistent with `expirationTime` — otherwise a talented
437+
// DoT (e.g. Improved Shadow Word: Pain) shows remaining time
438+
// exceeding the base `duration`.
439+
if (durMs != 0)
440+
duration = static_cast<double>(durMs) * 0.001;
432441
if (casterGuid != 0) {
433442
// `sourceUnit` is a token (nil when the caster maps to no
434443
// current token); `sourceGUID` is the raw "0x..." GUID and

‎src/aura/Source.cpp‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ struct Entry {
104104
uint64_t casterGuid;
105105
uint32_t spellId;
106106
uint32_t expirationMs; // 0 = infinite / unknown duration
107+
uint32_t durationMs; // applied duration (incl. caster mods); 0 = none
107108
bool used;
108109
};
109110

@@ -112,7 +113,7 @@ Entry g_cache[kCacheSize];
112113
int g_writeCursor = 0;
113114

114115
void Store(uint64_t targetGuid, uint32_t spellId, uint64_t casterGuid,
115-
uint32_t expirationMs) {
116+
uint32_t expirationMs, uint32_t durationMs) {
116117
if (targetGuid == 0 || spellId == 0 || casterGuid == 0)
117118
return;
118119

@@ -121,20 +122,21 @@ void Store(uint64_t targetGuid, uint32_t spellId, uint64_t casterGuid,
121122
if (e.used && e.targetGuid == targetGuid && e.spellId == spellId) {
122123
e.casterGuid = casterGuid;
123124
e.expirationMs = expirationMs;
125+
e.durationMs = durationMs;
124126
return;
125127
}
126128
}
127129
// Take a free slot, else an expired one, else evict round-robin.
128130
const uint32_t now = NowMs();
129131
for (auto &e : g_cache) {
130132
if (!e.used || (e.expirationMs != 0 && now >= e.expirationMs)) {
131-
e = {targetGuid, casterGuid, spellId, expirationMs, true};
133+
e = {targetGuid, casterGuid, spellId, expirationMs, durationMs, true};
132134
return;
133135
}
134136
}
135137
Entry &slot = g_cache[g_writeCursor];
136138
g_writeCursor = (g_writeCursor + 1) % kCacheSize;
137-
slot = {targetGuid, casterGuid, spellId, expirationMs, true};
139+
slot = {targetGuid, casterGuid, spellId, expirationMs, durationMs, true};
138140
}
139141

140142
// Drop entries whose timed aura has elapsed so the table doesn't fill with
@@ -196,11 +198,11 @@ void __fastcall SpellGo_h(uint64_t *itemGUID, uint64_t *casterGUID,
196198

197199
if (numTargets == 0) {
198200
// No explicit hit list (self-cast with caster-implicit target).
199-
Store(caster, spellId, caster, expirationMs);
201+
Store(caster, spellId, caster, expirationMs, durationMs);
200202
return;
201203
}
202204
for (int i = 0; i < numTargets; ++i)
203-
Store(targets[i], spellId, caster, expirationMs);
205+
Store(targets[i], spellId, caster, expirationMs, durationMs);
204206
}
205207

206208
const Game::HookAutoRegister _hook{Offsets::FUN_SPELL_GO,
@@ -210,13 +212,14 @@ const Game::HookAutoRegister _hook{Offsets::FUN_SPELL_GO,
210212
} // namespace
211213

212214
bool Get(uint64_t unitGuid, uint32_t spellId, uint64_t *outCaster,
213-
uint32_t *outExpirationMs) {
215+
uint32_t *outExpirationMs, uint32_t *outDurationMs) {
214216
if (unitGuid == 0 || spellId == 0)
215217
return false;
216218
for (const auto &e : g_cache) {
217219
if (e.used && e.targetGuid == unitGuid && e.spellId == spellId) {
218220
*outCaster = e.casterGuid;
219221
*outExpirationMs = e.expirationMs;
222+
*outDurationMs = e.durationMs;
220223
return true;
221224
}
222225
}

‎src/aura/Source.h‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131

3232
namespace Aura::Source {
3333

34-
// Looks up the cached caster + expiration for the aura `spellId` currently
35-
// on the unit identified by `unitGuid`. Returns true and fills the out
36-
// params on a hit. `*outExpirationMs` is an absolute `GetTickCount`-epoch
37-
// timestamp (0 = unknown / infinite-duration aura); `*outCaster` is the
38-
// caster's 64-bit GUID (never 0 on a hit). Returns false on a miss or for
39-
// zero inputs.
34+
// Looks up the cached caster + timing for the aura `spellId` currently on
35+
// the unit identified by `unitGuid`. Returns true and fills the out params
36+
// on a hit. `*outExpirationMs` is an absolute `GetTickCount`-epoch timestamp
37+
// (0 = unknown / infinite-duration aura); `*outDurationMs` is the applied
38+
// duration including the caster's modifiers (talents etc.; 0 = none) — use
39+
// it for the `duration` field so it stays consistent with `expirationTime`;
40+
// `*outCaster` is the caster's 64-bit GUID (never 0 on a hit). Returns false
41+
// on a miss or for zero inputs.
4042
bool Get(uint64_t unitGuid, uint32_t spellId, uint64_t *outCaster,
41-
uint32_t *outExpirationMs);
43+
uint32_t *outExpirationMs, uint32_t *outDurationMs);
4244

4345
} // namespace Aura::Source

0 commit comments

Comments
 (0)