Ace3 for Dummies
Ace3 is an addon framework for World of Warcraft to make writing addons easier. It's however not required for writing addons and it's recommended to understand the basics first before using Ace3.[1]
Getting started
We'll begin with creating a World of Warcraft\_retail_\Interface\AddOns\WelcomeHome
folder and a .TOC file inside with the same name:
WelcomeHome.toc
## Interface: 100207 ## Version: 1.0.0 ## Title: WelcomeHome ## Notes: Displays a welcome message when you get to your home zone ## Author: YourName Core.lua
Then create an empty text file called Core.lua in the same directory and start up WoW. After logging in, you should be able to see that your AddOn is being recognized by the game.
Bringing Ace3 to the Party
Download Ace3 from https://www.wowace.com/projects/ace3/files and unzip it into a subfolder called Libs
There are lots of Ace3 libraries for you to use, but for this tutorial we only need to keep the following libs and you can delete the rest:
We also need to tell WoW to load the Ace3 files when it loads your addon.
## Interface: 100207 ## Version: 1.0.0 ## Title: WelcomeHome ## Notes: Displays a welcome message when you get to your home zone ## Author: YourName ## SavedVariables: WelcomeHomeDB ## OptionalDeps: Ace3 Libs\LibStub\LibStub.lua Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml Libs\AceAddon-3.0\AceAddon-3.0.xml Libs\AceEvent-3.0\AceEvent-3.0.xml Libs\AceDB-3.0\AceDB-3.0.xml Libs\AceDBOptions-3.0\AceDBOptions-3.0.xml Libs\AceConsole-3.0\AceConsole-3.0.xml Libs\AceGUI-3.0\AceGUI-3.0.xml Libs\AceConfig-3.0\AceConfig-3.0.xml Core.lua
- 📝 Note 1:
AceConfig
depends onAceConsole
andAceGUI
but otherwise the load order does not matter. Just copy it from the Ace3 TOC if you want to be sure.[2]
- 📝 Note 2:
AceLocale
,AceTimer
andAceComm
are not included in this tutorial.
We've included the SavedVariables TOC field which we will need eventually. The OptionalDeps field is in case a user of your addon wants to use Ace3 as a standalone addon instead of the embedded libraries in your Libs folder.
Saying Hello
Open the Core.lua file and add the following:
WelcomeHome = LibStub("AceAddon-3.0"):NewAddon("WelcomeHome", "AceConsole-3.0")
function WelcomeHome:OnInitialize()
-- Called when the addon is loaded
self:Print("Hello World!")
end
function WelcomeHome:OnEnable()
-- Called when the addon is enabled
end
function WelcomeHome:OnDisable()
-- Called when the addon is disabled
end
This is the basic structure of an Ace3 addon. The first line creates an instance of the AceAddon class using the NewAddon method.
Since we will also be printing to the chat window and accepting slash commands, we include the AceConsole
mixin which e.g. includes the Print method.
Following that are three method overrides: OnInitialize
, OnEnable
, and OnDisable
respectively. The first is executed only once when the UI loads and the next two are executed when the addon is enabled and disabled. You can Enable
and Disable
Ace addons at will.
Open or /reload the game client, you should be able to see your chat message! Before we go any further, go back over to your code and remove the print message. It is generally considered bad form for your addon to toss a bunch of text into the chat window just because it has loaded. Too many addons do this already. You can also remove the OnDisable
method since we won't be using it.
Responding to Events
This addon will give a welcome message to the player when they arrive in their home zone. How will we know they are in their home zone? Simple, we will respond to the ZONE_CHANGED game event which the game fires when the player enters a new (sub)zone.
Before we can register an event, we need to include the AceEvent mixin into our addon.
WelcomeHome = LibStub("AceAddon-3.0"):NewAddon("WelcomeHome", "AceConsole-3.0", "AceEvent-3.0")
Once we've done that, we can use RegisterEvent to respond to the ZONE_CHANGED event.
function WelcomeHome:OnEnable()
self:RegisterEvent("ZONE_CHANGED")
end
We also need to add the handler for that event. There we can compare our hearthstone location from GetBindLocation with GetSubZoneText.
function WelcomeHome:ZONE_CHANGED()
local subzone = GetSubZoneText()
self:Print("You have changed zones!", GetZoneText(), subzone)
if GetBindLocation() == subzone then
self:Print("Welcome Home!")
end
end
You might be wondering if we should call UnregisterEvent from our OnDisable override, but we don't have to do that because AceEvent
already does it for us.
Now have your character leave the area he/she is presently in. You should see your message in the chat when you change zone and if you're in the same subzone as your hearthstone location it should give you a friendly message.
Chat Commands
Now let's add support for slash commands with RegisterChatCommand.
function WelcomeHome:OnInitialize()
self:RegisterChatCommand("wh", "SlashCommand")
self:RegisterChatCommand("welcomehome", "SlashCommand")
end
function WelcomeHome:SlashCommand(msg)
if msg == "ping" then
self:Print("pong!")
else
self:Print("hello there!")
end
end
GUI and Blizzard Interface Options
But why stop at chat commands? All cool addons have a Graphical User Interface these days so WelcomeHome shouldn't be any different!
First we define our options table.
local options = {
name = "WelcomeHome",
handler = WelcomeHome,
type = "group",
args = {
msg = {
type = "input",
name = "Message",
desc = "The message to be displayed when you get home.",
usage = "<Your message>",
get = "GetMessage",
set = "SetMessage",
},
},
}
And the getters/setters for our message. It knows where to look since handler
is defined.
function WelcomeHome:GetMessage(info)
return self.message
end
function WelcomeHome:SetMessage(info, value)
self.message = value
end
📝 Note: the info table is an advanced topic, so you can ignore that for now.
Now to include our addon options to the "AddOns" tab, all we need to do is call RegisterOptionsTable and AddToBlizOptions. The latter method returns a frame we need for opening our options panel, so we save it in self.optionsFrame
function WelcomeHome:OnInitialize()
LibStub("AceConfig-3.0"):RegisterOptionsTable("WelcomeHome", options)
self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("WelcomeHome", "WelcomeHome")
self:RegisterChatCommand("wh", "SlashCommand")
self:RegisterChatCommand("welcomehome", "SlashCommand")
-- default values
self.message = "Welcome Home!"
end
We want the addon to open a GUI for our options if there's no other input and otherwise we'll handle it like a normal chat command.
function WelcomeHome:SlashCommand(msg)
if not msg or msg:trim() == "" then
-- https://github.com/Stanzilla/WoWUIBugs/issues/89
InterfaceOptionsFrame_OpenToCategory(self.optionsFrame)
InterfaceOptionsFrame_OpenToCategory(self.optionsFrame)
else
self:Print("hello there!")
end
end
Making the Message More Prominent
Now let's add display the message somewhere that is a bit more prominent, for example the UIErrorsFrame
with AddMessage. We'll also add an option for it.
local options = { name = "WelcomeHome", handler = WelcomeHome, type = "group", args = { msg = { type = "input", name = "Message", desc = "The message to be displayed when you get home.", usage = "<Your message>", get = "GetMessage", set = "SetMessage", }, showOnScreen = { type = "toggle", name = "Show on Screen", desc = "Toggles the display of the message on the screen.", get = "IsShowOnScreen", set = "ToggleShowOnScreen" }, }, }
function WelcomeHome:OnInitialize() LibStub("AceConfig-3.0"):RegisterOptionsTable("WelcomeHome", options) self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("WelcomeHome", "WelcomeHome") self:RegisterChatCommand("wh", "SlashCommand") self:RegisterChatCommand("welcomehome", "SlashCommand") -- default values self.message = "Welcome Home!" self.showOnScreen = true end
function WelcomeHome:ZONE_CHANGED() if GetBindLocation() == GetSubZoneText() then if self.showOnScreen then UIErrorsFrame:AddMessage(self.message, 1, 1, 1) else self:Print(self.message) end end end
function WelcomeHome:IsShowOnScreen(info)
return self.showOnScreen
end
function WelcomeHome:ToggleShowOnScreen(info, value)
self.showOnScreen = value
end
Saving Configuration Between Sessions
You may have noticed is that your settings aren't persisted between sessions. When you logout and back in, you will have the default settings again. WoW provides a way to save your settings with SavedVariables, but there is an Ace way to do it with AceDB:New.
local defaults = {
profile = {
message = "Welcome Home!",
showOnScreen = true,
},
}
function WelcomeHome:OnInitialize() self.db = LibStub("AceDB-3.0"):New("WelcomeHomeDB", defaults, true) LibStub("AceConfig-3.0"):RegisterOptionsTable("WelcomeHome", options) self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("WelcomeHome", "WelcomeHome") self:RegisterChatCommand("wh", "SlashCommand") self:RegisterChatCommand("welcomehome", "SlashCommand") end
"WelcomeHomeDB"
refers to our SavedVariables entry in the TOC.- We pass
true
for the third argument ofAceDB:New()
so all characters will share the same "Default" profile. Otherwise if you don't specify anything, all characters will get their own specific profile. See the AceDB docs for more information on data types likeprofile
.
📝 Example: How AceDB magic handles defaults |
---|
-- https://gist.github.com/Meorawr/8773ae5dadc187a668dba4cfa1164c1d
local defaults = {
profile = {
foo = "foo",
bar = "bar",
foobar = "foobar",
},
};
local db = LibStub("AceDB-3.0"):New("MyAddOn_SavedVars", defaults, true);
DevTools_Dump({ db.profile });
-- Modify a value; expect 'foo' to change in output.
db.profile.foo = 42
DevTools_Dump({ db.profile });
-- Re-registering defaults effectively simulates what'll happen if you change
-- a default value in an addon update.
--
-- You'll see the following effects:
--
-- * 'foo' will be unmodified as we changed its value from the default.
-- * 'bar' will reflect the new default value below.
-- * 'foobar' will be removed as it was a default that no longer exists.
-- * 'baz' will be added.
--
-- Note that if we modified 'foobar' instead of 'foo' above its entry in the
-- database would _not_ be removed.
defaults = {
profile = {
foo = 123,
bar = 456,
baz = 789,
},
};
db:RegisterDefaults(defaults);
DevTools_Dump({ db.profile });
|
After that we need to update our old variables to the new ones, e.g. self.message
to self.db.profile.message
function WelcomeHome:ZONE_CHANGED() if GetBindLocation() == GetSubZoneText() then if self.db.profile.showOnScreen then UIErrorsFrame:AddMessage(self.db.profile.message, 1, 1, 1) else self:Print(self.db.profile.message) end end end
function WelcomeHome:GetMessage(info) return self.db.profile.message end function WelcomeHome:SetMessage(info, value) self.db.profile.message = value end function WelcomeHome:IsShowOnScreen(info) return self.db.profile.showOnScreen end function WelcomeHome:ToggleShowOnScreen(info, value) self.db.profile.showOnScreen = value end
Reload your UI and nothing should change. Except now if you change any of the settings, they will be persisted across restarts.
User Profiles
The profiles tab lets users change and reset profiles.
function WelcomeHome:OnInitialize() self.db = LibStub("AceDB-3.0"):New("TestDB", defaults, true) LibStub("AceConfig-3.0"):RegisterOptionsTable("WelcomeHome_options", options) self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("WelcomeHome_options", "WelcomeHome") local profiles = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db) LibStub("AceConfig-3.0"):RegisterOptionsTable("WelcomeHome_Profiles", profiles) LibStub("AceConfigDialog-3.0"):AddToBlizOptions("WelcomeHome_Profiles", "Profiles", "WelcomeHome")
Conclusion
You have started from nothing and created an addon that uses chat commands, responds to events and provides feedback to the user in a couple of different ways. Here is the final version in case you want to cheat and go right to the end.
WelcomeHome.toc
## Interface: 100207 ## Version: 1.0.0 ## Title: WelcomeHome ## Notes: Displays a welcome message when you get to your home zone ## Author: YourName ## SavedVariables: WelcomeHomeDB ## OptionalDeps: Ace3 Libs\LibStub\LibStub.lua Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml Libs\AceAddon-3.0\AceAddon-3.0.xml Libs\AceEvent-3.0\AceEvent-3.0.xml Libs\AceDB-3.0\AceDB-3.0.xml Libs\AceDBOptions-3.0\AceDBOptions-3.0.xml Libs\AceConsole-3.0\AceConsole-3.0.xml Libs\AceGUI-3.0\AceGUI-3.0.xml Libs\AceConfig-3.0\AceConfig-3.0.xml Core.lua
Core.lua
WelcomeHome = LibStub("AceAddon-3.0"):NewAddon("WelcomeHome", "AceConsole-3.0", "AceEvent-3.0")
local AC = LibStub("AceConfig-3.0")
local ACD = LibStub("AceConfigDialog-3.0")
local defaults = {
profile = {
message = "Welcome Home!",
showOnScreen = true,
},
}
local options = {
name = "WelcomeHome",
handler = WelcomeHome,
type = "group",
args = {
msg = {
type = "input",
name = "Message",
desc = "The message to be displayed when you get home.",
usage = "<Your message>",
get = "GetMessage",
set = "SetMessage",
},
showOnScreen = {
type = "toggle",
name = "Show on Screen",
desc = "Toggles the display of the message on the screen.",
get = "IsShowOnScreen",
set = "ToggleShowOnScreen"
},
},
}
function WelcomeHome:OnInitialize()
self.db = LibStub("AceDB-3.0"):New("WelcomeHomeDB", defaults, true)
AC:RegisterOptionsTable("WelcomeHome_options", options)
self.optionsFrame = ACD:AddToBlizOptions("WelcomeHome_options", "WelcomeHome")
local profiles = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db)
AC:RegisterOptionsTable("WelcomeHome_Profiles", profiles)
ACD:AddToBlizOptions("WelcomeHome_Profiles", "Profiles", "WelcomeHome")
self:RegisterChatCommand("wh", "SlashCommand")
self:RegisterChatCommand("welcomehome", "SlashCommand")
end
function WelcomeHome:OnEnable()
self:RegisterEvent("ZONE_CHANGED")
end
function WelcomeHome:ZONE_CHANGED()
if GetBindLocation() == GetSubZoneText() then
if self.db.profile.showOnScreen then
UIErrorsFrame:AddMessage(self.db.profile.message, 1, 1, 1)
else
self:Print(self.db.profile.message)
end
end
end
function WelcomeHome:SlashCommand(msg)
if not msg or msg:trim() == "" then
-- https://github.com/Stanzilla/WoWUIBugs/issues/89
InterfaceOptionsFrame_OpenToCategory(self.optionsFrame)
InterfaceOptionsFrame_OpenToCategory(self.optionsFrame)
else
self:Print("hello there!")
end
end
function WelcomeHome:GetMessage(info)
return self.db.profile.message
end
function WelcomeHome:SetMessage(info, value)
self.db.profile.message = value
end
function WelcomeHome:IsShowOnScreen(info)
return self.db.profile.showOnScreen
end
function WelcomeHome:ToggleShowOnScreen(info, value)
self.db.profile.showOnScreen = value
end
See also
- WelcomeHome: Your first Ace3 Addon - Original tutorial
- https://github.com/ketho-wow/HelloAce - Example Ace3 template
References