Circonians Stamina Bar Tutorial

From ESOUI Wiki

Jump to: navigation, search



Contents

Tutorial Preface

I would like to say that I hope that you get something out of this tutorial and go on to create great addons. This tutorial covers the very basics from folder/file structure, events, layers & levels, up to creating a working stamina bar. What follows is by no means the best way to do everything. It is meant for beginners who have no idea how to get started. I have done my best to check everything for errors, but theres no guaranteee. I'm not an expert but there are limited resources for beginners so I thought I would share what I have learned so far. This turned out a LOT longer than I expected it to when I started, but there is a lot of information here. I hope you enjoy, have fun.


Tutorial Prerequisite

While I will try to do my best to explain everything I think you need to know It would take forever to go into everything. I will try to make at least some comments explaining everything, but I would expect you to know some basics like what are: a for loop, if statement, variable, function, parameter, string, table, scope, local, global ect... Only very basic stuff. The terms aren't used it to many places and the 'if statement' and 'for loop' are pretty easy to figure out whats going on even if you don't have that background, but if you do not have this knowledge I suggest looking for some lua tutorials and starting there.


Code Comments

First lets get the basic concept of code comments out of the way because we will be using quite frequently throughout this tutorial.
Code comments are comments that you write in your code that are not run by the compiler. They are only there for your benefit to read so that you can remember wtf you were thinking when you wrote that crazy code.

Code comments in lua code

You can make comments inside of your code, so that you can remember what your code is doing or what you need to fix, and tell ESO to ignore it. These comments can be made two ways. You can comment a single line by starting it with --

-- This line is a comment

Or you can comment multiple lines starting with --[[ and ending your comment with ]]

--[[  These Lines are 
Comments and ignored by
the 
game]]

Do keep this in mind when reading the tutorial. Any line starting with -- is a comment and any code enclosed in --[[ ]] are comments and you do not need to include them in your code. They are only there to help explain the code as you read through it.

Below are examples of using each type of comment in some code. They are actual comments I made to myself so I didn't forget what the code was actually doing while I was trying to figure all of this stuff out for myself! Don't worry about what all of that code means for now. For now just try to grasp the idea behind writing comments.
An example of using a single line comment:

function CirconianBuffs.SetupNewBuffWindow(_Control, _iconFilename)
	-- I dont need to set the control anchors, we will reset all buff icon window anchors at once later --
 
	-- Set the size of my window
	_Control:SetDimensions(IconSize, IconSize)
 
	-- Set the window to be visible
	_Control:SetHidden(false)
 
	-- Set the transparency of the window to be completely solid
	_Control:SetAlpha(1)
 
	-- Set the Draw Layer (basically means which group of drawings its in) for the window to be in Layer Group 1
	_Control:SetDrawLayer(1)
 
	-- Set the Draw Level for the window to 0 so it is drawn first
	_Control:SetDrawLevel(0)
 
	-- Theres a lot more code, but thats enough to get the idea. Hey this is another single line comment ;)
end

An example of using a multi-line comment:

--[[
The following code goes through the list of buffs and for any buff that is not current release its object
back into the ZO_Pool, and then remove it from my BuffList table
--]]
	for Cur_BuffIndex=numCurBuffs, 1, -1 do 
		if BuffList[Cur_BuffIndex].IsBuffCurrent == false then
			CirconianBuffs.BuffPool:ReleaseObject(BuffList[Cur_BuffIndex].ControlKey)
			table.remove(BuffList, Cur_BuffIndex)
		end
	end

You may wonder why I'm using --]] instead of just ]] to end the comment; this is because by simply adding a single - to the start of the opening comment line I can make that into a single-line comment instead of a multi-line comment. The last --]] will then also become a single-line comment, in effect enabling the intervening code. It's a neat trick if you want to comment/uncomment blocks of code while you're testing your addon.

Code Comments in XML

You can create comments in your xml file by using the
<!-- to start a comment and --> to end a comment.

These comments can span multiple lines. Keep this in mind when reading the xml file. The commented sections are not necessary and ESO doesn't even read then. They register as comments and they are skipped over.

Below is an example of a comment in an xml file:

<GuiXml> 
<!-- These three
lines are all commented out and
ignored by the the game -->
</GuiXml>



Event Registering

What is an Event

This may seem a little out of order...we haven't talked about any code yet, but this is a pretty important concept for creating addons and it takes more than a couple of lines to explain. I want us to get it out of the way so when we get to our lua code we can focus on everything else.

First some examples of events so that you can have an idea of what an event even is. An event could be something like when you gain experience, when you open up your inventory screen, when you gain/loose stamina, change your target, ect...
When an event occurs certain code is run to handle whatever needs to be done for that event.
In the example of gaining XP, when that event occurs a certain function somewhere is called in the game that said "oh we gained xp, I better update the XP bar." and then it processes the necessary code to do so. In our code events are all capital letters and 'most' words are separated by an underscore. Here are a few examples:


Do note that those are three different events that can occur when you gain XP. Which event fires depends on what kind of XP you gained or how you gained it.
You can read the various events that can occur in the game at the esoui wikis event page. http://wiki.esoui.com/Events

Registering Events

How do we register our code to run when a certain event happens? Here is an example of how we can make our a function run when an event occurs.

EVENT_MANAGER:RegisterForEvent(<your_addon_name_here>, <event_to_register_to_here>, <function_to_run_here>)

With code it would look like this:

EVENT_MANAGER:RegisterForEvent(CirconianStaminaBar.name, EVENT_ADD_ON_LOADED, CirconianStaminaBar.OnAddOnLoaded)

That tells ESO that

Now everytime the EVENT_ADD_ON_LOADED event occurs the function I wrote in my addon called CirconianStaminaBar.OnAddOnLoaded will run.

You can only register one function to an event.
If you try to register multiple functions to the same event only the first function registered to that event will fire. For example if in my code I did:

EVENT_MANAGER:RegisterForEvent(CirconianStaminaBar.name, EVENT_EXPERIENCE_UPDATE, CirconianStaminaBar.UpdateXPBar)
EVENT_MANAGER:RegisterForEvent(CirconianStaminaBar.name, EVENT_EXPERIENCE_UPDATE, CirconianStaminaBar.DisplayXPInChatWindow)

