Шаблоны кода аддонов WoW Forever, готовые к вставке

Опубликовано Обновлено Данные беты по сборке 1.60.1.69913

Поделиться

Гайд по аддонам, страница 5 из 7

Скопируйте эти шаблоны для интерфейса 16001: Hello World, SavedVariables, панель, безопасная для секретных значений, раздел аддонов и блокировка в бою. Они основаны на документации Mainline из Warcraft Wiki, поэтому проверьте каждый шаблон на своей бета-сборке.

Спросите нашего потного ИИ-задрота о шаблонах кода аддонов

Hello World, SavedVariables, безопасные для секретов полосы, отсек аддона и другое

Всё, что охватывают наши гайды. Ответы берутся с этой страницы и из остальных гайдов, с реальными значениями на графиках и ссылками на разделы гайдов. Это ИИ, поэтому проверяйте источник, прежде чем цитировать.

Каждый блок — готовый шаблон для интерфейса 16001. Имя папки должно совпадать с именем TOC-файла.

Интерфейс TOC
16001
SavedVariables
Сообщается, что ошибку исправили
Addon compartment
Здесь не проверено
Блокировка в бою
Поставить в очередь до выхода из боя

Эта библиотека входит в гайд для разработчиков аддонов WoW Forever. О том, как устроен клиент и как это влияет на фрагменты кода, читайте в описании шаблонов аддонов WoW Forever. Сигнатуры отдельных функций можно проверить в шпаргалке по API Forever. Если нужен готовый аддон для скачивания, где вместе работают заметки, пробник, раздел аддонов и панель, безопасная для секретных значений, изучите эталонный аддон Forever Beacon.

Этот сайт не запускал ни один из этих шаблонов в клиенте Forever. Они следуют документации Mainline на Warcraft Wiki, указанным сообщениям о бете и сохраненному исходному коду клиента Blizzard из сборки 70338. Отдельная проверка ID проекта выполнялась в игре; это не означает, что эти примеры проверены в игре.

Дополнения от 13 октября ссылаются на сохраненное извлечение клиента 70338. Зеркала исходного кода по ссылкам могут показывать более позднюю сборку.

Функции с несколькими командами

Функция Lua может содержать несколько операций. Поместите каждую на отдельную строку между function и end; они выполняются по порядку при вызове функции. Этот пример выводит локальное сообщение и воспроизводит один звук интерфейса:

Ready.lua
local function AnnounceReady()
    print("[Forever Notes] Ready.")
    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
end

AnnounceReady()

Вам не нужны несколько определений функций или точки с запятой. Ранний return завершает функцию, а неперехваченная ошибка останавливает оставшиеся операции.Источник: Lua 5,1 Reference Manual, определения функций и блоки и обработка ошибок, проверено 13 окт. 2026. Операции Lua отличаются от команд макросов через косую черту, таких как /cast. Эти примеры выводят текст и воспроизводят звук; они не выполняют защищенные действия в бою.

В чате аналогичная диагностическая команда — /run print("Ready."); PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON). Храните повторно используемый код в файле Lua, указанном в TOC. Шпаргалка по ID звуков перечисляет используемые здесь константы.

Команда сообщения и псевдонимы команд через косую черту

Для сообщения, которое видно только вам, используйте print("Hello.") или DEFAULT_CHAT_FRAME:AddMessage("Hello.", 1, 0.82, 0). В чате введите /run print("Hello."). Они выводят текст локально, не отправляя сообщения в группу или личные сообщения.

Чтобы задать одну функцию с несколькими командами через косую черту, назначьте одному ключу SlashCmdList нумерованные псевдонимы. Этот полный пример из двух файлов заставляет и /wfgmessage Hello, и /wfgmsg Hello локально выводить переданный текст:

WFGMessageExample.toc
## Interface: 16001
## Title: WFG Message Example
## Notes: Two slash commands for one local message handler.
## Version: 0.1.0

