Writing your first addon

From ESOUI Wiki

Revision as of 06:34, 13 May 2014 by Aiiane (Talk | contribs)
Jump to: navigation, search


So you've decided that you're interested in creating UI addons for Elder Scrolls Online. Great! Here's a walk-through to get you started. For this example addon, we'll implement a simple indicator of whether or not the player is in combat.


Addon file structure

If you're looking to write addons, you've probably already used some. The addons that you've installed live in Documents\Elder Scrolls Online\live\AddOns, though the beginning of this path may vary slightly depending on your OS. Typically each addon will have its own named directory within the AddOns directory. This keeps all of the addon's files in one tidy collection.

Within an addon's folder, there are 4 types of files:

* A .txt file specifies the addon metadata, indicating the name of the addon, what other files should be loaded, et cetera.
* .lua files contain the code for the addon.
* .xml files can contain UI definitions and templates, if desired.
* Custom resources (e.g. textures) can be included as well.

For the most basic of addons, you only need two files - the metadata file and a code file:

Elder Scrolls Online\live\AddOns
  +-- FooAddon\
        +-- FooAddon.txt
        +-- FooAddon.lua

Metadata file (.txt)

It is customary to name both the directory and the metadata file using the addon's name to make them easy to find, so if your addon is named "Foo Addon", then the directory would be FooAddon and the metadata file would be FooAddon.txt to match.

## Title: Foo Addon
## APIVersion: 100003


While there are many options you can specify in your addon's metadata file, the most important ones (and the ones that every addon should have set) are the Title and APIVersion. The title is what will be displayed in the in-game addon list, and the APIVersion needs to match the current APIVersion specified by the client or else the addon will be considered "out of date" and disabled by default (to prevent completely broken UIs when Zenimax updates the API).

After the options comes a list of other files that should be loaded for the addon. In this starter addon, all there will be is a Lua file, so that is the only file listed.

Code files (.lua)

The code for your addon is where you actually implement whatever functionality you want your addon to have. Each code file will be loaded and run when the UI is initially loaded. Your addon can have as many code files as you want - addon authors will often split up code between multiple files to help keep it organized.

Try it - create an empty addon.

Try creating the directory and the two files mentioned above. Fill in FooAddon.txt with the items mentioned above. Leave FooAddon.lua as an empty file for now. Once you've saved both files, if you /reloadui in game (or press R from the addons window), your new addon should show up in the list. It won't do much of anything yet though since it has no code.

Basic skeleton code

We'll start off by creating a small amount of code (in FooAddon.lua) designed to give our addon a little structure and help it load gracefully. Note that all of the lines beginning with -- below are comments - they don't affect the actual code.

-- First, we create a namespace for our addon by declaring a top-level table that will hold everything else.
-- Since we'll be referencing the table a lot in this file, we also create a local copy.
FooAddon = {}
local FooAddon = FooAddon
-- This isn't strictly necessary, but we'll use this string later when registering events.
-- Better to define it in a single place rather than retyping the same string.
FooAddon.name = "FooAddon"
-- Next we create a function that will initialize our addon
function FooAddon:Initialize()
  -- ...but we don't have anything to initialize yet. We'll come back to this.
-- Then we create an event handler function which will be called when the "addon loaded" event
-- occurs. We'll use this to initialize our addon after all of its resources are fully loaded.
function FooAddon.OnAddOnLoaded(event, addonName)
  -- The event fires each time *any* addon loads - but we only care about when our own addon loads.
  if addonName == FooAddon.name then
-- Finally, we'll register our event handler function to be called when the proper event occurs.
EVENT_MANAGER:RegisterForEvent(FooAddon.name, EVENT_ADD_ON_LOADED, MyAddon.OnAddOnLoaded)

This skeleton code does 3 main things:

Save FooAddon.lua with the new code. If you reload the UI, it still won't visibly do anything since we haven't yet added the actual functionality - but you shouldn't get any UI errors either.

Adding functionality

Now it's time to make our addon actually do something visible. We want to create an indicator for whether or not the player is in combat. We'll start out by just displaying a message whenever the player enters and exits combat. To do this, we're going to need to handle another event.

If you look at the list of events provided by the API, you'll find EVENT_PLAYER_COMBAT_STATE among them. This event occurs whenever the player's combat state changes. It passes along a flag which indicates the player's new combat state - true if the player is in combat, and false if the player is not in combat. We'll create a handler for this event.

