AddOn Quick Questions

From ESOUI Wiki

Jump to: navigation, search

Contents

How do I install an addon?

Symlinking folders

You can use one AddOn installation over multiple user profiles by symlinking the folders from the system console like this:

Debugging for New Addon Devs

So you looked at one of the tutorials, and you did what was there but it's just not working! Here's a few easy things to trip up on: - Make sure that the name of the containing folder, and the name of the .txt file are EXACTLY the same. The string that is passed to the handler for EVENT_ADD_ON_LOADED should also match that. - Do you have a d() message in your addon intialization? Chat isn't initialized until the UI is finished loading, so you won't see it. Get pChat and add it as an optional depends on in the .txt file. - Check the .txt file and make sure the spelling of other files (specifically the main.lua file or whatever you call it) is spelled correctly and exactly.

How do I output something to the chat?

Send chat messages

You cannot send chat messages, but you are able to put text into the Chat of the client. There are two ways:


Debug Output

d() adds yellow text to the chat box. It's mostly used for debug/dump.

If you need to output d() messages from your .lua file or the loaded event, BugEaters[[1]], Pre-Init debug option is worth a look. Just don't forget to add it as optional dependency (so it is executed before your addon if present).

Alternatively, you can use pChat. (Which comes with a nice 'save chat after /reloadui function too')

How do I save settings on the local machine?

AddOn settings can be persisted on the local machine using saved variables. A saved variable is a table that your add-on declares in its text file. When your add-on is loaded, the saved variable is created in memory from its save file. When the add-on is unloaded, the table in memory is saved to a file on disk. The file is located in Windows: Documents/Elder Scrolls Online/<build flavor>/SavedVariables/<your add-on's saved variable name>.lua, Mac: ~/Documents/Elder Scrolls Online/<build flavor>/SavedVariables/<your add-on's saved variable name>.lua. To use a saved variable in your add-on:

## SavedVariables: MyAddOn_SavedVariables

In any of your lua files:

--declare local variable that will contain the settings accessor object you create in the Loaded Event
local savedVars
 
--all your other code, functions and event callbacks
 
--define Callback
local function OnAddOnLoaded(eventCode, addOnName)
    --Check if that is your addons on Load, if not quit
    if(addOnName ~= "<<your add-on's name>>") then return end
 
    --Unregister Loaded Callback
    EVENT_MANAGER:UnregisterForEvent("MyAddOn", EVENT_ADD_ON_LOADED)
 
    --create the default table
    --create the saved variable access object here and assign it to savedVars
end
 
--Register Loaded Callback
EVENT_MANAGER:RegisterForEvent("MyAddOn", EVENT_ADD_ON_LOADED, OnAddOnLoaded)
savedVars = ZO_SavedVars:New(savedVariableName, version, namespace, defaults, profile)
savedVars = ZO_SavedVars:NewAccountWide(savedVariableName, version, namespace, defaults, profile)
*For example, if you were storing a relative window position and you wanted to default it to 0, 0 then your default table might look like:
local defaults =
{
    offsetX = 0,
    offsetY = 0,
}
local offsetX = savedVars.offsetX
offsetX = 200
savedVars.offsetX = offsetX

If the SavedVariable contains int indexed values, you cannot properly get the highest number using #savedVars (it returns 0). If you need to easily iterate over the data in the saved variable, assign the table with int-indexes to a string Index, even if that results in the saved variables containing nothing but this one string index:

local savedVars
 
--In the Loaded event
--this table will be implicitly int indexed
local tbl= { "Hello", " ", "World" } 
-- Assigning the table 'tbl' to defaults table at the string index 'ExampleData'
local defaults = { ExampleData = table }
--Create the settings object as above
--Access the table in the settings, wheter default or previous sessions stored data
for i=1, #savedVars.ExampleData, 1 do
    --Output savedVars.ExampleData[i] in a meaningfull way
end

Why can't I call MoveForwardStart / TurnLeftStart / this function?

Some of the lua API functions are marked with accessControl="private." These functions cannot be called or even referenced in addons for security reasons. Look in ESO UI Documentation to see what accessControl is set for a function. No accessControl being set means that it is public.
Another group of API functions are marked with accessControl="protected". These function cannot be called while in combat, but calling them outside combat is not an issue. This restriction usually applies to actions the user cannot perform during combat.

Is there a way to dynamically create controls like buttons or labels?

Controls can be created dynamically using the CreateControlFromVirtual function and a virtual template defined in XML. The template defines what the created control will look like and can be as complex as a normal control (with sub-controls, properties, etc).

<GuiXml>
    <Button name="DynamicButton" virtual="true" text="Click">
        <Controls>
            <Label name="$(parent)InnerLabel" text="Label" font="ZoFontGame">
                <AnchorFill/>
            </Label>
        </Controls>
    </Button>
</GuiXml>

To use the template we call CreateControlFromVirtual. It has the following arguments:

for i = 1, 10 do
    local dynamicControl = CreateControlFromVirtual("DynamicButton", MyAddOnWindow, "DynamicButton", i)
end
 