WFGMessageExample.lua
WFGMessageExample.lua
local function ShowLocalMessage(text)
    if not text:find("%S") then
        print("Usage: /wfgmessage Hello")
        return
    end
    DEFAULT_CHAT_FRAME:AddMessage("[WFG Message] " .. text, 1, 0.82, 0)
end

SLASH_WFGMESSAGEEXAMPLE1 = "/wfgmessage"
SLASH_WFGMESSAGEEXAMPLE2 = "/wfgmsg"
SlashCmdList["WFGMESSAGEEXAMPLE"] = ShowLocalMessage

Используйте имя папки WFGMessageExample, затем перезапустите клиент после ее добавления. Регистрационный ключ в верхнем регистре должен совпадать в обеих глобальных переменных SLASH_. Регистрируйте псевдонимы при загрузке файла, а не при каждом запуске обработчика. Реестр команд через косую черту от Blizzard использует этот же подход с нумерованными глобальными переменными.Источник: реестр команд через косую черту от Blizzard, сохраненная сборка клиента 70338, изучено 13 окт. 2026.

Если под «командой сообщения» подразумевается отправка текста другим людям в чат, это отдельная операция: C_ChatInfo.SendChatMessage("Hello.", "PARTY") отправляет текст в чат группы, когда клиент это разрешает. Вызывайте ее только в ответ на намеренное действие игрока и проверяйте текущие ограничения чата. Сгенерированная документация помечает этот API как HasRestrictions; эта страница не утверждает, что произвольная автоматическая отправка разрешена.Источник: документация ChatInfo от Blizzard, SendChatMessage, сохраненная сборка клиента 70338, изучено 13 окт. 2026.

Покажите всплывающее сообщение

Зарегистрируйте отдельную запись своего аддона в StaticPopupDialogs, затем вызовите StaticPopup_Show с этим ключом. Этот пример показывает фиксированное информационное сообщение с кнопкой «ОК» и закрытием по Escape. Поместите его в файл Lua, загружаемый вашим TOC:

PopupMessage.lua
StaticPopupDialogs["WFG_EXAMPLE_READY_MESSAGE"] = {
    text = "Forever Notes is ready.",
    button1 = OKAY,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
}

SLASH_WFGPOPUPEXAMPLE1 = "/wfgpopup"
SlashCmdList["WFGPOPUPEXAMPLE"] = function()
    StaticPopup_Show("WFG_EXAMPLE_READY_MESSAGE")
end

После загрузки аддона введите /wfgpopup. Сообщение остается до закрытия благодаря timeout = 0. Используйте собственный ключ, сохраняйте определения диалогов Blizzard и показывайте окно намеренно, а не повторяйте вызов из цикла обновления. Этот пример не запускали в клиенте Forever.Источник: реализация StaticPopup от Blizzard, StaticPopup_Show, timeout, whileDead и hideOnEscape, сохраненная сборка клиента 70338, изучено 13 окт. 2026.

Привет

Самый простой аддон: он один раз выводит сообщение при входе и не обращается к именам, которые могут быть секретными.

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

Объявите таблицу в TOC: ## SavedVariables: BeaconNotesDB. Это глобальная переменная: клиент заполняет ее после выполнения ваших файлов. Обращайтесь к ней только в ADDON_LOADED и объединяйте значения по умолчанию с таблицей, чтобы не перезаписать настройки игрока.

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")

Не задавайте BeaconNotesDB = BeaconNotesDB or {} на уровне файла. Это ловушка 08 из списка распространенных ошибок ИИ при создании аддонов.

Панель, безопасная для секретных значений

На сборке 69913 значение UnitHealth("player") оказалось секретным числом. Согласно правилам Midnight из Warcraft Wiki, секретным может быть и UnitHealthMax. В Lua нельзя вычислить из них процент, но StatusBar принимает их напрямую.

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 do math on them.
    bar:SetMinMaxValues(0, UnitHealthMax("player"))
    bar:SetValue(UnitHealth("player"))
