Extensions

SpringBoard support editor extensions. They should be created in separate folders, in the springboard/exts folder, and they consist of two subfolders:

  • ui. This is where you should place all strictly unsynced extensions, like the Editor GUI and States.
  • cmd. This folder should contain files that should be shared by both synced and unsynced extensions, like Command and Model.

Example

We present a full example of a SpringBoard extension consisting of ui and cmd modules. This example is located in the exts/example folder of the repository.

First we will define the UI elements, given in the ui/example.lua file. At the top of the file, we will include the Editor class and make a new ExampleEditor subclass out of it, with which we will define our custom Editor.

SB.Include(Path.Join(SB.DIRS.SRC, 'view/editor.lua'))

ExampleEditor = Editor:extends{}

We will then register the newly defined class to make it accessible in the SpringBoard interface.

ExampleEditor:Register({
    name = "exampleEditor",
    tab = "Example",
    caption = "Example",
    tooltip = "Example editor",
    image = Path.Join(SB.DIRS.IMG, 'globe.png'),
})

Then in the init method, we will define the fields. We create two NumericFields: example and undoable, and we add them to the Editor.

function ExampleEditor:init()
    self:super("init")

    self:AddField(NumericField({
        name = "example",
        title = "Example:",
        tooltip = "Example value tooltip.",
        width = 140,
        minValue = -10,
        maxValue = 5,
    }))

    -- Note: as we are setting the value in synced only, we won't see the effect of undo in the editor.
    -- Consider using game rules if you want to be able to read in the UI as well.
    self:AddField(NumericField({
        name = "undoable",
        title = "Undoable:",
        tooltip = "This value can be used with undo/redo.",
        width = 140,
        minValue = -3,
        maxValue = 12,
    }))

    local children = {
        ScrollPanel:New {
            x = 0,
            y = 0,
            bottom = 30,
            right = 0,
            borderColor = {0,0,0,0},
            horizontalScrollbar = false,
            children = { self.stackPanel },
        },
    }

    self:Finalize(children)
end

To handle field changes, we will create an OnFieldChange method, and when fields change, we will create and execute appropriate Commands.

function ExampleEditor:OnFieldChange(name, value)
    if name == "example" then
        local cmd = HelloWorldCommand(value)
        SB.commandManager:execute(cmd)
    elseif name == "undoable" then
        local cmd = UndoableExampleCommand(value)
        SB.commandManager:execute(cmd)
    end
end

We also want to group all changes for the UndoableExampleCommand into a single undo/redo command on the command stack, and for that purpose we use the SetMultipleCommandModeCommand command.

function ExampleEditor:OnStartChange(name)
    if name == "undoable" then
        SB.commandManager:execute(SetMultipleCommandModeCommand(true))
    end
end

function ExampleEditor:OnEndChange(name)
    if name == "undoable" then
        SB.commandManager:execute(SetMultipleCommandModeCommand(false))
    end
end

We also need to define the two commands. This is done in separate files, in the cmd folder, which makes the Commands accessible from both unsynced (GUI) and synced (execution). The HelloWorldCommand is rather simple, and it just prints out a single line of text.

HelloWorldCommand = Command:extends{}
HelloWorldCommand.className = "HelloWorldCommand"

function HelloWorldCommand:init(number)
    self.number = number
end

function HelloWorldCommand:execute()
    Spring.Echo("Hello world: " .. tostring(self.number))
end

The UndoableExampleCommand is slightly more complicated as it also has a value that can be changed. In the :unexecute() method we revert it to its previous value.

UndoableExampleCommand = Command:extends{}
UndoableExampleCommand.className = "UndoableExampleCommand"

local value = 0
function UndoableExampleCommand:init(number)
    self.number = number
end

function UndoableExampleCommand:execute()
    Spring.Echo("Setting value: " .. tostring(self.number))
    self.old = value
    value = self.number
end

function UndoableExampleCommand:unexecute()
    Spring.Echo("Reverting to: " .. tostring(self.old))
    value = self.old
end

Note

Displaying a synchronized value in the GUI requires additional steps. Depending on how this value is kept, things like RulesParams can be used. Refer to the Spring documentation for details: https://springrts.com/wiki/Lua_SyncedCtrl#RulesParams https://springrts.com/wiki/Lua_SyncedRead#RulesParams

Extensions used in games

Zero-K’s metal spot extension.

This extension describes how the ObjectBridge API can be used to create new, custom editors for game world objects.