LuaWML/Misc

From The Battle for Wesnoth Wiki
< LuaWML
Revision as of 00:33, 2 January 2019 by Edward Chernenko (talk | contribs) (helper.set_wml_tag_metatable: this is deprecated. Replaced with wml.tag)

This page describes miscellaneous LuaWML objects and helpers.

wesnoth.game_config

Contrarily to the other values of the wesnoth table, game_config is simply a proxy table. Its fields offer an interface to the global settings of Wesnoth:

  • version: string (read only)
  • base_income: integer (read/write)
  • village_income: integer (read/write)
  • poison_amount: integer (read/write)
  • rest_heal_amount: integer (read/write)
  • recall_cost: integer (read/write)
  • combat_experience: integer (read/write)
  • kill_experience: integer (read/write)
  • last_turn: integer (read/write) turn limit, maximum number of turns
  • debug: boolean (read only)
  • mp_debug: boolean (read only)
  • campaign_type: string (read only) Indicates what type of game this is, e.g. "multiplayer"
  • theme: (Version 1.13.8 and later only) string (read/write) The ID of the current scenario theme. If the scenario uses the default theme from preferences, this will be an empty string.
  • mp_settings: table. In a multiplayer game, this is a proxy table which gives read only access to all MP-only configuration options which appear as attributes of [multiplayer] tag in a save game file:
    • active_mods: string (read only) A list of all active modifications
    • hash: string (read only) A hash of mp data
    • mp_campaign: string (read only) Name of mp campaign
    • mp_scenario: string (read only) ID of this mp scenario
    • mp_scenario_name: string (read only) Name of this mp scenario
    • scenario: string (read only) MP lobby title
    • difficulty_define: string (read only) The campaign difficulty string for an mp campaign
    • mp_village_gold: integer (read only)
    • mp_village_support: integer (read only)
    • mp_num_turns: integer (read only)
    • mp_era: string (read only) The id of the chosen era
    • mp_eras: string (read only) A list of all era ids
    • mp_fog: boolean (read only)
    • mp_shroud: boolean (read only)
    • mp_random_start_time: boolean (read only)
    • experience_modifier: integer (read only)
    • mp_use_map_settings: boolean (read only)
    • mp_countdown: boolean (read only) Whether the timer is enabled
    • mp_countdown_action_bonus: integer (read only)
    • mp_countdown_init_time: integer (read only)
    • mp_countdown_reservoir_time: integer (read only)
    • mp_countdown_turn_bonus: integer (read only)
    • observer: boolean (read only)
    • shuffle_sides: boolean (read only)
    • savegame: boolean (read only) Whether this is a reloaded game
    • side_users: string (read only) List of how sides are assigned to users (at game start)
  • era: table. A proxy table for the entire era tag corresponding to the current era. Its id will always match wesnoth.game_config.mp_settings.mp_era

Note: wesnoth.game_config.mp_settings, and wesnoth.game_config.era, will only exist if wesnoth.game_config.campaign_type == "multiplayer"


-- Poison a bit weak? Let's boost it!
wesnoth.game_config.poison_amount = 15
-- Warn users when they use bad settings:
if (wesnoth.game_config.mp_settings.shuffle_sides) then
   wesnoth.message("Warning: This scenario is not intended to be played with shuffle sides!")
end
-- Collect basic info about the current era:
local era = wesnoth.game_config.era
local helper = wesnoth.require("lua/helper.lua")
local count = 0
wesnoth.set_variable("era_name", era.name)
for multiplayer_side in helper.child_range(era, "multiplayer_side") do
    count = count + 1
    wesnoth.set_variable("faction" .. tostring(count) .. "_name", multiplayer_side.name)
    wesnoth.set_variable("faction" .. tostring(count) .. "_recruit", multiplayer_side.recruit)
end
wesnoth.set_variable("num_factions", count)

wesnoth.get_era

  • wesnoth.get_era(id)

A function which takes one argument, an era id, and returns the entire era tag corresponding to that id. For a list of valid era ids, use wesnoth.game_config.mp_settings.mp_eras.

wesnoth.current

