Как написать аддон для wow

Main Menu
  • WoW API
  • Widget API
  • Widget scripts
  • Lua API
  • FrameXML
  • Events
  • CVars
  • XML schema
  • Macro commands

  • Combat Log
  • Escape sequences
  • Hyperlinks
  • API changes
  • HOWTOs
  • Discord logo.png wowuidev

This guide describes how to make a simple HelloWorld addon, use slash commands and store user settings.

Getting started

  • You need a basic understanding of Lua, otherwise see Introduction to Lua or other tutorials.
  • A simple text editor like VS Code or Notepad++.

Hello World

Running scripts

You can execute Lua scripts from the chat window or in a macro with the /run or /script command. There is no difference between them.

/run print("Hello World!")

To quickly turn scripts like this into an addon, just remove the «/run» part and paste it into https://addon.bool.no/

Creating an AddOn

An addon consists of Lua/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 with the following files:
World of Warcraft_retail_InterfaceAddOnsHelloWorld

HelloWorld.lua

HelloWorld.toc

## Interface: 100005
## 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 100005 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.

  • VS Code

  • AddOn List

  • In-game

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.
  • Export, clone, 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.

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() or PlayMusic() you register for the PLAYER_LEVEL_UP event.

local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_LEVEL_UP")
f:SetScript("OnEvent", function()
	PlayMusic(642322) -- sound/music/pandaria/mus_50_toast_b_hero_01.mp3
end)

Handling multiple events

When registering multiple events it can be messy if there are a lot of them.

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)

Which can be refactored to this:

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
FrameXML: RegisterNewSlashCommand()

Slash commands are an easy way to let users interact with your addon. Any SLASH_* globals will automatically be registered as a slash command.

Using a slash command

-- increment the index for each slash command
SLASH_HELLOW1 = "/helloworld"
SLASH_HELLOW2 = "/hw"

-- define the corresponding slash command handler
SlashCmdList.HELLOW = function(msg, editBox)
	local name1, name2 = strsplit(" ", msg)
	if #name1 > 0 then -- check for empty string
		print(format("hello %s and also %s", name1, name2 or "Carol"))
	else
		print("Please give at least one name")
	end
end

We can also add a shorter /reload command.

SLASH_NEWRELOAD1 = "/rl"
SlashCmdList.NEWRELOAD = ReloadUI

SavedVariables

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: 100005
## 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 example 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. GetBuildInfo() is an API function.

local defaults = {
	sessions = 0,
	someOption = true,
	--someNewOption = "banana",
}

local function OnEvent(self, event, addOnName)
	if addOnName == "HelloWorld" then
		HelloWorldDB = HelloWorldDB or {}
		self.db = HelloWorldDB -- makes it more readable and generally a good practice
		for k, v in pairs(defaults) do -- copy the defaults table and possibly any new options
			if self.db[k] == nil then -- avoids resetting any false values
				self.db[k] = v
			end
		end
		self.db.sessions = self.db.sessions + 1
		print("You loaded this addon "..self.db.sessions.." times")
		print("someOption is", self.db.someOption)

		local version, build, _, tocversion = GetBuildInfo()
		print(format("The current WoW build is %s (%d) and TOC is %d", version, build, tocversion))
	end
end

local f = CreateFrame("Frame")
f:RegisterEvent("ADDON_LOADED")
f:SetScript("OnEvent", OnEvent)

SLASH_HELLOW1 = "/hw"
SLASH_HELLOW2 = "/helloworld"

SlashCmdList.HELLOW = function(msg, editBox)
	if msg == "reset" then
		HelloWorldDB = CopyTable(defaults) -- reset to defaults
		f.db = HelloWorldDB 
		print("DB has been reset to default")
	elseif msg == "toggle" then
		f.db.someOption = not f.db.someOption
		print("Toggled someOption to", f.db.someOption)
	end
end

Tips for troubleshooting tables:

  • /dump HelloWorldDB or /tinspect HelloWorldDB or /run for k, v in pairs(HelloWorldDB) do print(k, v) end shows the contents of a (global) table.
  • /run wipe(HelloWorldDB) or /run for k in pairs(HelloWorldDB) do HelloWorldDB[k] = nil end empties the table.
  • /run HelloWorldDB = nil; ReloadUI() removes the table reference and reloads the UI; this essentially completely resets your savedvariables.

Options Panel

Main article: Using the Interface Options Addons panel

It would be more user-friendly to provide a graphical user interface. This example prints a message when you jump, if the option is enabled. It also opens the options panel with the slash command.

FrameXML:

  • InterfaceOptions_AddCategory()
  • InterfaceOptionsCheckButtonTemplate
  • UIPanelButtonTemplate

Minimal example

Multiple options with reset button

Note

Note: This example is up to date for patch 10.0.0 and Classic

local f = CreateFrame("Frame")

local defaults = {
	someOption = true,
}

function f:OnEvent(event, addOnName)
	if addOnName == "HelloWorld" then
		HelloWorldDB = HelloWorldDB or CopyTable(defaults)
		self.db = HelloWorldDB
		self:InitializeOptions()
		hooksecurefunc("JumpOrAscendStart", function()
			if self.db.someOption then
				print("Your character jumped.")
			end
		end)
	end
end

f:RegisterEvent("ADDON_LOADED")
f:SetScript("OnEvent", f.OnEvent)

function f:InitializeOptions()
	self.panel = CreateFrame("Frame")
	self.panel.name = "HelloWorld"

	local cb = CreateFrame("CheckButton", nil, self.panel, "InterfaceOptionsCheckButtonTemplate")
	cb:SetPoint("TOPLEFT", 20, -20)
	cb.Text:SetText("Print when you jump")
	-- there already is an existing OnClick script that plays a sound, hook it
	cb:HookScript("OnClick", function(_, btn, down)
		self.db.someOption = cb:GetChecked()
	end)
	cb:SetChecked(self.db.someOption)

	local btn = CreateFrame("Button", nil, self.panel, "UIPanelButtonTemplate")
	btn:SetPoint("TOPLEFT", cb, 0, -40)
	btn:SetText("Click me")
	btn:SetWidth(100)
	btn:SetScript("OnClick", function()
		print("You clicked me!")
	end)

	InterfaceOptions_AddCategory(self.panel)
end

SLASH_HELLOW1 = "/hw"
SLASH_HELLOW2 = "/helloworld"

SlashCmdList.HELLOW = function(msg, editBox)
	InterfaceOptionsFrame_OpenToCategory(f.panel)
end

This reference example has a couple of checkboxes (with related functionality) and a reset button.

Multiple options with reset button  

GitHub source

HelloWorld.toc

## Interface: 100005
## Version: 1.0.2
## Title: Hello World
## Notes: My first addon
## Author: YourName
## SavedVariables: HelloWorldDB

Core.lua
Options.lua

Core.lua

HelloWorld = CreateFrame("Frame")