When the event EVENT_EXPERIENCE_UPDATE occurs ONLY the function CirconianStaminaBar.UpdateXPBar would run, because you can only register each event one time and that is the one I registered first in my code.

Unregistering Events

What if in we no longer want our code to run when an event occurs? How do we unregister an event?

EVENT_MANAGER:UnregisterForEvent(<your_addon_name_here>, <event_to_unregister_from_here>)

We don't have to specify a function for it to unregister because remember You can only register one function to an event.
An example with code:

EVENT_MANAGER:UnregisterForEvent(CirconianStaminaBar.name, EVENT_ADD_ON_LOADED)

Now everytime the EVENT_ADD_ON_LOADED event occurs the function called CirconianStaminaBar.OnAddOnLoaded will no longer run because we unregistered it.


Creating Addons

The first addon you create will probably be the most difficult for you. As of now I'm assuming you know absolutely nothing about what you need to write in your lua, txt, & xml files so we have a lot to learn just to get anything to work. After you get through your first tutorial and get everything working though it gets a lot easier. We will attempt to work on our stamina bar (status bar) in small steps.

Plan your Addon

First we have to decide what we want to do. Lets say that our main goal is to create a new stamina bar with our players name above it. This may not seem like the most useful idea for an addon, but we need to learn a few basics without making the code to complicated.


Addon Structure

Addon Folder

Addons are installed to the location:

C:\Users\<your user name>\Documents\Elder Scrolls Online\live\AddOns   (Windows)

  /Users/<your user name>/Documents/Elder Scrolls Online/live/AddOns   (Mac OS X)

When you create a folder containing your addon it must be placed in that directory in order for it to be loaded by ESO. Each addon must have its own unique folder name.
Do note that the name of your addon folder must match the filenames you use for your lua, xml, txt files, and addon name (the name you assign in your lua code file).
Well, that's not entirely true there are some exceptions, but this is a beginners tutorial and I want to stick with basics & best practices. There is no reason to randomly name your files and then you can't remember which files belong to which addon. If you follow that rule you wont have any problems.

For example the addon I wrote to follow along with this tutorial I named CirconianStaminaBar. So the folder/file structure for it looks like this:

\Documents\Elder Scrolls Online\live\AddOns
                                      |
                                      +---CirconianStaminaBar
                                          |
                                          +---CirconianStaminaBar.txt
                                          |
                                          +---CirconianStaminaBar.lua
                                          |
                                          +---CirconianStaminaBar.xml


Addon Files

The main file types that will be included in your addon folder are:

	<filename>.lua
	<filename>.xml
	<filename>.txt

In case your skipping around reading only parts of this I'm going to repeat the following because it is very important!
Your addon folder must match the filenames you use for your lua, xml, txt files, and addon name (the name you assign in your lua code file).
There are exceptions to that, but this is a beginners tutorial and I want to stick with the basics. If you follow that rule you wont have any problems.
For example the addon I wrote to follow along with this tutorial I named CirconianStaminaBar. So the folder/file structure for it looks like this:

\Documents\Elder Scrolls Online\live\AddOns
                                      |
                                      +---CirconianStaminaBar
                                          |
                                          +---CirconianStaminaBar.txt
                                          |
                                          +---CirconianStaminaBar.lua
                                          |
                                          +---CirconianStaminaBar.xml


<filename>.lua
The <filename>.lua file contains the lua code that runs your addon. This is where you will do all of your coding. Some addons may have multiple <filenames>.lua, but for this tutorial we are only going to deal with one of each type of file. Having more than one file is usually done to split up large amounts of code and make it all easier to follow/read/edit.

<filename>.xml
The <filename>.xml file contains the xml code that your addon uses to create controls in your addon. A few examples of controls would be: a text label (just what it sounds like..just some text), a status bar (like a health bar), or an image (a picture you put on the screen). There are of course more types but this is a beginner tutorial and that should give you an idea of what the xml file is used for. The xml file is optional, you can create all of the controls using lua code in your lua file if you want to.

<filename>.txt
The <filename>.txt file is what tells ESO what files to load/run to make your addon work.


File Structure

txt Structure

Below is the basic structure of a <filename>.txt:

## Title: Circonians Stamina Bar
## Description: Circonians Stamina Bar Version 1.0
## APIVersion: 100008
## OptionalDependsOn: LibStub
## DependsOn: LibAddonMenu-1.0
## SavedVariables: CirconianStaminaBarVariables
 
CirconianStaminaBar.lua
CirconianStaminaBar.xml

Now lets break it down line by line and see what is going on.


## Title: Circonians Stamina Bar

This line tells ESO what the name of your addon is. It is also the addon name that users will see when they open up their add-ons menu in game.


## Description: Circonians Stamina Bar Version 1.0

This line tells ESO to write a description under your addon name in the users add-ons menu (the in-game menu where you select what addonds to run).


## APIVersion: 100008

This should be set to the current API version for ESO. As of this writing (5/25/2014) the current version is 100004. If the most current version number is not used the addon will be considered out-of-date and will not be loaded when a user logs into the game (unless they have the special option to run out of date addons checked, but this option is off by default). How do you know what the most current API version is? Check the esoui wiki main page.http://wiki.esoui.com/Main_Page


## OptionalDependsOn: LibStub

This tells ESO that your addon uses the library LibStub (a versioning library that allows other libraries to easily register themselves and upgrade). The Optional DependsOn part tells ESO that if the user has LibStub installed then your addon wants to use it, but its OPTIONAL. If it doesn't exist, no big deal. We are not going to go into detail about libraries for this tutorial, but LibStub is a library that developers can use.

A library is basically just a bunch of code that someone wrote and packaged up so that others can use their functions without having to recreate all of the code themselves.
We will not be using this in the beginner tutorial, but I wanted to explain what to explain how to read it in case you see it if you go looking through the code of other addons.


## DependsOn: LibAddonMenu-1.0

This is the same as above except it tells ESO that your addon DEPENDS on LibAddonMenu-1.0 (a library used to create in game settings windows for your addon). Meaning that your telling ESO that your addon can not function without it. So if the user does not have LibAddonMenu-1.0 installed your addon will not load. This is to prevent errors from occurring. We are not going to go into detail about libraries for this tutorial, but LibAddonMenu-1.0 is a library that developers can use.

