LuaWML

From The Battle for Wesnoth Wiki
Revision as of 18:55, 22 March 2009 by Silene (talk | contribs) (Documented encoding of WML objects as Lua tables)

[edit]WML Tags

A:

abilities, about, achievement, achievement_group, add_ai_behavior, 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 (replay, weapon), attack_anim, attacks (special, stats), avoid;

B:

base_unit, background_layer, berserk, binary_path, break, brush;

C:

campaign, cancel_action, candidate_action, capture_village, case, chance_to_hit, change_theme, chat, checkbox, choice, choose, clear_global_variable, clear_menu_item, clear_variable, color_adjust, color_palette, color_range, command (action, replay), continue, credits_group, 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 (credits, options), era, event, experimental_filter_ability, experimental_filter_ability_active, experimental_filter_specials, 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_student, filter_vision, filter_weapon, filter_wml, find_path, fire_event, firststrike, floating_text, found_item, for, foreach, frame;

G:

game_config, get_global_variable, goal, gold, gold_carryover;

H:

harm_unit, has_ally, has_attack, has_unit, has_achievement, 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, intro), illuminates, image (intro, terrain), 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, post_movement_anim, pre_movement_anim, primary_attack, primary_unit, print, progress_achievement, 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_trait, 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, screen_fade, scroll, scroll_to, scroll_to_unit, secondary_attack, secondary_unit, section, select_unit, sequence, set_achievement, set_extra_recruit, set_global_variable, set_menu_item, set_recruit, set_specials, set_variable, set_variables, sheath_weapon_anim, show_if (message, objective, set_menu_item), show_objectives, side, skirmisher, slider, 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_defense_on, store_unit_type, store_unit_type_ids, store_villages, story, swarm, sub_achievement, switch, sync_variable;

T:

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

U:

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

V:

value, variable, variables, variant, 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]. 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 [args] tag can be used to pass a WML object to the script via its variadic local variable.

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 redirected 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 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:

_ = wesnoth.textdomain "wesnoth-tutorial"

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

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). The only time it makes sense to assign global variables 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 is preloaded with the following modules: basic (no name), string, table, and 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

Functionalities of the game engine are available through the functions of the wesnoth global table.

message

Dumps a string to the standard output for debugging purpose.

wesnoth.message "Hello World!"

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

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

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"

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]

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)