Maintenance for the week of December 23:
· [COMPLETE] NA megaservers for maintenance – December 23, 4:00AM EST (9:00 UTC) - 9:00AM EST (14:00 UTC)
· [COMPLETE] EU megaservers for maintenance – December 23, 9:00 UTC (4:00AM EST) - 14:00 UTC (9:00AM EST)

Short Guide to Addon Programming

zgrssd
zgrssd
✭✭✭✭
I recently had the impulse to learn lua and addon programming for ESO. Part because I missed functionalities, part because I do enjoy a programming challenge/new language and part because I thought I could make some stuff better then the orignal programmer (if possible within the constraints of the API).
I always learn quite a bit in a short time and thought I write down what I learned thus far.
This is not supposed to be a full Toutorial or Syntax reference (for that there is the rest of the internet). It's aimed towards fellow programmers for whom only Lua and/or "Lua in ESO" are new things. I cover some stuff I felt incompletely explained in the Toutorials on ESOUI.


Variables and Scope:
Variables in Lua are truly Global. They are shared not only between all files in you addon, but all addons loaded into the client.
It does not even stop at addons, every function provided by the API is also just a Global variable, shared between the API and all Addons. So only expose or overwrite something global if you really know you want it exposed/what you are doing.
Global Variables are never declared. They are assigned. If you try to access a variable that has not gotten a value, the value is "nil" (like null or nothing in other languages). Hence to "delete" a global variable, assign it nil. If you mispell a variable or function name, the results will be a nil (or the value of a global variable that has been written this way). So mind your spelling (and avoid globals where you can).
Think of global variables as a giant, string indexed table (see table below) to wich both addons and the API have full read/write access and nobody expects "his" values to have been overwritten by somebody else (or at least only in a meaningfull way so it still does the normal work).
To cite Linux after elevating to root: "With great power comes great responsibility."