A library is basically just a bunch of code that someone wrote and packaged up so that others can use their functions without having to recreate all of the code themselves.We will not be using this in the beginner tutorial, but I wanted to explain what to explain how to read it in case you see it if you go looking through the code of other addons.


## SavedVariables: CirconianStaminaBarVariables

This is used for saving user settings. It tells ESO to save stuff (you have to add certain code to your lua file and tell it what to save) to a variable called CirconianStaminaBarVariables inside of the SavedVariables folder located at: C:\Users\<your user name>\Documents\Elder Scrolls Online\live\SavedVariables The file name that it uses in the SavedVariables folder will be the same name as your addon name.


  

Yes, I know there are blank lines up there. It may be obvious to some, but in case you were wondering the blank lines in the txt file have no significance. They dont do anything. We only put them in there as a visual reference to help split up the code and make it easier to read.


The lines next lines below tell ESO what files it needs to load to make the addon work. Not that the starting directory for the path is your addons folder.

Libs/LibStub/LibStub.lua
Libs/LibAddonMenu-1.0/LibAddonMenu-1.0.lua

These first two lines represent libraries that I'm telling ESO to load. A library is basically a bunch of code or functions that someone has written and put into a nice neat package for you so that you don't have to recreate any of the work/code they've all ready written.


  

Yes, I know there are blank lines up there. It may be obvious to some, but in case you were wondering the blank lines in the txt file have no significance. They dont do anything. We only put them in there as a visual reference to help split up the code and make it easier to read.


CirconianStaminaBar.lua
CirconianStaminaBar.xml

These tell ESO to load my lua and xml files. Do be very careful about what order you load your files in. I have run into cases when I was trying to write some test code to learn something but I just couldn't get it to work and finally realized that I had my files loading in the wrong order. If a file loads and attempts to use something in another file that has not loaded yet it could cause problems.

xml Structure

<GuiXml> 
<!-- 
<GuiXml> 
This is the container for our xml code, must be here. Notice it has a closing tag at the end of this document. 
-->
   <Controls>
   <!-- Open a Control tag to place controls in. -->
 
      <TopLevelControl name="CirconianStaminaBarWindow" clampedToScreen="true" mouseEnabled="true" movable="true" hidden="false">	
      <!--
      A TopLevelControl is used as a container to group other controls together. 
      This allows us to create and or edit one control (the top level control) and then controls inside of it 'can' automatically 
      inherit attributes making coding/editing easier. There is probably a lot more to it...
 
      When we create controls there are many attributes we can set. They can be set here or we could set/edit them in our lua code file.
      Don't forget all of this is optional. These controls could be completely created in our lua code without even having an xml file.
      So why would we do it here in an xml file or do it in a lua file? I don't know, personal preference maybe?
      Maybe just the visual ease of reading. Although I like doing mine in lua code, I do admit an xml file is much easier to read.
 
      Attributes: in our top level control 
         name="CirconianStaminaBarWindow"
         First we give it a name....I think that one is obvious.
      clampedToScreen="true" 
         This tells ESO that our control MUST remain on screen at all times.
         The control can not be dragged off of the screen. Note: that does not mean it has to be visible.
      mouseEnabled="true" 
         This makes it so the control can respond to the mouse. Must be set to true if you want to be able to move your control	
         around with the mouse.
      movable="true" 
         This makes the control movable. Do note though if mouseEnabled is not set to true you wont be able
         to click on it and drag it around with your mouse.  
      hidden="false" 
         This one makes the control not hidden (makes it visible). Do note there are other factors that can affect if you see 
         your control or not. For example we could set this attribute to false, but if the alpha is set to 0 we wouldn't see it.
      -->
 
         <Dimensions x="64" y="64" />
         <!-- Sets the dimensions (size) for our control. x specifies the width, y specifies the height -->
 
         <Anchor point="TOPLEFT" relativeTo="GuiRoot" relativePoint="CENTER" offsetX="0" offsetY="0"/>
         <!-- 
         Anchor is how you tell the control where to be displayed on screen. You anchor it to some other control.
 
         Attributes:
         point="TOPLEFT" 
            This is the point on THIS control that you want to anchor to something else.
            Possible options are: TOPLEFT TOPRIGHT BOTTOMLEFT BOTTOMRIGHT TOP BOTTOM LEFT RIGHT CENTER
         relativeTo="GuiRoot"  
         Specifies what OTHER control you want to anchor it to.
            GuiRoot is the main window of the game.
         relativePoint="CENTER"  
            Specifies the point on the OTHER control (in this case GuiRoot) that you want to anchor to.
         offsetX="0" offsetY="0"
            Specifies how much to offest your controls position from that anchor point.
            offsetX is horizontally:  a positive number moves it right, a negative number moves it left.
            offsetY is vertically:  a positive number moves it down, a negative number moves it up	.		   
 
         So this basically says anchor our the TOPLEFT corner of our TopLevelControl to the CENTER of GuiRoot (the main screen)
         and offset it horizontally by 0 and verticaly by 0-1
         -->
 
         <Controls>
         <!-- 
         Now lets create more controls inside of our TopLevelControl. Seek a higher power....if your wondering why
         we need to open 'another' control tag...were all ready in a control tag why do we need another?
         This is how we specify that these controls are children of our TopLevelControl (which makes TopLevelControl a Parent)
         Since we are going to create controls inside of a control tag...inside of the TopLevelControl, that makes
         the TopLevelControl a Parent control....and any controls we create in the control tag below are children of the TopLevelControl.
 
         Children? Parent? What?
         When we have a
         Parent/Child structure of controls the children can inherit attributes from its parent. Which might mean if I set the parent window to be hidden 
         then the children are also hidden, the because the children inherit those (movement/hidden) attributes from their parents.
 
         There are some limitations to this. Can a child inherit everything from its parent? Does it by default?
         I have no clue, seek a higher power than me. But, do note that we could edit that inheritance in our code if we wanted so that a child 
         didn't inherit certain attributes. Or we could let it inherit them, but then just change the attributes later in our code.
         -->
 
            <Backdrop name="$(parent)Backdrop" edgeColor="FF0000" centerColor="6495ED" alpha="0.6" >
            <!-- 
            Create a backdrop...basically a background for our control.
            Do note that this time in our name we used $(parent)Backdrop. 
 
            $(parent) 
               Gets the name of the parent of this control (that would be CirconianStaminaBarWindow)
               and appends the word Backdrop. So our Backdrops name here is actually going to be CirconianStaminaBarWindowBackdrop.
 
            Attributes: 
            edgeColor="FF0000"
               Sets the color for the edges of our backdrop.
            centerColor="6495ED" 
               Sets the color for the center of our backdrop.
            00000000
               Do note that using this color code will make the edges/center trasparent!!
               So for example:  centerColor="00000000" would make the center trasparent, very useful!!!
            alpha="0.6"
               Sets the alpha (how transparent it is: values 0-1, 0 is invisible, 1 is completely solid) 
            -->
 
               <Dimensions x="300" y="50" />
               <Anchor point="TOPLEFT"  relativeTo="$(parent)" relativePoint="TOPLEFT"/>
               <!-- Notice here I didn't specify an offsetX or offsetY. You dont' have to. If you dont this just makes the offsets 0 -->
 
               <Edge edgeSize="6" />
               <!-- Sets the size of the edge of our backdrop. (How thick the border around our backdrop is). -->
 
            </Backdrop>
            <!-- Were done with our backdrop control, so close its tag -->
 
 
            <StatusBar name="$(parent)StatusBar" color="8A2BE2" alpha="1">
            <!-- 
            Now lets create a status bar. This is like a stamina bar. Something that you can set a minimum and maximum value for
			and then you get it a set value and it fills the bar in up to that value.
            -->
               <Dimensions x="300" y="50" />
               <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="0" offsetY="0"/>
               <!-- 
               Were going to anchor the top TOPLEFT corner of our statusbar to the TOPLEFT corner of the TopLevelControl (the parent)
               -->
 
               <Limits min="0" max="100" />
               <!-- 
               Sets the minimum and maximum values for your status bar. You could use 0 and 100.
               This way we can calculate what our % stamina is and then that would be the value we set for our status bar.
               But were actually going to end up setting the minimum to 0 and the maximum to our characters maximum amount of stamina later in our code.
               Then when we get the characters current amount of stamina we could just set that value to our status bar.
               -->
 
            </StatusBar>
            <!-- Were done with our StatusBar control, so close its tag -->
 
            <Label name="$(parent)Label" font="ZoFontWinH1" color="ffffff" text="Status Bar" verticalAlignment="CENTER" horizontalAlignment="LEFT"  alpha="0.85">			
            <!-- 
            Now lets create a label to put our characters name in.
 
            Attributes:  
            font="ZoFontWinH1" 
               This sets the font to use for our text.
               I think the rest are pretty self explanatory or we all ready went over it.
            -->	
 
               <Dimensions x="350" y="50" />
               <Anchor point="BOTTOMLEFT"  relativeTo="$(parent)StatusBar" relativePoint="TOPLEFT" offsetX="0" offsetY="0"/>
               <!-- 
               Were going to anchor the BOTTOMLEFT corner of this text label to the TOPLEFT level of $(parent)StatusBar.
               Remember that $(parent) means get the name of the parent control and append the text that comes after it so
               we are anchoring to CirconianStaminaBarWindow.
               -->
            </Label>
         </Controls>
      </TopLevelControl>
   </Controls>