end

local frame = CreateFrame("Frame")
frame:SetScript("OnEvent", Paint)
frame:RegisterUnitEvent("UNIT_HEALTH", "player")
frame:RegisterUnitEvent("UNIT_MAXHEALTH", "player")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")

Раздел аддонов

Для раздела аддонов на миникарте не нужна LibDataBroker: в TOC указываются глобальные функции, которые вызывает клиент. Warcraft Wiki описывает эти поля для Mainline. На этом сайте их не проверяли в клиенте Forever, поэтому проверьте, появляется ли запись.

CompartmentStub.toc
## Interface: 16001
## Title: Compartment Stub
## Notes: Minimap compartment without LibDataBroker.
## IconTexture: Interface\Icons\INV_Misc_Note_01
## 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

Очередь на время блокировки в бою

В бою, пока InCombatLockdown() имеет значение true, аддоны не могут показывать, скрывать, перемещать защищенные фреймы или менять их атрибуты. Поставьте эти изменения в очередь и выполните, когда сработает PLAYER_REGEN_ENABLED.

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 unprotected frames may Show() during combat.

Промпты, которые генерируют код в таком формате, собраны в готовых промптах для ИИ.

Пишите для Forever; сравнивайте именованную константу ID проекта

WOW_PROJECT_IDимел значение 18 в сохраненной проверке в игре на сборке беты Forever 1.60.1.70338 от 12 октября 2026. Исходный код Blizzard называет это значение WOW_PROJECT_CAMELOT. WOW_PROJECT_MAINLINE имел значение 1, а WOW_PROJECT_CLASSIC — 2, поэтому обычная ветка if WOW_PROJECT_ID == WOW_PROJECT_MAINLINE была ложной в этой сборке. Прежнее сообщение сообщества о 1 относится к сборке 69913; сохраняйте ее номер, а не считайте это сегодняшней константой.Источник: константы проекта Camelot от Blizzard, сохраненные файлы клиента 70338, изучено 13 окт. 2026. Константы измерены в клиенте беты 1.60.1.70338 12 окт. 2026.

Аддону, который работает только в Forever, нечего ветвить. Нужна версия под Forever, и об этом стоит сказать в комментарии, чтобы следующий читатель не искал ветку.

Detect.lua
-- Build 70338 recorded WOW_PROJECT_ID 18, named WOW_PROJECT_CAMELOT.
-- WOW_PROJECT_MAINLINE was 1 and WOW_PROJECT_CLASSIC was 2, so an
-- "is this Retail" or "is this Classic" branch was false; retest.
-- A Forever-only add-on writes for Forever directly.
local FOREVER_PROJECT_ID = WOW_PROJECT_CAMELOT

В прежнем примере вики ветка названа "camelot/forever", потому что Forever — тип игры внутри семейства Mainline. Общая архитектура не определяет ID проекта. Заявление Blizzard в WoW UI Discord, которое цитирует Wowhead, называет Forever и Midnight "two game types within the Mainline code family." («два типа игры в семействе кода Mainline»). В нем тип Forever также назван "Camelot (to be renamed before launch)" («Camelot, название будет изменено до запуска»), а о Forever сказано, что она разделяет "the vast majority of APIs available in 12.1.5," («подавляющее большинство доступных API»), поэтому страницы вики Mainline ниже вообще применимы.Источник: WoW: Forever Will Have Addon Changes from Midnight, проверено 28 сент. 2026 года.

Для общих аддонов, работающих с несколькими клиентами, сравните WOW_PROJECT_ID с WOW_PROJECT_CAMELOT, если эта константа существует, затем проверьте API нужной функции. Аддон только для Forever может работать непосредственно с Forever. Вы также можете разделять файлы через TOC. Для текущих игровых правил, флага Hardcore или варианта игры Classic/Modern используйте справочник GameRules; ID клиента не отвечает на эти вопросы.