As with game_config, current is a proxy table. Its fields are getter for game-related properties:

  • side: integer (read only)
  • turn: integer (read only)
  • event_context: WML table with attributes name, x1, y1, x2, y2, and children weapon, second_weapon, describing the trigger for the current event. (Version 1.13.2 and later only) unit_x, unit_y contain the location of the primary unit involved in the event. Currently the only case where this cam be different from x1 and y1 are enter_hex and exit_hex events.
  • synced_state (Version 1.13.0 and later only) whether the current code runs in a synced contex, this returns a string, the possible values are:
    • synced the current code runs on all mp clients, this is the normal context, in which all gamestatechaning actions should take place.
    • unsynced for example during select events or during the calculation of a wesnoth.theme_items, don't change the gamestate in this context because the current code only runs on one machine, so changign the gamestate here will cause OOS. Typical things to do here are UI related things, or entering the synced state via [do_command]
    • local_choice the current code was invoked by wesnoth.synchronize_choice and runs only on one local client to calculate the return value for wesnoth.synchronize_choice. You cannot enter the synced context with [do_command] now.
    • preload we are currently running a preload event or an even earlier event, this behaves similar to local_choice
wesnoth.message(string.format("Turn %d, side %d is playing.", wesnoth.current.turn, wesnoth.current.side))

wesnoth.end_turn

  • wesnoth.end_turn ([next_side])

like [endturn] actionwml, has a integer parmaeter that allows to specify the next side that should gain control, any integer in the range [1, 2*nsides] is allowed, where a number greater than nsides also changes the turn counter by one.

wesnoth.synchronize_choice

  • wesnoth.synchronize_choice(function, [ai_function])
  • (Version 1.13.2 and later only) wesnoth.synchronize_choice([description], function, [ai_function], [for_side])

Recovers a WML table that was computed on one client only or was stored in a replay. The actual computation is performed by the function passed as the first function argument, assuming that the client is the side currently playing. For all the other clients, the function will not be called. An optional second function can be passed; if present, it will be used instead of the first one when the client happens to be an AI (hence not enable to interact with a user interface).

local result = wesnoth.synchronize_choice(
  function()
    -- Called only on the client handling the current side, if it is a human.
    local choice = 0
    wesnoth.show_dialog(
      some_dialog_cfg, nil,
      function()
        choice = wesnoth.get_dialog_value "some_list"
      end)
    return { value = choice }
  end,
  function()
    -- Called only on the client handling the current side, if it is an AI.
    return { value = math.random(some_list_size) }
  end)
wesnoth.message(string.format("Selected item: %d", result.value))

Note: The return value must be a valid WML table - the same kind of thing you could store to a WML variable, and not, for instance, a proxy unit, anything else that uses metatables, or a lua table with another table as the value of a string attribute. Unlike other lua functions, wesnoth.synchronize_choice will NOT throw an error if the table is invalid, but will silently strip out the contents of any invalid subtag.

When wesnoth is running in debug mode (e.g. --debug flag on command line) synchronize_choice will chat a "Lua Warning" if it finds that the table returned was partially invalid.

(Version 1.13.2 and later only) This function takes now takes these arguments:

  • An optional translatable string describing the type of the user input. This is displayed to the other clients while one client executes the passed function. Defaults to "input".
  • A function: (as before).
  • An optional function: for ai sides (as before).
  • An optional integer: on which side the function should be evaluated. Defaults to the currently playing side. If the specified side is empty/null controlled the engine will choose another side.

wesnoth.synchronize_choices

  • wesnoth.synchronize_choices([description], function, [default_function], [for_sides])

(Version 1.13.2 and later only) Similar to the singular form above, this function takes a function parameter and evaluates it on the specified sides. It takes the following arguments:

  • An optional translatable string descibing the type of the user input. This is displayed to the other clients while the specified clients execute the passeed function. Defaults to "input"
  • A function that evaluates the choice returning a wml table. Unlike above, this function is called for ai and human sides (use if controller == "ai" for checking if it is a ai side)
  • An optional function for evaluating the choice in case this side was null controlled. If this function is called, it is called on all clients (unlike the first passed function) defaults to a function returning an empty table.
  • An array of integers specifying on which sides this function should be evaluated, the function is evaluated on all passed sides, each side may only appear once in this array. All specified sides execute the function simultaniously.

This function returns a table with integer as keys and WML tables as values. the keys are the sides where that action was evaluated. The values are the values computed by the passed function. Example:

[event]
  name = "start"
  [lua]
  code = <<
    wesnoth.set_variable("input1",nil)
    local result = wesnoth.synchronize_choices(
    function()
      local option1 = T.option { message = "No", T.command { T.set_variable { name = "input1", value = "No"}}}
      local option2 = T.option { message = "Yes", T.command { T.set_variable { name = "input1", value = "Yes"}}}
      wesnoth.fire(T.message{ message =  "Are you sure you want to play this game?", option1, option2})
      return { value = wesnoth.get_variable("input1") }
    end,
    {1,2})
    wesnoth.set_variable("input1",nil)
    wesnoth.message("Player 1 wants to play: " .. result[1].value)
    wesnoth.message("Player 2 wants to play: " .. result[2].value)
  >>
  [/lua]
[/event]

wesnoth.get_image_size

  • wesnoth.get_image_size(filename)

Returns the width and height of an image.

local w, h = wesnoth.get_image_size "units/transport/galleon.png"

wesnoth.compare_versions

  • wesnoth.compare_versions(version1, operator, version2)

Takes two versions strings and an operator, returns whether the comparison yields true. Follows the same rules like the #ifver preprocessor statement.

local function version_is_sufficient(required)
 if not wesnoth.compare_versions then return false end
 return wesnoth.compare_versions(wesnoth.game_config.version, ">=", required)
end
local required = "1.9.6"
if not version_is_sufficient(required) then wesnoth.message(string.format(
 "Your BfW version is insufficient, please get BfW %s or greater!", required)) end

wesnoth.have_file

  • wesnoth.have_file(filename)

Checks if the file (not necessarily a Lua file) or the directory passed as argument exists. Returns true if the file exists, false otherwise. Follows the same rules like the #ifhave preprocessor statement.

-- Does the user have installed the UMC Music Book 1?
local umc_music = wesnoth.have_file( "~add-ons/UMC_Music_Book_1/_main.cfg" )
-- and if we want to check for the folder?
local music_folder = wesnoth.have_file( "~add-ons/UMC_Music_Book_1/" )

wesnoth.debug

  • wesnoth.debug(wml_table)

Takes a userdata with metatable wml object or a wml table and dumps its content into a pretty string.

wesnoth.set_variable("number", 100)
local vconfig = wesnoth.tovconfig({ key = "$number", another_key = true,
    {"a_subtag", { a_key_in_the_subtag = "foo" }}
})
wesnoth.message(wesnoth.debug(vconfig))
wesnoth.message(wesnoth.debug(vconfig.__literal))

wesnoth.get_time_stamp

  • wesnoth.get_time_stamp()

This function retrieves the current time stamp, that is the amount of milliseconds passed from when the SDL library was initialized. It takes no arguments and returns an integer. WARNING: this function uses the same code as [set_variable] time=stamp, and so it is MP-unsafe. It is provided only for benchmark purposes and AI development, although it should work inside wesnoth.synchronize_choice() as well.

local stamp = wesnoth.get_time_stamp()

wesnoth.random

  • wesnoth.random([m, [n]])

(Version 1.13.2 and later only) This function returns a random number generated by the synced random generator which is also used by [set_variable]rand= (and thus also by helper.rand). This function has the same interface as math.random so it can take 0, 1 or 2 arguments.

In map generation scripts, the values returned by this function depend on the seed the map author has input (if any).

Be careful as random is not safe in certain events, see Multiplayer_safety.

wesnoth.unsynced

  • wesnoth.unsynced(func)

calls the given function in a unsynced context, that is in particular, while executing that function random results will not be synced in mp.

wesnoth.compile_formula

  • wesnoth.compile_formula(formula)

(Version 1.13.5 and later only) Compiles a Wesnoth Formula Language formula into a Lua callable userdata. The callable value returned by this function can take an optional table as argument, which defines variables available to the formula. It can also take a unit proxy as argument, which evaluates the formula in that unit's context just as if it had been used in a StandardUnitFilter.

A compiled formula can be converted back to valid code using the built-in tostring function.

wesnoth.eval_formula

  • wesnoth.eval_formula(formula, [variables])

(Version 1.13.5 and later only) Equivalent to wesnoth.compile_formula(formula)(variables). It can also evaluate an already-compiled formula, which is equivalent to calling the formula.

wesnoth.name_generator

  • wesnoth.name_generator(type, definition, [additional params])