function HelloWorld:OnEvent(event, ...)
	self[event](self, event, ...)
end
HelloWorld:SetScript("OnEvent", HelloWorld.OnEvent)
HelloWorld:RegisterEvent("ADDON_LOADED")

function HelloWorld:ADDON_LOADED(event, addOnName)
	if addOnName == "HelloWorld" then
		HelloWorldDB = HelloWorldDB or {}
		self.db = HelloWorldDB
		for k, v in pairs(self.defaults) do
			if self.db[k] == nil then
				self.db[k] = v
			end
		end
		self.db.sessions = self.db.sessions + 1
		print("You loaded this addon "..self.db.sessions.." times")

		local version, build, _, tocversion = GetBuildInfo()
		print(format("The current WoW build is %s (%d) and TOC is %d", version, build, tocversion))

		self:RegisterEvent("PLAYER_ENTERING_WORLD")
		hooksecurefunc("JumpOrAscendStart", self.JumpOrAscendStart)

		self:InitializeOptions()
		self:UnregisterEvent(event)
	end
end

function HelloWorld:PLAYER_ENTERING_WORLD(event, isLogin, isReload)
	if isLogin and self.db.hello then
		DoEmote("HELLO")
	end
end

-- note we don't pass `self` here because of hooksecurefunc, hence the dot instead of colon
function HelloWorld.JumpOrAscendStart()
	if HelloWorld.db.jump then
		print("Your character jumped.")
	end
end

function HelloWorld:COMBAT_LOG_EVENT_UNFILTERED(event)
	-- it's more convenient to work with the CLEU params as a vararg
	self:CLEU(CombatLogGetCurrentEventInfo())
end

local playerGUID = UnitGUID("player")
local MSG_DAMAGE = "Your %s hit %s for %d damage."

function HelloWorld:CLEU(...)
	local timestamp, subevent, _, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags = ...
	local spellId, spellName, spellSchool
	local amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand
	local isDamageEvent

	if subevent == "SWING_DAMAGE" then
		amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand = select(12, ...)
		isDamageEvent = true
	elseif subevent == "SPELL_DAMAGE" then
		spellId, spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand = select(12, ...)
		isDamageEvent = true
	end

	if isDamageEvent and sourceGUID == playerGUID then
		-- get the link of the spell or the MELEE globalstring
		local action = spellId and GetSpellLink(spellId) or MELEE
		print(MSG_DAMAGE:format(action, destName, amount))
	end
end

SLASH_HELLOW1 = "/hw"
SLASH_HELLOW2 = "/helloworld"

SlashCmdList.HELLOW = function(msg, editBox)
	InterfaceOptionsFrame_OpenToCategory(HelloWorld.panel_main)
end

Options.lua

HelloWorld.defaults = {
	sessions = 0,
	hello = false,
	mushroom = false,
	jump = true,
	combat = true,
	--someNewOption = "banana",
}

local function CreateIcon(icon, width, height, parent)
	local f = CreateFrame("Frame", nil, parent)
	f:SetSize(width, height)
	f.tex = f:CreateTexture()
	f.tex:SetAllPoints(f)
	f.tex:SetTexture(icon)
	return f
end

function HelloWorld:CreateCheckbox(option, label, parent, updateFunc)
	local cb = CreateFrame("CheckButton", nil, parent, "InterfaceOptionsCheckButtonTemplate")
	cb.Text:SetText(label)
	local function UpdateOption(value)
		self.db[option] = value
		cb:SetChecked(value)
		if updateFunc then
			updateFunc(value)
		end
	end
	UpdateOption(self.db[option])
	-- there already is an existing OnClick script that plays a sound, hook it
	cb:HookScript("OnClick", function(_, btn, down)
		UpdateOption(cb:GetChecked())
	end)
	EventRegistry:RegisterCallback("HelloWorld.OnReset", function()
		UpdateOption(self.defaults[option])
	end, cb)
	return cb
end

function HelloWorld:InitializeOptions()
	-- main panel
	self.panel_main = CreateFrame("Frame")
	self.panel_main.name = "HelloWorld"

	local cb_hello = self:CreateCheckbox("hello", "Do the |cFFFFFF00/hello|r emote when you login", self.panel_main)
	cb_hello:SetPoint("TOPLEFT", 20, -20)

	local cb_mushroom = self:CreateCheckbox("mushroom", "Show a mushroom on your screen", self.panel_main, self.UpdateIcon)
	cb_mushroom:SetPoint("TOPLEFT", cb_hello, 0, -30)

	local cb_jump = self:CreateCheckbox("jump", "Print when you jump", self.panel_main)
	cb_jump:SetPoint("TOPLEFT", cb_mushroom, 0, -30)

	local cb_combat = self:CreateCheckbox("combat", "Print when you damage a unit", self.panel_main, function(value)
		self:UpdateEvent(value, "COMBAT_LOG_EVENT_UNFILTERED")
	end)
	cb_combat:SetPoint("TOPLEFT", cb_jump, 0, -30)

	local btn_reset = CreateFrame("Button", nil, self.panel_main, "UIPanelButtonTemplate")
	btn_reset:SetPoint("TOPLEFT", cb_combat, 0, -40)
	btn_reset:SetText(RESET)
	btn_reset:SetWidth(100)
	btn_reset:SetScript("OnClick", function()
		HelloWorldDB = CopyTable(HelloWorld.defaults)
		self.db = HelloWorldDB
		EventRegistry:TriggerEvent("HelloWorld.OnReset")
	end)

	InterfaceOptions_AddCategory(HelloWorld.panel_main)

	-- sub panel
	local panel_shroom = CreateFrame("Frame")
	panel_shroom.name = "Shrooms"
	panel_shroom.parent = self.panel_main.name

	for i = 1, 10 do
		local icon = CreateIcon("interface/icons/inv_mushroom_11", 32, 32, panel_shroom)
		icon:SetPoint("TOPLEFT", 20, -32*i)
	end

	InterfaceOptions_AddCategory(panel_shroom)
end

function HelloWorld.UpdateIcon(value)
	if not HelloWorld.mushroom then
		HelloWorld.mushroom = CreateIcon("interface/icons/inv_mushroom_11", 64, 64, UIParent)
		HelloWorld.mushroom:SetPoint("CENTER")
	end
	HelloWorld.mushroom:SetShown(value)
end

-- a bit more efficient to register/unregister the event when it fires a lot
function HelloWorld:UpdateEvent(value, event)
	if value then
		self:RegisterEvent(event)
	else
		self:UnregisterEvent(event)
	end
end

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 can avoid leaking variables to the global environment.

HelloWorld.toc

## Interface: 100005
## Version: 1.0.0
## Title: Hello World

FileA.lua
FileB.lua

FileA.lua

local _, ns = ...
ns.foo = "Banana"

FileB.lua

