Difference between revisions of "LuaWML"
(→Events and WML actions: Add wesnoth.persistent_tags) |
(→WML table: Clean up so that all examples use recommended format, leaving only a brief note on the old, deprecated format) |
||
(39 intermediate revisions by 9 users not shown) | |||
Line 3: | Line 3: | ||
== The '''[lua]''' tag == | == The '''[lua]''' tag == | ||
− | This tag is a part of [[ActionWML]], thus can be used inside [event] and at other places where [[ActionWML]] can be used. It makes it possible to write actions with the [http://www.lua.org Lua 5. | + | This tag is a part of [[ActionWML]], thus can be used inside [event] and at other places where [[ActionWML]] can be used. It makes it possible to write actions with the [http://www.lua.org Lua 5.4] language. |
− | It | + | {{DevFeature1.13|?}} In addition to ActionWML, the '''[lua]''' tag may now be used in several other places. It can be placed at toplevel (outside of any tag) to make code that loads no matter what. ('''Note''': This should usually be avoided, unless you're making a [[CoreWML|custom core]].) It can be placed in any [[AddonsWML|addon module tag]], such as '''[scenario]''', '''[era]''', or '''[resource]''', to load code when the scenario boots up. Lua code placed in either of these places is executed even earlier than a '''preload''' event. And finally, it can now be used as [[ConditionalActionsWML#.5Blua.5D|ConditionalWML]]. In this case, the code must return either '''true''' or '''false''' to determine the outcome of the condition. |
− | + | The '''[lua]''' tag can contain the following contents: | |
− | [lua] | ||
− | + | * '''code''': A string containing the Lua script. | |
+ | * '''name''': An arbitrary string used to identify this tag in error messages. | ||
+ | * '''[args]''': Arbitrary data that will be passed to the Lua script as its only argument. It can be accessed in the Lua code via the special variadic variable <code>...</code>. | ||
+ | |||
+ | Since Lua makes usage of double quotes and curly braces, it is recommended to enclose the script between [[SyntaxWML#macro-protected-string|stronger quotes]] (as shown in the below example), as they prevent the preprocessor from performing macro expansion and tokenization. This is not strictly required, however. Example: | ||
<syntaxhighlight lang='wml'> | <syntaxhighlight lang='wml'> | ||
[lua] | [lua] | ||
− | code = << wesnoth. | + | code = << wesnoth.interface.add_chat_message "Hello World!" >> |
[/lua] | [/lua] | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | The '''[args]''' tag is useful if you need to pass WML variables or macro expansions into the code. | |
− | |||
− | |||
− | |||
− | The '''[args]''' | ||
<syntaxhighlight lang='wml'> | <syntaxhighlight lang='wml'> | ||
[lua] | [lua] | ||
− | code = << local t = ...; wesnoth. | + | code = << local t = ...; wesnoth.interface.add_chat_message(tostring(t.text)) >> |
[args] | [args] | ||
− | text = _ "Hello | + | text = _ "Hello $unit.name!" |
[/args] | [/args] | ||
[/lua] | [/lua] | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
+ | When code in a '''[lua]''' tag has errors, it's often hard to find the location of the error because the line numbers are not within the code string and not the entire file. To help with this, you can set the '''name''' key. When an error is reported, the value of '''name''' will be shown as if it were the filename, allowing you to more easily locate the tag that raised it. | ||
+ | |||
+ | == Testing out Lua in-game == | ||
+ | |||
+ | The Lua kernel can also be accessed from [[CommandMode|command mode]]: | ||
+ | |||
+ | :lua local u = wesnoth.units.find_on_map{ id = "Konrad" }[1]; u.moves = 5 | ||
+ | |||
+ | In addition, if you enable debug mode, you can access a <acronym title="Read, evaluate, print loop">REPL</acronym> console by pressing the Lua Console hotkey, which is bound to ~ by default. You can enable debug mode by using the <tt>:debug</tt> command, or by running Wesnoth with the <tt>--debug</tt> command-line argument. In the latter case, the Lua Console can even be summoned from the main menu screen. | ||
+ | |||
+ | You can use the Lua console to examine anything about the Lua environment and execute any Lua code you wish. It is recommended to use command mode to execute Lua that affects the map however, for example moving a unit. In the Lua Console you can use a special [[LuaAPI/wesnoth#wesnoth.print_attributes|dir()]] function to examine the contents of objects and modules. You can also access a special <tt>_</tt> variable which contains the result of the previous command executed in the console. Global variables assigned in the Lua console are not visible to scenario code, but remain between invocations of the console within a single game, as long as you don't reload. If you need to assign a global variable that is visible outside of the console, you can do so via the special variable <tt>_G</tt>. | ||
== Global environment == | == Global environment == | ||
− | All the Lua scripts of a scenario share the same global environment (aka Lua state). | + | All the Lua scripts of a scenario share the same global environment (aka Lua state). Unlike other parts of the configurable gamestate the Lua state is not stored in savefiles, thus [lua] tags in [scenario] are executed not only before the scenario starts but also each time the game is loaded. Functions defined in [lua] tags in [scenario] can be used in all [lua] tags in [event]s. |
<syntaxhighlight lang='wml'> | <syntaxhighlight lang='wml'> | ||
− | [ | + | [scenario] |
− | |||
− | |||
[lua] | [lua] | ||
code = << | code = << | ||
function narrator(t) | function narrator(t) | ||
-- Behave like the [message] tag. | -- Behave like the [message] tag. | ||
− | + | wml.fire("message", | |
{ speaker = "narrator", message = t.sentence }) | { speaker = "narrator", message = t.sentence }) | ||
end | end | ||
>> | >> | ||
[/lua] | [/lua] | ||
− | [/ | + | [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] | ||
+ | ... | ||
+ | [/scenario] | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 72: | Line 82: | ||
<syntaxhighlight lang='wml'> | <syntaxhighlight lang='wml'> | ||
− | [ | + | [scenario] |
− | |||
− | |||
[lua] | [lua] | ||
code = << | code = << | ||
Line 81: | Line 89: | ||
function wesnoth.wml_actions.narrator(t) | function wesnoth.wml_actions.narrator(t) | ||
-- Behave like the [message] tag. | -- Behave like the [message] tag. | ||
− | + | wml.fire("message", | |
{ speaker = "narrator", message = t.sentence }) | { speaker = "narrator", message = t.sentence }) | ||
end | end | ||
>> | >> | ||
[/lua] | [/lua] | ||
− | |||
− | + | [event] | |
− | + | name = turn 1 | |
− | + | [narrator] | |
− | + | sentence = _ "Hello world!" | |
− | + | [/narrator] | |
− | + | [narrator] | |
− | + | sentence = _ "How are you today?" | |
− | [/ | + | [/narrator] |
− | [/ | + | [/event] |
+ | [/scenario] | ||
</syntaxhighlight> | </syntaxhighlight> | ||
The global environment is not preserved over save/load cycles. Therefore, storing values in the global environment is generally a bad idea. The only time assigning global variables (including function definitions) makes sense is in a [lua] block directly in [scenario] or 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 is not preserved over save/load cycles. Therefore, storing values in the global environment is generally a bad idea. The only time assigning global variables (including function definitions) makes sense is in a [lua] block directly in [scenario] or 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: | + | The global environment initially contains the following built-in modules: '''string''', '''table''', and '''math'''. A '''wesnoth''' module is also available, which provides access to the C++ game engine. Additionally, the functions '''clock''', '''date''', '''time''' and '''difftime''' from the '''os''' module are also available (but keep in mind that they aren't multiplayer- and replay-safe), as well as '''traceback''' from the '''debug''' module. There are also a few other built-in modules specific to Wesnoth – for full details, see [[LuaAPI]]. |
At the start of a script, the variadic local variable '''...''' (three dots) is a proxy 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. | At the start of a script, the variadic local variable '''...''' (three dots) is a proxy 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. | ||
Line 165: | Line 173: | ||
[lua] | [lua] | ||
code = << | code = << | ||
+ | |||
local event_data = wesnoth.current.event_context | local event_data = wesnoth.current.event_context | ||
− | if | + | if wml.variables["target_hex.is_set"] and |
− | (event_data.x1 ~= | + | (event_data.x1 ~= wml.variables["target_hex.x"] or event_data.y1 ~= wml.variables["target_hex.y"]) |
then | then | ||
W.redraw() | W.redraw() | ||
Line 188: | Line 197: | ||
<syntaxhighlight lang='lua'> | <syntaxhighlight lang='lua'> | ||
− | if | + | if wml.variables["target_hex.is_set"] and |
− | (event_data.x1 ~= | + | (event_data.x1 ~= wml.variables["target_hex.x"] or event_data.y1 ~= wml.variables["target_hex.y"]) |
</syntaxhighlight> | </syntaxhighlight> | ||
− | whether the variable '' | + | whether the variable ''wml.variables["target_hex"]'' matches the event parameters. Since ''wml.variables'' is not a local variable, it is taken from the global environment. Usually, variables from the global environment are not persistent but the wesnoth engine maps the variable wml.variables to the storage of WML variables. |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
The body of the conditional then performs the [[InterfaceActionsWML#Other interface tags|[redraw]]] action. | The body of the conditional then performs the [[InterfaceActionsWML#Other interface tags|[redraw]]] action. | ||
Line 222: | Line 209: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | This short syntax is made possible by a line of the prelude that makes ''W'' a proxy for performing WML actions. | |
<syntaxhighlight lang='lua'> | <syntaxhighlight lang='lua'> | ||
− | W = H.set_wml_action_metatable {} | + | [scenario] |
+ | [lua] | ||
+ | code = << | ||
+ | H = wesnoth.require "helper" | ||
+ | W = H.set_wml_action_metatable {} | ||
+ | >> | ||
+ | [/lua] | ||
+ | ... | ||
+ | [/scenario] | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 231: | Line 226: | ||
<syntaxhighlight lang='lua'> | <syntaxhighlight lang='lua'> | ||
− | + | wml.fire("redraw") | |
+ | </syntaxhighlight> | ||
+ | or | ||
+ | <syntaxhighlight lang='lua'> | ||
+ | wesnoth.wml_actions.redraw {} | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 255: | Line 254: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | 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]. --> |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
== Encoding WML objects into Lua tables == | == Encoding WML objects into Lua tables == | ||
− | + | === WML table === | |
+ | Many functions in the Wesnoth Lua API expect a table representing a WML object (that is, the contents of a WML tag) to be passed as a parameter, for example [[LuaAPI/wml#wml.fire|wml.fire]]. Other functions return WML data converted to a Lua table. All these tables have the same format. The Lua API documentation refers to a table using this specific format convention as a "WML table". | ||
Scalar fields are transformed into WML attributes. For instance, the following Lua table | Scalar fields are transformed into WML attributes. For instance, the following Lua table | ||
Line 539: | Line 278: | ||
[dummy] | [dummy] | ||
a_bool = "yes" | a_bool = "yes" | ||
− | an_int = | + | an_int = 42 |
− | a_float = | + | a_float = 1.25 |
a_string = "scout" | a_string = "scout" | ||
a_translation = _ "Hello World!" | a_translation = _ "Hello World!" | ||
Line 546: | Line 285: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | 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 | + | 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 (it is possible to have a tag and an attribute with the same name). So child objects are stored as (string, table) pairs in the unnamed fields in definition order. This means that for every subtag appearing in the wml code there is an additional table "layer" in the corresponding WML table of the form <syntaxhighlight inline lang=lua>{tag = "tag_name", contents = {}}</syntaxhighlight>. The table under <tt>contents</tt> in this subtable then holds the wml attributes from inside the wml subtag. So every subtag other than the toplevel tag corresponds to two nested tables each. In code, this format is constructed using [[LuaAPI/wml#wml.tag|wml.tag]] to improve readability. For example <syntaxhighlight inline lang=lua>wml.tag_name{}</syntaxhighlight> is equivalent to the prior example. |
+ | |||
+ | '''Note''': As of 1.17, the structure changed slightly. Previously the additional table "layer" for each tag looked like <syntaxhighlight inline lang=lua>{[1] = "tag_name", [2] = {}}</syntaxhighlight>. For backwards compatibility, accessing [1] and [2] still return or set tag and contents respectively (in fact, it is a structure created by [[LuaAPI/wesnoth#wesnoth.named_tuple|wesnoth.named_tuple]]). However, in older versions, only [1] and [2] work. | ||
+ | |||
+ | For instance, the following Lua table | ||
<syntaxhighlight lang='lua'> | <syntaxhighlight lang='lua'> | ||
{ | { | ||
foo = 42, | foo = 42, | ||
− | + | wml.tag.bar { v = 1, w = 2 } }, | |
− | + | wml.tag.foo { x = false } }, | |
− | + | wml.tag.bar { y = "foo" } }, | |
− | + | wml.tag.foobar { z = 5, { wml.tag.barfoo {} } } } | |
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 579: | Line 322: | ||
[/foobar] | [/foobar] | ||
[/dummy] | [/dummy] | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 597: | Line 328: | ||
<syntaxhighlight lang=lua> | <syntaxhighlight lang=lua> | ||
a_int = cfg.foo -- "dummy.foo", 42 | a_int = cfg.foo -- "dummy.foo", 42 | ||
− | a_string = cfg[3] | + | a_string = cfg[3].contents.y -- "dummy.bar[1].y", "foo" |
− | a_table = cfg[4] | + | a_table = cfg[4].contents -- "dummy.foobar", { z = 5, { "barfoo", {} } } |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Consider using the [[LuaAPI/wml#wml.get_child|wml.get_child]] and [[LuaAPI/wml#wml.child_range|wml.child_range]] helper functions to ease the access to subtags. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | Consider using the [[ | ||
{{DevFeature1.13|5}} As a convenience, attributes with array values (tables with only integer keys) are concatenated into a string when converting a Lua table into WML. For example, the following Lua code: | {{DevFeature1.13|5}} As a convenience, attributes with array values (tables with only integer keys) are concatenated into a string when converting a Lua table into WML. For example, the following Lua code: | ||
Line 643: | Line 352: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | Functions registered in [[ | + | === vconfig userdata === |
+ | Functions registered in [[LuaAPI/wesnoth#wesnoth.wml_actions|wesnoth.wml_actions]] and other similar tables that provide hooks that the engine calls will receive their data either in lua tables encoding a WML object or as a [[LuaAPI/wml#wml.tovconfig|WML vconfig userdata]], which has the same structure but is read-only. Accessing fields or children on a vconfig performs variable substitution on the fly. Its '''__parsed''' and '''__literal''' fields provide translations to plain, writable tables. '''__literal''' returns the original text of the data (including dollar symbols in attributes and '''[insert_tag]''' children), while '''__parsed''' performs a full 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. | 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. | ||
Line 651: | Line 361: | ||
function wesnoth.wml_actions.event(cfg) | function wesnoth.wml_actions.event(cfg) | ||
-- Get the plain text from the user. | -- Get the plain text from the user. | ||
− | local new_cfg = cfg | + | local new_cfg = wml.literal(cfg) |
-- The expression below is equivalent to cfg.__parsed.first_time_only, | -- The expression below is equivalent to cfg.__parsed.first_time_only, | ||
-- only faster. It is needed, since the first_time_only attribute may | -- only faster. It is needed, since the first_time_only attribute may | ||
Line 660: | Line 370: | ||
new_cfg.first_time_only = first | new_cfg.first_time_only = first | ||
-- Call the engine handler. | -- Call the engine handler. | ||
− | old_event_handler(new_cfg) | + | old_event_handler(wml.tovconfig(new_cfg)) |
end | end | ||
</syntaxhighlight> | </syntaxhighlight> | ||
(Note: The above example will only affect nested events. Toplevel events will still default to ''first_time_only=yes''.) | (Note: The above example will only affect nested events. Toplevel events will still default to ''first_time_only=yes''.) | ||
+ | (Note2: You should not do that since it will break other addons that rely on the first_time_only=no default value.) | ||
<!-- This should probably be replaced with a better example. --> | <!-- This should probably be replaced with a better example. --> | ||
− | + | '''pairs''' and '''ipairs''' also work on vconfig objects. However, '''pairs''' works a little differently than on plain configs (tables) - it returns only string keys (attributes in WML terms) and not integer keys (tags in WML terms). | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | Another approach for handling userdata and tables in the same way, would be to convert the former into the latter beforehand. The [[LuaAPI/wml#wml.parsed|wml.parsed]] and [[LuaAPI/wml#wml.literal|wml.literal]] helpers take care of this for you. | |
− | |||
− | |||
− | The | + | The vconfig userdata provides two other special fields: '''__shallow_parsed''' and '''__shallow_literal'''. They return a table corresponding to the WML userdata with variable substitution performed on the attributes (or not). [insert_tag] tags have also been parsed, so the number of children is faithful. But contrarily to '''__parsed''' and '''__literal''', the process is not recursive: all the children are still WML userdata and variable substitution can still happen for them. These shallow translators are meant as optimized versions of the deep ones, when only the toplevel attributes need to be writable. |
− | == Skeleton of a | + | == Skeleton of a lua tag == |
− | The following | + | The following [lua] tag is a skeleton for a prelude enabling Lua in your WML events. It creates a table ''H'' containing the functions from helper.lua . It sets up a table ''T'' so be used for easier creation of valid WML tables. It also sets up a table ''V'' so that any access to it is redirected to the persistent WML storage. Finally, it loads a textdomain to be accessed through the ''_'' variable. |
<syntaxhighlight lang='wml'> | <syntaxhighlight lang='wml'> | ||
− | [ | + | [scenario] |
− | |||
− | |||
[lua] | [lua] | ||
code = << | code = << | ||
H = wesnoth.require "lua/helper.lua" | H = wesnoth.require "lua/helper.lua" | ||
− | + | T = wml.tag | |
− | T = | + | V = wml.variables |
− | V = | + | local _ = wesnoth.textdomain "my-campaign" |
− | _ = wesnoth.textdomain "my-campaign" | ||
-- Define your global constants here. | -- Define your global constants here. | ||
Line 712: | Line 405: | ||
>> | >> | ||
[/lua] | [/lua] | ||
− | [/ | + | ... |
+ | [/scenario] | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | It may be worth putting the whole Lua script above inside a separate file and having the | + | It may be worth putting the whole Lua script above inside a separate file and having the [lua] tag load it: |
<syntaxhighlight lang='wml'> | <syntaxhighlight lang='wml'> | ||
− | [ | + | [scenario] |
− | |||
− | |||
[lua] | [lua] | ||
code = << wesnoth.dofile "~add-ons/Whatever/file.lua" >> | code = << wesnoth.dofile "~add-ons/Whatever/file.lua" >> | ||
[/lua] | [/lua] | ||
− | [/ | + | ... |
+ | [/scenario] | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
+ | '''Note''': When a multiplayer scenario is hosted on the multiplayer server, separate Lua files are not transmitted over the network from the host to the peers. Therefore, it may happen that the Lua code from the separate files is executed on the host but not on the peers. This may result in out of-sync type errors. Only the Lua code that is embedded in the scenario WML, that is without wesnoth.dofile, will be transmitted and executed. To prevent these issues, the Lua files that are to be loaded with wesnoth.dofile must be distributed to the end-user explicitly. | ||
== Remarks on Random Numbers == | == Remarks on Random Numbers == | ||
− | The math.random function is not safe for replays and multiplayer games, since the random values will be different each time and on all the clients. Instead, the Lua code should use the [[ | + | The math.random function is not safe for replays and multiplayer games, since the random values will be different each time and on all the clients. Instead, the Lua code should use the [[LuaAPI/mathx#mathx.random|mathx.random]] function to synchronize random values. It has the same interface as math.random but is multiplayer-safe. |
+ | |||
+ | Also available is [[LuaAPI/mathx#mathx.random_choice|mathx.random_choice]], which takes the same argument in the same format as [[InternalActionsWML#.5Bset_variable.5D|[set_variable]]] rand=. | ||
<syntaxhighlight lang='lua'> | <syntaxhighlight lang='lua'> | ||
− | local random_variable = | + | local random_variable = mathx.random_choice("1,2,3") |
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | |||
== Random Lua table iteration == | == Random Lua table iteration == |
Latest revision as of 23:38, 1 November 2024
Contents
The [lua] tag
This tag is a part of ActionWML, thus can be used inside [event] and at other places where ActionWML can be used. It makes it possible to write actions with the Lua 5.4 language.
(Version 1.13.? and later only) In addition to ActionWML, the [lua] tag may now be used in several other places. It can be placed at toplevel (outside of any tag) to make code that loads no matter what. (Note: This should usually be avoided, unless you're making a custom core.) It can be placed in any addon module tag, such as [scenario], [era], or [resource], to load code when the scenario boots up. Lua code placed in either of these places is executed even earlier than a preload event. And finally, it can now be used as ConditionalWML. In this case, the code must return either true or false to determine the outcome of the condition.
The [lua] tag can contain the following contents:
- code: A string containing the Lua script.
- name: An arbitrary string used to identify this tag in error messages.
- [args]: Arbitrary data that will be passed to the Lua script as its only argument. It can be accessed in the Lua code via the special variadic variable
...
.
Since Lua makes usage of double quotes and curly braces, it is recommended to enclose the script between stronger quotes (as shown in the below example), as they prevent the preprocessor from performing macro expansion and tokenization. This is not strictly required, however. Example:
[lua]
code = << wesnoth.interface.add_chat_message "Hello World!" >>
[/lua]
The [args] tag is useful if you need to pass WML variables or macro expansions into the code.
[lua]
code = << local t = ...; wesnoth.interface.add_chat_message(tostring(t.text)) >>
[args]
text = _ "Hello $unit.name!"
[/args]
[/lua]
When code in a [lua] tag has errors, it's often hard to find the location of the error because the line numbers are not within the code string and not the entire file. To help with this, you can set the name key. When an error is reported, the value of name will be shown as if it were the filename, allowing you to more easily locate the tag that raised it.
Testing out Lua in-game
The Lua kernel can also be accessed from command mode:
:lua local u = wesnoth.units.find_on_map{ id = "Konrad" }[1]; u.moves = 5
In addition, if you enable debug mode, you can access a <acronym title="Read, evaluate, print loop">REPL</acronym> console by pressing the Lua Console hotkey, which is bound to ~ by default. You can enable debug mode by using the :debug command, or by running Wesnoth with the --debug command-line argument. In the latter case, the Lua Console can even be summoned from the main menu screen.
You can use the Lua console to examine anything about the Lua environment and execute any Lua code you wish. It is recommended to use command mode to execute Lua that affects the map however, for example moving a unit. In the Lua Console you can use a special dir() function to examine the contents of objects and modules. You can also access a special _ variable which contains the result of the previous command executed in the console. Global variables assigned in the Lua console are not visible to scenario code, but remain between invocations of the console within a single game, as long as you don't reload. If you need to assign a global variable that is visible outside of the console, you can do so via the special variable _G.
Global environment
All the Lua scripts of a scenario share the same global environment (aka Lua state). Unlike other parts of the configurable gamestate the Lua state is not stored in savefiles, thus [lua] tags in [scenario] are executed not only before the scenario starts but also each time the game is loaded. Functions defined in [lua] tags in [scenario] can be used in all [lua] tags in [event]s.
[scenario]
[lua]
code = <<
function narrator(t)
-- Behave like the [message] tag.
wml.fire("message",
{ speaker = "narrator", message = t.sentence })
end
>>
[/lua]
[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]
...
[/scenario]
In the example above, the redundant structure could be hidden behind macros. But it may be better to simply define a new WML tag.
[scenario]
[lua]
code = <<
-- The function is now placed in the wesnoth.wml_actions table
-- The tag is [narrator], same as the function name
function wesnoth.wml_actions.narrator(t)
-- Behave like the [message] tag.
wml.fire("message",
{ speaker = "narrator", message = t.sentence })
end
>>
[/lua]
[event]
name = turn 1
[narrator]
sentence = _ "Hello world!"
[/narrator]
[narrator]
sentence = _ "How are you today?"
[/narrator]
[/event]
[/scenario]
The global environment is not preserved over save/load cycles. Therefore, storing values in the global environment is generally a bad idea. The only time assigning global variables (including function definitions) makes sense is in a [lua] block directly in [scenario] or 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 built-in modules: string, table, and math. A wesnoth module is also available, which provides access to the C++ game engine. Additionally, the functions clock, date, time and difftime from the os module are also available (but keep in mind that they aren't multiplayer- and replay-safe), as well as traceback from the debug module. There are also a few other built-in modules specific to Wesnoth – for full details, see LuaAPI.
At the start of a script, the variadic local variable ... (three dots) is a proxy table representing WML data. This table is the content of the [args] sub-tag of the [lua] tag, if any.
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 event_data = wesnoth.current.event_context
if wml.variables["target_hex.is_set"] and
(event_data.x1 ~= wml.variables["target_hex.x"] or event_data.y1 ~= wml.variables["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 event_data = wesnoth.current.event_context
puts the event data into the event_data local variable. Since it is a moveto event, the event_data table contains the destination of the unit in the x1 and y1 fields.
The next two lines then test
if wml.variables["target_hex.is_set"] and
(event_data.x1 ~= wml.variables["target_hex.x"] or event_data.y1 ~= wml.variables["target_hex.y"])
whether the variable wml.variables["target_hex"] matches the event parameters. Since wml.variables is not a local variable, it is taken from the global environment. Usually, variables from the global environment are not persistent but the wesnoth engine maps the variable wml.variables to the storage of WML variables.
The body of the conditional then performs the [redraw] action.
W.redraw()
This short syntax is made possible by a line of the prelude that makes W a proxy for performing WML actions.
[scenario]
[lua]
code = <<
H = wesnoth.require "helper"
W = H.set_wml_action_metatable {}
>>
[/lua]
...
[/scenario]
Without this shortcut, the first statement would have been written
wml.fire("redraw")
or
wesnoth.wml_actions.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] 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"
Encoding WML objects into Lua tables
WML table
Many functions in the Wesnoth Lua API expect a table representing a WML object (that is, the contents of a WML tag) to be passed as a parameter, for example wml.fire. Other functions return WML data converted to a Lua table. All these tables have the same format. The Lua API documentation refers to a table using this specific format convention as a "WML table".
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 (it is possible to have a tag and an attribute with the same name). So child objects are stored as (string, table) pairs in the unnamed fields in definition order. This means that for every subtag appearing in the wml code there is an additional table "layer" in the corresponding WML table of the form {tag = "tag_name", contents = {}}
. The table under contents in this subtable then holds the wml attributes from inside the wml subtag. So every subtag other than the toplevel tag corresponds to two nested tables each. In code, this format is constructed using wml.tag to improve readability. For example wml.tag_name{}
is equivalent to the prior example.
Note: As of 1.17, the structure changed slightly. Previously the additional table "layer" for each tag looked like {[1] = "tag_name", [2] = {}}
. For backwards compatibility, accessing [1] and [2] still return or set tag and contents respectively (in fact, it is a structure created by wesnoth.named_tuple). However, in older versions, only [1] and [2] work.
For instance, the following Lua table
{
foo = 42,
wml.tag.bar { v = 1, w = 2 } },
wml.tag.foo { x = false } },
wml.tag.bar { y = "foo" } },
wml.tag.foobar { z = 5, { wml.tag.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]
z = 5
[barfoo]
[/barfoo]
[/foobar]
[/dummy]
So assuming cfg contains the above WML object, the following accesses are possible:
a_int = cfg.foo -- "dummy.foo", 42
a_string = cfg[3].contents.y -- "dummy.bar[1].y", "foo"
a_table = cfg[4].contents -- "dummy.foobar", { z = 5, { "barfoo", {} } }
Consider using the wml.get_child and wml.child_range helper functions to ease the access to subtags.
(Version 1.13.5 and later only) As a convenience, attributes with array values (tables with only integer keys) are concatenated into a string when converting a Lua table into WML. For example, the following Lua code:
{
x = {1, 2, 3, 4},
y = {7, 8, 9, 10}
}
produces the following WML table:
[dummy]
x=1,2,3,4
y=7,8,9,10
[/dummy]
vconfig userdata
Functions registered in wesnoth.wml_actions and other similar tables that provide hooks that the engine calls will receive their data either in lua tables encoding a WML object or as a WML vconfig userdata, which has the same structure but is read-only. Accessing fields or children on a vconfig performs variable substitution on the fly. Its __parsed and __literal fields provide translations to plain, writable tables. __literal returns the original text of the data (including dollar symbols in attributes and [insert_tag] children), while __parsed performs a full 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 = wesnoth.wml_actions.event
function wesnoth.wml_actions.event(cfg)
-- Get the plain text from the user.
local new_cfg = wml.literal(cfg)
-- 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(wml.tovconfig(new_cfg))
end
(Note: The above example will only affect nested events. Toplevel events will still default to first_time_only=yes.) (Note2: You should not do that since it will break other addons that rely on the first_time_only=no default value.)
pairs and ipairs also work on vconfig objects. However, pairs works a little differently than on plain configs (tables) - it returns only string keys (attributes in WML terms) and not integer keys (tags in WML terms).
Another approach for handling userdata and tables in the same way, would be to convert the former into the latter beforehand. The wml.parsed and wml.literal helpers take care of this for you.
The vconfig userdata provides two other special fields: __shallow_parsed and __shallow_literal. They return a table corresponding to the WML userdata with variable substitution performed on the attributes (or not). [insert_tag] tags have also been parsed, so the number of children is faithful. But contrarily to __parsed and __literal, the process is not recursive: all the children are still WML userdata and variable substitution can still happen for them. These shallow translators are meant as optimized versions of the deep ones, when only the toplevel attributes need to be writable.
Skeleton of a lua tag
The following [lua] tag is a skeleton for a prelude enabling Lua in your WML events. It creates a table H containing the functions from helper.lua . It sets up a table T so be used for easier creation of valid WML tables. It also sets up a table V so that any access to it is redirected to the persistent WML storage. Finally, it loads a textdomain to be accessed through the _ variable.
[scenario]
[lua]
code = <<
H = wesnoth.require "lua/helper.lua"
T = wml.tag
V = wml.variables
local _ = wesnoth.textdomain "my-campaign"
-- Define your global constants here.
-- ...
-- Define your global functions here.
-- ...
>>
[/lua]
...
[/scenario]
It may be worth putting the whole Lua script above inside a separate file and having the [lua] tag load it:
[scenario]
[lua]
code = << wesnoth.dofile "~add-ons/Whatever/file.lua" >>
[/lua]
...
[/scenario]
Note: When a multiplayer scenario is hosted on the multiplayer server, separate Lua files are not transmitted over the network from the host to the peers. Therefore, it may happen that the Lua code from the separate files is executed on the host but not on the peers. This may result in out of-sync type errors. Only the Lua code that is embedded in the scenario WML, that is without wesnoth.dofile, will be transmitted and executed. To prevent these issues, the Lua files that are to be loaded with wesnoth.dofile must be distributed to the end-user explicitly.
Remarks on Random Numbers
The math.random function is not safe for replays and multiplayer games, since the random values will be different each time and on all the clients. Instead, the Lua code should use the mathx.random function to synchronize random values. It has the same interface as math.random but is multiplayer-safe.
Also available is mathx.random_choice, which takes the same argument in the same format as [set_variable] rand=.
local random_variable = mathx.random_choice("1,2,3")
Random Lua table iteration
Table iteration order (pairs) is not strictly defined in Lua. If your code depends on the order of iteration, different clients may have different data in a multiplayer game. For example:
local table = { ["Mage"] = true, ["Wose"] = true }
local concat = ""
local bad_usage = next(table) -- wrong, leads to OOS
for k, _ in pairs(table) do -- wrong, leads to OOS
concat = concat .. k
end
To avoid the problem, sort the table keys before iterating. Or alternatively, use Lua "arrays" and the ipairs function, which have a strictly defined order and never lead to OOS. Example of correct code:
local array = { "Mage", "Wose" }
local good_usage = table[1] -- correct
local concat = ""
for _, v in ipairs(array) do -- correct
concat = concat .. v
end