Each block is a complete pattern for interface 16001. The folder name, the TOC file name, and the Title's identity should match.
This library is part of the WoW Forever AI Addon Guide. To understand the client design choices behind these snippets, see WoW Forever Addon Patterns. To check individual function signatures, refer to the Forever API Cheatsheet. For a finished, downloadable addon that wires journal, probe, compartment, and secret bars together, inspect the Forever Beacon reference addon.
Hello
The minimal viable addon that loads on Forever build 69913 without referencing secret names or legacy globals.
HelloForever.toc
## Interface: 16001
## Title: Hello Forever
## Notes: Prints once on login, without touching a secret name.
## Author: YourName
## Version: 0.1.0
## Category: Miscellaneous
HelloForever.lua
HelloForever.lua
local addonName = ...
local frame = CreateFrame("Frame")
frame:SetScript("OnEvent", function()
print("|cffd4a85a[" .. addonName .. "]|r Forever client accepted this addon.")
end)
frame:RegisterEvent("PLAYER_LOGIN")
SavedVariables
Declare the table in the TOC line: ## SavedVariables: BeaconNotesDB. The table name is a global because the client fills it. Touch it only in ADDON_LOADED, and merge default keys defensively to avoid clobbering existing user settings.
SavedVariables.lua
local addonName, ns = ...
local defaults = { scale = 1, locked = false }
local events = {}
function events:ADDON_LOADED(loaded)
if loaded ~= addonName then return end
BeaconNotesDB = BeaconNotesDB or {}
local db = BeaconNotesDB
for key, value in pairs(defaults) do
if db[key] == nil then db[key] = value end
end
ns.db = db
end
local frame = CreateFrame("Frame")
frame:SetScript("OnEvent", function(self, event, ...)
if events[event] then events[event](self, ...) end
end)
frame:RegisterEvent("ADDON_LOADED")
Never declare BeaconNotesDB = BeaconNotesDB or {} at file scope outside ADDON_LOADED — this is trap #8 in our Common AI Addon Pitfalls.
Secret-safe bar
On Forever, UnitHealth("player") and UnitHealthMax("player") return secret numbers in combat. You cannot calculate percentages in Lua or format them into strings. However, StatusBar widgets accept secret numbers directly.
HealthBar.lua
local bar = CreateFrame("StatusBar", nil, UIParent)
bar:SetSize(200, 14)
bar:SetPoint("CENTER", 0, -40)
bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
bar:SetStatusBarColor(0.55, 0.12, 0.12)
local function Paint()
-- Both values may be secret. The bar may have them. Lua may not.
bar:SetMinMaxValues(0, UnitHealthMax("player"))
bar:SetValue(UnitHealth("player"))
end
local frame = CreateFrame("Frame")
frame:SetScript("OnEvent", function(_, _, unit)
if unit == "player" or unit == nil then Paint() end
end)
if frame.RegisterUnitEvent then
frame:RegisterUnitEvent("UNIT_HEALTH", "player")
frame:RegisterUnitEvent("UNIT_MAXHEALTH", "player")
else
frame:RegisterEvent("UNIT_HEALTH")
end
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
Addon compartment
The modern minimap compartment works out of the box in Forever without third-party libraries like LibDataBroker. These three callback functions must be globals matching the TOC declarations exactly.
CompartmentStub.toc
## Interface: 16001
## Title: Compartment Stub
## Notes: Minimap compartment without LibDataBroker.
## AddonCompartmentFunc: CompartmentStub_OnClick
## AddonCompartmentFuncOnEnter: CompartmentStub_OnEnter
## AddonCompartmentFuncOnLeave: CompartmentStub_OnLeave
CompartmentStub.lua
CompartmentStub.lua
function CompartmentStub_OnClick()
print("|cffd4a85a[Stub]|r compartment click")
end
function CompartmentStub_OnEnter(_, owner)
GameTooltip:SetOwner(owner, "ANCHOR_LEFT")
GameTooltip:SetText("Compartment Stub", 1, 0.82, 0.45)
GameTooltip:Show()
end
function CompartmentStub_OnLeave()
GameTooltip:Hide()
end
Combat lockdown queue
During combat (InCombatLockdown() == true), mutating protected frames or attributes will cause execution failure. Queue state changes and flush them when PLAYER_REGEN_ENABLED fires.
Queue.lua
local pending = {}
local function Queue(fn)
if InCombatLockdown() then
pending[#pending + 1] = fn
else
fn()
end
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_REGEN_ENABLED")
frame:SetScript("OnEvent", function()
for i = 1, #pending do pending[i]() end
wipe(pending)
end)
-- Use Queue only for protected frames.
-- Your own unnamed frames may Show() during combat.
For ready-to-use prompts to generate clean Forever code using these templates, see our Ready-to-Use AI Prompts.