LuaAPI/wesnoth/game events

From The Battle for Wesnoth Wiki
< LuaAPI‎ | wesnoth
Revision as of 03:21, 13 September 2024 by Celtic Minstrel (talk | contribs) (wesnoth.game_events.on_mouse_button: Fix missing return type on the callback)

The game_events module contains functions for manipulating game event handlers and hooks that can be used for registering low-level game events.

Functions

(Version 1.17.6 and later only)

wesnoth.game_events.add

Registers a new event handler, as EventWML. This function is the most flexible method of adding an event, which exposes all the available options via the options table, but there are also two other ways to add an event – add_repeating is suitable for quick, recurring events, or add_menu for menu items, while add_wml is the underlying mechanism of the event tag and mostly exists just for quick compatibility.

  • wesnoth.game_events.add(options table)

Registers an event handler in the scenario. The argument is a table containing several optional named arguments. The possible arguments are:

  • name: The name of the event to handle. This can be a string (a single event name, a comma-separated list, or an expression of WML variables that can expand to one or more event names) or a table of event names as individual strings (which can also possibly contain WML variables). Default is usually an empty string, but note that either a name or ID is required. If menu_item is true, the name defaults to "menu item id", where "id" is the same ID as below.
  • id: The event ID, used to remove it later; also, if an event with the same ID is already registered, then this event will be ignored and will not be registered. Default is an empty string. Required if menu_item is true. Note that either a name or ID is required for any event.
  • menu_item: True if this is a menu item (an ID is required); this means removing the menu item will automatically remove this event. Default false. Note that this does not, by itself, mean the event will be triggered when the menu item is selected. The name determines when the event is triggered.
  • first_time_only: Controls whether this event will fire more than once. Default true, meaning that it fires only once and is then removed.
  • priority: Events execute in order of decreasing priority, and secondarily in order of addition.
  • filter: Filters that, if not passed, will prevent the event from firing. There are three possible formats for this:
    • A WML table with filter tags and attributes (like an [event] tag with no action content and no name or id). In this case the filters are parsed in the same way as they would be for an [event] tag. It can contain any supported event filter tag, or [insert_tag] tags that expand to a supported event filter tag.
    • A table of the form {filter_type = filter_contents}. The possible filter types are: formula, condition, side, unit, second_unit, attack, and second_attack. The formula filter is a string containing Wesnoth Formula Language code, while all other filters can either be a WML table containing the filter contents, or a string containing the name of a WML variable to load the filter from. In the second case, the variable will be read at event handling time, not at registration time, so modifying the variable will dynamically alter the event's filter. This is equivalent to using [insert_tag] (and delayed variable substitution, if nested) to define the filter in EventWML.
    • A function that optionally takes a WML table as argument and returns a boolean value.
  • filter_args: An optional WML table that will be passed to the filter function. This is used only if the filter is a function.
  • content: The content of the event. If action is specified, this is a WML table passed verbatim to that function when the event fires. If action is not specified, this will be interpreted as ActionWML to be executed when the event fires.
  • action: The function to call when the event triggers. It can take a WML table as argument (the content) but does not return anything.

wesnoth.game_events.add_repeating

  • wesnoth.game_events.add_repeating(event name, handler function, [priority], [on undo function])

Registers a recurring event (as [event]first_time_only=no) with the specified event name which will call the function when it triggers. The event name is the same as the name key in the options table in game_events.add.

The handler function takes no argument and returns nothing.

wesnoth.game_events.add_menu

  • wesnoth.game_events.add_menu(menu item id, handler function)

Registers a recurring event for a menu item. The first argument is the menu item ID, rather than an event name, so it cannot be an array or contain any commas. (TODO: Can it contain variables?) The event will be automatically linked to the menu item with the same ID and will be removed from the scenario if that menu item is removed. It will trigger when the menu item is selected.

The handler function takes no argument and returns nothing.

wesnoth.game_events.add_wml

  • wesnoth.game_events.add_wml(event config)

