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.http://web.archive.org/web/20130420080122/http://old.wowace.com/Ace3
==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: 
 ## 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: 
 ## 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.https://authors-old.curseforge.com/forums/world-of-warcraft/general-chat/lua-code-discussion/225419-working-with-aceconsole-3-0#c5
: 📝 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.
==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 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 with .
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 . 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.
 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: 
 ## 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
* WelcomeHome: Your first Ace3 Addon - Original tutorial
* https://github.com/ketho-wow/HelloAce - Example Ace3 template
References