Introduction¶
This documentation describes the usage and possibilities of Lua to extend Widelands.
Note
Files of type .lua
are very basic text files. Do not use any fancy word
processor (Word, OpenOffice and their like) because they produce the wrong
file format. Make sure to use a plain text editor (like Notepad under
Windows, nedit under Linux and TextEdit under Mac OS X).
Where Lua is used¶
Lua is currently used in the following places:
Scenarios¶
The most prominent usage of Lua is in scenarios: All scenario logic is scripted using Lua. How this works is described in the Scenario Tutorial.
Starting conditions of Tribes in non-scenario games¶
At the beginning of non scenario games, the player has a choice to use different starting conditions. There is the very essential one that just sets a hq and some starting wares and there are more sophisticated ones.
To add a new starting condition see Starting Conditions.
Win conditions¶
In non player scenarios, win conditions define when one single player has won
a game. The definitions of win conditions is very similar to defining
starting conditions which returns an array with name
, description
and func
.
Take a look at the endless_game wincondition
for a basic example. Let’s make up a quick example: The first player to have
200 logs in his HQ wins the game. All others loose. Save the following file as
data/scripting/win_conditions/harvest_logs.lua
.
include "scripting/coroutine.lua" -- for sleep()
include "scripting/win_conditions/win_condition_functions.lua" -- for broadcast_objective()
-- Some variable which get used throughout this win condition:
local textdomain = "win_conditions" -- For translations, textdomain must be win_conditions
push_textdomain(textdomain)
local wc_name = "Harvest logs" -- The name of win condition, not localized
local wc_descname = _("Harvest logs") -- wc_descname has to be exactly like wc_name, because it
-- will be used as the key to fetch the translation in C++
local wc_version = 1 -- The version of this win condition
local wc_desc = _("The first player with 200 logs in his headquarter wins!")
local wc_definition = { -- The table defining the win condition
name = wc_name,
description = wc_desc,
peaceful_mode_allowed = true, -- Enabling the checkbox for peaceful mode in game setup menu
func = function() -- The function processing the win condition
broadcast_objective("win_condition", -- Set objective with win condition for all players
wc_descname,
wc_desc)
local plrs = wl.Game().players -- All players
local winner = nil -- No winner yet
local losers = {} -- Table of loosers
local map = wl.Game().map -- To get access to map objects
while not winner do -- The main loop, it will run as long there is no winner
sleep(5000) -- Do this every 5 seconds
for idx, p in ipairs(plrs) do -- Iterate all players
local sf = map.player_slots[p.number].starting_field -- The starting field of this player
local hq = sf.immovable -- The headquarters of this player
if hq:get_wares("log") >= 200 then -- Check if more than 200 logs are stored
winner = p -- This player is the winner!
losers = plrs -- Store all players and ...
table.remove(losers,idx) -- ... remove the winner from the losers table
end
end
end
push_textdomain(textdomain) -- Each part containing localized strings
-- needs pushing the textdomain again
for idx,p in ipairs(losers) do -- Iterate all losers and send the status
p:send_to_inbox(_("You lost!"),
_("You lost this game!"),
{popup=true}
)
end
winner:send_to_inbox(_("You won!"), -- The winner get also the status
_("You won this game!"),
{popup=true}
)
pop_textdomain() -- Reset last textdomain
end,
}
pop_textdomain() -- Reset textdomain from file
return wc_definition -- Return the table of the defined wincondition
Hooks¶
Hooks are called by widelands when a certain event happened. They are a rather recent implementation and therefore still limited. More hooks might be implemented in the future.
You set a hook by setting a field in the global variable hooks
which must
be a dictionary. The only valid hook currently is the custom_statistic
hook which gives the ability to add one new statistic to the general
statistics menu. This is used in win conditions (e.g. collectors) and could
also be used in some missions in the future. To define a new statistic, use
something like this:
hooks.custom_statistic = {
name = _("Unchanging statistic"),
pic = "map:mycool_stat_picture.png", -- For the menu button
calculator = function(p)
-- Calculate the current value for this player
return p.number * 20
end,
}
Every time widelands samples the current statistics, it will call the
calculator
function for each player and expects an unsigned integer value
back.
Debug console¶
In widelands debug builds you can open a debug console by pressing
Ctrl+Shift+Space
. You can enter Lua commands here that act in the global
environment: That is if you are in a scenario you can access the global
variables and alter all Lua objects that are in the global scope:
print("Hello World!")
map = wl.Game().map
hq = map.player_slots[1].starting_field.immovable -- If this is a normal map
hq:set_workers("barbarians_builder", 100)
This makes for excellent cheating in debug builds, but note that this is for debug purposes only – in network games running Lua commands this way will desync and therefore crash the game and also replays where you changed the game state via the debug console will not work. It is very useful for debugging scenarios though. It is also possible to load a script from any directory which makes testing of functions very easy. Let’s assume you test a function like:
function all_players()
for idx, player in ipairs(wl.Game().players) do
print("Player:" player.name, player.number, player.tribe.name)
end
end
Save this as tests.lua
. Now start a normal game, open the debug console
by pressing Ctrl+Shift+Space
and enter dofile("/full/path/to/tests.lua")
.
Now you can run the function all_players()
. If the output is not what you
expected just change the function, load the file again with dofile
and
call the function again. For convenience you can get the last 5 commands back by
pressing the Up arrow key.
Regression testing infrastructure¶
The test
directory in the repository contains the regression test suite. A
test is either a savegame plus a set of Lua scripts (test_*.lua) or a map that
contains in its scripting directory a set of (test_*.lua and/or
editor_test*.lua which are only run in the Editor) files.
Each test starts Widelands using either the --editor
, --loadgame
or
--scenario
switch and additionally, the --script
switch can be supplied to
run a Lua script directly after the game is ready to take commands.
The tests communicate with the test runner through standard output. If a script outputs “All Tests passed.” the test is considered to pass, otherwise to fail. Whenever a savegame is written inside a test it is later loaded by the test runner as an additional test.
See also: Regression Tests