local addonName, ns = ...
print(addonName, ns.foo) -- prints "HelloWorld" and "Banana"

Or you can simply use a unique global variable.

FileA.lua

MyAddon = {}
MyAddon.value = 0

function MyAddon:DoSomething(increment)
	self.value = self.value + increment
end

MyAddon:DoSomething(2)

FileB.lua

MyAddon:DoSomething(3)
print(MyAddon.value) -- 5

Conclusion

You know how to write a simple addon from scratch! Go and publish it on CurseForge (guide), WoWInterface (guide) and/or wago.io.
If you want to cheat and rather start with a complete example it’s available here: HelloWorld.zip

Follow-up: Ace3 for Dummies

See also

  • MMO-Champion: Creating Your Own WoW Addon
Main Menu
  • WoW API
  • Widget API
  • Widget scripts
  • Lua API
  • FrameXML
  • Events
  • CVars
  • XML schema
  • Macro commands

  • Combat Log
  • Escape sequences
  • Hyperlinks
  • API changes
  • HOWTOs
  • Discord logo.png wowuidev

This guide describes how to make a simple HelloWorld addon, use slash commands and store user settings.

Getting started

  • You need a basic understanding of Lua, otherwise see Introduction to Lua or other tutorials.
  • A simple text editor like VS Code or Notepad++.

Hello World

Running scripts

You can execute Lua scripts from the chat window or in a macro with the /run or /script command. There is no difference between them.

/run print("Hello World!")

To quickly turn scripts like this into an addon, just remove the «/run» part and paste it into https://addon.bool.no/

Creating an AddOn

An addon consists of Lua/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 with the following files:
World of Warcraft_retail_InterfaceAddOnsHelloWorld

HelloWorld.lua

HelloWorld.toc

## Interface: 100005
## 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 100005 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.

  • VS Code

  • AddOn List

  • In-game

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.
  • Export, clone, 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.

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() or PlayMusic() you register for the PLAYER_LEVEL_UP event.

local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_LEVEL_UP")
f:SetScript("OnEvent", function()
	PlayMusic(642322) -- sound/music/pandaria/mus_50_toast_b_hero_01.mp3
end)

Handling multiple events

When registering multiple events it can be messy if there are a lot of them.

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)

Which can be refactored to this:

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
FrameXML: RegisterNewSlashCommand()

Slash commands are an easy way to let users interact with your addon. Any SLASH_* globals will automatically be registered as a slash command.

Using a slash command

-- increment the index for each slash command
SLASH_HELLOW1 = "/helloworld"
SLASH_HELLOW2 = "/hw"

-- define the corresponding slash command handler
SlashCmdList.HELLOW = function(msg, editBox)
	local name1, name2 = strsplit(" ", msg)
	if #name1 > 0 then -- check for empty string
		print(format("hello %s and also %s", name1, name2 or "Carol"))
	else
		print("Please give at least one name")
	end
end

We can also add a shorter /reload command.

SLASH_NEWRELOAD1 = "/rl"
SlashCmdList.NEWRELOAD = ReloadUI

SavedVariables

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: 100005
## 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 example 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. GetBuildInfo() is an API function.

local defaults = {
	sessions = 0,
	someOption = true,
	--someNewOption = "banana",
}

local function OnEvent(self, event, addOnName)
	if addOnName == "HelloWorld" then
		HelloWorldDB = HelloWorldDB or {}
		self.db = HelloWorldDB -- makes it more readable and generally a good practice
		for k, v in pairs(defaults) do -- copy the defaults table and possibly any new options
			if self.db[k] == nil then -- avoids resetting any false values
				self.db[k] = v
			end
		end
		self.db.sessions = self.db.sessions + 1
		print("You loaded this addon "..self.db.sessions.." times")
		print("someOption is", self.db.someOption)

		local version, build, _, tocversion = GetBuildInfo()
		print(format("The current WoW build is %s (%d) and TOC is %d", version, build, tocversion))
	end
end

local f = CreateFrame("Frame")
f:RegisterEvent("ADDON_LOADED")
f:SetScript("OnEvent", OnEvent)

SLASH_HELLOW1 = "/hw"
SLASH_HELLOW2 = "/helloworld"

SlashCmdList.HELLOW = function(msg, editBox)
	if msg == "reset" then
		HelloWorldDB = CopyTable(defaults) -- reset to defaults
		f.db = HelloWorldDB 
		print("DB has been reset to default")
	elseif msg == "toggle" then
		f.db.someOption = not f.db.someOption
		print("Toggled someOption to", f.db.someOption)
	end
end

Tips for troubleshooting tables:

  • /dump HelloWorldDB or /tinspect HelloWorldDB or /run for k, v in pairs(HelloWorldDB) do print(k, v) end shows the contents of a (global) table.
  • /run wipe(HelloWorldDB) or /run for k in pairs(HelloWorldDB) do HelloWorldDB[k] = nil end empties the table.
  • /run HelloWorldDB = nil; ReloadUI() removes the table reference and reloads the UI; this essentially completely resets your savedvariables.

Options Panel

Main article: Using the Interface Options Addons panel

It would be more user-friendly to provide a graphical user interface. This example prints a message when you jump, if the option is enabled. It also opens the options panel with the slash command.

FrameXML:

  • InterfaceOptions_AddCategory()
  • InterfaceOptionsCheckButtonTemplate
  • UIPanelButtonTemplate

Minimal example

Multiple options with reset button

Note

Note: This example is up to date for patch 10.0.0 and Classic

local f = CreateFrame("Frame")

local defaults = {
	someOption = true,
}

function f:OnEvent(event, addOnName)
	if addOnName == "HelloWorld" then
		HelloWorldDB = HelloWorldDB or CopyTable(defaults)
		self.db = HelloWorldDB
		self:InitializeOptions()
		hooksecurefunc("JumpOrAscendStart", function()
			if self.db.someOption then
				print("Your character jumped.")
			end
		end)
	end
end

f:RegisterEvent("ADDON_LOADED")
f:SetScript("OnEvent", f.OnEvent)

function f:InitializeOptions()
	self.panel = CreateFrame("Frame")
	self.panel.name = "HelloWorld"

	local cb = CreateFrame("CheckButton", nil, self.panel, "InterfaceOptionsCheckButtonTemplate")
	cb:SetPoint("TOPLEFT", 20, -20)
	cb.Text:SetText("Print when you jump")
	-- there already is an existing OnClick script that plays a sound, hook it
	cb:HookScript("OnClick", function(_, btn, down)
		self.db.someOption = cb:GetChecked()
	end)
	cb:SetChecked(self.db.someOption)

	local btn = CreateFrame("Button", nil, self.panel, "UIPanelButtonTemplate")
	btn:SetPoint("TOPLEFT", cb, 0, -40)
	btn:SetText("Click me")
	btn:SetWidth(100)
	btn:SetScript("OnClick", function()
		print("You clicked me!")
	end)

	InterfaceOptions_AddCategory(self.panel)
