Lua/Syntax
This article gives an overview of the basic language features and syntax of SRB2's Lua implementation. It is not intended as a tutorial for Lua newcomers or a complete documentation of the language. For the former, see the Lua Tutorial on the lua-users wiki. For the latter, consult the reference manual for Lua 5.1.
Operations and symbols
Red text denotes operations that are not part of standard Lua and have been added in BLUA.
|
|
|
|
|
Comments
Comment type | Syntax |
---|---|
Short comment (affects only the rest of the line) |
-- comment // comment
|
Long comment (affects all of the space between the two ends) |
--[[ comment ]] /* comment */
|
Escape sequences (\
)
The \
(backslash) character followed by one of certain other characters creates an escape sequence. Escape sequences are used within strings to insert special formatting commands (such as adding a new line) or characters that have special significance in Lua syntax and therefore cannot be inserted directly.
Escape sequence | Use |
---|---|
\a
|
Bell (has no effect in SRB2) |
\b
|
Backspace (has no effect in SRB2) |
\f
|
Form feed (has no effect in SRB2) |
\n
|
New line |
\r
|
Carriage return |
\t
|
Horizontal tab |
\v
|
Vertical tab (has no effect in SRB2) |
\\
|
Backslash |
\'
|
Apostrophe/Single quote |
\"
|
Quotation marks/Double quote |
Character input | |
\<number>
|
ASCII input (decimal). <number> should be up to 3 digits long.
|
\x<hex>
|
ASCII input (hexadecimal). <hex> should be 2 or 4 digits long.
|
\u<hex>
|
UTF-8-encoded Unicode input. <hex> should be 4 or 6 digits long.
|
If a number up to 3 digits long is placed after the \
character, Lua interprets this escape sequence as a character encoded in ASCII – for example, \97
or \097
will result in the 'a
' character. This can be used for obfuscation, as the codes cannot be read directly by humans but will automatically be interpreted as the corresponding characters by the game. Alternatively, the escape sequences \x
and \u
can be used to encode characters in hexadecimal format, either in ASCII or in UTF-8-encoded Unicode, respectively.
Types of variables
There are various types of variables that are available to Lua, which are detailed below. To check a variable's type, the function type() can be used, which returns a string displaying the type.
- number: In SRB2's Lua implementation, all numbers are integers. Floating-point numbers (e.g., 1.5) are not available. Where real numbers are needed, SRB2 uses fixed-point numbers, where the base unit is
FRACUNIT
(65536). For example, the number 1.5 is represented byFRACUNIT + FRACUNIT/2
or3*FRACUNIT/2
(98304). The math library offers fixed-point math functions for handling fixed-point numbers. - string: Plain text, consisting of a sequence of characters. A string containing the text string can be created with the syntax
"string"
, although'string'
and[[string]]
are also available as alternatives. Concatenation is used to link two or more strings or a mix of strings and other variable types together into a larger string. - boolean: Can have the value
true
orfalse
. Boolean variables are typically used for checking conditions, such as in If statements. In these situations, anil
value is also interpreted asfalse
. SRB2's Lua additionally interprets 0 asfalse
as well. - table: A collection of multiple values in a single variable, such as in
local table = {a,b,c,d,e}
. - nil: A variable type whose only possible value is
nil
. It is used to indicate a value of "nothing", i.e., the absence of a useful value. Accordingly,nil
is not equal to any other value, including 0. It is similar to C'sNULL
value, which is used to indicate unassigned pointers, although in CNULL
is equal to 0, whereasnil
is not. When no value is assigned to a newly created variable, it will have type nil and valuenil
. - function: Functions can be assigned to variables in Lua (see the Creating functions section). Keep in mind the difference between assigning a function to a variable and assigning a function's return value to a variable: The former is done with the syntax
local func = functionname
. This will makefunc
a function that is equivalent tofunctionname
, i.e., callingfunc()
will have the same effect as callingfunctionname()
. The latter is done with the syntaxlocal result = functionname()
. In this case,functionname()
will be run and the result will be stored inresult
. - userdata: The userdata type is used to encapsulate data structures from SRB2's source code itself that can be accessed or modified via Lua. Their usage is similar to tables. See Lua > Userdata types for the full list.
Variable assignment
Creating new "local" variables
Variables can be created inside functions or other blocks, or outside of any function or block (e.g., at the top of the script). They can be only be accessed inside the scope in which they defined – if a variable was defined in a function or block, it can only be used inside that function or block. In other words, variables are local to the function or block they were defined in, hence the name of the keyword used in the examples below. Furthermore, a variable always has to be defined before its first usage.
A variable that is created at the top of a Lua script, outside of any function or block, will be accessible everywhere in the Lua script and can therefore be considered a "global" variable. However, even such a variable will not be accessible outside of the Lua script (e.g., in another script). Also note that if the variable's value can change, the changes may not be kept consistent during netgames unless the variable is synchronized with the use of the NetVars
hook.
Syntax for creating new variables using "local":
- With no value set:
local name
- In this case, the variable will have a value of
nil
when accessed.
- In this case, the variable will have a value of
- With a value set:
local name = value
- Creating multiple new values:
local name1,name2 = value1,value2
- In this example,
name1
's value isvalue1
andname2
's isvalue2
. - This can be used to create as many variables as desired in the same line, although the line will become impractically long quickly.
- In this example,
- Blank table:
local name = {}
- Table of values without keys:
local name = {value1, value2, value3, value4}
- Tables without keys can be accessed with numbered indices. In this example,
name[1]
will returnvalue1
,name[2]
will returnvalue2
, etc. Contrary to C, the first index of Lua-created tables is 1 rather than 0.
- Tables without keys can be accessed with numbered indices. In this example,
- Table of keys with values:
local name = {a = value1, b = value2, c = value3, d = value4}
- In this example,
name.a
(orname["a"]
) will returnvalue1
, and similar forb
,c
andd
.
- In this example,
- Blank string:
local name = ""
- String:
local name = "text"
- Table/userdata references:
local mymobj = mobj
(assumingmobj
was already defined beforehand!)- This creates a reference to
mobj
calledmymobj
. It will behave exactly the same asmobj
. For example, ifmobj
is actually amobj_t
variable, as the name suggests, thenmymobj.type
will return the same value asmobj.type
. - Note that
mymobj
is a reference tomobj
, not a copy! If you modifymobj
,mymobj
will change accordingly, and vice versa!
- This creates a reference to
- Functions can also be defined this way (see the Creating functions section):
local name = function(argument) contents of function end
- The "not" keyword can invert a boolean type variable:
local name = not true
- In this example, name becomes the inverse of the value "true" (which is false)
local name = not false
- In this example, name becomes the inverse of the value "false" (which is true)
- The logical operators "and" and "or" can also be used for conditional variable assignments. They consider "false", "nil", and in case of SRB2's Lua, "0" as false, and anything else as true. "and" always returns its first operand if evaluates to false, and its second argument when it evaluates to true, while "or" returns its second operand when it evaluates to false, and its first when it evaluates to true.
local value = 4 and 5 -- value is 5 local value = 4 or 5 -- value is 4 local value = nil and 13 -- value is nil local value = nil or 13 -- value is 13
Creating custom variables for existing userdata structures
The following code adds a new variable with name newvar
and value value
to the existing userdata structure mobj
:
mobj.newvar = value
If newvar
does not already exist for the structure, it will automatically be added to the userdata structure and given the value value
. If newvar
already exists, its value is simply set to value
.
Be warned though – if a custom variable is accessed elsewhere before the code that creates it is executed, Lua will print errors in the console and remove the hook the access happened in. For example, this issue can occur if custom variables are created in a MapLoad
hook or a MobjSpawn
hook for MT_PLAYER
. If the Lua script is loaded mid-level, this will not happen until another map is loaded or the player respawns, respectively. If the custom variables are accessed in the meantime, they will not have been created yet and an error will occur.
Existing userdata types that can be given custom variables:
mobj_t
player_t
mobjinfo_t
mapheader_t
can be supplied custom variables via SOC lumps. Custom variable names should be prefixed withLua.
in a map's level header. In Lua scripts, they must be referred to in all-lowercase to be recognized properly.
Modifying existing variables
- Syntax:
variable = newvalue
- Assigning new values to multiple existing variables:
variable1,variable2 = value1, value2
- Setting a new value by performing an operation on the old value, e.g., addition:
variable = variable + value
- Pseudo-numbers can be used as shortcuts for the values being modified:
variable = $ + value
- Here,
$
is a pseudo-number representing the original value of the variable being modified.
- This works with multiple-variable assignment as well:
variable1, variable2 = $ + value1, $ - value2
- Here,
$
representsvariable1
in the first case, and thenvariable2
in the second.
$1
,$2
,$3
and further can be used when it is necessary to refer to specific variables during multiple-variable assignment, most useful for swapping variables or combining them with each other:variable1, variable2 = $2, $1
- Here,
$1
representsvariable1
and$2
representsvariable2
. In this example, the two variables' values are swapped.
- Just as with creating new variables with "local", the "or", "not" and "and" keywords can be used in assignments involving already existing variables, in the same fashions as shown previously.
Table manipulation
table[i]
would be the value of entry at index i, e.g., fortable = {[1] = value1, [2] = value2}
,table[1]
would returnvalue1
.- Anything equal to index 1 would be the same as
table[1]
and also returnvalue1
, e.g., adding variablelocal VALUEA = 1
to the above example,table[VALUEA]
would also returnvalue1
. - Strings may also be used as indices, e.g., for
table = {["a"] = value1, ["b"] = value2}
,table["a"]
andtable.a
would both returnvalue1
.
- Anything equal to index 1 would be the same as
#table
would be the number of entries in the table, e.g., fortable = {[1] = value1, [2] = value2, [3] = value3, [4] = value4}
,#table
would return 4.#table[i]
would be the length of the value of index i, e.g., fortable = {[1] = "crawla", [2] = "thok"}
,#table[1]
would return 6.
See also the Table library functions (Lua/Functions > Table library).
Functions
Creating functions
- Creating a new "local" function:
local function FunctionName(argument) --contents of function end
- Functions can also be created without "local". This allows them to be used outside of the Lua script they were defined in. This works only for creating custom functions beginning with the
A_
prefix[confirm? – discuss], allowing them to be used as actions:
function GlobalFunctionName(argument) --contents of function end
- Creating a new function with multiple arguments:
local function FunctionName(argument1, argument2) --contents of function end
- With no parameters:
local function FunctionName() --contents of function end
- Creating a "prototype" variable to be defined as a function later (for situations involving using a function before actually defining it properly):
local FunctionName --[code in-between the two parts] function FunctionName(argument1, argument2, [etc]) --contents of function end
- Implicitly creating a function, with no arguments. This can freely be made standalone anywhere inside functions or even outside of everything else, so to contain various sections of code if needed.
do --contents of function end
- Implicitly creating a function standalone, with arguments (should only be used within a function that requires a function as an argument, e.g.
addHook
orCOM_AddCommand
)function(argument1, argument2) --contents of function end
- Functions can also be made as local variables defined using implicitly created functions as above:
local name = do --[[ contents of function ]] end
- or
local name = function(argument1, argument2) --[[ contents of function ]] end
- Functions can also be tied to tables (including userdata such as
mobj_t
) in either of two ways:function myTable.FunctionName(argument1, argument2) --contents of function end
- Alternative version:
function myTable:FunctionName(argument1, argument2) --contents of function end
- Note the change from "." to ":" in the syntax. This allows the table itself to be used and modified inside the function, but the name is forced to be
self
whether you like it or not.
Note that any created arguments do not need to have any particular name whatsoever (aside from the one case above), nor do they need to be of any set variable type in most cases. There is exception to functions needed by functions such as addHook
and COM_AddCommand
as arguments, where the functions requested are to have up to a specific number of arguments which are to be used as certain variable types, but the names are still flexible when defining the functions; it's the ordering and variable types that matter most in this situation.
Functions can also be defined within functions themselves, though this renders them unusable outside of these areas.
Returning
The "return" keyword is used inside functions to end the function early (i.e., before the rest of it can run) and, if desired, to return one or multiple values as the function's output.
return value
– The function returns the single valuevalue
. This value can be of any type: a number, string, table/userdata pointers,true
/false
, or perhaps justnil
if the function should not return any value.return
– This is the same as usingreturn nil
; the function essentially returns nothing.return value1, value2
– The function returns multiple values. In this example, two variables are returned, but more can be returned as well.
Calling functions
- Calling a function standalone, without storing the output. This is used if the functions doesn't return any value, or if you don't need the values it might return.
FunctionName(argument)
- Functions that need multiple arguments work exactly the same way:
FunctionName(argument1, argument2, [etc])
- As do functions that need no arguments whatsoever:
FunctionName()
- For functions that return an output value of some sort, you can utilize it by storing it in a variable (either by creating a new one or modifying an existing variable's value):
local variable = FunctionName(argument1, argument2, [etc])
variable = FunctionName(argument1, argument2, [etc])
- For functions that return multiple output values:
variable1, variable2 = FunctionName(argument1, argument2, [etc])
variable1
will be set to the function's first output value, andvariable2
to the second output.- If only the first output of the function is needed, the function can just be treated as if it had only a single variable as above.
- However, if you need the second output and not the first output, it is common practice to use "_" as a "dummy" variable (which still has to be created as a typical variable), used as such:
_, variable2 = FunctionName(argument1, argument2, [etc])
- This trick can be further used to leave out variables other than the first, such as with a function that returns three values but from which you need only the first and third:
variable1, _, variable2 = FunctionName(argument1, argument2, [etc])
- Functions tied to tables work the same way as above:
table.FunctionName(argument1, argument2, [etc])
table:FunctionName(argument1, argument2, [etc])
variable = table.FunctionName(argument1, argument2, [etc])
- etc.
Variable argument count
The ...
keyword can be used in a function's argument definition to give it an undetermined number of additional arguments after the ones defined explicitly:
local function FunctionName(arg1, arg2, ...)
Inside the function, ...
refers to all arguments beyond the explicitly-defined ones. {...}
can be used to retrieve these arguments as a table.
Example function, using print()
messages to display the arguments' values in the console:
local function batchecho(player, ...)
local arg1, arg2, arg3 = ...
print("arg1 is "+arg1)
print("arg2 is "+arg2)
print("arg3 is "+arg3)
print("Now printing all args...")
for _,i in ipairs({...}) do
print(i)
end
end
COM_AddCommand("batchecho", batchecho)
Functions that do this can essentially take any number of arguments beyond the explicitly defined ones as needed, which will all be lumped together into ...
when the function is run. For instance, in the example above, any number of arguments can be typed after batchecho
in the console, such as batchecho 1 2
or batchecho a b c
.
If statements
If statements are used to decide what to do next based on whether a particular condition is fulfilled.
Note that SRB2's Lua allows the omission of the keyword then
, however it is considered good practice to not remove it.
- Single If statement block:
if condition then --contents to run if condition is true end
- Linked If statement blocks (examples of both "elseif" and "else" usage here):
if condition then --contents to run if condition is true elseif othercondition then --contents to run if othercondition is true else --contents to run if neither are true end
- Usage of "and" keyword, for requiring multiple conditions to be true:
if condition1 and condition2 then --contents to run if both condition1 AND condition2 are true end
- Usage of "or" keyword, for requiring one (or both) of two conditions to be true:
if conditiona or conditionb then --contents to run if conditiona OR conditionb are true (can be both) end
- Usage of "not" keyword, for inverted conditions:
if not condition then --contents to run if condition is NOT true end
- Examples of mixed usage of "and", "or" and "not" keywords:
if condition1 and not condition2 then --contents to run if condition1 is true AND condition2 is NOT true end
if condition1 or not condition2 then --contents to run if condition1 is true OR condition2 is NOT true end
if condition1 and (condition2a or condition2b) then --contents to run if condition1 is true AND either of condition2a OR condition2b is true end
if condition1 or (condition2a and condition2b) then --contents to run if condition1 is true OR both condition2a AND condition2b are true end
- The following examples exploit De Morgan's laws:
if not (condition1 or condition2) then --contents to run if both condition1 AND condition2 are NOT true --if either condition1 OR condition2 are true, the whole condition would be false end
if not (condition1 and condition2) then --contents to run if either of condition1 OR condition2 are NOT true --if both condition1 AND condition2 are true, the whole condition would be false end
Loops and iterators
For statements are used to repeat a set of statements an amount of times or until a condition is fulfilled.
Note that SRB2's Lua allows the omission of the keyword do
from all loops and iterators, however just like then
, it is considered good practice to not remove it.
Numeric For loops
- A simple numeric "for" loop. This repeatedly runs the content of the loop for as long as the value of the newly created variable
i
is between the numbersa
andb
. The value ofi
will start ata
and increase by 1 each time the loop is executed. After running the loop fori = b
, it will be finished.for i = a,b do --content to be iterated for all values of i from i = a to i = b end
- A "for" loop with a step number
c
specified. This increases the value ofi
byc
every time the loop is run:for i = a,b,c do --content to be iterated for all values of i from i = a to i = b, adding c to the value of i each time end
Keywords for For loops
- The keyword "break" can be used to break out of (stop) a loop completely, before it has finished running all iterations:
for i = 0,10 do --content to be iterated for all values of i from i = 0 to i = 10 --...except that the loop finishes at i = 5, as the loop is broken with the code below if i == 5 then break end end
- SRB2's Lua supports use of the keyword "continue", which skips the remainder of a loop iteration for one value and starts the next. If the next iteration is out of the range, this simply means the loop is finished:
for i = 0,10 do if i == 5 then continue end --this will print all values 0 to 10 in the console --the exception is 5, because the loop skips to the next value above before print(i) is reached print(i) end
- SRB2's Lua also supports use of "break N" to break out of N nested loops at once:
for i = 0,10 do --content to be iterated for all values of i from i = 0 to i = 10 for j = 0,10 do --content to be iterated for all values of j from j = 0 to j = 10, for every value of i --however, the code below breaks out of 2 loops, stopping both the j block and the i block at i = 5, j = 5 --in other words, this stops both loops at once if i == 5 and j == 5 then break 2 end end end
while/repeat
- A "while" loop. Repeatedly runs the content of the loop until the starting condition is
false
:while condition do --contents of loop end
- A "repeat" loop. Repeatedly runs the content of the loop until the ending condition is
true
:repeat --contents of loop until condition
Non-numeric For loops
The general syntax for generic, non-numeric iterators is this:
for var1,[extravars...] in func, state, start do
--contents of loop
end
var1
and[extravars...]
are locally created variables that will be available to use within the loop's contents:var1
is important in that it is used as a "key" within the iterator functionfunc
, starting at the value ofstart
untilfunc
no longer returns anything, which will stop the loop from running.[extravars...]
here represents the rest of the variables followingvar1
(e.g.,[extravars...]
could bevar2
,var3
,var4
, ...,varN
).
- Note that the variables do not have to be declared beforehand (e.g.,
local var1
), although if they are not, they will cease to exist once the loop has finished.
start
is the starting value ofvar1
for the iteration.state
is an extra value related to the functionfunc
. Depending on whatfunc
does, this could be anything, e.g., a table containing all the entries to run through, or a limit to the number of times the iterator is run.func
is the function to be run, with thestate
value and the current value ofvar1
(the "key") as its arguments (i.e.,func(state, var1)
is continuously run for as long as it has a return value). The return values should be the variables (var1
and[extravars...]
) for use within the loop's contents.
More commonly a function is used in place of func
, state
, index
, assuming it returns all three. Here is an example with a locally created function called iteratorfunc(a, b)
, with a function f(s, v)
, a state
value of c
and a start
value of d
:
local function iteratorfunc(a, b)
local f = function(s, v)
--function contents
end
local state = c
local start = d
return f, state, start
end
for var1 in iteratorfunc(1, 2) do
--contents of loop
end
Note that iteratorfunc
will be run once only, unlike its f
return value, which will be run for as long as it returns any values at all.
pairs and ipairs
These functions are commonly used for iterating through tables:
for k,v in pairs(myTable) do
--contents of loop
end
for k,v in ipairs(myTable) do
contents of loop
end
pairs()
and ipairs()
largely work in the same way; k
is the index of an entry in the table (or the "key"), and v
is the actual value of that entry in the table. The main difference is that ipairs()
loops are for ordered lists, and pairs()
loops are for unordered lists. ipairs()
loops are run from the first entry (k = 1
) onwards until the entry being checked is nil
, but pairs()
loops will run through all entries in the table whether they are nil
or not.
Example uses:
do
local emlist = {MT_EMERALD1, MT_EMERALD2, MT_EMERALD3, MT_EMERALD4, MT_EMERALD5, MT_EMERALD6, MT_EMERALD7}
for _,i in ipairs(emlist) do
mobjinfo[i].deathsound = sfx_ncitem
end
end
Here the ipairs()
loop above will change the DeathSound
of every emerald Object type's infotable to the sound of NiGHTS stars when collected (sfx_ncitem
). _
represents the key of each entry here, but it is clear we do not need its value in any case; meanwhile, i
for each entry here is an Object type number, which is far more useful of course – mobjinfo[i].deathsound
will then be mobjinfo[MT_EMERALD1].deathsound
, then mobjinfo[MT_EMERALD2].deathsound
, mobjinfo[MT_EMERALD3].deathsound
, and so on.
do
local props = {
spawnstate = S_INVISIBLE,
flags = MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT
}
for k,v in pairs(props) do
mobjinfo[MT_RING][k] = v
end
end
Here the pairs()
loop above changes specific Object type infotable properties of MT_RING
so that rings will become invisible and untouchable. The table props
is set up so spawnstate
and flags
are keys with a value each; k
can then act as the name of the property to change each time, and v
the new value of the property to change. mobjinfo[MT_RING][k] = v
will then be mobjinfo[MT_RING]["spawnstate"] = S_INVISIBLE
and mobjinfo[MT_RING]["flags"] = MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT
, respectively.
Special iterators
SRB2 provides extra iterator functions beyond the base Lua library, specifically designed to run through lists of userdata (most commonly for Objects or players). These are generally in the format:
for name in list.iterate do
--contents of loop
end
list
is the list being iterated through (only specific ones can be used, with .iterate
required to iterate through them), name
is the variable name for each of the items in the list to perform the contents of the loop on.
Uniquely for sector_t
userdata, sector.ffloors()
and sector.thinglist()
(for an example sector_t
userdata variable sector
) also can be iterated through – sector.ffloors()
will iterate though all FOFs (ffloor_t
) in the sector, and sector.thinglist()
through all Objects (mobj_t
) in the sector.
Metatables, metatable events and metamethods
Lua | [view] | |
Language features | Syntax • Metatables | |
SRB2 data | Actions • Constants • Functions • Global variables • Hooks • Userdata structures | |
SRB2Kart data | Kart Userdata structures • Kart Functions • Kart Hooks • Kart Global Variables and Constants | |
Tutorials | Freeslots and Object resources • Custom player ability |