</GuiXml>


You can read about various types of controls and attributes for your xml files here: http://wiki.esoui.com/UI_XML


lua Structure

CirconianStaminaBar = {}
 
-------------------------------------------------------------------------------------------------
--  Initialize Variables --
-------------------------------------------------------------------------------------------------
CirconianStaminaBar.name = "CirconianStaminaBar" 
CirconianStaminaBar.version = 1
 
-------------------------------------------------------------------------------------------------
--  OnAddOnLoaded  --
-------------------------------------------------------------------------------------------------
function CirconianStaminaBar.OnAddOnLoaded(event, addonName)
   if addonName ~= CirconianStaminaBar.name then return end
 
	CirconianStaminaBar:Initialize()
end
 
-------------------------------------------------------------------------------------------------
--  Initialize Function --
-------------------------------------------------------------------------------------------------
function CirconianStaminaBar:Initialize()
 
 
EVENT_MANAGER:UnregisterForEvent(CirconianStaminaBar.name, EVENT_ADD_ON_LOADED)
end
 
-------------------------------------------------------------------------------------------------
--  Register Events --
-------------------------------------------------------------------------------------------------
EVENT_MANAGER:RegisterForEvent(CirconianStaminaBar.name, EVENT_ADD_ON_LOADED, CirconianStaminaBar.OnAddOnLoaded)


Now lets break it down and see whats going on.


CirconianStaminaBar = {}

Creates an empty table called CirconianStaminaBar. Its empty because we don't have anything to put in it yet, but since its global declaring it at the start is a good idea. It will help us read/track our code easier. If your familiar with other languages a table is similar to an array.


-------------------------------------------------------------------------------------------------
--  Initialize Variables --
-------------------------------------------------------------------------------------------------

These three lines are not necessary. Do notice that all three lines start with -- so they are all comment lines. They are completely ignored by the game, It is only there for a visual reference so that when scrolling through my code looking for something it makes it easier to find sections of your code.


CirconianStaminaBar.name = "CirconianStaminaBar" 
CirconianStaminaBar.version = 1

This where we initialize any variables for our code. At the very least you will have a name for your addon and a version number. Here we store our tutorial name and version number into our table. Do note the table we are storing them in is the empty table we created at the start.


-------------------------------------------------------------------------------------------------
--  OnAddOnLoaded  --
-------------------------------------------------------------------------------------------------

This, again, is just a visual reference for the section of code where the OnAddOnLoaded function is.


function CirconianStaminaBar.OnAddOnLoaded(event, addonName)
--[[ check to see if its our add on that loaded. if not return 
	(means do not continue, leave this function )
 --]]
   if addonName ~= CirconianStaminaBar.name then return end
 
 --[[
If we made it this far, it is our addon so lets call our initialization function where we will do
 whatever we need to set up our addon. 
--]]
	CirconianStaminaBar:Initialize()