DynamicButton2:SetText("2")

How do events work?

Events allow your add-on to be notified when a specific "event" happens in the game client. These events vary widely and include things like getting new inventory items, receiving chat messages, and accepting quests. You add-on is "notified" in the form of the game client running a function that you registered earlier on. For example, when the client receives a group invitation for the player, EVENT_GROUP_INVITE_RECEIVED is triggered, calling all functions that have registered to be notified of it. When the functions are ran, they may be passed arguments that offer more information about the event. The first argument is always eventCode which is a number identifying the event. In the case of EVENT_GROUP_INVITE_RECEIVED, the second argument is the name of the person that is inviting the player to the group. You can look up what arguments come with each event in the ESO UI Documentation files.

So how does this look in lua?

Event handler function

local function OnGroupInviteReceived(eventCode, inviterName)
    --print the inviter's name to chat
    d(inviterName)
end

Register the event

function MyAddOn_OnInitialized(self)
    --Register on the top level control...
    self:RegisterForEvent(EVENT_GROUP_INVITE_RECEIVED, OnGroupInviteReceived)
 
    --OR, register with a string unique to your add-on
    EVENT_MANAGER:RegisterForEvent("MyAddOn", EVENT_GROUP_INVITE_RECEIVED, OnGroupInviteReceived)
end

You may also unregister events with the UnregisterEvent function.

If your addon can benefit from splitting up parts of its functionality into separate files to make them more manageable you can use the unique string to specify a combination of say addon and file to further control what happens when. This way, for example, you can have one file that does something in the PlayerActivated event function but can now safely tell it to unregister the event for that file without affecting a file that may have to process something every time your player is activated and not only when you first log in or reload.

NOTE: Bear in mind that the order these events are triggered and acted on are initially based on the order they are in the addon txt file.

For example

(file 1):

local function OnPlayerActivated(eventCode)
    d("Player Activated")
end
 
local function OnAddOnLoaded(eventCode,addon)
    if addon ~= "MyAddOn" then return end
    EVENT_MANAGER:RegisterForEvent("MyAddOn_File1", EVENT_PLAYER_ACTIVATED, OnPlayerActivated)
end
EVENT_MANAGER:RegisterForEvent("MyAddOn_File1", EVENT_ADD_ON_LOADED, OnAddOnLoaded)

(file 2):

local function OnPlayerActivated(eventCode)
    EVENT_MANAGER:UnregisterForEvent("MyAddOn_File2", eventCode)
    d("Player Logged In/Reloaded")
end
 
local function OnAddOnLoaded(eventCode,addon)
    if addon ~= "MyAddOn" then return end
    EVENT_MANAGER:RegisterForEvent("MyAddOn_File2", EVENT_PLAYER_ACTIVATED, OnPlayerActivated)
end
EVENT_MANAGER:RegisterForEvent("MyAddOn_File2", EVENT_ADD_ON_LOADED, OnAddOnLoaded)

How does screen resolution impact the UI?

To understand how the game lays out the UI we should start by talking about UI units. UI units are what you are specifying when you set the width, height, offsets, or any other positional values of a control. They are not the same as pixels and are based on aspect ratio instead of the resolution. The number of pixels that a UI unit actually represents varies based on the resolution of the player's screen. The reason why we use UI units is it allows up to guarantee that we have at least 1680 x 1050 UI unit space regardless of the player's resolution. This means that if we wanted to put a 1680 UI unit wide window into to game, it would fit on monitors from 1280 x 1024 pixels, to 2560 x 1980 pixels, to anything else. Lets look at some examples to see how UI units map to pixels in the game.

The above examples ignore one feature of the UI which is called global scale. This is the slider in the UI options that lets you make the UI larger or smaller. Global scale functions by changing the size of the canvas to impact the relative size of the windows. A small global scale makes the canvas bigger, which makes the windows seem smaller. A larger global scale makes the canvas smaller, which makes the windows seem bigger in comparison. For example, consider a 1000 x 1000 canvas with a 500 x 500 window centered in it. At a global scale of 0.5 the canvas becomes 2000 x 2000 (Original Canvas * (1 / Global Scale)). Whereas the 500 x 500 window used to be half the height and width (500 / 1000), it is now 25% of the height and width (500 / 2000).

What are Draw Level, Layer and Tier?

For a discussion of the difference between these control ordering settings see: Control Ordering

How do I generate my own "events" in Lua?

You cannot add events to or Raise Events in the Event_Manager. All you can do is adding handlers to it. Events there are added and fired from outside of the Lua Environment (and thus are out of the range for the Addons). Instead you have to use something called the "Callback Manager" to realise custom events. Creating Custom events is a 3 step process:

1. Setup:

Callback "Events" need a ID that is unique for the instance of the CallbackManager Class they are Fired in. If you choose to use the Global Callback manager (found under the global variable CALLBACK_MANAGER), high care must be taken to not collide with other Custom Events, especially as they do not have to be "registered" beforehand in any way. All the colission concerns of Global Variables apply to Custom Events registered in the Global Callback Manager. If you want to avoid this you can create a local Callback Manager (with the Scope of your Code) for Custom Events. Just bear in mind that you have to expose it globally if you want other code to be able to register the Custom Events in your Callback Manager. You create a Local Scope Callback manager this way:

local LocalCallbackManager = ZO_CallbackObject:Subclass()

From there just replace all mentions of 'CALLBACK_MANAGER' with 'LocalCallbackManager' to get your local Callback Manager instance instead of the global, shared one.

Regardless of which Callback Manager you use (the global one or a local one), you need a unique String ID for the Event. Ideally write it down as constant into a variable. Choose anything you are certain will be unique for this instance of the Callback Manager. If you want other code to be able to register your events, don't forget to also expose their names globally. Avoid just mentioning the Event Name in the Documentation (after all if they can use your variable, they have the right name without needing to know the right name which can be important if you change it later).

2. Firing the Event:

CALLBACK_MANAGER:FireCallbacks(<Event Name>, arg1, arg2, arg3, ..., argx)

This line will run all the Callbacks/Handlers (if there are any registered under <Event Name>), hand each handler a copy of each argument and then continue. If none are registered, it will just continue without doing any work (you do not have to guard against nil/empty collection problems).

3. Registering Custom Events:

--Somewhere before, this Firing Order is given anywhere in the Code:
CALLBACK_MANAGER:FireCallbacks("QuestTrackerTrackingStateChanged", self, true, TRACK_TYPE_QUEST, 0)
 
 
 
--Define the Custom event handler, jsut like you would for a normal Event
local function OnTrackStateChanged(questTracker, tracked, trackType, arg1, arg2)
    if tracked then
        d("A quest was tracked")
    else
        d("A quest was untracked")
    end
end
 
--Register the Custom Event Handler. Note that there is No ID for regsitration and Custom Events are identifeid via Strings
CALLBACK_MANAGER:RegisterCallback("QuestTrackerTrackingStateChanged", OnTrackStateChanged)

3b Unregistering Custom Events Handlers:

CALLBACK_MANAGER:UnregisterCallback(<eventName>, <callback>)

Just hand it the same function and event name as when registering it.

Are there Keypress events that we can tie into?

If you want custom bindings (like in the keybinding window), you can make a bindings.xml file for your add-on and add them there. You can look at the bindings.xml files for examples. You will also need to make a string entry with the name "SI_BINDING_NAME_X" using ZO_CreateStringId("SI_BINDING_NAME_X", "My Custom Bind Name") where X is the name of the binding you added and it should appear in the bindings window. For example:

<Bindings>
  <Layer name="SI_LAYER_NAME_STRING">
    <Category name="SI_CATEGORY_NAME_STRING">
      <Action name="TURN_RIGHT">
        <Down>TurnRightStart()</Down>
        <Up>TurnRightStop()</Up>
      </Action>
    </Category>
  </Layer>
</Bindings>

THIS SECTION GOES IN THE LUA FILE, NOT THE BINDINGS FILE

ZO_CreateStringId("SI_BINDING_NAME_TURN_RIGHT", "Turn Right")

You can choose to add sections for up, down, or both. The code in the section will be run each time the binding is activated (<Down> for key down and <Up> for key up).

You can also make a top level window keyboardEnabled="1" in the xml and it will eat up all keypress events when it is shown. This is best used for when your window has a completely different use of the keyboard and you don't care that bindings aren't triggered. For example, if your wrote a Tetris add-on and wanted WASD to control the piece, keyboardEnabled can do it. The script handlers for keys are <OnKeyDown>, <OnKeyUp>, and <OnChar>. OnChar passes the key, while OnKeyUp and OnKeyDown pass the key, ctrl, alt, and shift arguments as booleans. Search ESO UI Documentation for KEY_ globals and you will find all the key definitions. For example, KEY_A, KEY_Z, KEY_ENTER, KEY_BACKSPACE. If you want to add controls for moving the tetris piece left and right you might do the following:

<OnKeyDown>
    if(key == KEY_A) then
        --Start Moving Piece Left
    elseif(key == KEY_D) then
        --Start Moving Piece Right
    end
</OnKeyDown>
 
<OnKeyUp>
    if(key == KEY_A) then
        --Stop Moving Piece Left
    elseif(key == KEY_D) then
        --Stop Moving Piece Right
    end
</OnKeyUp>

Can I add my own sound files to play with PlaySound()?

At the moment this is not possible. However, ZOS has confirmed that they will probably add it eventually.

Nothing seems to load from my Addon LUA file

Make sure the file end-of-line format is set to windows mode (\r\n) instead of linux mode (\n). It can silently ignore the file if you have the incorrect line ending.

In what order are Addons loaded? I want mine to load before others!

Tests indicate addon loading rules only follow dependencies. Otherwise, the loading order is undefined. There's no rule saying addon A will load before addon B, newer folders before older folders, etc.
Only if B depends on A (optional or not optional), will A be run before B. So if you want your addon to run before others, you have to ask the authors to add an appropriate Optional dependency.


Personal tools
Namespaces
Variants
Actions
Menu
Wiki
Toolbox