Addon Development - Windower/packages GitHub Wiki

This page assumes you have read and understood the previous pages:

Here we will create a sample addon with commonly used features to guide you through the process of creating an addon. It will feature a simple display of some character stats, controllable via commands.

Setting up Windower 5

Make sure you enable the debug console in the Windower > Advanced menu when editing a profile. This helps significantly with debugging errors.

The debug console functions as both input and print output. As for input, it can handle everything the FFXI chat log does, so it handles both FFXI and Windower commands. You cannot, however, just type an invalid command and have it go to the default chatmode channel. It only accepts commands, either Windower or FFXI commands. Unlike v4's console it requires a single slash, same as commands in the FFXI chat log.

As for output, it prints a lot of error information any time an error happens, which is crucial for debugging.

It is also the default output channel for Lua's print command.

Create a new addon test

Go to the packages folder and create a new folder with the name of the addon (=test). The folder needs at least two files:

test/
|- test.lua
|- manifest.xml

The manifest needs to look like this:

<package>
  <name>test</name>
  <version>1.0.0.0</version>
  <type>addon</type>
  <dependencies/>
</package>

The name element must match the addon name. The version needs at least the major and minor version, optionally also the revision and build numbers. The type needs to be addon. Other known values for packages are library and service, but they deserve their own development guides as they follow some different rules. This page will focus only on addon development.

The dependencies element is used to define other libraries and services this addon relies on. Ideally services should not be accessed by addons at all, only by libraries written specifically for them, so this should really only list libraries that the addon depends on.

Only the libraries defined as other packages need to be listed as dependencies. Internal libraries like command and ui do not need to be listed as dependencies and are always available.

Note that the manifest is loaded only once. If, during development, you add a new dependency you will need to reset the packages internally using the /pkg reload command. After that you can reload. This goes for creating new addons as well, so if you only now created it and are already in-game, type /pkg reload and then you can load your addon by typing /load test. It should display a message that it loaded the addon.

> /pkg reload
> /load test
loaded test

To make sure it's really loaded let's give it something really simple to do:

print('testing...')

Then /reload test to get the desired output:

> /reload test
unloading test
testing...
loaded test

Using the UI

Windower 5 has an immediate GUI framework, unlike Windower 4 and many other GUI frameworks out there. For a developer that means there is a displaying function which is called on each frame which dictates which objects live where and how they appear.

local ui = require('core.ui')

ui.display(function()
    ui.text('Testing!')
end)

Reloading the addon now will simply display Testing! in the top-left of the screen. To adjust its position we just use the ui.location function:

local ui = require('core.ui')

ui.display(function()
    ui.location(300, 30)
    ui.text('Testing!')
end)

This will print the same as before, at the position (300, 30).

But now we want to print something useful, say the current character's name, HP, MP and TP. That info is available in various places, for example the player library. To be able to use it we first need to add it as a dependency to our manifest:

<package>
  <name>test</name>
  <version>1.0.0.0</version>
  <type>addon</type>
  <dependencies>
    <dependency>player</dependency>
  </dependencies>
</package>

Note: If you do not have a dependency already installed you will need to install the dependency manually. This is done by typing: /install <dependency name> i.e. /install player

Now we need to tell the package manager that the manifest has changes - /pkg reload. Now we can go back to editing the addon:

local ui = require('core.ui')
local player = require('player')

ui.display(function()
    ui.location(300, 30)
    ui.text(player.name)
end)

Now if we're logged in, this will display the player name. Once we log out, it will error. That's because player.name becomes nil when logging out and the function expects a string. Notice that the value of player.name was automatically updated on logout, unlike in Windower 4. We can easily work around that though by using player.name or '-'. That way the player name will be displayed if available, and a simple dash if not.

We can now expand it to display more information:

local ui = require('core.ui')
local player = require('player')

ui.display(function()
    ui.location(300, 30)
    ui.text(player.name or '-')
    ui.location(300, 60)
    ui.text(tostring(player.hp) or '-')
    ui.location(300, 90)
    ui.text(tostring(player.mp) or '-')
    ui.location(300, 120)
    ui.text(tostring(player.tp) or '-')
end)

Not pretty, but that's not what we're here for.

Using commands

Addons will often need to be controlled by user commands. This can be achieved with the command library. To register a command load the module and write:

local command = require('command')

Now let's assume we want to handle visibility and position of each piece of information via commands. Some sample commands that people might want to use are:

/test hp hide
/test mp show 500 40
/test name show
/test hp_percent show 500 60

First we need to adjust our display function to check the visibility and position before displaying an item:

local ui = require('core.ui')
local player = require('player')