For now, we'll use a function that's part of the API, d(), to output a message to the chat window. d is short for "debug" - it's intended to mostly be used by developers like you for outputting information during the development process.

Getting the initial combat state

First we'll want to add a variable to our addon's table to keep track of the player's current combat state, and initialize it when our addon loads. Change the FooAddon:Initialize function to look like this:

function FooAddon:Initialize()
  self.inCombat = IsUnitInCombat("player")

In this function, self is automatically set to whatever :Initialize() was called on - in this case, since we call the function from OnAddOnLoaded as FooAddon:Initialize(), self is just a shorter way of referring to FooAddon. This is automatic when using : to define and call functions rather than . - it's how "methods" can be implemented in Lua. You're not required to use : syntax - you could just use . and type out FooAddon.inCombat instead - but you may see this syntax in other code that you look at for examples, so it's good to know what it means.

In this case, the new code creates a variable in the FooAddon table called inCombat, and initializes it to the current combat state of the player via a call to the API function IsUnitInCombat().

Handling combat state changes

Now that we have the current state, we want to detect when it changes. We'll create and register another event handler to do this.

First, let's create the event handler that will be called when combat state changes. Add it as a new function:

function FooAddon.OnPlayerCombatState(event, inCombat)
  -- The ~= operator is "not equal to" in Lua.
  if inCombat ~= FooAddon.inCombat then
    -- The player's state has changed. Update the stored state...
    FooAddon.inCombat = inCombat
    -- ...and then announce the change.
    if inCombat then
      d("Entering combat.")
      d("Exiting combat.")

Once we've created the function, we need to register it as a handler for the event. We'll do this from the Initialize function (unlike the addon load event, which we did from the top level of the file), because we only want to start handling most events after our addon has completely loaded.

Update our initialize function so it looks like this:

function FooAddon:Initialize()
  self.inCombat = IsUnitInCombat("player")
  EVENT_MANAGER:RegisterForEvent(FooAddon.name, EVENT_PLAYER_COMBAT_STATE, FooAddon.OnPlayerCombatState)

Now if you save the file and then /reloadui in game, you should see messages appear in the first chat tab whenever your character enters and exits combat.

Adding a graphical component

A message in the chat window proves our addon is actually doing something, but it'd be nice to have something that's part of a more visual UI. Let's change it to a big red "Fighting!" notice that appears on-screen whenever we're in combat.

There are a couple of different ways to create controls (graphical units) in ESO. For this particular tutorial, we'll do it using XML definitions, but it's also possible to create controls through pure Lua code by making use of existing templates already defined by the game.

Creating a graphical control in XML

First, create FooAddon.xml - this is where our control definitions will go. Then update FooAddon.txt to list FooAddon.xml right before FooAddon.lua. (Files are loaded in the order listed, so we want to make sure our controls are loaded before our code might use them.)

Next, add the following content to FooAddon.xml:

    <TopLevelControl name="FooAddonIndicator">
      <Dimensions x="200" y="25" />
        <Label name="$(parent)Label" width="200" height="25" font="ZoFontWinH1" inheritAlpha="true"
            wrapMode="TRUNCATE" verticalAlignment="TOP" horizontalAlignment="CENTER" text="">
          <Anchor point="TOP" relativeTo="$(parent)" relativePoint="TOP" />

You're probably wondering what exactly all of this does. Here's an explanation of each part:

This is the wrapper tag for any XML file used for constructing ESO UI elements.
This container holds any definitions of controls.
Any control which isn't a child of another control is a TopLevelControl. Typically this means the highest level of organization for a given UI element - for instance the collection of all chat windows might all be in one single TopLevelControl for the chat system.
This tag is used to specify the size of a top-level control.
Once again, a container for controls - though in this case, the children of the current TopLevelControl, rather than a collection of top-level controls.
This defines a label - a piece of text that will be rendered as part of a UI element. It has various attributes used to customize how the text is displayed.
Anchors define how various controls should be positioned relative to one another. In this case, we're making the center of the top edge of our label be at the same spot as the center of the top edge of our top-level control, to make them line up neatly one inside the other.

The $(parent) placeholder is filled in by ESO when it loads the template - it automatically fills in the name of the control's parent. This can be especially useful when creating reusable templates for controls that might have different names. In this case, its usage means that the Label will wind up having the name FooAddonIndicatorLabel.

Saving settings

Further reading

Personal tools