Difference between revisions of "LuaWML/Events"

From The Battle for Wesnoth Wiki
(Added wesnoth.wml_actions)
(Mentioned hooks and clarified direct usage of functions from wml_actions)
Line 48: Line 48:
 
     helper.modify_unit({ id = unit_id }, { moves = 0 })
 
     helper.modify_unit({ id = unit_id }, { moves = 0 })
 
  end
 
  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 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.
 +
 +
==== 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.
 +
 +
* '''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.
 +
 +
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.
  
 
==== wesnoth.fire_event ====
 
==== wesnoth.fire_event ====
Line 76: Line 119:
  
 
  local names = cfg.name or helper.wml_error("[clear_variable] missing required name= attribute.")
 
  local names = cfg.name or helper.wml_error("[clear_variable] missing required name= attribute.")
 +
 +
==== helper.tovconfig ====
 +
 +
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(helper.tovconfig { name = "$varname" }) -- correct: "$varname" is replaced by "to_be_deleted" at the right time

Revision as of 20:37, 1 September 2010

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

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("message", { speaker="narrator", message=_ "Hello World!" })

wesnoth.register_wml_action

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.register_wml_action("freeze_unit",
    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.
[freeze_unit]
    id=Delfador
[/freeze_unit]

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 [print] tag so that messages are displayed with a bigger font.

local old_print_handler
old_print_handler = wesnoth.register_wml_action("print",
    function(cfg)
        -- Do not perform variable substitution.
        local new_cfg = cfg.__literal
        -- 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.

wesnoth.wml_actions

Template:DevFeature1.9

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.

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

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.

wesnoth.game_events

Template: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.

  • 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.

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.

wesnoth.fire_event

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.eval_conditional

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" } }
}

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.

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

helper.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.")

helper.tovconfig

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(helper.tovconfig { name = "$varname" }) -- correct: "$varname" is replaced by "to_be_deleted" at the right time