<details><summary>Технические примечания: константы дополнений</summary>

Сохраненный пример вики помещает LE_EXPANSION_LEVEL_CURRENT и LE_EXPANSION_CLASSIC внутрь ветки ID проекта Mainline. Эта ветка ложна в записанном клиенте Forever 70338. Клиент эпохи Midnight — другой тип игры в семействе Mainline; не выводите его ID проекта из этой связи.

</details>

Разделите файлы с помощью переменных TOC

Переменные TOC [Family] и [Game] раскрываются при загрузке, поэтому один AddonName.toc может указывать на разные файлы для каждого клиента без единого условия в Lua.

Split.toc
## Interface: 16001
## Title: Split Example

[Family]\File.lua
[Game]\File.lua

[Family]становится Mainline или Classic. [Game] становится Camelot в клиенте Forever. Среди других указанных типов игры — Standard, Vanilla и TBC.Источник: TOC format, проверено 28 сент. 2026 года.

На той же странице указан суффикс camelot для отдельного клиента, поэтому вместо файла по умолчанию загружается AddonName_Camelot.toc. В таблице интерфейсов 16001 указан в строке «Forever Beta», а строки «Forever Test» и «Forever» пусты. Не считайте, что этот номер сохранится после беты.

Как получить данные предмета, не обгоняя кэш

C_Item.GetItemInfoвозвращает 18 отдельных значений, а не одну таблицу: имя, ссылка, качество, уровень и далее до itemDescription. Функция возвращает nil, пока клиент не закэширует предмет, независимо от того, существует ли он.Источник: C_Item.GetItemInfo, проверено 28 сент. 2026 года.

Запросите данные, затем дождитесь события

ItemLookup.lua
local function OnItemData(_, event, itemID, success)
    if not success then return end
    local name = C_Item.GetItemInfo(itemID)
    if name then print(name, "is cached now") end
end

local frame = CreateFrame("Frame")
frame:SetScript("OnEvent", OnItemData)
frame:RegisterEvent("ITEM_DATA_LOAD_RESULT")

C_Item.RequestLoadItemDataByID(4306)

Начиная с патча 10.0.5, сервер может вернуть success = false для конкретного ID предмета. Проверяйте этот флаг, прежде чем снова вызывать GetItemInfo в той же сессии.Источник: ITEM_DATA_LOAD_RESULT, проверено 28 сент. 2026 года.

RequestLoadItemDataByID Функция принимает и полную ссылку на предмет: передайте ей то, что получили от обработчика подсказки или из ссылки в чате, не извлекая числовой ID.

Находите заклинание тем же способом

C_Spell.GetSpellInfoвозвращает одну таблицу, в отличие от вызова для предмета: spellInfo.name, spellInfo.iconID, spellInfo.castTime и остальные данные. Она заменила прежние глобальные функции GetSpellInfo и C_SpellBook.GetSpellInfo.Источник: C_Spell.GetSpellInfo, проверено 28 сент. 2026 года.

SpellLookup.lua
local function OnSpellData(_, event, spellID, success)
    local info = C_Spell.GetSpellInfo(spellID)
    if not info then return end
    -- success == false can still mean a partial record, such as no description.
    print(info.name, info.castTime, success and "full" or "partial")
end

local frame = CreateFrame("Frame")
frame:SetScript("OnEvent", OnSpellData)
frame:RegisterEvent("SPELL_DATA_LOAD_RESULT")

C_Spell.RequestLoadSpellData(2061)

Вики предупреждает, что success "Might return false when only partial spell data is available," («может вернуть false, если доступны только неполные данные о заклинании»), например все данные, кроме описания.Источник: SPELL_DATA_LOAD_RESULT, проверено 28 сент. 2026 года. Поэтому шаблон читает таблицу даже при false, а не прекращает работу. В списке ошибок ИИ при создании аддонов описаны и другие случаи, когда сгенерированный ответ пропускает подобную проверку.

