Lua/Syntax

From SRB2 Wiki
< Lua
Jump to: navigation, search

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.

Arithmetic
Operation Symbol(s)
Addition +
Subtraction -
Multiplication *
Division /
Modulo
(Remainder)
%
Exponentiation
(To the power of)
^
Bitwise
Operation Symbol(s)
AND &
OR |
XOR ^^
NOT ~
!
Left shift <<
Right shift >>
Comparison
Operation Symbol(s)
Equals ==
Not equals ~=
!=
Less than <
Greater than >
Less than/Equals <=
Greater than/Equals >=
Strings
Operation Symbol(s)
String formatting (short) "string"
'string'
String formatting (long) [[string]]
[=[string]=]
[==[string]==]
etc
Concatenation (separated) "string1"..variable.."string2"
"string1"+variable+"string2"
Concatenation (within strings) "string1 \$variable\ string2"
Misc.
Operation Symbol(s)
Assignment =
"Len"
(Length)
#
End of line
(optional)
;
"Dots"
(Variable list of arguments)
...

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 by FRACUNIT + FRACUNIT/2 or 3*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 or false. Boolean variables are typically used for checking conditions, such as in If statements. In these situations, a nil value is also interpreted as false. SRB2's Lua additionally interprets 0 as false 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's NULL value, which is used to indicate unassigned pointers, although in C NULL is equal to 0, whereas nil is not. When no value is assigned to a newly created variable, it will have type nil and value nil.
  • 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 make func a function that is equivalent to functionname, i.e., calling func() will have the same effect as calling functionname(). The latter is done with the syntax local result = functionname(). In this case, functionname() will be run and the result will be stored in result.
  • 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.
  • With a value set: local name = value
  • Creating multiple new values: local name1,name2 = value1,value2
    • In this example, name1's value is value1 and name2's is value2.
    • This can be used to create as many variables as desired in the same line, although the line will become impractically long quickly.
  • 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 return value1, name[2] will return value2, etc. Contrary to C, the first index of Lua-created tables is 1 rather than 0.
  • Table of keys with values: local name = {a = value1, b = value2, c = value3, d = value4}
    • In this example, name.a (or name["a"]) will return value1, and similar for b, c and d.
  • Blank string: local name = ""
  • String: local name = "text"
  • Table/userdata references: local mymobj = mobj (assuming mobj was already defined beforehand!)
    • This creates a reference to mobj called mymobj. It will behave exactly the same as mobj. For example, if mobj is actually a mobj_t variable, as the name suggests, then mymobj.type will return the same value as mobj.type.
    • Note that mymobj is a reference to mobj, not a copy! If you modify mobj, mymobj will change accordingly, and vice versa!
  • Functions can also be defined this way (see the Creating functions section): local name = function(argument) contents of function end
  • Another trick for variable assignment is to assign either one of two values, depending on whether each value checked in order is valid, using the "or" keyword:
    local name = true or false
    local name = false or true
    • In both examples, "true" is picked out of the two possible values; in the first case "true" is not false/nil/0 so it is chosen, and in the second case "false" is obviously false so the second value (which is "true") is picked out of the two.
  • 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 "and" keyword meanwhile works as an inverse of "or"; if the first value checked in order is not false, nil or 0, the second value is chosen, otherwise the first value is chosen:
    local name = true and false
    local name = false and true
    • In both examples, "false" is picked out of the two possible values; in the first case "true" is clearly not false/nil/0 so the second value is chosen (which is "false"), and in the second case "false" is obviously false so is picked out of the two again!

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:

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, $ represents variable1 in the first case, and then variable2 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 represents variable1 and $2 represents variable2. 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

For the following examples, the example table is named table and has the contents {a = value1, b = value2, c = value3, d = value4}. Furthermore, several additional constants are defined: local VALUEA, VALUEB, VALUEC, VALUED = 1,2,3,4.

  • table[i] = value of entry at index i, e.g., table[1] would return value1.
    • table[VALUEA] would in this instance be the same as table[1] and also return value1.
    • table["a"] and table.a would both return the value of a as set above, which also is value1.
  • #table = number of entries in the table. In this example, it would return 4.
  • #table[i] = index of table[i]'s value, e.g., #table[1] would return 1.
    • The above example would be rather pointless, however. This is more useful in cases such as #table[VALUEA] or #table["a"] (both also equal to 1), where the index isn't clear from the start.

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 or COM_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 value value. This value can be of any type: a number, string, table/userdata pointers, true/false, or perhaps just nil if the function should not return any value.
  • return – This is the same as using return 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, and variable2 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.

  • Single If statement block:
    if condition
    
    	--contents to run if condition is true
    
    end
    
  • Linked If statement blocks (examples of both "elseif" and "else" usage here):
    if condition
    
    	--contents to run if condition is true
    
    elseif othercondition
    
    	--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
    
    	--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
    
    	--contents to run if conditiona OR conditionb are true (can be both)
    
    end
    
  • Usage of "not" keyword, for inverted conditions:
    if not condition
    
    	--contents to run if condition is NOT true
    
    end
    
  • Examples of mixed usage of "and", "or" and "not" keywords:
    if condition1 and not condition2
    
    	--contents to run if condition1 is true AND condition2 is NOT true
    
    end
    
    if condition1 or not condition2
    
    	--contents to run if condition1 is true OR condition2 is NOT true
    
    end
    
    if condition1 and (condition2a or condition2b)
    
    	--contents to run if condition1 is true AND either of condition2a OR condition2b is true
    
    end
    
    if condition1 or (condition2a and condition2b)
    
    	--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)
    
    	--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)
    
    	--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

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 numbers a and b. The value of i will start at a and increase by 1 each time the loop is executed. After running the loop for i = b, it will be finished.
    for i = a,b
    
    	--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 of i by c every time the loop is run:
    for i = a,b,c
    
    	--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
    
    	--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
    		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
    
    	if i == 5
    		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
    
    	--content to be iterated for all values of i from i = 0 to i = 10
    
    	for j = 0,10
    
    		--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
    			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
    
    	--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

	--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 function func, starting at the value of start until func no longer returns anything, which will stop the loop from running.
    • [extravars...] here represents the rest of the variables following var1 (e.g., [extravars...] could be var2, 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 of var1 for the iteration.
  • state is an extra value related to the function func. Depending on what func 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 the state value and the current value of var1 (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)

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

	--contents of loop

end
for k,v in ipairs(myTable)
 
	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)
		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 Wing Logos 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)
		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

	--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 thinkers.iterate, an argument is also provided in the iterator function itself, for determining which thinkers to run through:

  • thinkers.iterate("mobj") will exclusively output only mobj_t userdata; all others are skipped.
  • thinkers.iterate("all") will run through all thinkers. However, only mobj_t userdata is of any use as other thinker types will just output "light" userdata, which is generally useless for these purposes.

Uniquely also 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

Main article: Lua/Metatables
  Lua [view]
Language features SyntaxMetatables
SRB2 data ActionsConstantsFunctionsGlobal variablesHooksUserdata structures
Tutorials Freeslots and Object resourcesCustom player ability