Compare commits

...
3 Commits
Author SHA1 Message Date
Brues 89d86bffb7 addon-compat: unregister ShaguTweaks libpredict's backported TBC events
ShaguTweaks' libpredict sender frame (ShaguTweaksPredictionSender) registers
TBC-era events alongside its vanilla ones — COMBAT_LOG_EVENT_UNFILTERED and
UNIT_SPELLCAST_START/STOP/FAILED/INTERRUPTED/SENT (libpredict.lua:342-347). In
stock 1.12.1 those never fire, so registering them was a harmless no-op and only
the vanilla SPELLCAST_* path ran. ClassicAPI now backports those events, so
libpredict's TBC branches fire too and double-process cast/heal predictions.

Unregister the six TBC events on libpredict.sender once ShaguTweaks loads,
restoring vanilla-only behavior. Guarded on the addon existing and the
libpredict table being present.
2026-08-03 17:06:07 -05:00
Brues a0d8d6d723 fix(events): forward leading-nil event payloads to callbacks
EventRegistry callbacks (RegisterOnceFrameEventAndCallback, etc.) dropped any
payload whose first arg was nil — e.g. this client's reload PLAYER_ENTERING_WORLD
(arg1=nil, arg2=1) delivered nil to every callback parameter. Root cause is a
WoW Lua 5.0 quirk: unpacking a table whose hole lands right after a present
element into a `func(first, ...)` collapses the receiving vararg count to 0, so
the payload after `owner` was silently lost.

Fix both build-then-unpack sites in CallbackRegistry to pass the leading arg
explicitly and the payload as the unpacked tail:
- TriggerEvent Function branch: func(owner, unpack(arg, 1, arg.n))
- GenerateClosure: func(bound[1], unpack(rest, 1, restN))

Verified in-game: RegisterOnceFrameEventAndCallback("PLAYER_ENTERING_WORLD", cb)
now delivers arg2=1 to the callback's 2nd parameter on reload.
2026-08-03 14:23:12 -05:00
Brues e0e4fbcf84 docs: list the new frame-attribute + modern-script-arg APIs
The attribute methods and the modern-script-args toggle shipped on this
branch but weren't in the README index, and the toggle wasn't documented
at all.

- README: add frame:SetAttribute / SetAttributeNoHandler / ClearAttribute /
  GetAttribute, the OnAttributeChanged script, and SetModernScriptArgs /
  GetModernScriptArgs to the Frame row.
- docs/API.md: document SetModernScriptArgs(enable) / GetModernScriptArgs()
  (opt-in modern positional handler args) with a Frame TOC entry. The
  attribute methods and OnAttributeChanged were already covered.