Оба вызова принимают ID или название заклинания. Если передать название, C_Spell.GetSpellInfo сначала ищет текущее замещающее заклинание игрока, поэтому для превращенной способности может вернуть значок замещающего заклинания.

Откладывайте работу без скрипта OnUpdate

C_Timer.Afterпланирует однократный вызов функции. В синем посте, процитированном вики, говорится, что система таймеров выполняет работу только при создании или удалении таймера, а обработчик OnUpdate вызывает Lua каждый отображаемый кадр.Источник: C_Timer.After, проверено 28 сент. 2026 года.

Debounce.lua
local pendingSave = false

local function ScheduleSave()
    if pendingSave then return end
    pendingSave = true
    C_Timer.After(2, function()
        pendingSave = false
        -- update an in-memory table or refresh a preview here
    end)
end

Use this to debounce follow-up work after a setting changes, such as updating an in-memory table or refreshing a preview. The timer does not write SavedVariables to disk; the client persists declared variables on logout or UI reload.

Читайте метаданные своего TOC

C_AddOns.GetAddOnMetadata(name, variable)читает Title, Notes, Author, Version или любое пользовательское поле X- прямо из вашего TOC-файла, без учета регистра.Источник: C_AddOns.GetAddOnMetadata, проверено 28 сент. 2026 года.

Version.lua
local addonName = ...
local version = C_AddOns.GetAddOnMetadata(addonName, "Version") or "dev"
print(("%s v%s loaded"):format(addonName, version))

В отличие от старой глобальной функции GetAddOnMetadata, этот вызов выдает ошибку, если имя не соответствует установленному аддону. Поэтому оборачивайте в pcall все запросы, в которых не уверены. Выводите версию во всплывающей подсказке раздела аддонов, чтобы игрок мог сообщить ее при описании ошибки, не открывая TOC-файл.

Добавьте панель настроек с помощью Settings API

По данным вики, Settings API переработали в патче 10.0.0, а в 11.0.2 внесли дополнительные несовместимые изменения. Код настроек из руководства, написанного до 10,0, не будет соответствовать приведенным ниже вызовам. То же относится к ответам ИИ, обученного на таких руководствах.Источник: Settings API, проверено 28 сент. 2026 года.

Options.lua
local addonName, ns = ...

function ns.BuildOptions(db)
    local category = Settings.RegisterVerticalLayoutCategory("Beacon Notes")
    local setting = Settings.RegisterAddOnSetting(
        category, "BeaconNotes_Locked", "locked", db,
        "boolean", "Lock the notes frame", false
    )
    Settings.CreateCheckbox(category, setting, "Prevents dragging the frame.")
    Settings.RegisterAddOnCategory(category)
end

-- Call ns.BuildOptions(ns.db) at the end of your ADDON_LOADED handler.

Создавайте панель внутри ADDON_LOADED после того, как приведенный выше шаблон SavedVariables заполнит таблицу. На уровне файла сохраненной таблицы еще нет, поэтому настройка будет привязана к nil.

Установите метку на карте без защищенного вызова

Согласно отчету сообщества о бете Warcraft Forever, на этом клиенте защищена функция C_SuperTrack.SetSuperTrackedUserWaypoint. Вызов молча завершается с ошибкой и только увеличивает счетчик ошибок интерфейса в панели AddOns. Этот счетчик "only resets on a full client restart, not on /reload." («сбрасывается только при полном перезапуске клиента, а не при команде /reload»).Источник: Addons, проверено 28 сент. 2026 года. В том же отчете говорится, что безопасно ставить метку с помощью C_Map.SetUserWaypoint. Считайте оба утверждения неподтвержденными, пока не проверите их.