end

SLASH_HELLOW1 = "/hw"
SLASH_HELLOW2 = "/helloworld"

SlashCmdList.HELLOW = function(msg, editBox)
	InterfaceOptionsFrame_OpenToCategory(f.panel)
end

This reference example has a couple of checkboxes (with related functionality) and a reset button.

Multiple options with reset button  

GitHub source

HelloWorld.toc

## Interface: 100005
## Version: 1.0.2
## Title: Hello World
## Notes: My first addon
## Author: YourName
## SavedVariables: HelloWorldDB

Core.lua
Options.lua

Core.lua

HelloWorld = CreateFrame("Frame")

function HelloWorld:OnEvent(event, ...)
	self[event](self, event, ...)
end
HelloWorld:SetScript("OnEvent", HelloWorld.OnEvent)
HelloWorld:RegisterEvent("ADDON_LOADED")

function HelloWorld:ADDON_LOADED(event, addOnName)
	if addOnName == "HelloWorld" then
		HelloWorldDB = HelloWorldDB or {}
		self.db = HelloWorldDB
		for k, v in pairs(self.defaults) do
			if self.db[k] == nil then
				self.db[k] = v
			end
		end
		self.db.sessions = self.db.sessions + 1
		print("You loaded this addon "..self.db.sessions.." times")

		local version, build, _, tocversion = GetBuildInfo()
		print(format("The current WoW build is %s (%d) and TOC is %d", version, build, tocversion))

		self:RegisterEvent("PLAYER_ENTERING_WORLD")
		hooksecurefunc("JumpOrAscendStart", self.JumpOrAscendStart)

		self:InitializeOptions()
		self:UnregisterEvent(event)
	end
end

function HelloWorld:PLAYER_ENTERING_WORLD(event, isLogin, isReload)
	if isLogin and self.db.hello then
		DoEmote("HELLO")
	end
end

-- note we don't pass `self` here because of hooksecurefunc, hence the dot instead of colon
function HelloWorld.JumpOrAscendStart()
	if HelloWorld.db.jump then
		print("Your character jumped.")
	end
end

function HelloWorld:COMBAT_LOG_EVENT_UNFILTERED(event)
	-- it's more convenient to work with the CLEU params as a vararg
	self:CLEU(CombatLogGetCurrentEventInfo())
end

local playerGUID = UnitGUID("player")
local MSG_DAMAGE = "Your %s hit %s for %d damage."

function HelloWorld:CLEU(...)
	local timestamp, subevent, _, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags = ...
	local spellId, spellName, spellSchool
	local amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand
	local isDamageEvent

	if subevent == "SWING_DAMAGE" then
		amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand = select(12, ...)
		isDamageEvent = true
	elseif subevent == "SPELL_DAMAGE" then
		spellId, spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand = select(12, ...)
		isDamageEvent = true
	end

	if isDamageEvent and sourceGUID == playerGUID then
		-- get the link of the spell or the MELEE globalstring
		local action = spellId and GetSpellLink(spellId) or MELEE
		print(MSG_DAMAGE:format(action, destName, amount))
	end
end

SLASH_HELLOW1 = "/hw"
SLASH_HELLOW2 = "/helloworld"

SlashCmdList.HELLOW = function(msg, editBox)
	InterfaceOptionsFrame_OpenToCategory(HelloWorld.panel_main)
end

Options.lua

HelloWorld.defaults = {
	sessions = 0,
	hello = false,
	mushroom = false,
	jump = true,
	combat = true,
	--someNewOption = "banana",
}

local function CreateIcon(icon, width, height, parent)
	local f = CreateFrame("Frame", nil, parent)
	f:SetSize(width, height)
	f.tex = f:CreateTexture()
	f.tex:SetAllPoints(f)
	f.tex:SetTexture(icon)
	return f
end

function HelloWorld:CreateCheckbox(option, label, parent, updateFunc)
	local cb = CreateFrame("CheckButton", nil, parent, "InterfaceOptionsCheckButtonTemplate")
	cb.Text:SetText(label)
	local function UpdateOption(value)
		self.db[option] = value
		cb:SetChecked(value)
		if updateFunc then
			updateFunc(value)
		end
	end
	UpdateOption(self.db[option])
	-- there already is an existing OnClick script that plays a sound, hook it
	cb:HookScript("OnClick", function(_, btn, down)
		UpdateOption(cb:GetChecked())
	end)
	EventRegistry:RegisterCallback("HelloWorld.OnReset", function()
		UpdateOption(self.defaults[option])
	end, cb)
	return cb
end

function HelloWorld:InitializeOptions()
	-- main panel
	self.panel_main = CreateFrame("Frame")
	self.panel_main.name = "HelloWorld"

	local cb_hello = self:CreateCheckbox("hello", "Do the |cFFFFFF00/hello|r emote when you login", self.panel_main)
	cb_hello:SetPoint("TOPLEFT", 20, -20)

	local cb_mushroom = self:CreateCheckbox("mushroom", "Show a mushroom on your screen", self.panel_main, self.UpdateIcon)
	cb_mushroom:SetPoint("TOPLEFT", cb_hello, 0, -30)

	local cb_jump = self:CreateCheckbox("jump", "Print when you jump", self.panel_main)
	cb_jump:SetPoint("TOPLEFT", cb_mushroom, 0, -30)

	local cb_combat = self:CreateCheckbox("combat", "Print when you damage a unit", self.panel_main, function(value)
		self:UpdateEvent(value, "COMBAT_LOG_EVENT_UNFILTERED")
	end)
	cb_combat:SetPoint("TOPLEFT", cb_jump, 0, -30)

	local btn_reset = CreateFrame("Button", nil, self.panel_main, "UIPanelButtonTemplate")
	btn_reset:SetPoint("TOPLEFT", cb_combat, 0, -40)
	btn_reset:SetText(RESET)
	btn_reset:SetWidth(100)
	btn_reset:SetScript("OnClick", function()
		HelloWorldDB = CopyTable(HelloWorld.defaults)
		self.db = HelloWorldDB
		EventRegistry:TriggerEvent("HelloWorld.OnReset")
	end)

	InterfaceOptions_AddCategory(HelloWorld.panel_main)

	-- sub panel
	local panel_shroom = CreateFrame("Frame")
	panel_shroom.name = "Shrooms"
	panel_shroom.parent = self.panel_main.name

	for i = 1, 10 do
		local icon = CreateIcon("interface/icons/inv_mushroom_11", 32, 32, panel_shroom)
		icon:SetPoint("TOPLEFT", 20, -32*i)
	end

	InterfaceOptions_AddCategory(panel_shroom)
end