Local Variables have a scope limited to whatever block they were declared in. Local Varriables are the only ones that have to been "declared" in the classical sense.
At biggest scope will still be in the entire File they are declared in (if declared outside of any function it's effective scope is the .lua file). At same name local variables always take precedence before global ones. And more local ones from those of "higher" blocks.
Make it a habit to write local in front of every variable. Don't even think about it. If you do need it global still mark it local now and explicitly expose it via the Namespace construct at the end of the file (see table).
Function Parameters and running numbers declared in a for are automatically local to the block they belong to, but if you need a temp variable or even a seperate counter for a more complex loop forgetting local can cause untold havoc (because more then one piece of code might try to access the same global variable). It certainly did for me.

http://lua-users.org/wiki/ScopeTutorial


Types:
Variables have no fixed type. At any given time any Variable could be a int, a float, a string, a Function Reference, a Reference to a complex type (like a Saved Variables Access instance) or even a table - wich is just a collection of normal variables with indexers (each of wich can be any of the above, including another table).
People who programmed in PHP and other languages without type decalrations will find this aspect familiar.
That also means you need a good tool for printing variables that can iterate over all the elements as deep as needed. A way to dump the actuall values instead just checking the asumed values. Personally I like kikito's inspect for displaying the content of a variable for debugging:
https://github.com/kikito/inspect.lua
Note that d() (see debugging and libDebug) seems to have similar abilities, but they seem slower/might lag behind.

Functions:
You are not declaring a function named "X". You declare a variable named "X" and assign it an Anonymous function.
Functions do know via wich variable name they have been first assigned for debugging purposes so it may seem as if they have a name. But in realiy they do not.
This also means that handing a function as parameter is easy - they are after all just another variable type.

As functions are handeled in variables a function must have been declared and assigned before it is used (otherwise the value of the variable is still nil). Keep that in mind when ordering .lua files in your Textfile and functions in your lua file: If it needs to be assigned before your Code file accesses it, it has to come before it in the list (but keep the difference between .lua file and the OnLoad/Initialsiation events in mind)

Functions can take as many parameter as you want.
They can be called with anything from the full list of arguments to no argument (non-assigned arguments are supposed to be nil, extra arguments are ignored)
They can also return as many parameters as you want. They do not return a table, but instead just spew out the entire list. Those you do not assign/use directly are ignored. In some cases only the first return value is used by default, but when using one function as argument for another the return values are matched to the parameters (String.Format is one function that returns more then one value).


Table:
This is the only collection type and is also often used to simulate structures and classes. And namespaces during global exposure.
Tables are the only type that has to be initialized before it can be used, but you can easily initiate a emtpy one and assign it as many values as you like in a loop. You can also use array intialisers to set core values here and now.
There is no fixed type for indexers and no fixed size.

You can have int indexers or string indexers and mix both indexers on the same table. If you want to remove something from the table, assign nil to that index (as you would with a global/local variable). The number 1 and the string "1" are distinct for indexing purposes.

Int Indexer:
Also called the list or array style (with list being the more precise term)
Int indexes in Lua always start with 1. 0 can still be assigned and read as any other index, but as a rule of thumb you should avoid starting anywhere else but 1. You only end up having to differentiate between "array I declared manually" and "arrays the system or array initialisers created", wich does not aid your productivity.
You can get the highest int indexer for a table by putting a # before the table name. (#myTable) wich is helpfull for for-looping over the table. Keep in mind there might be nil values between 1 and #myTable and string indexes values cannot be iterated over this way (for those see the internet)
Note: No mater how you layed them out, # will not work on the top level of settings objects (See persisting data below)

String indexer:
Also called the "Dictionary Style". As said earlier enumerating over them is harder the itterating over the int indexed part of a table

string indexer Syntax Sugar:
Writing myTable.SomeStringIndex is equal too myTable["SomeStringInex"]. This syntax sugar is core of how you simulate structures or namespaces with string indexes tables. But keep in mind that no mater how it looks, it is still a string index for a table. So expect index related exceptions if you write something wrong.

Hybrid table:
As said before, you can mix string and int indexers. Some call the Hybrid tables, but otherwise there is no difference between a pure int or pure string indexed table.

namespaces via tables:
As all Addons and even the API use the same global values and there is no special namespacing system, having unqiue names for your exposed functions can be tricky. But using tables it is quite possible to have a globally unique name (that you can also change as nessesary without affecting he internals of your code)
http://www.lua.org/pil/15.2.html

Metatable:
There are no classes in Lua. While the XML UI has a concept of Inheritance (at least it seems so), there is nothing like this in Lua.
However you can simulate classes via Metatables.
Metatables can be assigned to table variables as well as primitive types.


What is the place of a lua file?
For all intents, .lua files are really just very large functions whose local variables are accessible by local functions even after the code has finished. Global variables stay around anyway (till they are overwritten by somebody else). Think of them as the Constructors of your addon. Every event or callback you define inside it will be able to access anything else inside the file (that is why local is a good idea).
This includes that you can just cancel thier execution with a return, like any other function (libStub makes use of that)

As opposed to that, the OnAddonLoad and OnInitilaised events are explicitly raised after all constructors/addon.lua files have already run. As such your will always register the on-load events off all addons, wheter they have been loaded before or after yours (because loading has long passed at this time). So make sure to filter for your addons event. Using the
if addonname ~= "<Your Addons Name>" then return end
cosntruct is the fastest way.

When is the file compiled?
Every time the UI is loaded, all addons are read in and executed (unless they have been disabeled in the menu)
The UI is loaded every time you log in or give the /reloadui command, via chat, Addon menu or a Addon (yes, there is a Addon for that)
So all you need for "recompilation" of a changed file is saving everything and hitting /reloadui
As long as you do not reload the last loaded version will stay in memory.

The UI XML:
People who worked with XAML/WPF before will instanly find familiar concepts here. It's a XML based markup language for UI Elements. Unfortunately it shares two other things with XAML (early versions): The most precise debug message you will get is "Something went wrong while parsing the XML" (without even telling you the line) and mispelled Markup is silently ignored, wich can result in all kinds of "X does not work" cases. Plus sometimes some values (like moveable) depend on other values (like mousenabeled) to be set, but nothing in the documentation says so.
95% of all "nil where value expected" here mean: you tried to assign an event in XML, but forgot to name wich event code should handle it. Or the events name in XML and Lua do not match. Or the event is not properly exposed globally under that name.

Debugging:
The fastest way to give something out into the chat is using the function d() (propably for Debug or Dump, but some call it the "loot message" too).
This will put the data as yellow text into your chat windows current tab. The input will not be shared between tabs - wichever is active will take it, all others will ignore it. It's as close as any addon can get to "sending a message to chat" (as addons cannot send chat messages).
Unfortunately by default it does not seem to work during the Initialistion event as the chat is not yet loaded (at least as far as displaying d() messages goes) and there is no buffering in the build in version of d(). As part of it's functionality, libDebug overwrites d() to implement buffering, as long as "Show Pre-Init Messages" is set to true.

Stucture of the Addon-Manifest:
This explains it all
http://wiki.esoui.com/Addon_manifest_(.txt)_format

Persisting data between Character sessions and sharing data between Characters (account wide data):
The game provides a SavedVarriables system:
http://wiki.esoui.com/AddOn_Quick_Questions#How_do_I_save_settings_on_the_local_machine.3F
They are stored in a folder at the same level as the addon folder, in a .lua file named after the addon
Using them requires the following steps:
1. you add the name of the settings to your Manifest file. Remember this name for later
2. you declare a local variable at the top of your .lua file. This is where the settings instances will be stored and where they will be accessed by the functions of your addon. Note that you cannot yet assign the value. You have to wait till the Loaded Event of your addon.

During said loading event you do:
3. You declare the default value to be used. While this might seem a useless overhead for most runs, this is how this part works. The default values come before you even try to read the settings from the disk!
4. You try to access the Variables using ZO_SavedVars:New (this constructs the Settings Object), giving the name you specified in the manifest (Step 1), a version number (start with 1) and the default value you created at step 3
If there are values and their version matches up, they are loaded from the disk.
If there are no values or there are values but with wrong version, the default is used. From now on it behaves as if there always had been the values on the disk
Note: When inspecting the value of the object it seems that the default values are added under the metatable.

If you want Character specific saved variables, use ZO_SavedVars:New(). You can share the same Step 1 name, as the file will automagically stuctured based on gameaccount-name and Character Name.
If you want account wide variables (including sharing information between characters when you cannot access other characters data at runtime), use ZO_SavedVars:NewAccountWide() instead.
Profile and Namespace are optional and can be nil, but I prefer to use different namespaces for global and character variables (unless I just make my own profile management in the global saved variables). All this does is add another "layer" of indexes to the saved var table.

Deleting/new default for settings files:
If something in the structure or default values of your SavedVariable changed, there are only two ways to refresh it:
1) Log out or disable the addon and /reloadui. Wait some time till the values have been written to disk (there is some caching in place^so it might take a few seconds). Then delete file. Relog/reactivate addons afterwards.
2) Change the version, save, /reloadui, so the current values are ignored and default is used
For obvious reasons I prefer 2 during development. But deployed versions should "stick" to thier format unless something major changed.


