Getting started

Your first plugin

The template plugin, file by file.

Every new plugin starts from the template: a blank plugin with the callbacks wired up and its settings saved for you. Unzip it into scripts, rename the my-plugin folder to your plugin's name, add that name to scripts\plugins.txt and start editing app.lua.

lua-plugin-template.zip
The four files below, in a my-plugin folder.
Download template
scripts/
  my-plugin/
    main.lua
    app.lua
    config/
      config.lua
      config_api.lua

main.lua

The entry point. It runs once when the plugin starts: it loads the app, initializes it, and defines the callbacks the game calls, each handing off to the app. Add a callback here when you need one.

local app = require("app")

app:init()

function on_stop(reason) app:shutdown(reason) end
function on_tick() app:tick() end
function on_render() app:render() end
function on_key_pressed(key) return app:on_key_pressed(key) end

app.lua

The plugin itself. App is a table with methods: App.new() makes the one app, and every function defined with : gets it as self, so state kept in self lasts between ticks and frames.

local config_api = require("config.config_api")

local App = {}
App.__index = App

function App.new()
    return setmetatable({}, App)
end

function App:init()
    config_api.init()
end

function App:shutdown(reason)
end

function App:tick()
    config_api.save()
end

function App:render()
end

function App:on_key_pressed(key)
    if game_ui.text_box_has_focus() then
        return false
    end
    return false
end

return App.new()
  • init runs once, when the plugin starts (and again after every reload). It loads the settings.
  • tick runs every game tick. Game logic goes here. It also saves the settings when they changed.
  • render runs every frame, and is the only place ui, gfx and gfx3d work. Keep it light and do heavy work in tick or a task.
  • shutdown runs when the plugin stops. Undo any game settings the plugin changed.
  • on_key_pressed gets every key press. Return true to keep the key from the game. The first check lets keys through while the player is typing in chat.

config/config.lua

The settings and their defaults. In the template it's empty:

return function()
    return {}
end

Add a setting by adding it here with its default value. Only settings listed here are saved and loaded; see Settings for how saved values are merged.

return function()
    return {
        enabled = true,
        range = 1200.0,
        color = Color.hex("#4FC3F7C0"),
    }
end

config/config_api.lua

Loads, returns and saves the settings, on top of the config module. It usually needs no changes.

local defaults = require("config.config")

local config_api = {}

local live

function config_api.init()
    live = config.init(defaults())
end

function config_api.save()
    config.save()
end

function config_api.cfg()
    return live
end

return config_api

Anywhere in the plugin, config_api.cfg() is the settings table: read a field to get a setting and assign one to change it. Changes are saved by themselves.

Adding a window

Draw from App:render. A window with a close button tied to a setting needs two settings in config/config.lua:

return function()
    return {
        show_window = true,
        range = 1200.0,
    }
end

Then, in app.lua:

local cfg = config_api.cfg

function App:render()
    if not cfg().show_window then return end

    ui.set_next_window_size(Vec2(360, 200), ui.Cond.FirstUseEver)
    local open, visible = ui.begin_window(script.name, true)
    if visible then
        ui.text("Hello from " .. script.name)
        local changed
        changed, cfg().range = ui.slider_float("Range", cfg().range, 100, 1500, "%.0f")
    end
    ui.end_window()

    if open == false then
        cfg().show_window = false
    end
end

Save the file and press reload on the plugin's row to see it. Everything a plugin prints with print shows up in the Lua Console.