function HelloWorld.UpdateIcon(value)
	if not HelloWorld.mushroom then
		HelloWorld.mushroom = CreateIcon("interface/icons/inv_mushroom_11", 64, 64, UIParent)
		HelloWorld.mushroom:SetPoint("CENTER")
	end
	HelloWorld.mushroom:SetShown(value)
end

-- a bit more efficient to register/unregister the event when it fires a lot
function HelloWorld:UpdateEvent(value, event)
	if value then
		self:RegisterEvent(event)
	else
		self:UnregisterEvent(event)
	end
end

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 can avoid leaking variables to the global environment.

HelloWorld.toc

## Interface: 100005
## Version: 1.0.0
## Title: Hello World

FileA.lua
FileB.lua

FileA.lua

local _, ns = ...
ns.foo = "Banana"

FileB.lua

local addonName, ns = ...
print(addonName, ns.foo) -- prints "HelloWorld" and "Banana"

Or you can simply use a unique global variable.

FileA.lua

MyAddon = {}
MyAddon.value = 0

function MyAddon:DoSomething(increment)
	self.value = self.value + increment
end

MyAddon:DoSomething(2)

FileB.lua

MyAddon:DoSomething(3)
print(MyAddon.value) -- 5

Conclusion

You know how to write a simple addon from scratch! Go and publish it on CurseForge (guide), WoWInterface (guide) and/or wago.io.
If you want to cheat and rather start with a complete example it’s available here: HelloWorld.zip

Follow-up: Ace3 for Dummies

See also

  • MMO-Champion: Creating Your Own WoW Addon

Типа лекция 3-я: Содержимое файла констант и языковых файлов.

Начнем рассмотрение содержимого с файла MVUAConsts.lua.

MVUA = {
	Name = "MVUA",
	Version = "0.0.0.1",
	Const {
		aaa = "aaa",
		ColCount = 5,
	},
}
MVUA.bbb = "bbb"

В начале создаем глобальную переменную с именем MVUA и инициализируем ее как таблицу. Внутри этой таблицы мы будем создавать все переменные, константы и функции аддона. По сути, это реализация объектно ориентированного стиля программирования, а таблица MVUA у нас будет корневым объектом аддона. В языке LUA переменные и функции могут быть глобальными или локальными. Нужно стараться, чтобы в коде аддона было минимум глобальных переменных и функций, т.к. такую переменную могут перетереть или переписать другие аддоны. Или наоборот, наш аддон перепишет переменную или функцию другого аддона или вообще пользовательского интерфейса клиента игры. Такие случаи приводят к глюкам в игре, которые очень трудно отследить и устранить. Если аддон маленький и весь содержится в одном файле, то все функции и переменные можно объявить как локальные. В нашем случае файлов несколько, и для того чтобы функции видели переменные и функции из других файлов без глобальных переменных не обойтись. При описываемом подходе глобальная переменная будет всего одна — MVUA.

В таблице MVUA создаем переменную Name и присваиваем ей имя аддона. Переменную Name мы будем использовать для регистрации событий, обрабатываемых аддоном. Переменная Version нужна для отображения версии в окне аддона или в окне настроек аддона. Ей нужно присвоить такое же значение как и в файле MVUA.txt в директиве Version. Соответственно при изменении версии аддона, она меняется в двух местах — в файле описания и в файле констант. Создавать переменную Version приходится потому, что через API получить значение директивы не получается. После этих двух переменных описываются и инициализируются другие переменные и константы. Описание констант лучше вынести в отдельную таблицу, в данном случае Const. Переменные можно описывать как внутри инициализации таблицы MVUA (пример переменная Const) так и после (пример переменная bbb). Если констант много, то будет удобно разделить их по смыслу на несколько групп и для разных групп использовать разные таблицы внутри таблицы Const.

Постарайтесь вынести в файл констант все, не зависящие от языка, константы из текста аддона, чтобы в LUA коде основного модуля не было явных констант. Поясню мысль на примере. Пусть аддон рисует в своем окне таблицу из 5 колонок. И в основном коде есть несколько функций изменяющих информацию в этих колонках. Указание в этих функциях в цикле явного количества колонок, например

for i = 1, 5 do
	MVUA.ColCtrl[i]:SetText(MVUA.i18n.ColName[i])
end	

плохая идея. Если в будущем понадобится увеличить количество колонок, то выискивать по коду все места, где было упоминание количества колонок, накладно. Правильнее будет в файле MVUAConsts.lua определить константу MVUA.Const.ColCount и присвоить ей значение 5, а в коде уже писать:

local c = MVUA.Const
local l = MVUA.i18n
local r = MVUA.ColCtrl
for i = 1, c.ColCount do
	r[i]:SetText(l.ColName[i])
end	

В данном случае применена еще одна хитрость. Дело в том, что доступ к локальным переменным идет в разы быстрее чем к глобальным, по этому до цикла мы определяем три ссылки на таблицы — c, l и r. По скольку ссылки являются локальными переменными, доступ к ним будет быстрее, а более долгий доступ к глобальной переменной MVUA будет выполнен один раз для каждой из ссылок. Более того, использование ссылок позволило нам сократить количество операций поиска индекса (это когда внутри таблицы ищется переменная или функция). В первом примере глобальные переменные индексировались 10 раз, а локальные 20, во втором примере 3 и 16 раз соответственно. В дальнейшем опишу используемый для подобных целей макрос «:» и переменную self.

Далее рассмотрим содержимое языковых файлов en.lua и остальных.

Есть несколько способов организовать поддержку нескольких языков в аддоне. Можно, как рекомендуют ZOS’ы, использовать глобальную переменную EsoStrings и для работы с ней стандартные функции ZO_CreateStringId и SafeAddString, но мне этот способ не нравится, т.к. в таблица EsoStrings глобальная и в ней уже содержится очень много строк клиента игры, да и некоторые аддоны туда тоже строк добавляют. Правда у ZOS’го способа есть и приимущество — добавленную строку можно использовать из нескольких аддонов. Да и некоторые функции ZOS’ов требуют, чтобы им параметры передавались таким образом.

Я же использую другой подход — определяю текстовые константы. Например, файл en.lua:

MVUA.i18n = {
	TabName = "Table",
	ColName = {"Column 1","Column 2","Column 3","Column 4","Column 5"},
}

и ему соответствующий ru.lua:

MVUA.i18n = {
	TabName = "Таблица",
	ColName = {"Колонка 1","Колонка 2","Колонка 3","Колонка 4","Колонка 5"},
}

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

обязательно

нужно добавить

во все

языковые файлы. Можно воспользоваться другим подходом, при котором все строки обязательно должны быть только в en.lua, а в остальных языковых файлах только те, для которых есть перевод. Например, файл en.lua:

MVUA.i18n = {}
local l = MVUA.i18n
l.TabName = "Table"
l.ColName = {"Column 1","Column 2","Column 3","Column 4","Column 5"}

и ему соответствующий ru.lua:

