Vampify v0.4.0

This commit is contained in:
2026-08-30 15:37:26 +02:00
parent ad50ebe636
commit d3ad691770
14 changed files with 271 additions and 41 deletions
+40
View File
@@ -117,6 +117,42 @@ function G.onDamage(fn) table.insert(listeners, fn) end
local goSeen = false
local damageSeen = false
-- ---- "did the PLAYER cast this, or did it proc?" (per-hit export field `cast`) ----------------
--
-- The same SPELL_GO_SELF that latches goSeen above is also the only observable in the client that
-- separates a CAST spell from a PROCCED one: it precedes the damage events of a cast and never
-- fires for an item/enchant proc (the sentence right above G.deriveAoE says exactly this, and it
-- is why deriveAoE had to exist at all). Hypothesis H2 -- that Vampirism is gated on the TRIGGER
-- PATH ("was this triggered by an aura") rather than on "is this an item proc" -- turns on that
-- distinction for one and the same spell id, so the offline analysis needs it recorded per hit
-- rather than inferred from timing (see core/perhit.lua's formatHitLine comment).
--
-- Keyed by spell id, and deliberately WITHOUT the eviction sweep this project requires of
-- per-GUID tables: the key space here is the player's own castable spell ids, which is bounded by
-- the spellbook and does not grow with time or with mob spawns. That is the exact property the
-- eviction rule exists to protect against, and it does not apply.
local lastGo = {}
-- Pure, so the predicate is testable without firing events: "was there a cast of this spell id
-- within `window` seconds before `now`". Default window 1.5s -- comfortably longer than the gap
-- between SPELL_GO_SELF and the damage events of the same cast (the GO precedes them within the
-- same or the next frame), and short enough that the PREVIOUS cast of the same spell (global
-- cooldown 1.5s at the very fastest) cannot be mistaken for this one.
local CAST_WINDOW = 1.5
G.CAST_WINDOW = CAST_WINDOW
function G.noteCast(spellId, t)
if spellId then lastGo[spellId] = t end
end
function G.castSeenFor(spellId, now, window)
if not spellId then return false end
local t = lastGo[spellId]
if not t or not now then return false end
local dt = now - t
return dt >= 0 and dt <= (window or CAST_WINDOW)
end
-- ---- AoE derivation from distinct targets, cast or not (spec 4.2, extended 2026-08-22) --------
--
-- Originally written as the fallback for weapon/item procs (Force Reactive Disc's damage shield
@@ -248,6 +284,10 @@ if CreateFrame then
-- actual decision for every hit of this cast still runs through G.deriveAoE below, the
-- exact same distinct-target proof a no-cast proc already has to provide.
goSeen = true
-- arg2 is the cast's spell id (see the event's field list at the top of this file).
-- Noting it is what lets a later damage event of the SAME id say "this one was cast",
-- which is the `cast` field of the per-hit export -- see G.castSeenFor above.
G.noteCast(arg2, GetTime())
elseif event == "SPELL_DAMAGE_EVENT_SELF" then
local amount, tgt, spellId, caster = G.decodeSpell(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
-- Trust arg2 rather than the _SELF suffix, and never count a hit on ourselves.