Difference between revisions of "LuaWML/Events"

From The Battle for Wesnoth Wiki
(Mentioned hooks and clarified direct usage of functions from wml_actions)
(Add move notice)
 
(40 intermediate revisions by 16 users not shown)
Line 1: Line 1:
 +
{{Template:LuaMove}}
 +
 
This page describes the [[LuaWML]] functions and helpers for interacting with events and action handlers.
 
This page describes the [[LuaWML]] functions and helpers for interacting with events and action handlers.
  
 
==== wesnoth.fire ====
 
==== wesnoth.fire ====
  
Fires a WML action. Argument 1 is the name of the action. Argument 2 is the WML table describing the action. Note: WML variables are substituted.
+
* '''wesnoth.fire(''wml_action_name'', ''wml_action_contents'')'''
  
wesnoth.fire("message", { speaker="narrator", message=_ "Hello World!" })
+
Fires a [[ActionWML|WML action]]. Argument 1 is the name of the action. Argument 2 is the WML table describing the action. Note: WML variables are substituted.
  
==== wesnoth.register_wml_action ====
+
<syntaxhighlight lang=lua>
 +
wesnoth.fire("message", { speaker="narrator", message=_ "Hello World!" })
 +
</syntaxhighlight>
  
Registers the second argument as a handler for the given action tag. When the game encounters this tag an event, it fires the action handler and passes the content of the WML object as the first argument. If the registered function raises an error with a message starting with ''~wml:'', it will not be displayed as a Lua error with a backtrace but as a standard WML error. (See also [[#helper.wml_error]].) The following script defines a [freeze_unit] tag that sets to 0 the move points of the unit with the given id.
+
==== wesnoth.wml_actions ====
  
wesnoth.register_wml_action("freeze_unit",
+
This is not a function but an associative table indexed by WML action names. It contains functions performing the corresponding actions. Using these functions is slightly different from calling [[#wesnoth.fire]], which automatically casts the input to a vconfig before passing it to the function.
    function(cfg)
 
        local unit_id = cfg.id or error("~wml:[freeze_unit] expects an id= attribute.", 0)
 
        helper.modify_unit({ id = unit_id }, { moves = 0 })
 
    end
 
)
 
  
# The new tag can now be used in plain WML code.
+
<syntaxhighlight lang=lua>
[freeze_unit]
+
function wesnoth.wml_actions.freeze_unit(cfg)
    id=Delfador
+
    local unit_id = cfg.id or helper.wml_error "[freeze_unit] expects an id= attribute."
[/freeze_unit]
+
    helper.modify_unit({ id = unit_id }, { moves = 0 })
 +
end
 +
</syntaxhighlight>
  
The function returns the previous action handler. Its metatable appears as '''"wml action handler"'''. This handler can be called to delegate part of the action, if needed. For instance, the following script modifies the [[InterfaceActionsWML#Other interface tags|[print]]] tag so that messages are displayed with a bigger font.
+
The new tag can now be used in plain WML code.
  
local old_print_handler
+
<syntaxhighlight lang=wml>
old_print_handler = wesnoth.register_wml_action("print",
+
[freeze_unit]
    function(cfg)
+
    id=Delfador
        -- Do not perform variable substitution.
+
[/freeze_unit]
        local new_cfg = cfg.__literal
+
</syntaxhighlight>
        -- Modify the size field and call the previous handler.
 
        new_cfg.size = (cfg.size or 12) + 10
 
        old_print_handler(cfg)
 
    end
 
)
 
  
Note that the data coming from the WML tag are stored in a read-only object and variable substitution happens on the fly when accessing attributes and children. The metatable of this proxy object appears as '''"wml object"''' See [[LuaWML#Encoding WML objects into Lua tables]] for more details.
+
You can override functions already assigned to the table. This is useful if you need to extend functionality of core tags. For instance, the following script overrides the [[InterfaceActionsWML#Other interface tags|[print]]] tag so that messages are displayed with a bigger font.
  
==== wesnoth.wml_actions ====
+
<syntaxhighlight lang=lua>
 +
function wesnoth.wml_actions.print(cfg)
 +
  cfg.size = (cfg.size or 12) + 10
 +
  wml_actions.print(cfg)
 +
end
 +
</syntaxhighlight>
 +
 
 +
Note: When calling an action handler directly through its function stored in ''wesnoth.wml_actions'', the engine is not involved. As a consequence, whether variable substitution will happen is up to the handler. In particular, if the argument is a plain table, the caller should have substituted WML variables beforehand to be on the safe side. Moreover, table arguments might be modified by the action handler, so they should usually not be reused for consecutive calls. If variable substitution should happen and/or table content should be preserved, one can call [[#wesnoth.tovconfig]] and pass its result to the handler. Calling [[#wesnoth.fire]] is another possibility.
 +
 
 +
==== wesnoth.wml_conditionals ====
 +
 
 +
{{DevFeature1.13|0}}
 +
 
 +
This is an associative table like wesnoth.wml_actions. You can use it to define new conditional wml tags that will be recognized in WML when using [if], [show_if], [while], etc., or more generally when '''wesnoth.eval_conditional''' is run.
 +
 
 +
Use it like
 +
 
 +
<syntaxhighlight lang=lua>
 +
function wesnoth.wml_conditionals.foo(cfg)
 +
    local bar = cfg.bar or error("[foo] tag did not have 'bar' attribute")
  
{{DevFeature1.9}}
+
    return (bar == "baz")
 +
end
 +
</syntaxhighlight>
  
This is not a function but an associative table indexed by WML action names. It contains functions performing the corresponding actions. Using these functions is similar to calling [[#wesnoth.fire]], while setting entries of the table is similar to calling [[#wesnoth.register_wml_action]].
+
If this lua code is executed, it would make the following syntax be valid WML in your add-on:
  
function wesnoth.wml_actions.freeze_unit(cfg)
+
<syntaxhighlight lang=wml>
    local unit_id = cfg.id or helper.wml_error "[freeze_unit] expects an id= attribute."
+
[if]
    helper.modify_unit({ id = unit_id }, { moves = 0 })
+
  [foo]
end
+
      bar = $X
 +
  [/foo]
 +
  [then]
 +
      [message]
 +
        ...
 +
      [/message]
 +
  [/then]
 +
[/if]
 +
</syntaxhighlight>
  
Note: when calling an action handler directly through its function stored in ''wesnoth.wml_actions'', the engine is not involved. As a consequence, whether variable substitution will happen is up to the actual handler. So, if the argument is a plain table, the caller should have substituted WML variables beforehand to be on the safe side. Another solution is to call [[#helper.tovconfig]] and pass its result to the handler.
+
You cannot override the meaning of any core conditional tags.
  
 
==== wesnoth.game_events ====
 
==== wesnoth.game_events ====
 
{{DevFeature1.9}}
 
  
 
This is not a function but an associative table indexed by engine action names. It contains function hooks the engine calls whenever it performs a particular action.
 
This is not a function but an associative table indexed by engine action names. It contains function hooks the engine calls whenever it performs a particular action.
Line 59: Line 82:
 
* '''on_save''': function called when the engine (auto)saves a scenario file; it should return a WML table and the children of this table are added to the savefile.
 
* '''on_save''': function called when the engine (auto)saves a scenario file; it should return a WML table and the children of this table are added to the savefile.
 
* '''on_load''': function called when the engine loads a scenario file; its argument is a WML table that contains all the children of the savefile that the engine did not handle.
 
* '''on_load''': function called when the engine loads a scenario file; its argument is a WML table that contains all the children of the savefile that the engine did not handle.
 +
* '''on_event''': function called before each WML event is executed; its argument is the event name; other event arguments can be recovered from [[LuaWML:Misc#wesnoth.current|wesnoth.current.event_context]].
 +
* '''on_mouse_action''': function called when the user leftclicks on hex; its arguments are x,y of location selected. {{DevFeature1.13|10}} Might be from earlier 1.13, not tested.
 +
 +
''on_mouse_action'' has several gotchas. Currently (1.14.1), the sequence goes like this:
 +
* The player clicks the mouse (mousedown followed by mouse up in the same hex).
 +
* on_mouse_action triggers, for that hex.
 +
* the click has its normal UI effect (such as selecting or moving a unit), based on the '''current''' position of the mouse.
 +
Thus, if you display any sort of animation during the on_mouse_action function, the player may have ''moved'' the mouse, causing a mouse-click effect to happen in a place they didn't expect, which is also different than the location that was passed to on_mouse_action. In addition, there are the usual gotchas about this function not being synchronized for network games or replays. See also: https://github.com/wesnoth/wesnoth/issues/2325
  
 
The ''on_save'' and ''on_load'' hooks can be used to manipulate data that are neither meant to be forwarded to the next level nor substituted on the fly. (For either of these two purposes, WML variables are the best choice.) For instance, toplevel tags like [item], [event], [time_area], and so on, could typically be handled by such hooks.
 
The ''on_save'' and ''on_load'' hooks can be used to manipulate data that are neither meant to be forwarded to the next level nor substituted on the fly. (For either of these two purposes, WML variables are the best choice.) For instance, toplevel tags like [item], [event], [time_area], and so on, could typically be handled by such hooks.
  
-- some value that survives save/load cycles, but that is not forwarded to the next level
+
<syntaxhighlight lang=lua>
local level_local_data = 0
+
-- 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)
+
local old_on_load = wesnoth.game_event.on_load
    for i = 1,#cfg do
+
function wesnoth.game_event.on_load(cfg)
        if cfg[i][1] == "my_data" then
+
    for i = 1,#cfg do
            -- recover the value stored in the savefile
+
        if cfg[i][1] == "my_data" then
            level_local_data = cfg[i][2].value
+
            -- recover the value stored in the savefile
            -- erase the child, since it has been handled
+
            level_local_data = cfg[i][2].value
            table.remove(cfg, i)
+
            -- erase the child, since it has been handled
            break
+
            table.remove(cfg, i)
        end
+
            break
    end
+
        end
    -- call the previous hook, in case there are still some containers in the savefile
+
    end
    old_on_load(cfg)
+
    -- call the previous hook, in case there are still some containers in the savefile
end
+
    old_on_load(cfg)
+
end
local old_on_save = wesnoth.game_events.on_save
+
 
function wesnoth.game_events.on_save()
+
local old_on_save = wesnoth.game_events.on_save
    -- call the previous hook, in case it had some containers to store
+
function wesnoth.game_events.on_save()
    local cfg = old_on_save()
+
    -- call the previous hook, in case it had some containers to store
    -- add our own container to them
+
    local cfg = old_on_save()
    table.insert(cfg, { "my_data", { value = level_local_data } })
+
    -- add our own container to them
    -- tell the engine to store them in the savefile
+
    table.insert(cfg, { "my_data", { value = level_local_data } })
    return cfg
+
    -- tell the engine to store them in the savefile
end
+
    return cfg
 +
end
 +
</syntaxhighlight>
  
 
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 has to be set inside a [lua] tag outside or at [scenario] level.
 
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 has to be set inside a [lua] tag outside or at [scenario] level.
 +
 +
 +
Note: Some tag names are reserved for engine use and should not be modified using the above on_save/on_load method. These tag names are:
 +
"color_palette", "color_range", "era", "event", "generator", "label",
 +
"lua", "menu_item", "music", "next_item_name", "objectives", "side", "sound_source",
 +
"story", "terrain_graphics", "time", "time_area", "tunnel", "used_item", "variables"
 +
 +
Note: a on_event handler will not prevent undoing of that event, so usually you need to add an event to diallow undo to prevent OOS. You can add an event handler for that event inside a on_event callback. A possible way to define a disallow_undo function is:
 +
 +
<syntaxhighlight lang=lua>
 +
function disallow_undo()
 +
wesnoth.wml_actions.event { name = wesnoth.current.event_context.name }
 +
end
 +
</syntaxhighlight>
 +
Which should then be called from every on_event callback which changes the gamestate.
 +
 +
{{DevFeature1.13|5}}:
 +
The event names passed to ''on_event'' always use underscores instead of spaces
 +
 +
==== wesnoth.persistent_tags ====
 +
 +
{{DevFeature1.13|12}}
 +
 +
This is an associative table defining tags that should be persisted in saved games. Each tag is itself a table containing two functions, read and write. The write function is called in <tt>on_load</tt> and passed a function as a parameter which takes a WML table and adds it the saved game under the specified tag; the read function is called once per matching tag found in the saved game, and is passed a WML table of its contents. Note the asymmetry here: if you're saving an array, the write function is responsible for saving the entire array (and is only called once), while the read function is only responsible for loading one item (and is called several times).
 +
 +
Example:
 +
 +
<syntaxhighlight lang=lua>
 +
local inventory = {}
 +
 +
function wesnoth.persistent_tags.inventory.read(cfg)
 +
    inventory[cfg.side] = cfg
 +
end
 +
 +
function wesnoth.persistent_tags.inventory.write(add)
 +
    for i = 1, #wesnoth.sides do
 +
        add(inventory[i])
 +
    end
 +
end
 +
</syntaxhighlight>
 +
 +
Notice that you don't need to create <tt>wesnoth.persistent_tags.inventory</tt> as an empty table first; you can simply define the read and write functions.
  
 
==== wesnoth.fire_event ====
 
==== wesnoth.fire_event ====
 +
 +
* '''wesnoth.fire_event(''event_name'', [''x1'', ''y1'', [''x2'', ''y2'']], [''first_weapon'', [''second_weapon'']])'''
  
 
Fires all the WML events with the given name. Optional parameters allow passing two locations and two tables. These parameters will be matched against the [filter], [filter_second], [filter_attack], and [filter_second_attack] of any event handler, and are used to fill the WML variables "unit", "second_unit", "weapon", and "second_weapon". These parameters can also be read through ''current.event_context''. The function returns a boolean indicating whether the game state was modified.
 
Fires all the WML events with the given name. Optional parameters allow passing two locations and two tables. These parameters will be matched against the [filter], [filter_second], [filter_attack], and [filter_second_attack] of any event handler, and are used to fill the WML variables "unit", "second_unit", "weapon", and "second_weapon". These parameters can also be read through ''current.event_context''. The function returns a boolean indicating whether the game state was modified.
  
 
  wesnoth.fire_event("explosion", 17, 42, { damage = "fire" })
 
  wesnoth.fire_event("explosion", 17, 42, { damage = "fire" })
 +
 +
==== wesnoth.fire_event_by_id ====
 +
{{DevFeature1.13|6}}
 +
 +
* '''wesnoth.fire_event_by_id(''event_id'', [''x1'', ''y1'', [''x2'', ''y2'']], [''first_weapon'', [''second_weapon'']])'''
 +
 +
Fires a single WML event with the given id. Optional parameters allow passing two locations and two tables. These parameters will be matched against the [filter], [filter_second], [filter_attack], and [filter_second_attack] of the event handler, and are used to fill the WML variables "unit", "second_unit", "weapon", and "second_weapon". These parameters can also be read through ''current.event_context''. The function returns a boolean indicating whether the game state was modified.
 +
 +
wesnoth.fire_event_by_id("explosion_1", 17, 42, { damage = "fire" })
 +
 +
==== wesnoth.add_event_handler ====
 +
 +
* '''wesnoth.add_event_handler(''cfg'')'''
 +
 +
{{DevFeature1.13|0}}
 +
 +
Registers a new event handler. This takes a WML table containing the same information normally used by the [[EventWML#The_.5Bevent.5D_Tag|[event]]] tag.
 +
 +
==== wesnoth.remove_event_handler ====
 +
 +
* '''wesnoth.remove_event_handler(''id'')'''
 +
 +
{{DevFeature1.13|0}}
 +
 +
Removes an event handler. This requires the event handler to have been assigned an [[EventWML#id|id]] at creation time.
  
 
==== wesnoth.eval_conditional ====
 
==== wesnoth.eval_conditional ====
 +
 +
* '''wesnoth.eval_conditional(''conditional_tags'')'''
  
 
Returns true if the conditional described by the WML table passes. Note: WML variables are substituted.
 
Returns true if the conditional described by the WML table passes. Note: WML variables are substituted.
  
local result = wesnoth.eval_conditional {
+
<syntaxhighlight lang=lua>
  { "have_unit", { id = "hero" } },
+
local result = wesnoth.eval_conditional {
  { "variable", { name = "counter", numerical_equals = "$old_counter" } }
+
  { "have_unit", { id = "hero" } },
}
+
  { "variable", { name = "counter", numerical_equals = "$old_counter" } }
 +
}
 +
</syntaxhighlight>
 +
 
 +
==== wesnoth.tovconfig ====
 +
 
 +
* '''wesnoth.tovconfig(''config'')'''
 +
 
 +
Converts a WML table into a proxy object which performs variable substitution on the fly.
 +
 
 +
<syntaxhighlight lang=lua>
 +
wesnoth.set_variable("varname", "to_be_deleted")
 +
wesnoth.wml_actions.clear_variable { name = "to_be_deleted" }              -- correct
 +
wesnoth.wml_actions.clear_variable { name = "$varname" }                    -- error: try to delete a variable literally called "$varname"
 +
wesnoth.wml_actions.clear_variable(wesnoth.tovconfig { name = "$varname" }) -- correct: "$varname" is replaced by "to_be_deleted" at the right time
 +
</syntaxhighlight>
  
 
==== helper.set_wml_action_metatable ====
 
==== helper.set_wml_action_metatable ====
  
Sets the metable of a table so that it can be used to fire WML actions. Returns the table. The fields of the table are then simple wrappers around a call to [[#wesnoth.fire]].
+
* '''helper.set_wml_action_metatable{}'''
 +
 
 +
Sets the metatable of a table so that it can be used to fire WML actions. Returns the table. The fields of the table are then simple wrappers around a call to [[#wesnoth.fire]].
  
local W = helper.set_wml_action_metatable {}
+
<syntaxhighlight lang=lua>
W.message { speaker = "narrator", message = "?" }
+
local W = helper.set_wml_action_metatable {}
 +
W.message { speaker = "narrator", message = "?" }
 +
</syntaxhighlight>
  
 
==== helper.wml_error ====
 
==== helper.wml_error ====
 +
 +
* '''helper.wml_error(''message'')'''
  
 
Interrupts the current execution and displays a chat message that looks like a WML error.
 
Interrupts the current execution and displays a chat message that looks like a WML error.
  
local names = cfg.name or helper.wml_error("[clear_variable] missing required name= attribute.")
+
<syntaxhighlight lang=lua>
 +
local names = cfg.name or helper.wml_error("[clear_variable] missing required name= attribute.")
 +
</syntaxhighlight>
 +
 
 +
==== helper.literal ====
 +
 
 +
* '''helper.literal(''config'')'''
  
==== helper.tovconfig ====
+
Returns the ''__literal'' field of its argument if it is a userdata, the argument itself otherwise. This function is meant to be called when a WML action handler can be called indifferently from WML (hence receiving a userdata) or from Lua (hence possibly receiving a table).
  
Converts a WML table into a proxy object which performs variable substitution on the fly.
+
<syntaxhighlight lang=lua>
 +
function wml_actions.display_literal_value(cfg)
 +
  cfg = helper.literal(cfg)
 +
  wesnoth.message(tostring(cfg.value))
 +
end
 +
</syntaxhighlight>
 +
 
 +
Note: when the argument is a plain table, the function returns it as is. In particular, modifying the fields of the returned table causes the original table to be modified too.
 +
 
 +
==== helper.parsed ====
 +
 
 +
* '''helper.parsed(''config'')'''
 +
 
 +
Returns the ''__parsed'' field of its argument if it is a userdata, the argument itself otherwise. See also [[#helper.literal]].
 +
 
 +
==== helper.shallow_literal ====
 +
 
 +
* '''helper.shallow_literal(''config'')'''
 +
 
 +
Returns the ''__shallow_literal'' field of its argument if it is a userdata, the argument itself otherwise. See also [[#helper.literal]].
 +
 
 +
==== helper.shallow_parsed ====
 +
 
 +
* '''helper.shallow_parsed(''config'')'''
 +
 
 +
Returns the ''__shallow_parsed'' field of its argument if it is a userdata, the argument itself otherwise. See also [[#helper.literal]].
 +
 
 +
=== on_event.lua ===
 +
 
 +
{{DevFeature1.13|6}}
 +
 
 +
* '''on_event(''name'', [ ''priority'',] ''function'')'''
 +
 
 +
The file ''data/lua/on_event.lua'' provides a simple way to register lua functions as wml event handlers.
 +
 
 +
<syntaxhighlight lang=lua>
 +
local on_event = wesnoth.require("lua/on_event.lua")
 +
on_event("moveto", function(context)
 +
  if context.x1 == 10 and context.y1 == 11 then
 +
    wesnoth.message("unit moved to (10,11)")
 +
  end
 +
end)
 +
 
 +
on_event("die", function(context)
 +
  if context.x1 == 20 and context.y1 == 21 then
 +
    wesnoth.message("unit died at (20,21)")
 +
  end
 +
end)
 +
</syntaxhighlight>
  
wesnoth.set_variable("varname", "to_be_deleted")
+
The passed function will be called with '''wesnoth.current.event_context''' as paremter. This isn't as powerful as wml event handlers though: Filters must be implemented with an if in the function body (as shown above with ''context.x1 == 10 and context.x1 == 11''). There is no way to remove those event handlers, in particular they behave like ''first_time_only=no'' events. They are not persisted on save/load so they must be re-defined on each load (e.g. in a preload event).
wesnoth.wml_actions.clear_variable { name = "to_be_deleted" }              -- correct
+
Events are executed from lowest priority to highest.
wesnoth.wml_actions.clear_variable { name = "$varname" }                    -- error: try to delete a variable literally called "$varname"
+
[[Category: Lua Reference]]
wesnoth.wml_actions.clear_variable(helper.tovconfig { name = "$varname" }) -- correct: "$varname" is replaced by "to_be_deleted" at the right time
 

Latest revision as of 13:26, 12 February 2021



This page describes the LuaWML functions and helpers for interacting with events and action handlers.

wesnoth.fire

  • wesnoth.fire(wml_action_name, wml_action_contents)

Fires a WML action. Argument 1 is the name of the action. Argument 2 is the WML table describing the action. Note: WML variables are substituted.

wesnoth.fire("message", { speaker="narrator", message=_ "Hello World!" })

wesnoth.wml_actions

This is not a function but an associative table indexed by WML action names. It contains functions performing the corresponding actions. Using these functions is slightly different from calling #wesnoth.fire, which automatically casts the input to a vconfig before passing it to the function.

function wesnoth.wml_actions.freeze_unit(cfg)
    local unit_id = cfg.id or helper.wml_error "[freeze_unit] expects an id= attribute."
    helper.modify_unit({ id = unit_id }, { moves = 0 })
end

The new tag can now be used in plain WML code.

[freeze_unit]
    id=Delfador
[/freeze_unit]

You can override functions already assigned to the table. This is useful if you need to extend functionality of core tags. For instance, the following script overrides the [print] tag so that messages are displayed with a bigger font.

function wesnoth.wml_actions.print(cfg)
  cfg.size = (cfg.size or 12) + 10
  wml_actions.print(cfg)
end

Note: When calling an action handler directly through its function stored in wesnoth.wml_actions, the engine is not involved. As a consequence, whether variable substitution will happen is up to the handler. In particular, if the argument is a plain table, the caller should have substituted WML variables beforehand to be on the safe side. Moreover, table arguments might be modified by the action handler, so they should usually not be reused for consecutive calls. If variable substitution should happen and/or table content should be preserved, one can call #wesnoth.tovconfig and pass its result to the handler. Calling #wesnoth.fire is another possibility.

wesnoth.wml_conditionals

(Version 1.13.0 and later only)

This is an associative table like wesnoth.wml_actions. You can use it to define new conditional wml tags that will be recognized in WML when using [if], [show_if], [while], etc., or more generally when wesnoth.eval_conditional is run.

Use it like

function wesnoth.wml_conditionals.foo(cfg)
    local bar = cfg.bar or error("[foo] tag did not have 'bar' attribute")

    return (bar == "baz")
end

If this lua code is executed, it would make the following syntax be valid WML in your add-on:

[if]
   [foo]
      bar = $X
   [/foo]
   [then]
      [message]
         ...
      [/message]
   [/then]
[/if]

You cannot override the meaning of any core conditional tags.

wesnoth.game_events

This is not a function but an associative table indexed by engine action names. It contains function hooks the engine calls whenever it performs a particular action.

  • on_save: function called when the engine (auto)saves a scenario file; it should return a WML table and the children of this table are added to the savefile.
  • on_load: function called when the engine loads a scenario file; its argument is a WML table that contains all the children of the savefile that the engine did not handle.
  • on_event: function called before each WML event is executed; its argument is the event name; other event arguments can be recovered from wesnoth.current.event_context.
  • on_mouse_action: function called when the user leftclicks on hex; its arguments are x,y of location selected. (Version 1.13.10 and later only) Might be from earlier 1.13, not tested.

on_mouse_action has several gotchas. Currently (1.14.1), the sequence goes like this:

  • The player clicks the mouse (mousedown followed by mouse up in the same hex).
  • on_mouse_action triggers, for that hex.
  • the click has its normal UI effect (such as selecting or moving a unit), based on the current position of the mouse.

Thus, if you display any sort of animation during the on_mouse_action function, the player may have moved the mouse, causing a mouse-click effect to happen in a place they didn't expect, which is also different than the location that was passed to on_mouse_action. In addition, there are the usual gotchas about this function not being synchronized for network games or replays. See also: https://github.com/wesnoth/wesnoth/issues/2325

The on_save and on_load hooks can be used to manipulate data that are neither meant to be forwarded to the next level nor substituted on the fly. (For either of these two purposes, WML variables are the best choice.) For instance, toplevel tags like [item], [event], [time_area], and so on, could typically be handled by such hooks.

-- 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 = 1,#cfg do
        if cfg[i][1] == "my_data" then
            -- recover the value stored in the savefile
            level_local_data = cfg[i][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, { "my_data", { value = level_local_data } })
    -- tell the engine to store them in the savefile
    return cfg
end

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 has to be set inside a [lua] tag outside or at [scenario] level.


Note: Some tag names are reserved for engine use and should not be modified using the above on_save/on_load method. These tag names are:

"color_palette", "color_range", "era", "event", "generator", "label",
"lua", "menu_item", "music", "next_item_name", "objectives", "side", "sound_source",
"story", "terrain_graphics", "time", "time_area", "tunnel", "used_item", "variables"

Note: a on_event handler will not prevent undoing of that event, so usually you need to add an event to diallow undo to prevent OOS. You can add an event handler for that event inside a on_event callback. A possible way to define a disallow_undo function is:

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

Which should then be called from every on_event callback which changes the gamestate.

(Version 1.13.5 and later only): The event names passed to on_event always use underscores instead of spaces

wesnoth.persistent_tags

(Version 1.13.12 and later only)

This is an associative table defining tags that should be persisted in saved games. Each tag is itself a table containing two functions, read and write. The write function is called in on_load and passed a function as a parameter which takes a WML table and adds it the saved game under the specified tag; the read function is called once per matching tag found in the saved game, and is passed a WML table of its contents. Note the asymmetry here: if you're saving an array, the write function is responsible for saving the entire array (and is only called once), while the read function is only responsible for loading one item (and is called several times).

Example:

local inventory = {}

function wesnoth.persistent_tags.inventory.read(cfg)
    inventory[cfg.side] = cfg
end

function wesnoth.persistent_tags.inventory.write(add)
    for i = 1, #wesnoth.sides do
        add(inventory[i])
    end
end

Notice that you don't need to create wesnoth.persistent_tags.inventory as an empty table first; you can simply define the read and write functions.

wesnoth.fire_event

  • wesnoth.fire_event(event_name, [x1, y1, [x2, y2]], [first_weapon, [second_weapon]])

Fires all the WML events with the given name. Optional parameters allow passing two locations and two tables. These parameters will be matched against the [filter], [filter_second], [filter_attack], and [filter_second_attack] of any event handler, and are used to fill the WML variables "unit", "second_unit", "weapon", and "second_weapon". These parameters can also be read through current.event_context. The function returns a boolean indicating whether the game state was modified.

wesnoth.fire_event("explosion", 17, 42, { damage = "fire" })

wesnoth.fire_event_by_id

(Version 1.13.6 and later only)

  • wesnoth.fire_event_by_id(event_id, [x1, y1, [x2, y2]], [first_weapon, [second_weapon]])

Fires a single WML event with the given id. Optional parameters allow passing two locations and two tables. These parameters will be matched against the [filter], [filter_second], [filter_attack], and [filter_second_attack] of the event handler, and are used to fill the WML variables "unit", "second_unit", "weapon", and "second_weapon". These parameters can also be read through current.event_context. The function returns a boolean indicating whether the game state was modified.

wesnoth.fire_event_by_id("explosion_1", 17, 42, { damage = "fire" })

wesnoth.add_event_handler

  • wesnoth.add_event_handler(cfg)

(Version 1.13.0 and later only)

Registers a new event handler. This takes a WML table containing the same information normally used by the [event] tag.

wesnoth.remove_event_handler

  • wesnoth.remove_event_handler(id)

(Version 1.13.0 and later only)

Removes an event handler. This requires the event handler to have been assigned an id at creation time.

wesnoth.eval_conditional

  • wesnoth.eval_conditional(conditional_tags)

Returns true if the conditional described by the WML table passes. Note: WML variables are substituted.

local result = wesnoth.eval_conditional {
  { "have_unit", { id = "hero" } },
  { "variable", { name = "counter", numerical_equals = "$old_counter" } }
}

wesnoth.tovconfig

  • wesnoth.tovconfig(config)

Converts a WML table into a proxy object which performs variable substitution on the fly.

wesnoth.set_variable("varname", "to_be_deleted")
wesnoth.wml_actions.clear_variable { name = "to_be_deleted" }              -- correct
wesnoth.wml_actions.clear_variable { name = "$varname" }                    -- error: try to delete a variable literally called "$varname"
wesnoth.wml_actions.clear_variable(wesnoth.tovconfig { name = "$varname" }) -- correct: "$varname" is replaced by "to_be_deleted" at the right time

helper.set_wml_action_metatable

  • helper.set_wml_action_metatable{}

Sets the metatable of a table so that it can be used to fire WML actions. Returns the table. The fields of the table are then simple wrappers around a call to #wesnoth.fire.

local W = helper.set_wml_action_metatable {}
W.message { speaker = "narrator", message = "?" }

helper.wml_error

  • helper.wml_error(message)

Interrupts the current execution and displays a chat message that looks like a WML error.

local names = cfg.name or helper.wml_error("[clear_variable] missing required name= attribute.")

helper.literal

  • helper.literal(config)

Returns the __literal field of its argument if it is a userdata, the argument itself otherwise. This function is meant to be called when a WML action handler can be called indifferently from WML (hence receiving a userdata) or from Lua (hence possibly receiving a table).

function wml_actions.display_literal_value(cfg)
   cfg = helper.literal(cfg)
   wesnoth.message(tostring(cfg.value)) 
end

Note: when the argument is a plain table, the function returns it as is. In particular, modifying the fields of the returned table causes the original table to be modified too.

helper.parsed

  • helper.parsed(config)

Returns the __parsed field of its argument if it is a userdata, the argument itself otherwise. See also #helper.literal.

helper.shallow_literal

  • helper.shallow_literal(config)

Returns the __shallow_literal field of its argument if it is a userdata, the argument itself otherwise. See also #helper.literal.

helper.shallow_parsed

  • helper.shallow_parsed(config)

Returns the __shallow_parsed field of its argument if it is a userdata, the argument itself otherwise. See also #helper.literal.

on_event.lua

(Version 1.13.6 and later only)

  • on_event(name, [ priority,] function)

The file data/lua/on_event.lua provides a simple way to register lua functions as wml event handlers.

local on_event = wesnoth.require("lua/on_event.lua")
on_event("moveto", function(context)
  if context.x1 == 10 and context.y1 == 11 then
    wesnoth.message("unit moved to (10,11)")
  end
end)

on_event("die", function(context)
  if context.x1 == 20 and context.y1 == 21 then
    wesnoth.message("unit died at (20,21)")
  end
end)

The passed function will be called with wesnoth.current.event_context as paremter. This isn't as powerful as wml event handlers though: Filters must be implemented with an if in the function body (as shown above with context.x1 == 10 and context.x1 == 11). There is no way to remove those event handlers, in particular they behave like first_time_only=no events. They are not persisted on save/load so they must be re-defined on each load (e.g. in a preload event). Events are executed from lowest priority to highest.

This page was last edited on 12 February 2021, at 13:26.