(Version 1.13.5 and later only) Constructs a name generator for use in generating names using either the Markov chain algorithm used in older versions of Wesnoth or the context-free grammar generator used since 1.13.5. The type parameter indicates which algorithm to use (either markov or cfg). The definition can be a string, just like it would be in a config file, or it can be formatted as a table. Additional parameters may be passed, depending on the type of generator. The function returns a callable userdata, which will return a new name each time it is called (with no parameters).

  • Markov chain: A Markov chain generator works by analyzing a list of input names and noticing tendencies in the way the letters are strung together. It can then apply those tendencies to produce new similar names that were not in the original list. Longer lists give better results. The definition is a list of names, formatted either as a comma-separated string or as an array-like table. The Markov generator can take two additional parameters. The first additional parameter is chain_size, and the second is max_length.
    • A value greater than 1 for the chain_size causes the analyzer to consider the words in chunks, which is similar to analyzing them syllable by syllable instead of letter by letter. The default chain size is 2, meaning that the analyzer treats words as consisting of 2-character syllables.
    • The max_length places a cap on the total length of the name. The default value is 12 characters.
  • Context-free grammar: A context-free grammar is a way of specifying how strings can be constructed. The definition may be specified as a multi-line string, just as described in the preceding link, or it can be formatted as a table where the keys are non-terminals and the values are what they expand to. The expansion of each non-terminal can be formatted either as a |-separated list as described in the preceding link, or as an array-like table. (Mixing the two forms is permissible too.) The context-free generator has no additional parameters.

wesnoth.format

  • wesnoth.format(format_string, values)

Similar to string.format but using Wesnoth's variable substitution syntax, and also supporting translatable strings. Any valid config can be passed as the values, though typically you will only need key-value pairs. You could also substitute WML variables into a string by passing wesnoth.get_all_vars() as the second parameter.

This function preserves the translatability of the input format string.

wesnoth.format_conjunct_list

  • wesnoth.format_conjunct_list(list_of_strings)

Formats a list of usually-translatable strings into a localized list string of the form "A, B, and C".

wesnoth.format_disjunct_list

  • wesnoth.format_disjunct_list(list_of_strings)

Formats a list of usually-translatable strings into a localized list string of the form "A, B, or C".

wml.tag

  • wml.tag.something

Can be used to create subtags with less brackets, for example:

W.event { name = "new turn", wml.tag.message { speaker = "narrator", message = "?" } }

helper.modify_unit

  • helper.modify_unit(filter, keys)

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

Note: This appears to be less powerful than the [modify_unit] tag and may be removed at some point in the future.

helper.move_unit_fake

  • helper.move_unit_fake(unit, destination)

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)

helper.rand

  • helper.rand(spec)

(A shortcut to set_variable's rand= since math.rand is an OOS magnet and therefore disabled.) Pass a string like you would to set_variable's rand=.

create a random unit at (1, 1) on side=1 :

wesnoth.put_unit(1, 1, { type = helper.rand("Dwarvish Fighter,Dwarvish Thunderer,Dwarvish Scout") })

Note that random is only safe in certain events, see Multiplayer Safety.

helper.round

  • helper.round(n)

Unlike other languages (Python, Perl, Javascript, ...), Lua does not include a round function. This helper function allows rounding numbers, following the "round half away from zero method", see Wikipedia [[1]]. Returns the number rounded to the nearest integer.

-- this number will be rounded up
helper.round(345.67) -- returns 346
-- this one will be rounded down
helper.round(543.21) -- returns 543
-- an integer stays integer
helper.round(123) -- returns 123
-- works also for negative numbers
helper.round(-369.84) -- returns -370
helper.round(-246.42) -- returns -246

helper.shuffle

This function randomly sorts in place the elements of the table passed as argument, following the Fisher-Yates algorithm. It returns no value. WARNING: this function uses Lua's math.random(), and so it is not MP-safe. It is provided mainly for AI development, although it should work inside wesnoth.synchronize_choice() as well.

local locs = wesnoth.get_locations( { terrain="G*" } )
helper.shuffle( locs )

(Version 1.13.2 and later only) This function now uses the synced RNG by default and should not cause OOS anymore. It is also possible now to pass a different random generator as a second argument; a random generator is a function that takes two integers a and b and returns a random integer in the range [a,b]. For example, math.random can be passed to get the 1.12 behavior:

local locs = wesnoth.get_locations( { terrain="G*" } )
helper.shuffle( locs, math.random )