Scripted Events

Script events provide a mechanism for the game to notify running scripts when a noteworthy change in the game or UI state has occurred. Scripts can listen for events by adding lua functions to be called when a particular event occurs. When the event occurs the game calls each registered listener function in sequence, giving each an opportunity to respond to the change in the state of the game.

Examples of available events include FactionTurnStart, PanelOpenedCampaign, CharacterCreated and ComponentLClickUp. Listener functions registered for the FactionTurnStart event would be called whenever a faction starts its turn, listeners for the ComponentLClickUp event would be called whenever the user left-clicks on the interface, and so on.

Additionally, the game provides a context object to each listener function it calls. The context object encapsulates information about the state change that has occured, and may be queried in script to find out more information about the event.

A list of supported script events*, and the information that each provides via its context object, is given in the external model hierarchy documentation here: Model Hierarchy

*not all available events are listed

Relevant in Campaign Loaded in Campaign
Relevant in Battle Loaded in Battle
Relevant in Frontend Loaded in Frontend
Back to top

Listening for Events

The script may register a listener for an event with the core:add_listener function. Once registered, listeners may be later removed with core:remove_listener if required.

The example below shows a FactionTurnEnd listener. Two callbacks are provided as part of the listener - a conditional check as the third argument and a target function as the fourth. Each callback is provided a context object which is generated by the triggering code. The methods that can be called on this context object vary from event to event, but are listed in the external model hierarchy documentation: Model Hierarchy

If a conditional check is specified, it is called each time the FactionTurnEnd event is triggered, and must return true (or a value that equates to true when evaluated as a boolean - i.e. any value other than nil or false) for the conditional check to pass. Alternatively, the boolean value true may be supplied in place of a conditional function, in which case the condition always passes.

If the conditional check passes (or true was supplied in place of a conditional check), then the target function is called.

The fifth argument, if set to false, causes this listener to shut down and remove itself after the target function is called the first time. If set to true, the listener will persist until it is removed with core:remove_listener.

Example - FactionTurnEnd listener:

Listen for a FactionTurnEnd event for a specific faction.
core:add_listener(
    "faction_turn_end_listener",                                        -- Listener name, by which the listener may be later cancelled if necessary
    "FactionTurnEnd",                                                    -- Script event to listen for.
    function(context)                                                    -- Optional conditional check.
        return context:faction():name() == "wh_main_emp_empire"
    end,
    function(context)                                                    -- Target function.
        empire_ending_turn(context:faction())
    end,
    false                                                                -- Should the listener persist after the target function has been called the first time?
);

Example - Persistent left-click listener:

core:add_listener(
    "example_click_listener",
    "ComponentLClickUp",
    true,                                                                -- This listener performs no conditional check, always calling the target function
    function(context),
        handle_left_click(context.string, UIComponent(context.component))
    end,
    true                                                                -- This listener persists and doesn't shut down after the target callback is called the first time
);
Back to top

Game Areas

Script events are used extensively in campaign and by the user interface to notify script of state changes. They are not used so prominently in battle, though. Battle scripts rely more on polling and on bespoke handler mechanisms - see the Handlers section of this documentation.

Back to top

In More Detail

The events system is underpinned by a global lua table called events which is created each time the script loads. This table contains many subtables, one for each event which may be triggered by the game.

The events table and related subtables may be seen in the events.lua file which lives in script folder.

A listener for an event is "registered" by adding a function to the subtable related to the event. When the game code triggers an event, it looks for the subtable in the events table that matches that event’s name, and calls each element within that subtable with the generated event context object as a single argument. Removal of a listener means removing the appropriate function entry from the event subtable.

The listener functionality provided by the core object adds another layer on top of this functionality. See the functions listed in the Event Handling section of this documentation. While listeners may be added directly to the events table, it is strongly recommended to use the listener functionality provided by core.

Back to top

Adding New Events

When a new event type is added, a subtable with the same name must also be added in events.lua. Without it the game will not be able to trigger the event. In this case, an error message will be printed to the Lua console spool.

Back to top

Script-Generated Events

The script may also generate events of its own with the functions core:trigger_event and core:trigger_custom_event. A script event being triggered doesn't need to be present in the events table, the triggering functions will add it if not found.

Last updated 12/08/2022 11:56:59