This is simply the underlying implementation of the event tag. The argument is the entire content of an event tag. It will parse name, id, delayed_variable_substitution, first_time_only, filters, and ActionWML from this config and use that to register an event.

wesnoth.game_events.remove

  • wesnoth.game_events.remove(id)

Removes the event handler with the given ID, if it exists. If it does not exist, this does nothing.

wesnoth.game_events.fire

  • wesnoth.game_events.fire(name, [first location, [second location]], [event data]) → processed?

Fires all events with the given name, passing any specified data into the event. If the locations passed in have a unit on them, that unit will become the primary or secondary unit of the event. The event data is a config that can contain arbitrary data.

The most common use of event data is to pass in the weapons for an attack event - to do this, add a primary_attack and secondary_attack tag to the config. Other data used by built-in events includes the damage_inflicted value in an attack hit event, or the owner_side value in a village capture event. For custom events, you can put anything you want in here.

wesnoth.game_events.fire_by_id

  • wesnoth.game_events.fire(id, [first location, [second location]], [event data]) → processed?

Same as fire, but triggers the event with a specific ID instead of all events with the given name.

Hooks

wesnoth.game_events.on_event

  • wesnoth.game_events.on_eventfunction(event_name)

This is a lower-level method of handling events. It triggers on all events before any registered event handlers and is passed the name of the event as its sole parameter - the rest of the event info is available via wesnoth.current.event_context. This hook is called before any registered event handlers (eg [event] tags) for the event. The event names passed to on_event always use underscores instead of spaces.

Note: a on_event handler will not prevent undoing of that event, so if your callback intends to change the game state, you will need to take extra care to avoid out-of-sync errors. The easiest way is to have the callback register a new event with the same name, which will have the side-effect of disallowing undo. Since on_event is called before the event is fully processed, this new event will be triggered as soon as the callback exits. You could even wrap this functionality into a function:

function disallow_undo()
	wesnoth.wml_actions.event { name = wesnoth.current.event_context.name }
end

wesnoth.game_events.on_load

  • wesnoth.game_events.on_loadfunction(scenario wml)

This is a lower-level method of handling the load game event and the wesnoth.persistent_tags mechanism. It is called whenever the game is loaded, before the on_load event is triggered. If overriding this, be sure to call the original version of the function, as wesnoth.persistent_tags also depends on this event.

The scenario WML passed to this function is not the complete WML of the scenario but only the tags that the core Wesnoth engine does not know how to handle. You will never find an [event] or [side] tag in this table, for example. Although some core tags are already handled using this mechanism, you should not rely on this - for example, reading [item] tags in on_load is not guaranteed to work in future versions. The following is a list of all such tags:

color_palette   color_range  display  end_level_data  era           event             generator
item            label        lua      menu_item       modification  modify_unit_type  music
next_item_name  objectives   options  side            sound_source  story             terrain_graphics
time            time_area    tunnel   undo_stack      used_item     variables

Note: since the on_load hook is called very early in the scenario, it cannot be set inside a [lua] tag in an [event], not even a preload one - it's called even before preload fires. It can only be set inside a [lua] tag outside an event, either at [scenario] level or global.

wesnoth.game_events.on_save

  • wesnoth.game_events.on_savefunction() → scenario wml

The counterpart to on_load, this function is called whenever the game is saved. Any returned WML is merged into the scenario WML in the saved game. Usually the higher-level wesnoth.persistent_tags mechanism is recommended instead of overriding this directly. If overriding this, be sure to call the original version of the function, as wesnoth.persistent_tags also depends on this event.

This callback can only be used to add custom data to the saved game. You can't use it to add extra data that the engine already knows about, such as events or sides, as such tags will be ignored when merging the result into the saved game WML. Although some core tags are already handled using this mechanism, you should not rely on this - for example, adding [item] tags in on_save is not guaranteed to work in future versions. See the on_load description above for a complete list of these tags.

