Wowpedia

We have moved to Warcraft Wiki. Click here for information and the new URL.

READ MORE

Wowpedia
Register
No edit summary
Tags: WoW API docs Mobile edit Mobile web edit
mNo edit summary
Tag: WoW API docs
Line 54: Line 54:
 
[[File:Tutorial_createaddon05.png|thumb|right|Event payload in the chat window and [[MACRO_eventtrace|/etrace]]]]
 
[[File:Tutorial_createaddon05.png|thumb|right|Event payload in the chat window and [[MACRO_eventtrace|/etrace]]]]
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
local function OnEvent(frame, event, ...)
+
local function OnEvent(self, event, ...)
 
print(event, ...)
 
print(event, ...)
 
end
 
end
Line 75: Line 75:
 
When registering multiple events, consider dispatching them to their own dedicated function. The <code>_</code> single underscore is by convention a [https://stackoverflow.com/questions/5893163/what-is-the-purpose-of-the-single-underscore-variable-in-python dummy variable] when we're not interested in that variable.
 
When registering multiple events, consider dispatching them to their own dedicated function. The <code>_</code> single underscore is by convention a [https://stackoverflow.com/questions/5893163/what-is-the-purpose-of-the-single-underscore-variable-in-python dummy variable] when we're not interested in that variable.
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
local function OnEvent(frame, event, ...)
+
local function OnEvent(self, event, ...)
 
if event == "ADDON_LOADED" then
 
if event == "ADDON_LOADED" then
 
local addOnName = ...
 
local addOnName = ...
Line 156: Line 156:
 
SavedVariables are only accessible once the respective {{api|t=e|ADDON_LOADED}} event fires. This prints how many times you logged in (or reloaded) with the addon enabled.
 
SavedVariables are only accessible once the respective {{api|t=e|ADDON_LOADED}} event fires. This prints how many times you logged in (or reloaded) with the addon enabled.
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
local function OnEvent(frame, event, addOnName)
+
local function OnEvent(self, event, addOnName)
 
if addOnName == "HelloWorld" then -- name as used in the folder name and TOC file name
 
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 = HelloWorldDB or {} -- initialize it to a table if this is the first time
Line 177: Line 177:
 
}
 
}
   
local function OnEvent(frame, event, addOnName)
+
local function OnEvent(self, event, addOnName)
 
if addOnName == "HelloWorld" then
 
if addOnName == "HelloWorld" then
 
-- makes a copy instead of pointing HelloWorldDB to defaults
 
-- makes a copy instead of pointing HelloWorldDB to defaults

Revision as of 00:25, 29 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.

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.

Tutorial createaddon05

Event payload in the chat window and /etrace

local function OnEvent(self, 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. You can /reload instead of restarting the game client when adding a sound file.

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(self, 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.

Tutorial createaddon06

Using 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. This prints how many times you logged in (or reloaded) with the addon enabled.

local function OnEvent(self, 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(self, event, addOnName)
	if addOnName == "HelloWorld" then
		-- makes a copy instead of pointing HelloWorldDB to defaults
		-- 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, e.g. AwesomeHelloWorld = {}

Either way, it's a good idea to modularize the addon if it grows too big. Like 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 folder and publish it on CurseForge (guide) and/or WoWInterface (guide).

For automatically packaging and pushing updates see Using the BigWigs Packager with GitHub Actions.

See also