2026-08-03 01:57:45 -05:00
4 changed files with 70 additions and 10 deletions
+15
View File
@@ -43,3 +43,18 @@ end
if C_AddOns.DoesAddOnExist("SpecialTalentUI") then
EventUtil.ContinueOnAddOnLoaded("SpecialTalentUI", CAPI_ApplyStandardColorGlobals)
end
-- ShaguTweaks libpredict register's TBC events that ClassicAPI backports.
if C_AddOns.DoesAddOnExist("ShaguTweaks") then
EventUtil.ContinueOnAddOnLoaded("ShaguTweaks", function()
local libp = ShaguTweaks.libpredict
if libp then
libp.sender:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
libp.sender:UnregisterEvent("UNIT_SPELLCAST_START")
libp.sender:UnregisterEvent("UNIT_SPELLCAST_STOP")
libp.sender:UnregisterEvent("UNIT_SPELLCAST_FAILED")
libp.sender:UnregisterEvent("UNIT_SPELLCAST_INTERRUPTED")
libp.sender:UnregisterEvent("UNIT_SPELLCAST_SENT")
end
end)
end
+19 -9
View File
@@ -26,10 +26,17 @@ local function GenerateClosure(func, ...)
for i = 1, boundN do bound[i] = arg[i] end
return function(...)
local callN = arg.n
local merged = {}
for i = 1, boundN do merged[i] = bound[i] end
for i = 1, callN do merged[boundN + i] = arg[i] end
return func(unpack(merged, 1, boundN + callN))
-- Pass the first bound arg (the owner, always bound[1] here) explicitly
-- and everything else as the unpacked tail. Merging bound + call into
-- one table and unpacking THAT collapses the vararg count on this Lua
-- 5.0 build when a hole lands right after a present element — same trap
-- fixed in TriggerEvent's Function branch. Keeping owner out of the
-- unpacked table keeps a leading-nil payload inside the vararg tail.
local rest = {}
local restN = 0
for i = 2, boundN do restN = restN + 1; rest[restN] = bound[i] end
for i = 1, callN do restN = restN + 1; rest[restN] = arg[i] end
return func(bound[1], unpack(rest, 1, restN))
end
end
@@ -145,11 +152,14 @@ function CallbackRegistryMixin:TriggerEvent(event, ...)
if funcs then
for owner, func in pairs(funcs) do
-- Stock 3.3.5 passes the owner as the first arg so callers can
-- distinguish multiple registrations of the same function.
local n = arg.n
local merged = { owner }
for i = 1, n do merged[i + 1] = arg[i] end
func(unpack(merged, 1, n + 1))
-- distinguish multiple registrations of the same function. Pass it
-- explicitly with the payload as the unpacked tail. Do NOT build a
-- { owner, ... } table and unpack THAT: on this Lua 5.0 build,
-- unpacking a table whose hole lands right after a present element
-- (owner) into a `func(first, ...)` collapses the vararg count to 0,
-- silently dropping a leading-nil event payload (verified in-game
-- with reload's PLAYER_ENTERING_WORLD, arg1=nil arg2=1).
func(owner, unpack(arg, 1, arg.n))
end
end
end
+1 -1
View File
@@ -35,7 +35,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
| [Expansion](docs/API.md#expansion) | `ClassicExpansionAtLeast`, `ClassicExpansionAtMost`, `GetClassicExpansionLevel` |
| [Faction](docs/API.md#faction) | `C_Reputation.GetFactionDataByIndex`, `C_Reputation.GetFactionStandings`, `C_Reputation.GetLastStandingChange`, `C_Reputation.GetWatchedFactionData`, `C_Reputation.SetWatchedFactionByID`, `GetFactionIDByIndex`, `GetFactionInfoByID`, `GetFactionParentID` |
| [Focus](docs/API.md#focus) | `ClearFocus`, `FocusUnit` |
| [Frame](docs/API.md#frame) | `region:SetPoint("point")` (one-arg form), `region:SetSize`, `region:GetSize`, `region:IsMouseOver`, `region:GetRect`, `region:IsDragging`, `frame:SetShown`, `frame:SetResizeBounds`, `frame:HookScript`, `frame:IsEventRegistered`, `frame:GetEffectiveAlpha` |
| [Frame](docs/API.md#frame) | `region:SetPoint("point")` (one-arg form), `region:SetSize`, `region:GetSize`, `region:IsMouseOver`, `region:GetRect`, `region:IsDragging`, `frame:SetShown`, `frame:SetResizeBounds`, `frame:HookScript`, `frame:IsEventRegistered`, `frame:GetEffectiveAlpha`, `frame:SetAttribute`, `frame:SetAttributeNoHandler`, `frame:ClearAttribute`, `frame:GetAttribute`, `OnAttributeChanged` (script), `SetModernScriptArgs`, `GetModernScriptArgs` |
| [FriendList](docs/API.md#friendlist) | `C_FriendList.IsWhoQueryPending`, `C_FriendList.SendWhoQueryByName` |
| [GameObject](docs/API.md#gameobject) | `C_GameObjectInfo.GetGameObjectInfoByID`, `C_GameObjectInfo.RequestLoadGameObjectByID`, `ClosestGameObjectPosition` |
| [Glue](docs/API.md#glue) | `C_Glue.IsOnGlueScreen` |
+35
View File
@@ -165,6 +165,7 @@ build instructions.
- [`frame:IsEventRegistered(event)`](#frameiseventregisteredevent)
- [`frame:GetEffectiveAlpha()`](#framegeteffectivealpha)
- [`frame:SetAttribute` / `SetAttributeNoHandler` / `ClearAttribute` / `GetAttribute` (+ unit-frame mouseover)](#framesetattributename-value--framesetattributenohandlername-value--frameclearattributename--framegetattribute)
- [`SetModernScriptArgs(enable)` / `GetModernScriptArgs()`](#setmodernscriptargsenable--getmodernscriptargs)
- [FriendList](#friendlist)
- [`C_FriendList.SendWhoQueryByName(name)`](#c_friendlistsendwhoquerybynamename)
@@ -3923,6 +3924,40 @@ per-frame handler slot for this one name (1.12 frames are never destroyed, so
the slot never goes stale). Recursion-guarded, so a handler may itself call
`SetAttribute`.
### `SetModernScriptArgs(enable)` / `GetModernScriptArgs()`
Global toggle (not a frame method) for **modern positional script arguments**.
Vanilla always invokes a frame-script handler with **zero** Lua arguments — the
handler reads `this` / `arg1..argN` / `event` as globals. With this enabled, every
handler additionally receives its values as real positional arguments, so modern
addon ports written as `function(self, delta) … end` work unmodified:
- most scripts: `(self, arg1..argN)` — e.g. `OnMouseWheel(self, delta)`,
`OnClick(self, button)`, `OnValueChanged(self, value)`, `OnUpdate(self, elapsed)`.
- `OnEvent`: `(self, event, arg1..argN)`.
It's purely **additive** — the `this` / `arg1` / `event` globals are still set, so
vanilla-style handlers keep working; a handler declaring no parameters just ignores
the extras. `SetModernScriptArgs(enable)` sets the state and returns it;
`GetModernScriptArgs()` returns the current state.
**Default OFF (opt-in).** It reimplements the tail of the engine's hottest Lua
path (the runner that fires for every `OnUpdate`, every frame), so it stays off
unless you ask for it; while off the dispatch path is exactly vanilla. Enable it
once at load if your addon relies on the modern handler signature.
```lua
SetModernScriptArgs(true) -- returns true
local f = CreateFrame("Frame")
f:EnableMouseWheel(true)
f:SetScript("OnMouseWheel", function(self, delta)
-- `self` and `delta` are bound; `this` / `arg1` still work too
end)
GetModernScriptArgs() -- true
```
## FriendList
### `C_FriendList.SendWhoQueryByName(name)`