local l = MVUA.i18n
l.TabName = "Таблица"
l.ColName = {"Колонка 1","Колонка 2","Колонка 3","Колонка 4","Колонка 5"}

Обращаю внимание, что при таком подходе таблица MVUA.i18n инициализируется только в английском файле, а в остальных переписываются только значения переменных.


Изменено 30 октября, 2017 пользователем ForgottenLight

So you want to write a WoW addon? Well, you better get used to crappy docs, old websites, and skimpy examples. The fact that it’s all community driven and nothing official from Blizzard doesn’t make it any easier. I recently wrote my first addon for classic called Track Sales, so I know your pain. It’s not all doom and gloom though, there’s really only a few key concepts to understand and everything else falls into place. Though this guide isn’t specific to retail or classic, please use the retail version to follow along. The version I wrote this with is 80200.

First and foremost, if you’re struggling with your addon, the best advice I could give you is not to google, not to read the docs, not to read this post, but to read other addons! I’ve found hands down that has been the best way to learn how to do things. Generally, googling and reading docs are great tools but when developing Track Sales nothing was better than seeing how other people did things. Aim for smaller addons that will be easier to understand. Reading other people’s code is always an excellent way to learn (for anything). You’ll be amazed at the things you can learn just from reading someone else’s code. Clone your favorite open source project and check it out someday!

Hello Azeroth

We’ll start with a Hello World example. In _retail_/Interface/Addons create a folder named MyAddon, inside create a lua file named MyAddon.lua. Place this statement print("Hello Azeroth") in the file.

Next we need to tell WoW about our addon, this is done in the toc file. Create a new file named MyAddon.toc and add the below snippet.

# Your client version is probably different than 80200
# In game run "/run print((select(4, GetBuildInfo())))" and use that for the interface number

## Interface: 80200
## Version: 1.0.0
## Title: My Addon 
## Notes: My First Addon
## Author: You 

MyAddon.lua
# additional lua files

This tells WoW what client version our addon supports (if the current version is newer than 80200 you’ll see the “addon out of date” message), our addon name, and the files our addon needs. We’ll come back to the toc file later but for now load up WoW. On the character select screen check to see My Addon is listed in the addon menu. If it isn’t then double check that everything is correct. When you load into the game you should see “Hello Azeroth” appear in the chat.

Key Concepts

Building an addon can really be boiled down to these concepts. The rest is filling in the details of your addon.

  • Events
  • Hooks
  • Saved Variables
  • Ace (3rd party helpers, optional)
  • Console Commands
  • UI (sorry, I won’t be covering this, see Extended Character Stats for ideas)
  • Testing

Events

Events are fired when various actions happen in the game world ie open mail, learned a new spell, etc. We can register to events with the code below.

MyAddon = { }

local frame = CreateFrame("Frame")
frame:RegisterEvent("MAIL_SHOW")
frame:RegisterEvent("AUTOFOLLOW_BEGIN")

-- this syntax doesn't work in classic * 
frame:SetScript("OnEvent", function(this, event, ...)
    MyAddon[event](MyAddon, ...)
end)

function MyAddon:MAIL_SHOW()
    print("Mail Shown")
end

function MyAddon:AUTOFOLLOW_BEGIN(...)
    local player = ...
    print("Following "..player)
end

MyAddon = { } is essentially just a namespace. Lua doesn’t actually have namespaces but it’s the same concept as many languages such as c#, java, and c++. With this we can avoid using global variables.

Unfortunately, I can’t share much about CreateFrame other than “this is how you register events”. You’ll also use frames if you want to create a UI.

I have no idea what that crazy SetScript syntax is I just know that’ll dispatch all events efficiently. It works by convention so you’ll have to make sure the function name matches the event name. If you don’t want to be restriced to that convention AceEvent can help.

When you open the mailbox and start following a player you should see the messages appear. You can find other events here.

Hooks

Hooks are very similiar to events except that hooks fire before or after the original function (the built in WoW function). Hooks allow you to modify the parameters that get passed to the original function or if you wish, not call the original function at all…so you could really mess things up. “Secure Hooks” are the safe version to use. To create a hook we’ll use hooksecurefunc which is a “safe post-hook”, for your use case you may need pre or post hooks, docs.

MyAddon = { }

hooksecurefunc("PickupContainerItem", function(...) MyAddon:PickupContainerItem(...) end)
hooksecurefunc("TakeInboxMoney", function(mailId) MyAddon:TakeInboxMoney(mailId) end)

function MyAddon:PickupContainerItem(container, slot)    
    local _, _, _, _, _, _, link = GetContainerItemInfo(container, slot)

    print(link)
end

function MyAddon:TakeInboxMoney(mailId)   
    local _, _, sender, subject, money = GetInboxHeaderInfo(mailId)    
    
    print("Sender "..sender, "Subject "..subject, "Money "..money)
end

GetContainerItemInfo and GetInboxHeaderInfo are WoW apis, you can find other functions here. If you LEFT click an item in your inventory you should see it’s item link printed. To test TakeInboxMoney send some gold to an alt and loot it from mail. Don’t autoloot it! That would need the AutoLootMailItem hook. If it’s not working, try disabling other addons and try again. There can be some intricacies to hooks, see the notes section. Alternatively, try the AceHook package.

Saved Variables

SavedVariables are values saved to disk that can be used between game sessions. The values are stored in the WTF folder (Warcraft Text Files not WTF) upon logout. There are two types of saved variables, SavedVariables and SavedVariablesPerCharacter. They are saved on a per account and per character basis respectively. Don’t get tricked like I did, they’re nothing but global variables! You just have to add the saved variables to the toc file.

## SavedVariables: AccountDB
## SavedVariablesPerCharacter: CharacterDB

# you can use multiple variables comma delimited

In your addon you’ll usually want to initialize these the first time someone logs in with it. The PLAYER_LOGIN event (OnEnable if using Ace) is probably the best place to do this because I found some of the WoW apis aren’t ready at other times. Let’s add a login counter to demonstrate.

MyAddon = { }

local frame = CreateFrame("Frame")

-- trigger event with /reloadui or /rl
frame:RegisterEvent("PLAYER_LOGIN")

frame:SetScript("OnEvent", function(this, event, ...)
    MyAddon[event](MyAddon, ...)
end)

function MyAddon:PLAYER_LOGIN()

    self:SetDefaults()

    AccountDB.loginCount = AccountDB.loginCount + 1  
    CharacterDB.loginCount = CharacterDB.loginCount + 1

    print("You've logged in "..AccountDB.loginCount.." times")
    print(UnitName("Player").." logged in "..CharacterDB.loginCount.." times")
end

function MyAddon:SetDefaults()
    
    if not AccountDB then 

        AccountDB = {
            loginCount = 0
        }

        print("Global Initialized")
    end

    if not CharacterDB then 

        CharacterDB = {
            loginCount = 0
        }

        print("Character Initialized")
    end
end

Ace