Метки на карте нужны аддонам с гайдами. Zygor Guides рекламирует "3D Waypoint Arrow" («3D-стрелка метки»), а RestedXP использует слоган "Follow the arrow" («Следуйте за стрелкой»). Перед тем как создавать собственный код меток на основе аддона с маршрутами, проверьте, как он работает на бете.

Waypoint.lua
local mapID = C_Map.GetBestMapForUnit("player")

if mapID and C_Map.CanSetUserWaypointOnMap(mapID) then
    local pos = C_Map.GetPlayerMapPosition(mapID, "player")
    if pos then
        C_Map.SetUserWaypoint(UiMapPoint.CreateFromVector2D(mapID, pos))
    end
end

В том же отчете ChatFrame_OpenChat и запись защищенных атрибутов в собственные рамки юнитов Blizzard также названы защищенными действиями. Для ссылок по Shift-клику безопасен HandleModifiedItemClick. Ни один из этих трех вызовов не выдает перехватываемую ошибку, поэтому обертка pcall не покажет, что вызов завершился неудачно. Перед выпуском сверьте новый шаблон с гайдом для разработчиков аддонов и загрузкой Forever Beacon, поскольку ни один фрагмент этой страницы еще не запускался в клиенте Forever.

Ключевые факты

  • Интерфейс TOC Изменено16001 в строке Forever Beta таблицы TOC на Warcraft Wiki; проверьте командой /dump select(4, GetBuildInfo())Classic: в той же таблице у Classic Era указано 11509.
  • SavedVariablesСообщается о потере данных между сессиями в середине сентября; в ответе от 25 сентября сказано, что сборка 70009 снова загружает их
  • Раздел аддонов Новое в ForeverПоля TOC задокументированы для Mainline; здесь их еще не проверяли в клиенте Forever
  • Блокировка в бою Как в ClassicВ бою нельзя показывать, скрывать или перемещать защищенные фреймы; поставьте эти действия в очередь до PLAYER_REGEN_ENABLED

Источники

  1. TOC formatWarcraft Wiki
  2. Saving variables between game sessionsWarcraft Wiki
  3. WOWF Beta: Addon SavedVariables appear to write correctly to disk but are not restored at startupWorld of Warcraft Forums (player bug report)
  4. Addon compartmentWarcraft Wiki
  5. InCombatLockdownWarcraft Wiki
  6. Secret ValuesWarcraft Wiki
  7. AddonsWarcraft Forever
  8. WOW_PROJECT_IDWarcraft Wiki
  9. WoW: Forever Will Have Addon Changes from MidnightWowhead
  10. C_Item.GetItemInfoWarcraft Wiki
  11. ITEM_DATA_LOAD_RESULTWarcraft Wiki
  12. C_Spell.GetSpellInfoWarcraft Wiki
  13. C_Timer.AfterWarcraft Wiki
  14. C_AddOns.GetAddOnMetadataWarcraft Wiki
  15. Settings APIWarcraft Wiki
  16. What the WoW Forever beta breaks for addons: secret health values, dead secure snippets, new APIsWoW Forever Builds
  17. SPELL_DATA_LOAD_RESULTWarcraft Wiki
  18. RestedXP home pageRestedXP
  19. Zygor Guides home pageZygor Guides
  20. Blizzard client source: ProjectConstants.lua (retained build 70338; mirror may change)Blizzard Entertainment (source mirrored by Gethe)
  21. Blizzard client source: SlashCommandsRegistry.lua (retained build 70338; mirror may change)Blizzard Entertainment (source mirrored by Gethe)
  22. Blizzard client source: StaticPopup.lua (retained build 70338; mirror may change)Blizzard Entertainment (source mirrored by Gethe)
  23. Lua 5.1 Reference ManualLua.org
  24. Blizzard client source: ChatInfoDocumentation.lua (retained build 70338; mirror may change)Blizzard Entertainment (source mirrored by Gethe)