Difference between revisions of "LuaWML"

From The Battle for Wesnoth Wiki
(Mentioned how to access the previous handler)
(Updated Lua documentation)
Line 5: Line 5:
 
{{DevFeature}}
 
{{DevFeature}}
  
This tag is a subtag of the '''[event]'''. It makes it possible to write actions with the [http://www.lua.org Lua 5.1] language.
+
This tag is a subtag of the '''[event]''' tag. It makes it possible to write actions with the [http://www.lua.org Lua 5.1] language.
  
 
The tag supports only the '''code''' key, which is a string containing the Lua scripts. Since Lua makes usage of the quotes and the { and } symbols, it is certainly wise to enclose the script between stronger quotes, as they prevent the preprocessor from performing macro expansion and tokenization.
 
The tag supports only the '''code''' key, which is a string containing the Lua scripts. Since Lua makes usage of the quotes and the { and } symbols, it is certainly wise to enclose the script between stronger quotes, as they prevent the preprocessor from performing macro expansion and tokenization.
Line 13: Line 13:
 
  [/lua]
 
  [/lua]
  
The '''[args]''' tag can be used to pass a WML object to the script via its variadic local variable.
+
The Lua kernel can also be accessed from the [[CommandMode|command mode]]:
 +
 
 +
:lua local u = wesnoth.get_units({ id = "Konrad" })[1]; u.moves = 5
 +
 
 +
The '''[args]''' sub-tag can be used to pass a WML object to the script via its variadic local variable "'''...'''".
 +
 
 +
[lua]
 +
    code = << local t = ...; wesnoth.message(tostring(t.text)) >>
 +
    [args]
 +
        text = _ "Hello world!"
 +
    [/args]
 +
[/lua]
 +
 
 +
== Global environment ==
 +
 
 +
All the Lua scripts of a scenario share the same global environment (aka Lua state). For instance, a function defined in an event can be used in all the events that happen after it.
 +
 
 +
[event]
 +
    name = preload
 +
    first_time_only = no
 +
    [lua]
 +
        code = <<
 +
            function narrator(t)
 +
                -- Behave like the [message] tag.
 +
                wesnoth.fire("message",
 +
                  { speaker = "narrator", message = t.sentence })
 +
            end
 +
        >>
 +
    [/lua]
 +
[/event]
 +
 +
[event]
 +
    name = turn 1
 +
    [lua]
 +
        code = << narrator(...) >>
 +
        [args]
 +
            sentence = _ "Hello world!"
 +
        [/args]
 +
    [/lua]
 +
    [lua]
 +
        code = << narrator(...) >>
 +
        [args]
 +
            sentence = _ "How are you today?"
 +
        [/args]
 +
    [/lua]
 +
[/event]
 +
 
 +
In the example above, the redundant structure could be hidden behind macros. But it may be better to simply define a new WML tag.
 +
 
 +
[event]
 +
    name = preload
 +
    first_time_only = no
 +
    [lua]
 +
        code = <<
 +
            -- The function is now local, since its name does not have to be
 +
            -- visible outside this Lua scripts.
 +
            local function handler(t)
 +
                -- Behave like the [message] tag.
 +
                wesnoth.fire("message",
 +
                  { speaker = "narrator", message = t.sentence })
 +
            end
 +
            -- Create a new tag named [narrator].
 +
            wesnoth.register_wml_action("narrator", handler)
 +
        >>
 +
    [/lua]
 +
[/event]
 +
 +
[event]
 +
    name = turn 1
 +
    [narrator]
 +
        sentence = _ "Hello world!"
 +
    [/narrator]
 +
    [narrator]
 +
        sentence = _ "How are you today?"
 +
    [/narrator]
 +
[/event]
 +
 
 +
The global environment is not preserved over save/load cycles. Therefore, storing values in the global environment is generally a bad idea (unless it has been redirected to WML variables, see [[#set_wml_var_metatable|set_wml_var_metatable]]). The only time assigning global variables (including function definitions) makes sense is during a [[EventWML#Predefined 'name' Key Values|preload]] event, as this event is always run. Therefore, helper functions defined at that time will be available to all the later scripts.
 +
 
 +
The global environment initially contains the following modules: [http://www.lua.org/manual/5.1/manual.html#5.1 basic] (no name), [http://www.lua.org/manual/5.1/manual.html#5.4 string], [http://www.lua.org/manual/5.1/manual.html#5.5 table], and [http://www.lua.org/manual/5.1/manual.html#5.6 math]. A '''wesnoth''' module is also available, it provides access to the [[#Interface to the C++ engine|C++ engine]].
 +
 
 +
At the start of a script, the variadic local variable '''...''' (three dots) is a table representing [[#Encoding WML objects into Lua tables|WML data]]. This table is the content of the '''[args]''' sub-tag of the '''[lua]''' tag, if any. The table also provides the fields '''x1''', '''y1''', '''x2''', and '''y2''', containing map locations, and the sub-tables '''weapon''' and '''second_weapon''' containing attacks, if relevant for the current event.
  
 
== Examples ==
 
== Examples ==
Line 94: Line 175:
 
     (args.x1 ~= target_hex.x or args.y1 ~= target_hex.y)
 
     (args.x1 ~= target_hex.x or args.y1 ~= target_hex.y)
  
whether the variable ''target_hex'' matches the event parameters. Since ''target_hex'' is not a local variable, it is taken from the global environment (a table implicitly named ''_G'', so it is actually ''_G.target_hex''). The global environment is not persistent, so it cannot be used to store data. In order to make it useful, it was redirected to the storage of WML variables by the following ''preload'' event.
+
whether the variable ''target_hex'' matches the event parameters. Since ''target_hex'' is not a local variable, it is taken from the global environment (a table implicitly named ''_G'', so it is actually ''_G.target_hex''). The global environment is not persistent, so it cannot be used to store data. In order to make it useful, it was mapped to the storage of WML variables by the following ''preload'' event.
  
 
  [event]
 
  [event]
Line 114: Line 195:
 
     (args.x1 ~= wesnoth.get_variable("target_hex.x") or args.y1 ~= wesnoth.get_variable("target_hex.y")
 
     (args.x1 ~= wesnoth.get_variable("target_hex.x") or args.y1 ~= wesnoth.get_variable("target_hex.y")
  
The body of the conditional then performs the [[InterfaceActionsWML|redraw]] action.
+
The body of the conditional then performs the [[InterfaceActionsWML#Other interface tags|redraw]] action.
  
 
  W.redraw()
 
  W.redraw()
Line 137: Line 218:
 
  end
 
  end
  
The function fires a [[InterfaceActionsWML|message]] and passes a WML object containing the usual two fields to it. The second field is initialized by concatenating the function argument with another string. Both strings are prefixed by the ''_'' symbol to mark them as translatable. (Note that ''_'' is just a unary function, not a keyword.) Again, this is made possible by a specific line of the prelude:
+
The function fires a [[InterfaceActionsWML#[message]|[message]]] action and passes a WML object containing the usual two fields to it. The second field is initialized by concatenating the function argument with another string. Both strings are prefixed by the ''_'' symbol to mark them as translatable. (Note that ''_'' is just a unary function, not a keyword.) Again, this is made possible by a specific line of the prelude:
  
 
  _ = wesnoth.textdomain "wesnoth-tutorial"
 
  _ = wesnoth.textdomain "wesnoth-tutorial"
  
 
A longer translation of the tutorial is available at [https://gna.org/patch/download.php?file_id=5483].
 
A longer translation of the tutorial is available at [https://gna.org/patch/download.php?file_id=5483].
 
== Global environment ==
 
 
All the Lua scripts of a scenario shares the same global environment (aka Lua state). This environment is not preserved over save/load cycles. Therefore, storing values in the global environment is a generally a bad idea (unless it has been redirected to WML variables, see [[#set_wml_var_metatable|set_wml_var_metatable]]). The only time it makes sense to assign global variables is during a [[EventWML|preload]] event, as this event is always run. Therefore, helper functions defined at that time will be available to all the later scripts.
 
 
The global environment is preloaded with the following modules: [http://www.lua.org/manual/5.1/manual.html#5.1 basic] (no name), [http://www.lua.org/manual/5.1/manual.html#5.4 string], [http://www.lua.org/manual/5.1/manual.html#5.5 table], and [http://www.lua.org/manual/5.1/manual.html#5.6 math]. A '''wesnoth''' module is also available, see below.
 
 
At the start of the script, the variadic local variable '''...''' (three dots) contains a table. It contains the content of the '''[args]''' sub-tag of the '''[lua]''' tag. The table also provides (if they make sense for the current event) the fields '''x1''', '''y1''', '''x2''', and '''y2''', containing map locations, and the sub-tables '''weapon''' and '''second_weapon''' containing attacks.
 
  
 
== Interface to the C++ engine ==
 
== Interface to the C++ engine ==
  
Functionalities of the game engine are available through the functions of the '''wesnoth''' global table.
+
Functionalities of the game engine are available through the functions of the '''wesnoth''' global table. Some of these functions return proxy tables. Writes to fields maked "read-only" are ignored. The '''__cfg''' fields return plain tables; in particular, writes do not modify the original object, and reads return the values from the time the dump was performed.
  
 
==== message ====
 
==== message ====
Line 167: Line 240:
 
==== fire ====
 
==== fire ====
  
Fires a WML action (argument 1); argument 2 is the WML table describing the action, while arguments 3-6 are two locations (not sure what they can be useful for).
+
Fires a WML action. Argument 1 is the name of the action. Argument 2 is the WML table describing the action.
  
 
  wesnoth.fire("message", { speaker="narrator", message=_ "Hello World!" })
 
  wesnoth.fire("message", { speaker="narrator", message=_ "Hello World!" })
Line 190: Line 263:
 
  _ = wesnoth.textdomain("my-campaign")
 
  _ = wesnoth.textdomain("my-campaign")
 
  wesnoth.set_variable("my_unit.description", _ "the unit formerly known as Hero")
 
  wesnoth.set_variable("my_unit.description", _ "the unit formerly known as Hero")
 +
 +
The metatable of the function proxy appears as '''"message domain"'''. The metatable of the translatable strings (results of the proxy) are '''"translatable string"'''.
  
 
==== dofile ====
 
==== dofile ====
Line 199: Line 274:
 
==== register_wml_action ====
 
==== 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.
+
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. The following script defines a [freeze_unit] tag that sets to 0 the move points of the unit with the given id.
  
-- Create a [freeze_unit] tag that sets to 0 the move points of the unit with the given id.
 
 
  wesnoth.register_wml_action("freeze_unit",
 
  wesnoth.register_wml_action("freeze_unit",
 
     function(cfg)
 
     function(cfg)
Line 214: Line 288:
 
  [/freeze_unit]
 
  [/freeze_unit]
  
The previous action handler is passed as the second argument of the function. It can be called to delegate part of the action, if needed.
+
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.
  
  -- Modifies the [print] tag so that messages are displayed with a bigger font.
+
  local old_print_handler
  wesnoth.register_wml_action("print",
+
  old_print_handler = wesnoth.register_wml_action("print",
     function(cfg, old_print_handler)
+
     function(cfg)
         cfg.size = (cfg.size or 12) + 10
+
         -- 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)
 
         old_print_handler(cfg)
 
     end
 
     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 [[#Encoding WML objects into Lua tables]] for more details.
  
 
==== get_units ====
 
==== get_units ====
Line 241: Line 320:
 
* '''petrified''', '''canrecruit''': booleans (read only)
 
* '''petrified''', '''canrecruit''': booleans (read only)
 
* '''role''', '''facing''': strings (read/write)
 
* '''role''', '''facing''': strings (read/write)
* '''__cfg''': WML table (read only)
+
* '''__cfg''': WML table (dump)
 +
 
 +
The metatable of these proxy tables appears as '''"unit"'''.
  
 
==== get_side ====
 
==== get_side ====
Line 256: Line 337:
 
* '''objectives_changed''': boolean (read/write)
 
* '''objectives_changed''': boolean (read/write)
 
* '''team_name''': string (read/write)
 
* '''team_name''': string (read/write)
* '''__cfg''': WML table (read only)
+
* '''__cfg''': WML table (dump)
 +
 
 +
The metatable of these proxy tables appears as '''"side"'''.
  
 
==== get_unit_type_ids ====
 
==== get_unit_type_ids ====
Line 275: Line 358:
 
* '''name''': translatable string (read only)
 
* '''name''': translatable string (read only)
 
* '''max_moves''', '''max_experience''', '''max_hitpoints''', '''level''', '''cost''': integers (read only)
 
* '''max_moves''', '''max_experience''', '''max_hitpoints''', '''level''', '''cost''': integers (read only)
* '''__cfg''': WML table (read only)
+
* '''__cfg''': WML table (dump)
 +
 
 +
The metatable of these proxy tables appears as '''"unit type"'''.
  
 
==== get_terrain ====
 
==== get_terrain ====
Line 374: Line 459:
 
     [/foobar]
 
     [/foobar]
 
  [/dummy]
 
  [/dummy]
 +
 +
Functions registered by [[#register_wml_action|wesnoth.register_wml_action]] receive their data in a userdata object which has the exact same structure as above. It is read-only however. Accessing fields or children performs variable substitution on the fly. Its '''__parsed''' and '''__literal''' fields provide translations to plain tables (therefore writable). '''__literal''' returns the original text of the data (including dollar symbols), while '''__parsed''' performs a last global variable substitution.
 +
 +
For instance, if you cannot stand any longer the fact that '''first_time_only''' is set to yes by default for the '''[event]''' tag, you can redefine it. But we have to be careful not to cause variable substitution, since the engine would perform a second variable substitution afterwards.
 +
 +
local old_event_handler
 +
old_event_handler = register_wml_action("event",
 +
    function(cfg)
 +
        -- Get the plain text from the user.
 +
        local new_cfg = cfg.__literal
 +
        -- The expression below is equivalent to cfg.__parsed.first_time_only,
 +
        -- only faster. It is needed, since the first_time_only attribute may
 +
        -- reference variables.
 +
        local first = cfg.first_time_only
 +
        -- Modify the default behavior of first_time_only.
 +
        if first == nil then first = false end
 +
        new_cfg.first_time_only = first
 +
        -- Call the engine handler.
 +
        old_event_handler(new_cfg)
 +
    end
 +
)
 +
 +
Note that, since the object is a userdata and not a table, '''pairs''' and '''ipairs''' are unfortunately not usable on it. So scripts have to work at a lower level. For instance, the following function returns the first sub-tag with a given name and it works both on WML tables and WML userdata:
 +
 +
function get_child(cfg, name)
 +
    for i = 1, #cfg do
 +
        local v = cfg[i]
 +
        if v[1] == name then return v[2] end
 +
    end
 +
end
  
 
== Helper functions ==
 
== Helper functions ==

Revision as of 06:03, 11 September 2009

[edit]WML Tags

A:

abilities, about, add_ai_behavior, advance, advanced_preference, advancefrom, advancement, advances, affect_adjacent, ai, allied_with, allow_end_turn, allow_extra_recruit, allow_recruit, allow_undo, and, animate, animate_unit, animation, aspect, attack, attack_anim, attacks, avoid;

B:

base_unit, berserk, binary_path, break, brush;

C:

campaign, cancel_action, candidate_action, capture_village, case, chance_to_hit, change_theme, chat, choose, clear_global_variable, clear_menu_item, clear_variable, color_adjust, color_range, command (action, replay), continue, criteria;

D:

damage, death, deaths, default, defend, defends, defense, delay, deprecated_message, destination, difficulty, disable, disallow_end_turn, disallow_extra_recruit, disallow_recruit, do, do_command, drains, draw_weapon_anim;

E:

editor_group, editor_music, editor_times, effect, else (action, animation), elseif, endlevel, end_turn (action, replay), enemy_of, engine, entry, era, event, extra_anim;

F:

facet, facing, fake_unit, false, feedback, female, filter (concept, event), filter_adjacent, filter_adjacent_location, filter_attack, filter_attacker, filter_base_value, filter_condition, filter_defender, filter_enemy, filter_location, filter_opponent, filter_own, filter_owner, filter_radius, filter_recall, filter_second, filter_second_attack, filter_self, filter_side, filter_vision, filter_weapon, filter_wml, find_path, fire_event, firststrike, floating_text, for, foreach, frame, full_heal;

G:

game_config, get_global_variable, goal, gold, gold_carryover;

H:

harm_unit, has_ally, has_attack, has_unit, have_location, have_unit, heal_on_hit, heal_unit, healed_anim, healing_anim, heals, hide_help, hide_unit, hides;

I:

idle_anim, if (action, animation), illuminates, image, init_side, insert_tag, inspect, item, item_group;

J:

jamming_costs, join;

K:

kill, killed;

L:

label, language, leader, leader_goal, leadership, leading_anim, levelin_anim, levelout_anim, lift_fog, limit, literal, load_resource, locale, lock_view, lua;

M:

male, menu_item, message, micro_ai, missile_frame, modification, modifications, modify_ai, modify_side, modify_turns, modify_unit, modify_unit_type, move, move_unit, move_unit_fake, move_units_fake, movement_anim, movement costs, movetype, multiplayer, multiplayer_side, music;

N:

not, note;

O:

object, objective, objectives, on_undo, open_help, option, options, or;

P:

part, petrifies, petrify, place_shroud, plague, poison, portrait, post_movement_anim, pre_movement_anim, primary_attack, primary_unit, print, put_to_recall_list;

R:

race, random_placement, recall (action, replay), recalls, recruit, recruit_anim, recruiting_anim, recruits, redraw, regenerate, remove_event, remove_item, remove_object, remove_shroud, remove_sound_source, remove_time_area, remove_unit_overlay, repeat, replace_map, replace_schedule, replay, replay_start, reset_fog, resistance (ability, unit), resistance_defaults, resource, return, role, rule;

S:

save, scenario, scroll, scroll_to, scroll_to_unit, secondary_attack, secondary_unit, section, select_unit, sequence, set_extra_recruit, set_global_variable, set_menu_item, set_recruit, set_specials, set_variable, set_variables, sheath_weapon_anim, show_if (message, set_menu_item), show_objectives, side, skirmisher, slow, snapshot, sound, sound_source, source (replay, teleport), special_note, specials, split, stage, standing_anim, statistics, status, store_gold, store_items, store_locations, store_map_dimensions, store_reachable_locations, store_relative_direction, store_side, store_starting_location, store_time_of_day, store_turns, store_unit, store_unit_defense, store_unit_type, store_unit_type_ids, store_villages, story, swarm, switch, sync_variable;

T:

target, team, teleport (ability, action), teleport_anim, terrain, terrain_defaults, terrain_graphics, terrain_mask, terrain_type, test, test_condition, text_input, textdomain, theme, then, tile, time, time_area, topic, toplevel, trait, transform_unit, traveler, true, tunnel, tutorial;

U:

unhide_unit, unit, unit_overlay, unit_type, unit_worth, units, unlock_view, unpetrify, unstore_unit, unsynced;

V:

value, variable, variables, variation, victory_anim, village, vision_costs, volume;

W:

while, wml_message, wml_schema;

Z:

zoom;

The [lua] tag

Template:DevFeature

This tag is a subtag of the [event] tag. It makes it possible to write actions with the Lua 5.1 language.

The tag supports only the code key, which is a string containing the Lua scripts. Since Lua makes usage of the quotes and the { and } symbols, it is certainly wise to enclose the script between stronger quotes, as they prevent the preprocessor from performing macro expansion and tokenization.

[lua]
    code = << wesnoth.message "Hello World!" >>
[/lua]

The Lua kernel can also be accessed from the command mode:

:lua local u = wesnoth.get_units({ id = "Konrad" })[1]; u.moves = 5

The [args] sub-tag can be used to pass a WML object to the script via its variadic local variable "...".

[lua]
    code = << local t = ...; wesnoth.message(tostring(t.text)) >>
    [args]
        text = _ "Hello world!"
    [/args]
[/lua]

Global environment

All the Lua scripts of a scenario share the same global environment (aka Lua state). For instance, a function defined in an event can be used in all the events that happen after it.

[event]
    name = preload
    first_time_only = no
    [lua]
        code = <<
            function narrator(t)
                -- Behave like the [message] tag.
                wesnoth.fire("message",
                  { speaker = "narrator", message = t.sentence })
            end
        >>
    [/lua]
[/event]

[event]
    name = turn 1
    [lua]
        code = << narrator(...) >>
        [args]
            sentence = _ "Hello world!"
        [/args]
    [/lua]
    [lua]
        code = << narrator(...) >>
        [args]
            sentence = _ "How are you today?"
        [/args]
    [/lua]
[/event]

In the example above, the redundant structure could be hidden behind macros. But it may be better to simply define a new WML tag.

[event]
    name = preload
    first_time_only = no
    [lua]
        code = <<
            -- The function is now local, since its name does not have to be
            -- visible outside this Lua scripts.
            local function handler(t)
                -- Behave like the [message] tag.
                wesnoth.fire("message",
                  { speaker = "narrator", message = t.sentence })
            end
            -- Create a new tag named [narrator].
            wesnoth.register_wml_action("narrator", handler)
        >>
    [/lua]
[/event]

[event]
    name = turn 1
    [narrator]
        sentence = _ "Hello world!"
    [/narrator]
    [narrator]
        sentence = _ "How are you today?"
    [/narrator]
[/event]

The global environment is not preserved over save/load cycles. Therefore, storing values in the global environment is generally a bad idea (unless it has been redirected to WML variables, see set_wml_var_metatable). The only time assigning global variables (including function definitions) makes sense is during a preload event, as this event is always run. Therefore, helper functions defined at that time will be available to all the later scripts.

The global environment initially contains the following modules: basic (no name), string, table, and math. A wesnoth module is also available, it provides access to the C++ engine.

At the start of a script, the variadic local variable ... (three dots) is a table representing WML data. This table is the content of the [args] sub-tag of the [lua] tag, if any. The table also provides the fields x1, y1, x2, and y2, containing map locations, and the sub-tables weapon and second_weapon containing attacks, if relevant for the current event.

Examples

The following WML event is taken from Wesnoth' tutorial. It will serve as an example to present how Lua scripts are embedded into Wesnoth. The event is fired whenever a unit from side 1 (that is, the hero controlled by the user) moves to a tile that is not the one set in the WML variable target_hex.

# General catch for them moving to the wrong place.
[event]
    name=moveto
    first_time_only=no
    [allow_undo][/allow_undo]
    [filter]
        side=1
    [/filter]

    [if]
        [variable]
            name=target_hex.is_set
            equals=yes
        [/variable]
        [then]
            [if]
                [variable]
                    name=x1
                    equals=$target_hex.x
                [/variable]
                [variable]
                    name=y1
                    equals=$target_hex.y
                [/variable]
                [then]
                [/then]
                [else]
                    [redraw][/redraw]
                    [message]
                        speaker=narrator
                        message=_ "*Oops!
You moved to the wrong place! After this message, you can press 'u' to undo, then try again." +
                        _ "
*Left click or press spacebar to continue..."
                    [/message]
                [/else]
            [/if]
        [/then]
    [/if]
[/event]

A Lua script that performs the same action is presented below.

[event]
    name=moveto
    first_time_only=no
    [allow_undo][/allow_undo]
    [filter]
        side=1
    [/filter]

    [lua]
        code = <<
            local args = ...
            if target_hex.is_set and
               (args.x1 ~= target_hex.x or args.y1 ~= target_hex.y)
            then
                W.redraw()
                narrator_says(_ "*Oops!\nYou moved to the wrong place! After this message, you can press 'u' to undo, then try again.")
            end
        >>
    [/lua]
[/event]

Here is a more detailed explanation of the Lua code. Its first line

local args = ...

loads the parameter of the script into the args local variable. Since it is a moveto event, the args table contains the destination of the unit in the x1 and y1 fields.

The next two lines then test

if target_hex.is_set and
   (args.x1 ~= target_hex.x or args.y1 ~= target_hex.y)

whether the variable target_hex matches the event parameters. Since target_hex is not a local variable, it is taken from the global environment (a table implicitly named _G, so it is actually _G.target_hex). The global environment is not persistent, so it cannot be used to store data. In order to make it useful, it was mapped to the storage of WML variables by the following preload event.

[event]
    name=preload
    first_time_only=no
    [lua]
        code = <<
            H = wesnoth.dofile("lua/helper.lua")
            -- skipping some other initializations
            -- ...
            H.set_wml_var_metatable(_G)
        >>
    [/lua]
[/event]

Without a prelude redirecting _G, the conditional would have been written

if wesnoth.get_variable("target_hex.is_set") and
   (args.x1 ~= wesnoth.get_variable("target_hex.x") or args.y1 ~= wesnoth.get_variable("target_hex.y")

The body of the conditional then performs the redraw action.

W.redraw()

Again, this short syntax is made possible by a line of the prelude that makes W a proxy for performing WML actions.

W = H.set_wml_action_metatable({})

Without this shortcut, the first statement would have been written

wesnoth.fire("redraw")

Finally the script displays a message by

narrator_says(_ "*Oops!\nYou moved to the wrong place! After this message, you can press 'u' to undo, then try again.")

The narrator_says function is defined in the prelude too, since the construct behind it occurs several times in the tutorial. In plain WML, macros would have been used instead. The definition of the function is

function narrator_says(m)
    W.message { speaker="narrator",
                message = m .. _ "\n*Left click or press spacebar to continue..." }
end

The function fires a [[InterfaceActionsWML#[message]|[message]]] action and passes a WML object containing the usual two fields to it. The second field is initialized by concatenating the function argument with another string. Both strings are prefixed by the _ symbol to mark them as translatable. (Note that _ is just a unary function, not a keyword.) Again, this is made possible by a specific line of the prelude:

_ = wesnoth.textdomain "wesnoth-tutorial"

A longer translation of the tutorial is available at [1].

Interface to the C++ engine

Functionalities of the game engine are available through the functions of the wesnoth global table. Some of these functions return proxy tables. Writes to fields maked "read-only" are ignored. The __cfg fields return plain tables; in particular, writes do not modify the original object, and reads return the values from the time the dump was performed.

message

Displays a string in the chat window and dumps it to the lua/info log domain (--log-info=scripting/lua on the command-line).

wesnoth.message "Hello World!"

The chat line header is "<Lua>" by default, but it can be changed by passing a string before the message.

wesnoth.message("Big Brother", "I'm watching you.") -- will result in "<Big Brother> I'm watching you."

fire

Fires a WML action. Argument 1 is the name of the action. Argument 2 is the WML table describing the action.

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

get_variable

Loads a variable with the given WML name (argument 1) and converts it into a Lua object. Returns nil if the name does not point to anything, a scalar for a WML attribute, and a table for a WML object. Argument 2, if true, prevents the recursive conversion if the name points to an object; an empty table is returned in this case.

wesnoth.fire("store_unit", { variable="my_unit", { "filter", { id="hero" } } })
local heros_hp = wesnoth.get_variable("my_unit[0].hitpoints")

set_variable

Converts and stores a Lua object (argument 2) to a WML variable (argument 1). A WML object is created for a table, an attribute otherwise.

wesnoth.set_variable("my_unit.hitpoints", heros_hp + 10)

textdomain

Creates a function proxy for lazily translating strings from the given domain.

_ = wesnoth.textdomain("my-campaign")
wesnoth.set_variable("my_unit.description", _ "the unit formerly known as Hero")

The metatable of the function proxy appears as "message domain". The metatable of the translatable strings (results of the proxy) are "translatable string".

dofile

Replaces basic.dofile for loading files. Loads the given filename (relative to the content directory) and executes it in an unprotected environment (that is, exceptions propagate to the caller). Returns the values returned by the executed script.

helper = wesnoth.dofile "lua/helper.lua"

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. 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 #Encoding WML objects into Lua tables for more details.

get_units

Returns an array of all the units matching the WML filter passed as the first argument.

local leaders_on_side_two = get_units({ side = 2, canrecruit = true })
local name_of_leader = leaders_on_side_two[1].name

Units are proxy tables with the following fields:

  • x, y: integers (read only)
  • side: integer (read/write)
  • id, side_id: strings (read only)
  • name: translatable string (read only)
  • hitpoints, max_hitpoints, experience, max_experience, max_moves: integers (read only)
  • moves: integer (read/write)
  • resting: boolean (read/write)
  • petrified, canrecruit: booleans (read only)
  • role, facing: strings (read/write)
  • __cfg: WML table (dump)

The metatable of these proxy tables appears as "unit".

get_side

Returns the team with given number.

local team = wesnoth.get_side(1)
team.gold = team.gold + 50

Teams are proxy tables with the following fields:

  • gold, village_gold, base_income: integers (read/write)
  • total_income: integer (read only)
  • objectives, user_team_name: translatable strings (read/write)
  • objectives_changed: boolean (read/write)
  • team_name: string (read/write)
  • __cfg: WML table (dump)

The metatable of these proxy tables appears as "side".

get_unit_type_ids

Returns an array containing all the unit type IDs the engine knows about.

local unit_types = wesnoth.get_unit_type_ids()
wesnoth.message(string.format("%d unit types registered. First one is %s.", #unit_types, unit_types[1]))

get_unit_type

Returns the unit type with the corresponding ID.

local lich_cost = wesnoth.get_unit_type("Ancient Lich").cost

Unit types are proxy tables with the following fields:

  • id: string
  • name: translatable string (read only)
  • max_moves, max_experience, max_hitpoints, level, cost: integers (read only)
  • __cfg: WML table (dump)

The metatable of these proxy tables appears as "unit type".

get_terrain

Returns the terrain code for the given location.

local is_grassland = wesnoth.get_terrain(12, 15) == "Gg"

set_terrain

Modifies the terrain at the given location.

function create_village(x, y)
    wesnoth.set_terrain(x, y, "Gg^Vh")
end

get_terrain_info

Returns the terrain details for the given terrain code.

local is_keep = wesnoth.get_terrain_info(wesnoth.get_terrain(12, 15)).keep

Terrain info is a plain table with the following fields:

  • id: string
  • name, description: translatable strings
  • castle, keep, village: booleans
  • healing: integer

get_village_owner

Returns the side that owns the village at the given location.

local owned_by_side_1 = wesnoth.get_village_owner(12, 15) == 1

set_village_owner

Gives ownership of the village at the given location to the given side (or remove ownership if none).

wesnoth.set_village_owner(12, 15, 1)

get_map_size

Returns the width and the height of the map.

local w,h = wesnoth.get_map_size()

Encoding WML objects into Lua tables

Function wesnoth.fire expects a table representing a WML object as its second argument (if needed). Function wesnoth.set_variable allows to modify whole WML objects, again by passing it a table. Function wesnoth.get_variable transforms a WML object into a table, if its second argument is not set to true. All these tables have the same format.

Scalar fields are transformed into WML attributes. For instance, the following Lua table

{
    a_bool = true,
    an_int = 42,
    a_float = 1.25,
    a_string = "scout",
    a_translation = _ "Hello World!"
}

is equivalent to the content of the following WML object

[dummy]
    a_bool = "yes"
    an_int = "42"
    a_float = "1.25"
    a_string = "scout"
    a_translation = _ "Hello World!"
[/dummy]

WML child objects are not stored as Lua named fields, since several of them can have the same tag. Moreover, their tags can conflict with the attribute keys. So child objects are stored as pairs string + table in the unnamed fields in definition order. For instance, the following Lua table

{
    foo = 42,
    { "bar", { v = 1, w = 2 } },
    { "foo", { x = false } },
    { "bar", { y = "foo" } },
    { "foobar", { { "barfoo", {} } } }
}

is equivalent to the content of the following WML object

[dummy]
    foo = 42
    [bar]
        v = 1
        w = 2
    [/bar]
    [foo]
        x = no
    [/foo]
    [bar]
        y = foo
    [bar]
    [foobar]
        [barfoo]
        [/barfoo]
    [/foobar]
[/dummy]

Functions registered by wesnoth.register_wml_action receive their data in a userdata object which has the exact same structure as above. It is read-only however. Accessing fields or children performs variable substitution on the fly. Its __parsed and __literal fields provide translations to plain tables (therefore writable). __literal returns the original text of the data (including dollar symbols), while __parsed performs a last global variable substitution.

For instance, if you cannot stand any longer the fact that first_time_only is set to yes by default for the [event] tag, you can redefine it. But we have to be careful not to cause variable substitution, since the engine would perform a second variable substitution afterwards.

local old_event_handler
old_event_handler = register_wml_action("event",
    function(cfg)
        -- Get the plain text from the user.
        local new_cfg = cfg.__literal
        -- The expression below is equivalent to cfg.__parsed.first_time_only,
        -- only faster. It is needed, since the first_time_only attribute may
        -- reference variables.
        local first = cfg.first_time_only
        -- Modify the default behavior of first_time_only.
        if first == nil then first = false end
        new_cfg.first_time_only = first
        -- Call the engine handler.
        old_event_handler(new_cfg)
    end
)

Note that, since the object is a userdata and not a table, pairs and ipairs are unfortunately not usable on it. So scripts have to work at a lower level. For instance, the following function returns the first sub-tag with a given name and it works both on WML tables and WML userdata:

function get_child(cfg, name)
    for i = 1, #cfg do
        local v = cfg[i]
        if v[1] == name then return v[2] end
    end
end

Helper functions

The following functions are provided by the lua/helper.lua library. They are stored inside a table that is returned when loading the file by wesnoth.dofile.

helper = wesnoth.dofile "lua/helper.lua"

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.

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

set_wml_var_metatable

Sets the metable of a table so that it can be used to access WML variables. Returns the table. The fields of the tables are then proxies to the WML objects with the same names; reading/writing to them will directly access the WML variables.

helper.set_wml_var_metatable(_G)
my_persistent_variable = 42

modify_unit

Modifies all the units satisfying the given filter (argument 1) with some WML attributes/objects (argument 2). This is a Lua implementation of the MODIFY_UNIT macro.

helper.modify_unit({ id="Delfador" }, { moves=0 })

move_unit_fake

Fakes the move of a unit satisfying the given filter (argument 1) to the given position (argument 2). This is a Lua implementation of the MOVE_UNIT macro.

helper.move_unit_fake({ id="Delfador" }, 14, 8)

Skeleton of a preload event

The following event is a skeleton for a prelude enabling Lua in your WML events. It creates a table H containing the functions from helper.lua and a table W that serves as a proxy for firing WML actions. It also sets up the global environment so that any access to an undefined global variable is redirected to the persistent WML storage.

[event]
    name=preload
    first_time_only=no
    [lua]
        code = <<
            H = wesnoth.dofile("lua/helper.lua")
            W = H.set_wml_action_metatable({})
            _ = wesnoth.textdomain "my-campaign"

            -- Define your global constants here.
            -- ...

            H.set_wml_var_metatable(_G)

            -- Define your global functions here.
            -- ...
        >>
    [/lua]
[/event]