Ace3 for Dummies

From Warcraft Wiki
Jump to navigation Jump to search

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.

AddOn List

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:

Tutorial Ace 2.png

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 on AceConsole and AceGUI 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 and AceComm 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.

Tutorial Ace 3.png

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.

Tutorial Ace 4.png

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

Tutorial Ace 5.gif

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

Tutorial Ace 6.png

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 of AceDB: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 like profile.
📝 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.

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
Follow-up: Using the BigWigs Packager with GitHub Actions


See also

References