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.