end -- designates the end of this function

This is the function that WILL fire when our addon is loaded. Note I said that it WILL fire because it does not happen automatically. We must tell ESO to fire this function when the addon loads. We will do that part later. Also note that we are storing this function in our table that is why the name of the function has CirconianStaminaBar. (its hard to see but theres a period after that in front of OnAddOnLoaded. Also know that we could name this anything we wanted, but that would just make the code confusing. OnAddOnLoaded makes sense and you dont want to make your code confusing.

The function takes two parameters which I've named event and addonName. Both of these parameters are needed because we are going to register this function to run when our add on loads. ESO automatically passes the event code and the addon name to any function registered to this event. Also keep in mind that when we register this function to load with the EVENT_ADD_ON_LOADED, it fires when ANY add on is loaded. That is why in our code we had to check and see if it was our addon that was loading.

ESO automatically passes the event code as the first parameter to every function registered to any event.
When you look at events to see what parameters they pass to your functions on esoui wiki http://wiki.esoui.com/Events it does not show the event code being the first parameter, but that is the way it works for every event.



-------------------------------------------------------------------------------------------------
--  Initialize Function --
-------------------------------------------------------------------------------------------------
function CirconianStaminaBar:Initialize()
 
	--[[ 
	Insert code to initialize our addon, we'll do this later. first lets just make sure we
	can get the basic stuff to run without errors.
	--]]
 
--[[ 
This next line unregisters our addon for the event EVENT_ADD_ON_LOADED. Once we've initialized
 our add on we do not want this code running again. This section of code was only to set up our add on.
--]]	
--[[ unregister the our addon from the EVENT_ADD_ON_LOADED so that code won't run again --]]
EVENT_MANAGER:UnregisterForEvent(CirconianStaminaBar.name, EVENT_ADD_ON_LOADED)
end

In this section we write code to initialize our function. Note this is only to set it up and get everything the way it should be when the add on loads. Any future changes like for our tutorial, like setting our stamina bar when your stamina changes, will be handled else where.


-------------------------------------------------------------------------------------------------
--  Register Events --
-------------------------------------------------------------------------------------------------
EVENT_MANAGER:RegisterForEvent(CirconianStaminaBar.name, EVENT_ADD_ON_LOADED, CirconianStaminaBar.OnAddOnLoaded)

This last section of code is used to register events for our functions. This makes certain parts of our code will run when certain events occur in the game.
It registers:

will run whenever the event occurs. See the tutorial section on Event Registering for more information about events. Event Registering


Write the basics

We decided that we want to create a new stamina bar with our players name above it.

I'm going to name my addon CirconianStaminaBar so we need to create a folder for our addon called CirconianStaminaBar. Inside of it we need files called CirconianStaminaBar.txt , a CirconianStaminaBar.xml, and a CirconianStaminaBar.lua.

If you forget where to put the folder see: Addon Folder
If you want a refresher about the file structure see: Addon Files


The txt File

Type out or copy the following code and put it in your CirconianStaminaBar.txt file. I would suggest typing it for yourself. You will make mistakes, but the more mistakes you make the more you learn. Then when you are trying to write another addon later and make mistakes you may know where to look because it happened before when you were writing this one.
If you forget what something is scroll up and read the section The txt File or click here: txt Structure
Do make sure you notice this is not the exact same txt file we read about above. A few lines have been left out because they are for more advanced things and we will not be covering them in this tutorial

## Title: Circonians Stamina Bar
## Description: Circonians Stamina Bar Version 1.0
## APIVersion: 100004
 
CirconianStaminaBar.lua
CirconianStaminaBar.xml

All of this code has all ready been explained above so were done with that file. If you forget what something means scroll up and read about the txt file structure or click here The txt Structure


The xml File

Type out or copy the following code and put it in your CirconianStaminaBar.txt file. I would suggest typing it for yourself. You will make mistakes, but the more mistakes you make the more you learn. Then when you are trying to write another addon later and make mistakes you may know where to look because it happened before when you were writing this one.
If you forget what something is scroll up and read the section The xml File or click here: xml File

<GuiXml> 
   <Controls>
   <TopLevelControl name="CirconianStaminaBarWindow" clampedToScreen="true" mouseEnabled="true" movable="true" hidden="false">	
      <Dimensions x="64" y="64" />
      <Anchor point="TOPLEFT" relativeTo="GuiRoot" relativePoint="CENTER" offsetX="0" offsetY="0"/>
      <Controls>
         <Backdrop name="$(parent)Backdrop" edgeColor="FF0000" centerColor="6495ED" alpha="0.6" >
            <Dimensions x="300" y="50" />
            <Anchor point="TOPLEFT"  relativeTo="$(parent)" relativePoint="TOPLEFT"/>
            <Edge edgeSize="6" />
         </Backdrop>
 
         <StatusBar name="$(parent)StatusBar" color="8A2BE2" alpha="1">
            <Dimensions x="300" y="50" />
            <Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="0" offsetY="0"/>
            <Limits min="0" max="100" />
         </StatusBar>
 
         <Label name="$(parent)Label" font="ZoFontWinH1" color="ffffff" text="Status Bar" verticalAlignment="CENTER" horizontalAlignment="LEFT"  alpha="0.85">			
            <Dimensions x="350" y="50" />
            <Anchor point="BOTTOMLEFT"  relativeTo="$(parent)StatusBar" relativePoint="TOPLEFT" offsetX="0" offsetY="0"/>
         </Label>
      </Controls>
   </TopLevelControl>
   </Controls>
</GuiXml>

Now were done with the xml file.


The lua File

Type out or copy the following code and put it in your CirconianStaminaBar.lua file. I would suggest typing it for yourself. You will make mistakes, but the more mistakes you make the more you learn. Then when you are trying to write another addon later and make mistakes you may know where to look because it happened before when you were writing this one.
If you forget what something is scroll up and read the section The txt File or click here: lua Structure

CirconianStaminaBar = {}
 
-------------------------------------------------------------------------------------------------
--  Initialize Variables --
-------------------------------------------------------------------------------------------------
CirconianStaminaBar.name = "CirconianStaminaBar" 
CirconianStaminaBar.version = 1
 
-------------------------------------------------------------------------------------------------
--  OnAddOnLoaded  --
-------------------------------------------------------------------------------------------------
function CirconianStaminaBar.OnAddOnLoaded(event, addonName)
   if addonName ~= CirconianStaminaBar.name then return end
 
	CirconianStaminaBar:Initialize()
end
 
-------------------------------------------------------------------------------------------------
--  Initialize Function --
-------------------------------------------------------------------------------------------------
function CirconianStaminaBar:Initialize()
	-- Sets the value of our status bar to 50
	CirconianStaminaBarWindowStatusBar:SetValue(50)
 
	-- Gets our characters name
	local ourName = GetUnitName("player")
 
	-- Sets the text for our label to ourName
	CirconianStaminaBarWindowLabel:SetText(ourName)
 
EVENT_MANAGER:UnregisterForEvent(CirconianStaminaBar.name, EVENT_ADD_ON_LOADED)
end
 
-------------------------------------------------------------------------------------------------
--  Register Events --
-------------------------------------------------------------------------------------------------
EVENT_MANAGER:RegisterForEvent(CirconianStaminaBar.name, EVENT_ADD_ON_LOADED, CirconianStaminaBar.OnAddOnLoaded)

With two exceptions, which I will discuss below, we've all ready discussed everything here. If you forget what something is scroll up and read about the section on The lua Structure or click lua Structure

One thing I added was:

	-- Gets our characters name
	local ourName = GetUnitName("player")
 
	-- Sets the text for our label to ourName
	CirconianStaminaBarWindowLabel:SetText(ourName)

The first part calls the function GetUnitName("player")

GetUnitName(string unitTag)
	Returns: string name
You can find out about different functions you can call here: http://wiki.esoui.com/API#Unit

unitTag can be one of the following strings:


Since were trying to get OUR name, we need to use the unitTag "player"

Where did I get those from? Check out the esoui wiki page and click on Globals or find it here: http://wiki.esoui.com/Globals

Since the function says that it returns our name as a string we need to be able to capture that. So I created a local variable (because we only need to access the name right here in this function) and assigned it the return value of the GetUnitName("player")
The second part of that:

-- Sets the text for our label to ourName
CirconianStaminaBarWindowLabel:SetText(ourName)



The other line I added is:

	CirconianStaminaBarWindowStatusBar:SetValue(50)

This sets our status bars value to 50. Remeber in our xml file we named our status bar: $(parent)StatusBar and its parent was named: CirconianStaminaBarWindow That is why we are using the control name: CirconianStaminaBarWindowStatusBar

When we want to call a function belonging to a control we do it with this syntax

	<control_name>:<function_name>(<possible_parameters>)


So its always the control name first, followed by a colon : and then the function name followed by (<possible_parameters>)
Notice I said <possible_parameters> this is because not every function requires you to pass it values.

For example if we did

	CirconianStaminaBarWindowStatusBar:GetValue()

It would return the value of the control (notice it has no parameters)

Log in and check for errors

Now log into the game, make sure that the add-on is turned on and see if it works. Hopefully all is well and your staring at an ugly status bar with your name above it.

StatusBar1.jpg



Adjustments

Now I see a couple of things I don't like so were going to fix them.

Drag Problems

First notice that if you try dragging it around on the screen that it is possible but only if you grab it with your mouse in just the right place. Mainly in this area:

StatusBar2.jpg


This is because we set the TopLevelControl to be mouseEnabled and Movable, but remember we only made the TopLevelControl 64x64. Its not very big. So clicking on that small area is the only place we can click to move it.

There are options here, we could set each of the controls to mouseEnabled and movable, but I don't want the label to get separated from the status bar. Not to mention theres no reason the TopLevel Control shouldn't be at least as big as the status bar. Lets go back to our xml and make it larger.

In your xml file and change the dimensions of our TopLevelControl:

<GuiXml> 
   <Controls>
      <TopLevelControl name="CirconianStaminaBarWindow" clampedToScreen="true" mouseEnabled="true" movable="true" hidden="false" >
      <!-- change the dimensions here to 300 by 50. This will be big enough to cover the whole status bar -->
         <Dimensions x="300" y="50" />

That should fix our drag problem, reload your ui and check it out.

The Border

Now notice we have a nice red border around...part of our background. But the status bar (purple part on the left side) is covering our border. This is a layering problem.

The basic idea behind layering is that when things are drawn on the screen they are drawn in layers and levels. In our status bar (ignoring the TopLevelControl and our label for the moment) the background image (blue partially transparent with red border) is drawn first and then the status bar (the purple part) is drawn ON TOP of that background. So it overlaps it. Since the status bar has its alpha value set to 1 it is completely solid and we can't see the border through it. Now we could change the alpha value of the s, but it would still overlap the border, the colors would mix and it wouldn't look right. You might be thinking we could draw the fill first and then the background, but look what happens if we do that: (I'll explain how shortly)

StatsBar3.jpg


It may be hard to tell in the picture above, but the color of our status bar has changed slightly (its more blue). This is because the background is blue and its overlapping the status bar. So the colors mix. Not to mention if our background was solid (alpha = 1) we wouldn't even be able to see the status bar at all. So this is not the solution.

Instead we need to change the layering for our control.

Another problem I see is that although I did want the background to be partially transparent I wanted the border to be a solid bright red. I want that ugly see through blue background, a nice solid red border, and the original fill (original purple color we specified in the XML document). So heres how were going to fix it.

Were going to edit our current background and remove the red border and we'll draw it first so we see it in the background. Were going to adjust our status bar so it gets drawn second, overlapping our purty blue background giving us the original color we desire. Last we will create another backdrop that has no center coloring (see through) but has the solid red border we desire and we'll draw this on top.

Layers & Levels...Oh My!

When things are drawn on the screen they are drawn in a certain order. We can manipulate that order by using layers and levels in our controls. Think of layers as groups of items and levels as the order they are to be drawn within that layer. An object could have multiple layers and multiple levels within each layer. phew So lets say that you had two controls. Maybe your writing an app that creates a new inventory window with slots and icons for items that go in those slots. You may have many layers & levels. We might need to set up layers so that everything draws in the right order. The basic Idea behind it would be to set up layers and levels like this.

Layer 0:
   Level 0:  Draw the Inventory screen background first.
   Level 1:  Draw the Inventory Slot background second.
   Level 2:  Draw the Inventory slot frame third.
   
Layer 1: 
   Level 0:  Draw the Item background (background of item to put into the slot) first.
   Level 1:  Draw the Item Icon second.
   Level 2:  Draw the Item frame third.


IF WE DIDN'T group those items into layers and only set levels (or vice-versa) also we would have a problem. Since the Inventory slot frame is level 2, it would be drawn over our Item background and Item Icon. Since we can group them in layers and in levels the problem could be fixed as written above.
Layer 0 would be drawn first (our inventory screen). It would draw the inventory background first, then the slot background, and then the slot frame.
Layer 1 would be drawn next. It would draw the item background, then the item icon, then the item frame.


Great! So how do I do it?

For our example we need to create a new backdrop that we can use for our border and then set up our controls so that it draws the background first, then the status bar, then the border. So first make a second copy of your backdrop in the xml file and then we'll alter everything. We want to make these changes.




On our original backdrop add the layer and level attributes, set them both to 0. Also set the edgeSize of the Edge tag to 0 or the edge color to 00000000 (both will make the edge/border disappear), we are going to draw the border in our other backdrop.

<!-- This is the original copy of our backdrop -->
<Backdrop name="$(parent)Backdrop" edgeColor="00000000" layer="0" level="0" centerColor="6495ED" alpha="0.6"  hidden="false">
	<Dimensions x="300" y="50" />
	<Anchor point="TOPLEFT"  relativeTo="$(parent)" relativePoint="TOPLEFT"/>
	<Edge edgeSize="0" />
</Backdrop>

You might be thinking the new backdrop with a border will be on top so it would hide this border anyway, why bother setting it to be transparent? Its true we don't have to. But since our plan is for it to not be seen we might as well hide it. It could be helpful in the long run. What if we made a mistake in our code and our new border didn't show up, but I could still see this old one that is partially transparent? You might spend a long time trying to figure out why your border in the new backdrop is partially transparent when you'de actually be looking at the wrong control! We have no reason to leave it visible so lets hide it.




On our status bar add the layer and level attributes. Set the layer to 0 so its in the same layer group, but set its level to 1 so it is drawn over our backdrop

<StatusBar name="$(parent)StatusBar" layer="0" level="1" color="8A2BE2" alpha="1" hidden="false">
	<Dimensions x="300" y="50" />
	<Anchor point="TOPLEFT" relativeTo="$(parent)" relativePoint="TOPLEFT" offsetX="0" offsetY="0"/>
	<Limits min="0" max="100" />
</StatusBar>



On the copy of our backdrop add the layer and level attributes.


You cant have more than one control with the same name If you do your addon wont run !!!!!!

<!-- Our second backdrop, this is the copy you made of our original backdrop -->
<Backdrop name="$(parent)Border" edgeColor="FF0000" layer="0" level="2" centerColor="00000000" alpha="1"  hidden="false">
	<Dimensions x="300" y="50" />
	<Anchor point="TOPLEFT"  relativeTo="$(parent)" relativePoint="TOPLEFT"/>
	<Edge edgeSize="6" />
</Backdrop>


Now log in and test it out. Congratulations! You should now have a...little less ugly status bar.

Example.jpg



Ok now that we've got some basic XML practice in and some controls to work with, back to the coding.


Altering our code to do stuff

In our CirconianStaminaBar.lua file lets make some alterations first get rid of this line from our function CirconianStaminaBar:Initialize()

CirconianStaminaBarWindowStatusBar:SetValue(50)

Now were going to figure out what our stamina really is and set it.

We will do this using the GetUnitPower() function.

To see what other kinds of built in functions you can call view the esoui wiki main page and click on functions. Or find it here: Unit Functions
GetUnitPower(string unitTag, CombatMechanicType powerType) 
	Returns: integer current, integer max, integer effectiveMax

But first we need to know what unitTag and CombatMechanicType to pass to the function.

unitTag can be one of the following strings:


Since were trying to get OUR stamina, we need to use the unitTag "player"

Where did I get those from? Check out the esoui wiki page and click on Globals or find it here: Unit Functions

For our CombatMechanicType we have a few options:


I think this ones pretty obvious, we need to use POWERTYPE_STAMINA

Where did I get those from? Check out the esoui wiki page and click on Globals or find it here: Combat Mechanic Types


The last thing to note is the return types.

GetUnitPower(string unitTag, CombatMechanicType powerType) 
	Returns: integer current, integer max, integer effectiveMax

This function returns 3 values.


We can capture all 3 returned values like this:

current, max, effectiveMax = GetUnitPower("player", POWERTYPE_STAMINA)


However, we really should make them local since we only need them locally in our function:

local current, max, effectiveMax = GetUnitPower("player", POWERTYPE_STAMINA)



What if we level and our maximum stamina increases? We should check that. There are better ways than doing it here, we could trap the level update event, but since were just starting out lets worry about just making it work first. Also note buffs could alter your maximum stamina so trapping the level update event alone still wouldn't be good enough.

We can call functions for controls by using :
For example our status bar has a function we can call called SetMinMax which sets the minimum and maximum values for the status bar. To call the function SetMinMax we must first use the controls name followed by : and then the function (no spaces in between) like this

CirconianStaminaBarWindowStatusBar:SetMinMax(min, max)
How did I figure that out? Check the esoui controls wiki page here: Status Bar Control

Updating Our Stamina

Now that we have that information we can set the new values for our status bar.


Lets create a new function in our lua file to update our new stamina bar.

-------------------------------------------------------------------------------------------------
--  Other Functions --
-------------------------------------------------------------------------------------------------
function CirconianStaminaBar.UpdateStamina()
	-- Call the GetUnitPower function to get our current and max stamina
	local current, max, effectiveMax = GetUnitPower("player", POWERTYPE_STAMINA)
 
	--Add this line: Sets the minimum and maximum values for our status bar
	CirconianStaminaBarWindowStatusBar:SetMinMax(0, max)
 
	-- Sets the value for our status bar (how much it is filled in)
	CirconianStaminaBarWindowStatusBar:SetValue(current)
end



Now we need to call that function from our initialization function so that it sets up the correct values when we load our addon.

function CirconianStaminaBar:Initialize()
	-- Remove this line
	--CirconianStaminaBarWindowStatusBar:SetValue(50)
 
	-- Add this line
	CirconianStaminaBar.UpdateStamina()
 
 
	-- Gets our characters name
	local ourName = GetUnitName("player")
 
	-- Sets the text for our label to ourName
	CirconianStaminaBarWindowLabel:SetText(ourName)
 
        EVENT_MANAGER:UnregisterForEvent(CirconianStaminaBar.name, EVENT_ADD_ON_LOADED)
end

Now reload your UI and see if you get any errors. If not were good to go for the next step.

Setting the Event

Remeber Events? If not scroll up and read the section on Event Registering or click here Event Registering

Next we need to set our UpdateStamina() to run when a certain event happens.

You can go to the esoui main page and click on events to find events or view the event we need here: EVENT_POWER_UPDATE

The event that we need is:

EVENT_POWER_UPDATE (string unitTag, luaindex powerIndex, integer powerType, integer powerValue, integer powerMax, integer powerEffectiveMax)


Lets add that to the very bottom of our lua code in our register events section

-------------------------------------------------------------------------------------------------
--  Register Events --
-------------------------------------------------------------------------------------------------
EVENT_MANAGER:RegisterForEvent(CirconianStaminaBar.name, EVENT_ADD_ON_LOADED, CirconianStaminaBar.OnAddOnLoaded)
EVENT_MANAGER:RegisterForEvent(CirconianStaminaBar.name, EVENT_POWER_UPDATE, CirconianStaminaBar.UpdateStamina)


Reload your UI and you should have a real live working stamina bar. Congrats !!!


You may notice that its a big jerky. As you sprint around it jumps around a bit. I think this is either because the game updates these values slowly and/or because of the way the cost of stamina for sprinting is calculated. For example if the cost of sprinting is 15% of your maximum stamina every 5 seconds (I have no clue just making an example) then it would reduce your stamina in large chucks of 15% of the stamina bar every 5 seconds, so it makes it look like it is jumping around a bit.
Even the original attribute bars jump around a lot. We could animate the change in values to make it look a lot smoother, but that is beyond this tutorial.




Final Adjustments

Now I do want to point something else out. We originally wrote our UpdateStamina function and didn't yet know we were going to register it for an event. Although our stamina bar should work just fine what if we wanted to make 3 bars: one for health, stamina, and magicka? We could call GetUnitPower for each of the three types yes, but there is no need. Instead of using GetUnitPower() lets try something else.

From the wiki page for our event we see:EVENT_POWER_UPDATE

EVENT_POWER_UPDATE (string unitTag, luaindex powerIndex, integer powerType, integer powerValue, integer powerMax, integer powerEffectiveMax)

This part here:

(string unitTag, luaindex powerIndex, integer powerType, integer powerValue, integer powerMax, integer powerEffectiveMax)

Means that it passes all of those things to our function. Notice there is a unitTag, powerType, powerValue, powerMax ect... in it? There is no need to use GetUnitPower since were only calling this function when the event runs. We can just get the information that we need from what the event sends us. We will have to check which powerType is is though, you dont want to update your stamina bar with your magicka power!

All right lets make one last change.

Do not forget, when you register a function for an event and the event fires, calling your function, the first parameter it passes to your function is an event code.

So despite the wiki page showing:

EVENT_POWER_UPDATE (string unitTag, luaindex powerIndex, integer powerType, integer powerValue, integer powerMax, integer powerEffectiveMax)

Your function will actually recieve these parameters:

EVENT_POWER_UPDATE (integer eventCode, string unitTag, luaindex powerIndex, integer powerType, integer powerValue, integer powerMax, integer powerEffectiveMax)



Lets make those changes:

-- Add the parameters to our function
function CirconianStaminaBar.UpdateStamina(eventCode, unitTag, powerIndex, powerType, powerValue, powerMax, powerEffectiveMax) 
	-- get rid of this line (notice I just commented it out so you can see it).
	--local curStamina, curMax, curEFMax = GetUnitPower("player", POWERTYPE_STAMINA)
 
	-- Add an if statement to check and make sure its the correct powerType
	if powerType == POWERTYPE_STAMINA then
		-- curMax doesn't exist anymore we got rid of it, change it to powerMax passed to us by the event --
		CirconianStaminaBarWindowStatusBar:SetMinMax(0, powerMax)
 
		-- Same here curStamina doesn't exist anymore, replace it with powerValue passed to us by the event --
		CirconianStaminaBarWindowStatusBar:SetValue(powerValue)
	end
end



But that causes one last problem. If we do it that way, when we initialize our addon it tries to call this function to set up our stamina bar but no parameters will be passed to it. So we need to change the way that works.

function CirconianStaminaBar:Initialize()
	-- Remove this line
	--CirconianStaminaBar.UpdateStamina()
 
	-- and we'll just use the GetUnitPower function to initialize our stamina bar
	-- Call the GetUnitPower function to get our current and max stamina
	local current, max, effectiveMax = GetUnitPower("player", POWERTYPE_STAMINA)
 
	--Add this line: Sets the minimum and maximum values for our status bar
	CirconianStaminaBarWindowStatusBar:SetMinMax(0, max)
 
	-- Sets the value for our status bar (how much it is filled in)
	CirconianStaminaBarWindowStatusBar:SetValue(current)
 
 
	-- Gets our characters name
	local ourName = GetUnitName("player")
 
	-- Sets the text for our label to ourName
	CirconianStaminaBarWindowLabel:SetText(ourName)
 
        EVENT_MANAGER:UnregisterForEvent(CirconianStaminaBar.name, EVENT_ADD_ON_LOADED)
end



Now we should finally be in business !!!

glhfdgb ;)

NEXT TUTORIAL: Tutorial 2: Saved Variables

Circonians Tutorials Page: Tutorials Index



Personal tools
Namespaces
Variants
Actions
Menu
Wiki
Toolbox