If you’ve been reading about addons you’ve probably heard about Ace. At first I thought there was something special between WoW and Ace but nope, Ace is nothing but a 3rd party wrapper library over the WoW API. It’s designed to make some things simpler to do, and I say some because for your use case Ace might just get in the way (I ditched AceDB). Only use Ace if you really need it, some modules have a steep learning curve and it’s easy to get bogged down in the details of Ace instead of focusing on the important parts – writing your addon! In this post I’ll show how to use AceHook and AceConsole. Ace Console will be used for chat commands and also contains Ace’s Print function.

Download Ace from the site and extract the files somewhere. In your addon folder create a new folder named libs. Copy the folders LibStub, AceAddon-3.0, AceConsole-3.0, and AceHook-3.0 into libs. Next create a new file named embeds.xml in your addon’s root folder and add the following

<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..FrameXMLUI.xsd">
	<Script file="libsLibStubLibStub.lua"/>
	<Include file="libsAceAddon-3.0AceAddon-3.0.xml"/>
	<Include file="libsAceConsole-3.0AceConsole-3.0.xml"/>
	<Include file="libsAceHook-3.0AceHook-3.0.xml"/>
</Ui>

Now we’ll tell WoW about these libraries by adding the embeds.xml to the toc file

## Interface: 80200
## Version: 1.0.0
## Title: My Addon 
## Notes: My First Addon
## Author: You

MyAddon.lua
embeds.xml

With Ace embedded now we can aceify the lua file

--the ace way of initializing addons, add the ace modules 
--you want to use as trailing parameters in :NewAddon()
MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceConsole-3.0", "AceHook-3.0")

function MyAddon:OnInitialize()				

    self:SecureHook("PickupContainerItem")

    local frame = CreateFrame("Frame")
    frame:RegisterEvent("MAIL_SHOW")
    frame:RegisterEvent("AUTOFOLLOW_BEGIN")

    frame:SetScript("OnEvent", function(this, event, ...)
        MyAddon[event](MyAddon, ...)
    end)

    self:Print("Hello from Ace Console")
end

function MyAddon:OnEnable()
    --initialize Saved Variables and other start up tasks
end

function MyAddon:PickupContainerItem(container, slot)    
    _, _, _, _, _, _, link = GetContainerItemInfo(container, slot)

    self:Print(link)
end

function MyAddon:MAIL_SHOW()
    print("Mail Shown")
end

function MyAddon:AUTOFOLLOW_BEGIN(...)
    local player = ...
    print("Following "..player)
end

Now your addon is bootstrapped with Ace. What changed is how the addon is initialized and the new OnInitialize and OnEnable methods. OnInitialize can be used to register events, create hooks, and anything else you might need to do on startup.

If it all worked you should see Hello from Ace Console printed when you log in. Open the mailbox and follow random players, those should still work too.

Console Commands

To use slash commands in your addon you can use Ace Console to register commands and parse arguments.

MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceConsole-3.0")

function MyAddon:OnInitialize()				

    self:RegisterChatCommand("myaddon", "SlashCommands")
end

-- /myaddon 1 2 3
function MyAddon:SlashCommands(args)

    local arg1, arg2, arg3 = self:GetArgs(args, 3)	
    
    self:Print(arg1, arg2, arg3)
end

You can choose to handle arguments manually or use Ace Config, it all depends on your use case. I’d start with manual first.

UI

Sorry, I didn’t write a UI for Track Sales so I won’t be covering it. You can check out an early version of Extended Character Stats for some general ideas, visit curse for the latest. I advise you do this piece last and make sure the backend works first. Developing the UI and backend together is already hard enough no matter what environment you’re in.

Testing

The hardest part about developing an addon is testing it. Sadly, there’s no WoW sandbox or “Addon Dev Island” you can go to and have unlimited ability to test. Everything will have to be done manually in game. One nice thing is that you can call your addon’s functions any time with /run MyAddon:FunctionName(). With this it’s usually best to isolate the WoW api calls as best you can and have most of your addon’s logic in functions that you can easily test with /run. You’re also going to have to sprinkle Print statements everywhere. There’s a couple debug commands that may be useful as well. To have code changes take effect run /reloadui or /rl.

What Tripped Me Up

A couple things that weren’t immediately obvious to me was how to call functions in other files and how to reference Ace modules in other files. This is done by variables that get passed in from WoW to every lua file. You can access them by placing local addonName, ns = ... at the top of any file. addonName will be a string containing your addon name and ns acts as a namespace. This thread details it well.

To see an example add two new files MyAddonModule.lua and MyAddonHelper.lua Don’t forget to add them to the toc file!

-- MyAddon.lua

MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")

function MyAddon:Test()				
    
    MyAddonModule:SayHello()
end

-- MyAddonModule.lua

MyAddonModule = MyAddon:NewModule("Module", "AceConsole-3.0")

local addonName, ns = ...

function MyAddonModule:SayHello()				

    self:Print("Hello from Module")
    ns:SayHelloHelper()
end

-- MyAddonHelper.lua

local addonName, ns = ...

function ns:SayHelloHelper()				

    print("Hello from Helper")
end

Test with /run MyAddon:Test()

With these key concepts you’ll be able to build any addon. The tricky part is figuring out which hooks and events you need. ctrl + f is going to be your best friend here, becoming familiar with the naming convention helps as well. There’s always forums like the WoW addon reddit if you have questions.

Further Reading

  • Coding Tips
  • Hooks Api
  • Events Api
  • Classic Specific Info *
  • Ace

← WoW AddOn

The following is a guide for getting started with building WoW AddOns. Creating your own AddOns can be a difficult process at first, but can be very satisfying and is a great way to learn programming in general.

If you are mostly interested in what files and structure comprise a WoW AddOn, you can skip to WoW AddOn, and come back here or move on from there.

What should I make[edit]

Pick a simple goal or idea for your AddOn. It’s often a good idea to just make an AddOn that only opens a window or prints a message to get started, especially when everything is new for you. After that works the way it should, and you have learned everything necessary to make an AddOn work at all, you can move on to adding one piece at a time toward your goals. Writting software in general is often a never ending process because you can always think of something new to add or improve.

Examples of good questions to ask yourself:

  • What’s my end goal?
  • What information will I need from WoW for my AddOn to work?
  • Will I need to make windows (called ‘frames’ in WoW)?
  • Will I need to interact with Blizzard’s windows or frames?
  • Will I need to print messages to the chat box?
  • Do I know how to talk to WoW in my AddOn to do everything I want to do?

How do I make it[edit]

Research the programming environment:

  • Lua — This is the programming language used by WoW for UI AddOns.
  • WoW AddOn — These pages explain the basic structure of a WoW AddOn.
  • World of Warcraft API — These are the functions and ways to talk to WoW with Lua.
  • Curse Forge or WoW Interface — Find a real but simple example of an existing AddOn. Most real AddOns, even the simple ones, however can be difficult to read for getting started, with many moving parts.
  • Lua editors — A long list of code editors and other tools often used by other AddOn creators.