Structure of a .lua file (while nothing is nesseary to be in this order, it seems a very simply and understandable order and helps read others files):
--libStub block, if you use it

--A bunch of (local) variables that has to be shared across functions
--Needs to include variables used for saved variable objects

--Function1, cannot use any function declared after it but any variable above it

--Function2, wich can use Function1

--Function3, wich can use Function1 and Function2

--[...] (A bunch of functions omitted)

--Function to handle the EVENT_ADD_ON_LOADED Handler

--Here you regsiter all Event handlers
--Especially the function of the EVENT_ADD_ON_LOADED

--Exposure of functions in global variable via namespace pattern
--the optional return from the Namespace pattern


Libraries:
There is the old saying to "never reinvent the wheel".
And nothing can be as productive as "using the code of other peoples" to solve a problem.
Libraries is .lua code specifically designed to be used by other addons. While they might have some standalone features, their main purpose is to be used in other addons.
I mostly used the ones for esoui.com, wich can be found here:
http://www.esoui.com/downloads/cat53.html

Here is list of libraries you might find helpfull:
libStub:
http://www.esoui.com/downloads/info44-LibStub.html
You can easily have multiple instances and even version of the same library in your addon folder. Not only bring several addons them along, you might have a standalone copy. In this case wichever version is last executed would be the one used (as all addons access the same global variables for exposure, so it's "last come last served"). Unless the Library uses libStub. It's a library with the sole purpose of being a library version tool.
Using the libStub block other libraries .lua file will first check if a newer or the same version has already been loaded/executed. If that is the case, the .lua file is just skipped (via an early return).
If not (no or older version) the .lua file is executed. This makes certain that only the most recent version is in memory and that if a dozen addons bring the same version along only one of them is executed.
For optimal result also add the libraries to your "OptionalDependsOn". If the user has a standalone version installed that one is much more likely to be up to date. While it would still end up "on top" eventually, the lesser versions might still be executed costing unessesary time.
libStub uses two values to identify a library: The major and minor. It they have different major values, they are different libraries. If they have different minors, they are just different versions of the same library. You only change the major if something changed with the exposed functions (like names or wich functions are provided) or if otherwise backwards compatibility is not certain.

libAddonMenu:
http://www.esoui.com/downloads/info7-LibAddonMenu.html
Do you like to add a addon settings page to the game menu? While you could write the code yourself, there is a library for that wich takes care of the plumbing.
I think the Documentation is good enough that it does not need explanations.

libDebug:
http://www.esoui.com/downloads/info348-LibDebug.html
The first rule of all debugging is to figure out what the real value of a variable is (because when you have bug it is always not the value you asumed).
The second rule is to have error messages in a copy/pasteable format (this can be especially true for normal users)
Aside from changing d() so output can be buffered (and includes d() messages that come in before the chat is ready) it also provides a way to replace the error dialog with a version that allows copy to clipboard for exceptions. (Note that I had some issues with it swallowed messages, so I only use it if I really need this part).


Final words:
Keep in mind that addons can only do what the API allows them to do. As with all programming (short of drivers or OS) your code is only guest on the computer it is run on. If your addon does not play well with the other guests, takes up too much time/resources or tries to do stuff a guest should not do, it will get kicked out.
If you see something that is obviously ineffective/overly complicated built, chances are high that this is the only way this thing can be build at all. At least till the API is revised.
Elana Peterson (EU), Dominion, Imperial Sorc, Rune & Alchemy Crafting Char
Leonida Peterson (EU), Daggerfall, Kajiit Nightblade, Tank & main Crafter
Kurga Peterson (EU), Ebonhart, Ork Dragonknight, Provision Mule
Coldblood Peterson (EU) Argonian Templer, Daggerfall, Healer
Incendia Peterson (EU), Dominion, Dunmer Dragonknight, fire DPS & healer
Haldor Belendor (EU), Ebonhart, Breton Sorcerer, Tank
Fuliminictus Peterson (EU), Ebonhart, Altmer Sorcerer, Electric DPS

Me babbling about PvE roles and Armor, Short Guide to Addon Programming (for Programmers)

If you think anything I or somebody else said violates the Rules of this Forum, you are free to flag my posts. Till I get any notifcaion from this, I just asume you know you have no case against me or Zenimax disagrees with you.
  • Cairenn
    Cairenn
    ✭✭✭
    zgrssd wrote: »
    I cover some stuff I felt incompletely explained in the Toutorials on ESOUI.
    It's a wiki for a reason. You're welcome to add to it.
    Cairenn
    Co-founder & Administrator
    ESOUI
  • Gwarok
    Gwarok
    ✭✭✭
    Bookmarked... TYVM for this thread topic.
    Edited by Gwarok on June 26, 2014 3:04AM
    "Strive for balance of all things. When the scales tip to one side or the other, someone or somethings gets short-changed. When someone gets short-changed, unpredictability and strife unbalance the world around us...To achieve freedom from greed, from want, and from strife, all parties in any exchange MUST find balance." -House Hlaalu's Philosophy of Trade

    "I am ALWAYS very busy, so I KNOW what's best. You need to stay away from the waterfall. TRUST ME, you're better off keeping busy than playing in the stream....Do you know how to swim, Little Scrib?"

    "I am but a simple farmer". -Rags'nar LodesBroke

    #SKOOMA!

    (Juliet):
    ...it is nor hand, nor foot,
    Nor arm, nor face, nor any other part
    Belonging to a man.
    O, be some other name!
    What's in a name?
    That which we call a rose?
    By any other name would smell as sweet.
    Retain that dear perfection to which he owes...
    (Act II, Scene II -William Shakespeare's: Romeo & Juliet -1595 A.D.)



  • zgrssd
    zgrssd
    ✭✭✭✭
    Cairenn wrote: »
    zgrssd wrote: »
    I cover some stuff I felt incompletely explained in the Toutorials on ESOUI.
    It's a wiki for a reason. You're welcome to add to it.
    I did already.
    That new section about Saved Vars is my rework of the old stuff.
    And I did add several explanaionts to the Chat and Horse API's (but I did make the mistake to ocassionaly include the empty bracktes in the link's).

    Besides that one was written 2 months ago. The API changed twice since I have written this. Equally extensive has my understanding of Lua changed. Back then I did not even knew that that tables were Call by Reference by default.
    Two months in IT is like 10 months in Real Life.
    Elana Peterson (EU), Dominion, Imperial Sorc, Rune & Alchemy Crafting Char
    Leonida Peterson (EU), Daggerfall, Kajiit Nightblade, Tank & main Crafter
    Kurga Peterson (EU), Ebonhart, Ork Dragonknight, Provision Mule
    Coldblood Peterson (EU) Argonian Templer, Daggerfall, Healer
    Incendia Peterson (EU), Dominion, Dunmer Dragonknight, fire DPS & healer
    Haldor Belendor (EU), Ebonhart, Breton Sorcerer, Tank
    Fuliminictus Peterson (EU), Ebonhart, Altmer Sorcerer, Electric DPS

    Me babbling about PvE roles and Armor, Short Guide to Addon Programming (for Programmers)

    If you think anything I or somebody else said violates the Rules of this Forum, you are free to flag my posts. Till I get any notifcaion from this, I just asume you know you have no case against me or Zenimax disagrees with you.
  • Cairenn
    Cairenn
    ✭✭✭
    zgrssd wrote: »
    I did already.
    That new section about Saved Vars is my rework of the old stuff.
    And I did add several explanaionts to the Chat and Horse API's (but I did make the mistake to ocassionaly include the empty bracktes in the link's).

    Besides that one was written 2 months ago. The API changed twice since I have written this. Equally extensive has my understanding of Lua changed. Back then I did not even knew that that tables were Call by Reference by default.
    I know you have, I seem to recall thanking you for your help on it. I was just saying. :)
    zgrssd wrote: »
    Two months in IT is like 10 months in Real Life.
    Ain't that just the truth. Heh.
    Cairenn
    Co-founder & Administrator
    ESOUI
  • sylviermoone
    sylviermoone
    ✭✭✭✭✭
    OK, I admit I saw this thread, clicked it and didn't read a damn thing (yet) cuz I've had a few beers and just wanna get to Cyrodil.

    However: as I scrolled (very quickly) through the OP, I'm really looking forward to giving it my full attention tomorrow.

    I wouldn't say I know zilch about programming, but i am sure NOT a pro, but I'm not freaking stupid either. And I've been thinking about learning some more specifically so I can work on addons. I run more addons than I care to admit, many which have redundant functions, but I only enable specific parts because I like the way that part looks. I'd like to try and consolidate the parts into one functioning thing.

    Maybe I read this tomorrow and say "well, that isn't what I was looking for", but I bet not. Thanks for this thread, and reading my verbose reply. :smile:
    Co-GM, Angry Unicorn Traders: PC/NA
    "Official" Master Merchant Tech Support
    and Differently Geared AF
    @sylviermoone
  • GrfxGawd
    GrfxGawd
    ✭✭✭
    I'm finding your contributions to be a cummulation of what you've gleaned from reviewing the code of others. The hallmark of a good programmer is that they are creatives. Those who innovate can afford to be generous and share the fount of information they know because they will always be creating the leading edge. Others stay in the field by application of that knowledge. The rest pass through merely by "stealing sheep". I've often tried to explain to outsiders that one month of real time is equal to a year in most other industries. People who haven't done it have trouble grasping the scope and depth of it. Not to mention the acumen of those who've been creating that edge for over 25 years. Seasoned professionals can be a rare treat if found.
    You know, I was once an optimistic gamer just like you. Then I took a patch to the game client from Zenimax.
  • UlsethSamori
    I'm going to give this a shot. I have a few ideas for ESO user interface add-ons.
  • ljhopkins_1
    ljhopkins_1
    Soul Shriven
    ok not to sound stupid, but how / where do i find a list of hooks to ESOL.
    such as if wanted to access the quick slots set var. to say assign to F key
  • yodased
    yodased
    ✭✭✭✭✭
    ✭✭✭✭✭
    Tl;dr really weigh the fun you have in game vs the business practices you are supporting.
  • Sasky
    Sasky
    ✭✭✭
    ok not to sound stupid, but how / where do i find a list of hooks to ESOL.
    such as if wanted to access the quick slots set var. to say assign to F key

    Keybindings are a bit different from normal variable assignments. See the ESOUI wiki for how to do that. Also, feel free to ask any questions in the ESOUI forums. You'll get more/faster responses from other people who make addons.

    And to quote @katkat42‌
    katkat42 wrote: »
    Plus, we have cookies! B)
    Sasky (Zaniira, Daggerfall Covenant)
    Addons: AutoInvite, CyrHUD, Others
  • Garkin
    Garkin
    ✭✭✭
    Garkin / EU / CSF guild
    My addons: SkyShards, LoreBooks, Dustman, Map Coordinates, No, thank you, ... (full list)
    I'm taking care of: Azurah - Interface Enhanced, Srendarr - Aura, Buff & Debuff Tracker and more
    My folder with updated/modified addons: DROPBOX
  • Blockz888
    Blockz888
    ✭✭✭
    Hey guys

    Im looking for someone to make a small addon.

    I just want it to basically paste a text into zone chat wherever you are once you log in.

    Would this be easy or hard?

    Please please someone make this for me! :'') or give me a guide how to? I have zero experience with the programming but i am keen.
    Leader of The Werewolf Legion, DC WW PVP Guild for Azura, we either pvping or training for pvp. If interested in joining send me an invite, brutiss.
  • Ayantir
    Ayantir
    ✭✭✭✭
    http://wiki.esoui.com/Writing_your_first_addon

    +
    local once = true
    local myFunction()
    
    if (once) then
        d("blablabla")
        once = false
    end
    
    EVENT_MANAGER:RegisterForEvent("MyAddon", EVENT_PLAYER_ACTIVATED, myFunction)
    
    Obsessive Compulsive Elder Scrolls addons Coder
    A Few millions downloads of ESO addons now.
    Master crafter on my main char since release. All tradeskills, recipes \o/, researchs (since long), 35 styles known
    My little french Guild: Cercle de l'Eveil
Sign In or Register to comment.