The initial fire used FUN_00702690, whose exec-context mutation (DAT_00ceeac0)
is only valid from the engine's top-level frame-script dispatch; firing it from
inside the item builder (mid-Script_Set*, and in the wild under a stack of addon
Set* wrappers) raised through the outer wrappers. Two fixes make the fire safe
for normal handlers:
- Call the inner invoker FUN_00704d50(handler, tooltip) directly. It binds the
global `this` = tooltip and runs the handler under its own protected pcall
(0 args), self-contained and context-independent.
- Snapshot lua_gettop before the fire and lua_settop back after, so the still-
running outer C code sees exactly the stack it left. Without this the invoker
left the stack shifted and the outer code / other DLLs' hooks indexed a stale
slot -> ACCESS_VIOLATION.
Verified in-game: SetScript / HookScript / GetScript all work, `this` is the
tooltip (a1 is nil -- vanilla convention, not modern `self`), and no-op /
print / AddLine handlers fire cleanly on bag / equipped / merchant / auction.
Docs: correct the handler example to use `this` (not a `self` arg), and add the
caveat that re-entrant tooltip rebuilds from the handler (ShowCompareItem, Set*
on another tooltip) can crash in a hook-heavy client and must be deferred
(C_Timer.After(0, ...)) -- the event fires mid-build.
Backports the modern OnTooltipSetItem tooltip script to 1.12 as a real
frame script, settable via the standard SetScript / GetScript / HookScript
and firing whenever a tooltip's item is set (with the tooltip as self). Lets
addons annotate item tooltips by hooking one script instead of wrapping every
Set* method -- the destructive wrap-and-rebuild pattern (AtlasLoot's
AddSourceLine) that clobbers the tooltip's item linkage.
Fully native, two Game::HookAutoRegister co-hooks in Tooltip::SetItemEvent:
- Resolver (FUN_005295d0, the CGGameTooltip vtable script-name -> slot
vmethod): for "OnTooltipSetItem" (a name the engine doesn't know) return a
pointer to a C-side per-tooltip 8-byte {handler, context} cell. The tooltip
object (alloc size 0x460) has no free slot, and the resolver's only contract
is "return where the handler lives", so external storage behaves exactly
like the engine's own slots -- proven by OnTooltipCleared (+0x44c).
- Item builder (FUN_0052B650, the shared funnel for SetBagItem /
SetInventoryItem / SetHyperlink / SetMerchantItem / SetAuctionItem / ...):
after the build, fire the handler via FUN_00702690 (the no-arg runner the
clear uses for OnTooltipCleared). Recursion-guarded, and suppressed via
Tooltip::SetItemEvent::Suppressor during Tooltip::Compare's internal
equipped-item build so a handler doesn't run mid line-shift.
Item only for now; OnTooltipSetSpell / OnTooltipSetUnit follow the same
pattern with their own builders.