Start with making a simple AddOn that has almost no moving parts, where you can see what all of the basic parts are and move forward from there. Use the reference material described above, especially WoW AddOn, as that describes what really makes up an AddOn.

After completing your first one you will then have enough skill to start to build the real AddOn you wanted to originally make.

The following sections help with some of the various aspects of completing these first steps.

Lua[edit]

Lua is a language used by many different games for their customizable UI AddOn code and other code. In WoW, if you learn a few Lua basics it will go a very long way to creating your first WoW AddOn, and toward understanding all of the other documentation and help here.

World of Warcraft API[edit]

The World of Warcraft API has a list of functions that you can interact with and to talk with WoW using Lua. These are your building blocks for manipulating WoW windows and chat boxes and so forth. Looking at these for the first time can be very daunting. But don’t be discouraged. After getting more familiar with Lua the WoW API gets much easier, and the documentation gets much easier to read. Keep in mind there are many different ways to accomplish your goals, so when going though the API keep looking until you find a way that best suits what you need.

Editing Tools[edit]

Before you can write any sort of code, you’ll want to have a tool that lets you edit your AddOn files. However do not get overly distracted with the tools themselves while trying to get your first AddOn created. Any text editor, like Windows Notepad can edit the files. All AddOn files are plain text files. The editors can make the coding and many repetitive tasks easier for day to day editing, but it’s often very difficult to learn the tools and try to learn the AddOn at the same time.

To review the list of tools and get one you’d like, head to the Lua editors page.

Anatomy of a WOW Addon[edit]

Strongly suggest looking here: WoW AddOn, and then come back here to this section, to review this mini-review of the same material to put into better context. The WoW AddOn page shows all of the AddOn elements, where then are explained here for getting started.

File Types[edit]

There are three main types of files that you’ll need to worry about with AddOn editing:

  • TOC file — This file is required for any WoW AddOn, and supplies WoW with information about your AddOn and its files.
  • LUA file — This contains Lua code for your AddOn.
  • XML file — This holds the layout of UI windows, buttons, and similar for your AddOn.

TOC File[edit]

This is the file that instructs WoW in some basics about your AddOn and what other files you want loaded. Here’s a short example of one. If you pasted this code into a ‘MyAddOn.toc’ file it would work as a beginning toc file:

## Interface: 80205
## Title : My AddOn
## Notes: This AddOn does nothing but display a frame with a button
## Author: My Name
MyAddOn.xml
MyAddOn.lua

You can read more about what you need or can put in a TOC over at The TOC Format.

Lua Files[edit]

Lua files contain functional pieces of code. You may choose to only have one of these or break up your code into multiple files for a specific purpose. Here’s a short example of one:

function MyAddOn_OnLoad()
    SlashCmdList["MyAddOn"] = MyAddOn_SlashCommand;
    SLASH_MYADDON1= "/myaddon";
    this:RegisterEvent("VARIABLES_LOADED")
end
function MyAddOn_SlashCommand()
    print("Hello, WoW!")
end

XML Files[edit]

XML files can be used to specify the visual style of your frames. More importantly though a frame allows you to easily pass events to your Lua files. Frame XML files are how Blizzard defines most of their own UI windows in the game. Check out XML User Interface for details. Here’s a short example of an XML file:

<Script file="MyAddon.lua"/> 
<Frame name="MyAddon"> 
    <Scripts> 
        <OnLoad> 
            MyAddon_OnLoad();
        </OnLoad>
    </Scripts>
</Frame>

Start Coding[edit]

Start with the tutorials extracted by the Interface AddOn Kit. The WoW HOWTOs here has a ton of great examples to help you learn. Don’t be shy to dig through someone else’s AddOn for help, just be sure to not steal code, and give credit where credit is due.

Beware Lua functions and variables are globals by default and are directly visible to the other addons. If authors blindly use simple and common names, like:

addonEnabled = true;
function GetArrowsCount()
  ...
end

an addon will easily conflict with variables of the same name from other addons or from the Blizzard interface. Instead, you should write your addon more like a Lua module: place all the data and functions in a single global table, named after the addon, and use local declarations otherwise (no other globals), like:

MarksmanAddOn = { };              -- about the only global name in the addon
local addonEnabled = true;        -- local names are all ok
function MarksmanAddOn:GetArrowsCount()
  ...                             -- function is now inside the MarksmanAddOn global table
end
MarksmanAddOn.arrowsCount = MarksmanAddOn:GetArrowsCount()

For large add-ons this can get complicated, and the current version of Lua doesn’t offer much to help. Use a full, clear, name for the global table, and assign a shorter name in your .lua files, like:

local mod = MarksmanAddOn;

-- then you can write
mod.arrowsCount = mod:GetArrowsCount()

Other than the global table for the addon, you may still need globals for the saved variables and the addon slash commands (see below) if you have any.

Slash Commands[edit]

A slash command is one way for the user to interact with your AddOn. These will be the easiest way for a beginner to let the user supply command and change options.
The HOWTO: Create a Slash Command page covers this pretty well.

A Basic UI Frame[edit]

The XML User Interface page covers a lot of a great basics.

SavedVariables[edit]

The Saving variables between game sessions article covers the key points. For folks new to AddOns but not other programming languages, just bear in mind that:

  • The SavedVariables is only read from on UI loading, not real time, and only saved to on UI unloading. This will generally be from a user logging in or logging out.
  • Basically, WoW makes a SavedVariables file for you named after your AddOn. You only get to specify in your TOC *which* variables get saved into that file.

Advanced but Important Topics[edit]

Getting the WoW API[edit]

After learning all of the basics, there are times where you may want to see how the Blizzard UI code works to help you in making your own AddOn. And getting information on how the WoW UI works can be helpful to making your AddOn function.

See Extracting WoW user interface files for getting the Blizzard UI AddOn source code which can also be used as a reference.

Localization[edit]

It’s a good idea to plan from as early as possible to have your AddOn be localizable, even if you yourself only speak one language. Review this great article for some things to think about: HOWTO: Localize an AddOn.

External links[edit]

Icon-edit-22x22.png

Note: This is a generic section stub. You can help expand it by clicking Sprite-monaco-pencil.png Edit on the section title.

External link FreeKode.org World of Warcraft first addon May 3, 2015

External link GitHub freekode/TestAddon

By GalacticGlum


Last Updated:
2021/02/13

Changelog
Patch: 8.2.5

Favorite:
Rating: 3.9/5
( 41
Votes)
Table of Contents

Понравилась статья? Поделить с друзьями:
  • Как написать адаптивную образовательную программу
  • Как написать агрессия правильно
  • Как написать агрессивный бит
  • Как написать агрегатор
  • Как написать агитационный пост