local options = {
    name = { x = 300, y = 30, show = true },
    hp = { x = 300, y = 60, show = true },
    mp = { x = 300, y = 90, show = true },
    tp = { x = 300, y = 120, show = true },
}

ui.display(function()
    for key, info in pairs(options) do
        if info.show then
            ui.location(info.x, info.y)
            ui.text(tostring(player[key] or '-'))
        end
    end
end)

Now the display function looks up all the values and their positions dynamically, so to adjust it with the command handler, we just need to adjust the values in that table.

To be able to catch /test commands Windower 4 used __addon.command = 'test' at the beginning of the file. In Windower 5 we can achieve that with the following line:

local test_command = command.new('test')

Now we have configured the /test command, but we don't have any handlers registered yet. Windower 5 supports a number of convenience functions for argument checking, so we can save a lot of boilerplate compared to Windower 4:

test_command:register(handle_field, '<field:string>', '<visibility:one_of(show,hide)>', '[x:number]', '[y:number]')

This line defines the function to call (handle_field, which we haven't implemented yet) as well as the number and format of the arguments. The first argument is required (indicated by the <>) and of string type. The second argument is also required and one of show or hide. The third and fourth arguments are both optional (denoted by []) and of numeric type.

So that helps us define the handler:

local handle_field = function(field, visibility, x, y)
    local info = options[field]
    info.show = visibility == 'show'
    if x and y then
        info.x = x
        info.y = y
    end
end

And that's it! Well, almost... if we enter the last command of the ones we mentioned above it would error, because hp_percent is not a known value. To fix that we can simply create it when it's accessed:

local handle_field = function(field, visibility, x, y)
    local info = options[field]
    if not info then
        info = { x = 0, y = 0} -- set default positions because these are optional and may be nil
        options[field] = info
    end

    info.show = visibility == 'show'
    if x and y then
        info.x = x
        info.y = y
    end
end

And with that, everything works as expected. Now it supports adding any value that is available in the player table dynamically via commands.

Persisting settings

We would not want to enter these settings every time we log in. And adding those commands to an initialization script is also annoying. So instead we'll try to make it remember our settings. That's where the settings library comes in. At first we need to adjust the manifest for the new dependency:

<package>
  <name>test</name>
  <version>1.0.0.0</version>
  <type>addon</type>
  <dependencies>
    <dependency>player</dependency>
    <dependency>settings</dependency>
  </dependencies>
</package>

Now we can require('settings') to get access to the settings library. The change from our current code to the one with saved settings is rather trivial:

local settings = require('settings')

local defaults = { -- Change this from `options` to `defaults`
    name = { x = 300, y = 30, show = true },
    hp = { x = 300, y = 60, show = true },
    mp = { x = 300, y = 90, show = true },
    tp = { x = 300, y = 120, show = true },
}

local options = settings.load(defaults) -- This loads character-specific settings based on the provided defaults

And with this the settings are persisted to file. But we need to manually save after each write:

local handle_field = function(field, visibility, x, y)
    local info = options[field]
    if not info then
        info = { x = 0, y = 0} -- set default positions because these are optional and may be nil
        options[field] = info
    end

    info.show = visibility == 'show'
    if x and y then
        info.x = x
        info.y = y
    end

    settings.save()
end

And with this we have character-specific settings that persist across logins.

And so on...

There is more to be done, but this is as far as the example needs to go. It should give people an idea of how to code for the Windower 5 API. It didn't cover all possibilities even within the subset of features we used, but for more information check the docs of the individual libraries. You should also examine existing addons to check how they work, that is always a great resource for learning.

Here a final recap of our entire addon that we wrote during this guide:

local settings = require('settings')
local command = require('command')
local player = require('player')
local ui = require('core.ui')

local defaults = {
    name = { x = 300, y = 30, show = true },
    hp = { x = 300, y = 60, show = true },
    mp = { x = 300, y = 90, show = true },
    tp = { x = 300, y = 120, show = true },
}

local options = settings.load(defaults) -- This loads character-specific settings based on the provided defaults

local handle_field = function(field, visibility, x, y)
    local info = options[field]
    if not info then
        info = { x = 0, y = 0} -- Set default positions because these are optional and may be nil
        options[field] = info
    end

    info.show = visibility == 'show'
    if x and y then
        info.x = x
        info.y = y
    end

    settings.save()
end

local test_command = command.new('test')
test_command:register(handle_field, '<field:string>', '<visibility:one_of(show,hide)>', '[x:number]', '[y:number]')

ui.display(function()
    for key, info in pairs(options) do
        if info.show then
            ui.location(info.x, info.y)
            ui.text(tostring(player[key] or '-'))
        end
    end
end)
⚠️ **GitHub.com Fallback** ⚠️