Here's an example of how to use on_load and on_save to handle a custom tag:

-- some value that survives save/load cycles, but that is not forwarded to the next level
local level_local_data = 0

local old_on_load = wesnoth.game_event.on_load
function wesnoth.game_event.on_load(cfg)
    for i, tag in ipairs(cfg) do
        if tag[1] == "my_data" then
            -- recover the value stored in the savefile
            level_local_data = tag[2].value
            -- erase the child, since it has been handled
            table.remove(cfg, i)
            break
        end
    end
    -- call the previous hook, in case there are still some containers in the savefile
    old_on_load(cfg)
end

local old_on_save = wesnoth.game_events.on_save
function wesnoth.game_events.on_save()
    -- call the previous hook, in case it had some containers to store
    local cfg = old_on_save()
    -- add our own container to them
    table.insert(cfg, wml.tag.my_data{ value = level_local_data })
    -- tell the engine to store them in the savefile
    return cfg
end

wesnoth.game_events.on_mouse_action

  • wesnoth.game_events.on_mouse_actionfunction(x, y)

This function is called whenever the user left clicks on a hex, and is passed the coordinates of the clicked hex. This could be used for many purposes, such as a true long-ranged attack, but it has some problems.

The callback does not cancel the normal action that would have occurred as a result of the click, such as selecting or moving a unit. Furthermore, if the on_mouse_action callback takes some time to execute, it's possible the mouse moves during this time, and when the callback has completed, the action taken depends on the new position of the mouse, rather than the position that was originally clicked. Because of this bug, it is probably a bad idea to display any sort of animation in this callback or do anything that takes time.

In addition, this callback is unsynced, so it is not safe for use in multiplayer games unless you do manual sychronization.

Some discussion of the problems with this callback can be found in this enhancement request.

wesnoth.game_events.on_mouse_button

(Version 1.17.22 and later only)

  • wesnoth.game_events.on_mouse_buttonfunction(x, y, button, event) → consume?

Registers a handler for mouse button events occurring on a valid map tile and when the UI is in a normal play state. It does not receive notification for any events on a GUI, the minimap, or when a message is up.

  • x, y: The map coordinates the event occurred on
  • button: The name of the button as a string, which will be one of "left", "middle", "right", "mouse4", or "mouse5".
  • event: One of "down", "up", or "click"

The usual order of events is down, up, and then click. However, left and middle clicks currently occur at the mouse down, so the order is different for these two. The most common use case will be to handle click events and ignore the rest. The callback may consume a click event, preventing the game engine from invoking it's default action, by returning true. The return value when called for down or up events is ignored.

Order of button events
Button Order
left down, click, up
middle down, click, up
right down, up, click
mouse4 down, up, click
mouse5 down, up, click

At this time, there is no Lua support for drag and drop events. When a right, mouse4 or mouse5 down occurs on one tile, but then an up event on a different one (i.e., the mouse is dragged), there will be no click event. For a proper click event, both down and up must occur on the same tile. While this restriction is not currently possible with left and middle clicks, the behavior may be changed in a future release so that all mouse button events are handled consistently.

WARNING: Your event handler should be as brief as possible! It should check for the exact events it is looking for and immediately return false for all others. It should not attempt to display any messages, ask the user questions, pontificate the meaning of life, sell memorabilia on ebay, etc. Any intensive operations (requiring more than a few milliseconds) should be delegated to a coroutine or otherwise scheduled to occur later (e.g., setting by a "flag" variable).

Example

wesnoth.game_events.on_mouse_button = function(x, y, button, event)
    if not (button == "right" and event == "click") then
        return false
    end

    -- Do stuff here
    return false
end

wesnoth.game_events.on_mouse_move

  • wesnoth.game_events.on_mouse_movefunction(x, y)

This function is called when the mouse moves. More specifically, it's called whenever the currently hovered hex changes. This callback does not share most of the shortcomings of the on_mouse_action callback, but it is still unsynced.