Tags: WoW API docs Mobile edit Mobile web edit |
Tag: WoW API docs |
||
Line 134: | Line 134: | ||
print(format("hello %s and also %s", name1, name2 or "")) |
print(format("hello %s and also %s", name1, name2 or "")) |
||
end |
end |
||
+ | |||
+ | We can also add a shorter /reload command. |
||
+ | <syntaxhighlight lang="lua"> |
||
+ | SLASH_NEWRELOAD1 = "/rl" |
||
+ | SlashCmdList.NEWRELOAD = ReloadUI |
||
+ | </syntaxhighlight> |
||
== Saving variables between sessions == |
== Saving variables between sessions == |
Revision as of 00:53, 26 December 2020
The AddOn system was designed by Slouken. Prior to Patch 2.0.1 you could even write an addon to be a full rotation and leveling bot or one that would travel for you. So how do you create a chat command? Play a John Cena sound on levelup? Automatically greet and annoy your guild members?
Getting started
For this tutorial you will need:
Hello World
You can run Lua scripts from the chat window or in a macro. To quickly turn this into an addon, just remove the "/run" part and paste it into https://addon.bool.no/
/run print("Hello World!")
An addon can consist of Lua and XML files and a TOC file. We won't be using XML since most things that are possible in XML can also be done in Lua.
Go to your AddOns folder and create a new folder: World of Warcraft\_retail_\Interface\AddOns\HelloWorld
HelloWorld.lua
print("Hello World!")
HelloWorld.toc
## Interface: 100206 ## Version: 1.0.0 ## Title: Hello World ## Notes: My first addon ## Author: YourName HelloWorld.lua
- The name of the TOC file must match the folder name or the addon won't be detected by the game.
- The TOC Interface metadata
100206
as returned by GetBuildInfo() tells which version of the game the addon was made for. If they don't match then the addon will be marked out-of-date in the addon list.
Load up World of Warcraft, the addon should show up in the addon list and greet you upon login.
- Tutorial createaddon01.png
File Structure
Development tips
See also: Lua Coding Tips
- When updating addon code use /reload to test the new changes, you may want to put it on a macro hotkey; as well as temporarily disabling any unnecessary addons that would increase loading time.
- Get an error reporting addon like BugSack or turn on
/console scriptErrors 1
- There is the /dump slash command for general debugging, /etrace for showing events and /fstack for debugging visible UI elements.
- Download or bookmark Blizzard's user interface code a.k.a. the FrameXML. If you don't know what a specific API does it's best to just reference it in FrameXML. Not everything is documented so we generally look through the code from Blizzard or other addons.
- For VS Code the Lua extension by Sumneko adds IntelliSense features like code completion.
Responding to events
- Main article: Handling events
Almost every action in the game is an Event which tells the UI that something happened. For example CHAT_MSG_CHANNEL fires when someone sends a message in a chat channel like General and Trade.
To respond to events you create a frame with CreateFrame() and register the events to it. The ...
is a vararg expression, it holds any variable number of arguments.
local function OnEvent(frame, event, ...)
print(event, ...)
end
local f = CreateFrame("Frame")
f:RegisterEvent("CHAT_MSG_CHANNEL")
f:SetScript("OnEvent", OnEvent)
Another example, to play a sound on levelup with PlaySoundFile() you register for the PLAYER_LEVEL_UP event. You can get a sound file from e.g. here, rename it to johncena.mp3
and put it in the addon's folder. Doing a /reload should make the game client detect a newly added sound file if it's currently running.
local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_LEVEL_UP")
f:SetScript("OnEvent", function() -- anonymous function
PlaySoundFile("Interface/AddOns/HelloWorld/johncena.mp3")
end)
Handling multiple events
When registering multiple events, consider dispatching them to their own dedicated function. The _
single underscore is by convention a dummy variable when we're not interested in that variable.
local function OnEvent(frame, event, ...)
if event == "ADDON_LOADED" then
local addOnName = ...
print(event, addOnName)
elseif event == "PLAYER_ENTERING_WORLD" then
local isLogin, isReload = ...
print(event, isLogin, isReload)
elseif event == "CHAT_MSG_CHANNEL" then
local text, playerName, _, channelName = ...
print(event, text, playerName, channelName)
end
end
local f = CreateFrame("Frame")
f:RegisterEvent("ADDON_LOADED")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:RegisterEvent("CHAT_MSG_CHANNEL")
f:SetScript("OnEvent", OnEvent)
local f = CreateFrame("Frame")
function f:OnEvent(event, ...)
self[event](self, event, ...)
end
function f:ADDON_LOADED(event, addOnName)
print(event, addOnName)
end
function f:PLAYER_ENTERING_WORLD(event, isLogin, isReload)
print(event, isLogin, isReload)
end
function f:CHAT_MSG_CHANNEL(event, text, playerName, _, channelName)
print(event, text, playerName, channelName)
end
f:RegisterEvent("ADDON_LOADED")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:RegisterEvent("CHAT_MSG_CHANNEL")
f:SetScript("OnEvent", f.OnEvent)
Slash commands
- Main article: Creating a slash command
Slash commands are an easy way to let users interact with your addon. Any SLASH_*
globals will automatically be registered as a slash command.
-- increment the index for each slash command SLASH_HELLOW1 = "/helloworld" SLASH_HELLOW2 = "/hellow" SLASH_HELLOW3 = "/hw" -- define the corresponding slash command handler SlashCmdList.HELLOW = function(msg, editBox) local name1, name2 = strsplit(" ", msg) print(format("hello %s and also %s", name1, name2 or "")) end
We can also add a shorter /reload command.
SLASH_NEWRELOAD1 = "/rl"
SlashCmdList.NEWRELOAD = ReloadUI
Saving variables between sessions
- Main article: Saving variables between game sessions
To store data or save user settings, set the SavedVariables
in the TOC which will persist between sessions. You can /reload instead of restarting the game client when updating the TOC file.
## Interface: 100206 ## Version: 1.0.0 ## Title: Hello World ## Notes: My first addon ## Author: YourName ## SavedVariables: HelloWorldDB HelloWorld.lua
SavedVariables are only accessible once the respective ADDON_LOADED event fires.
local function OnEvent(frame, event, addOnName)
if addOnName == "HelloWorld" then -- name as used in the folder name and TOC file name
HelloWorldDB = HelloWorldDB or {} -- initialize it to a table if this is the first time
HelloWorldDB.sessions = (HelloWorldDB.sessions or 0) + 1
print("You loaded this addon "..HelloWorldDB.sessions.." times")
end
end
local f = CreateFrame("Frame")
f:RegisterEvent("ADDON_LOADED")
f:SetScript("OnEvent", OnEvent)
This example initializes the SavedVariables with default values. It also updates the DB when new keys are added to the defaults table.
The CopyTable() function is defined in FrameXML. Depending on the addon consider using AceDB for managing SavedVariables.
local db
local defaults = {
sessions = 0,
someOption = true,
}
local function OnEvent(frame, event, addOnName)
if addOnName == "HelloWorld" then
-- makes a copy instead of pointing HelloWorldDB to the defaults table
-- since tables are passed by reference https://stackoverflow.com/a/6128322
HelloWorldDB = HelloWorldDB or CopyTable(defaults)
-- add any new options from defaults
for k, v in pairs(defaults) do
if HelloWorldDB[k] == nil then -- don't reset false values
HelloWorldDB[k] = v
end
end
db = HelloWorldDB -- use a local variable which points to the DB for readability
db.sessions = db.sessions + 1
print("You loaded this addon "..db.sessions.." times")
print("someOption is", db.someOption)
self:UnregisterEvent(event) -- skip checking for any other addons
end
end
local f = CreateFrame("Frame")
f:RegisterEvent("ADDON_LOADED")
f:SetScript("OnEvent", OnEvent)
SLASH_HELLOW1 = "/helloworld"
SLASH_HELLOW2 = "/hellow"
SLASH_HELLOW3 = "/hw"
SlashCmdList.HELLOW = function(msg, editBox)
if msg == "reset" then
HelloWorldDB = CopyTable(defaults) -- reset to defaults
db = HelloWorldDB -- update our table reference
print("DB has been reset to default")
elseif msg == "toggle" then
db.someOption = not db.someOption
print("Toggled someOption to", db.someOption)
end
end
Tip: use /dump HelloWorldDB
or /tinspect HelloWorldDB
to show the contents of a global table.
Using the AddOn namespace
- Main article: Using the AddOn namespace
The addon namespace is a private table shared between Lua files in the same addon. This way you don't have to expose or leak variables to the global environment.
## Interface: 100206 ## Version: 1.0.0 ## Title: Hello World FileA.lua FileB.lua
FileA.lua
local _, addonTbl = ...
addonTbl.foo = "Friends"
FileB.lua
local addonName, addonTbl = ...
print(addonName, addonTbl.foo) -- prints "HelloWorld" and "Friends"
You can alternatively use a single unique global variable and access that from your files, like AwesomeHelloWorld = {}
Either way, it's a good idea to modularize the addon if it grows too big by e.g. putting the GUI into options.lua and the rest into core.lua
Conclusion
You should now understand how to write a simple addon! To share it with other players zip up the addon's folder and publish it on CurseForge (guide) and/or WoWInterface (guide).
For automatically packaging and uploading addon updates see Using the BigWigs Packager with GitHub Actions.
See also
- Getting started with writing addons
- AddOn programming introduction
- WelcomeHome - Your first Ace3 Addon