Operations and symbols

Red text is used to denote operations that have been added to SRB2's syntax; ergo, are not a part of vanilla Lua implementations.

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 check

==

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 typically used within strings, where it is normally impossible to use the characters themselves directly in a Lua script.

If a number up to 3 digits long is placed after the \ character, Lua interprets this as an escape sequence encoded in ASCII – for example, \97 or \097 will result in the 'a' character. This can be used for obfuscation, as the game will interpret the code as whatever character is mapped to it.

Alternatively, the escape sequences \x and \u can be used to place characters hexadecimally, as raw hex literals or UTF-8-encoded Unicode respectively.

Types of variables

There are various types of variables that are available to Lua, all of which are detailed below (The function type() can be handy for checking the variable type, as it outputs a string displaying the type!):

number: self-explanatory, though floats are not available for SRB2's Lua meaning numbers like "1.5" do not work in Lua, only whole-value numbers

string: this is just plain text, which can be printed to the console or otherwise; usually written in the format "string", though 'string' and [[string]] are available as alternative string formats. Concatenation is used to link two or more strings or a mix of strings and other variable types together

boolean: can be true or false. These are typically used for truth checking such as in if statements (a "nil" value traditionally also acts as "false" in some situations, however SRB2's Lua additionally allows 0 to be used for "false" as well)

table: contains a number of values in a single variable, such as in local table = {a,b,c,d,e}

nil: not a number, not equal to 0, not anything whatsoever, just nil. When no value is given to a newly created variable, this is typically the default type set (C's NULL is similar in function to this, but in C NULL is only meant for blank pointers)

function: functions can also be treated as variables in some situations, in some cases even created (see the Creating functions section). Keep in mind the difference between setting a variable's value to be equivalent to an existing function and setting a variable's value to the result of a function: local func = functionname will make using func be exactly the same as using functionname itself, so func() would be the same as functionname(); local result = functionname() however will only give the result of functionname() after the contents of which have been run

userdata: misc. structures from SRB2's source code itself available to be accessed/modified, in general these are similar to tables; see Lua > Userdata types for the full list.

Variable assignment

Creating new "local" variables

These can be in general created in or outside of functions or other blocks for general use within them, as long as they are defined before their actual usage. However, they cannot be used outside of the block(s) they live inside, as they are local to said block, hence the name of the keyword used in the examples below.

To make a "global" variable for the entire script, one could create a local variable right at the top of a Lua script outside of everything else. However, this variable's value may not be kept consistent during netgames (if it is changed at all) without use of the "NetVars" Lua hook (Note: this hook is untested, and may not work properly).

Syntax for usage of creating new variables using "local":

With no value set: local name

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 works for all further numbers of variables being set in the same line, though this will become long quickly.

Blank table: local name = {}

Table of values: local name = {value1, value2, value3, value4}

in this example, name[1] is the same as value1, name[2] is value2, etc. Contrary to C, the first entry number of Lua-created tables is 1 rather than 0.

in this example mymobj would be exactly the same as mobj, allowing e.g., mymobj.type to work as long as mobj is actually a mobj_t variable.

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 structs/userdata

mobj.newvar = value

When the above is done, if newvar or the wanted variable name doesn't already exist for the structure, mobj in this example, this will automatically be created and given the new value when run. Otherwise this works the same way as using the "local" keyword.

Be warned though; if these are used in certain ways before they actually exist yet (mainly if newvar happens to be a structure such as mobj_t itself, and e.g newvar.target is used), Lua will print errors in the console and remove the hook the event happened in.

This issue can be prone to happen if a Lua script is loaded mid-level, and e.g custom variables for userdata/structures are initialised in a place that as a result hasn't happened such as "MobjSpawn" hooks for MT_PLAYER or "MapLoad".

mapheader_t can be supplied custom variables via SOC lumps (such as in the MAINCFG), where custom variable names should be prefixed with LUA. in a map's level header; they should also explicitly be referred to in all-lowercase in Lua scripts to be recognised properly.

Modifying existing variables

Syntax:

variable = newvalue

Assigning multiple new values to existing variables:

variable1,variable2 = value1, value2

Setting a new value by doing 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 also:

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 variable numbers during assignment, most useful for crossing over the variables with each other:

variable1, variable2 = $2, $1

Here "$1" reprsents variable1 and "$2" represents variable2; in this instance the two variables' values are effectively swapped over!

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 examples here table is the name of the example table, with contents {a = value1, b = value2, c = value3, d = value4}

Functions

Creating functions

Creating a new "local" function:

localfunctionFunctionName(argument)//contentsoffunctionend

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 in SOCs as actions):

functionGlobalFunctionName(argument)//contentsoffunctionend

Creating a new function with multiple arguments:

localfunctionFunctionName(argument1,argument2)//contentsoffunctionend

With no parameters:

localfunctionFunctionName()//contentsoffunctionend

Creating a "prototype" variable to be defined as a function later (for situations involving using a function before actually defining it properly):

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 very useful in the context of functions; they are frequently needed inside them to end a function before the rest of it can run, AND perhaps even "return" a final value for the function to essentially output for use.

return value – the function returns a single value for the function's output. This can be any type of value; a number, string, table/userdata pointers, true/false, or perhaps just nil.

return – this is the same as using return nil; the function returns essentially nothing.

return value1, value2 – returns multiple values for the functions's two outputs. This can be extended to further numbers of output variables, which can all be assigned to separate variables if needed.

Calling functions

Calling a function standalone, usually best for functions that don't return any value at all (or don't return any values you currently need).

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 utilise them as such (works with both creating new variables 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, say, the first output of the function is needed, the function can be just 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 practise 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 miss out variables rather than the first, such as with a function that returns 3 values but from which you need only the first and third:

variable1, _, variable2 = FunctionName(argument1, argument2, [etc])

Functions tied to tables work much the same way as everything above:

table.FunctionName(argument1, argument2, [etc])

table:FunctionName(argument1, argument2, [etc])

variable = table.FunctionName(argument1, argument2, [etc])

and so on and so on...

Variable argument count

The ... keyword can be used as an argument definition in functions to retrieve an undetermined number of args after the ones defined explicitly:

localfunctionFunctionName(arg1,arg2,...)

Inside the function, ... refers to all arguments beyond explicitly-defined arguments in functions. {...} can be used to get these arguments as a table.

Sample function, using print() messages to display the arguments' values in the console:

localfunctionbatchecho(player,...)localarg1,arg2,arg3=...print("arg1 is "+arg1)print("arg2 is "+arg2)print("arg3 is "+arg3)print("Now printing all args...")for_,iinipairs({...})doprint(i)endendCOM_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 itself 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

Often we'll need to check if something is true or not before doing something, so these are important to know about:

Single if statement block:

ifcondition//contentstorunifconditionistrueend

Linked if statement blocks (examples of both "elseif" and "else" usage here):

For loops and other iterators

Numeric For loops

Simple numeric "for" loop, this runs the content inside a number of times for as long as a newly created variable "i" is between numbers a and b. The value of "i" will start at a and increase by 1 each time until after running the loop for i = b. The loop will then be finished.

fori=a,b//contenttobeiteratedfromallvaluesofifromi=atoi=bend

A for loop with a step number "c" specified. This can be used to skip every other number if needed:

Non-numeric For loops

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 (function), starting at the value of start until function no longer returns anything, which will stop the loop from running.

[extravars...] here represents the rest of the variables following var1 (i.e., [extravars...] could be var2, var3, var4 ... varN).

Note that "local var1" and so on are not required beforehand, though the variables will cease to exist once the loop has finished unless this is done.

start is the starting value of var1 for the iteration.

state is an extra value related to the function. Depending on what function 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.

function is the function to be run, with the state value and the current value of var1 (the "key") as its arguments (i.e function(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 function, 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:

pairs() and ipairs() largely work in the same way; k is the entry number 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.

Here the ipairs() loop above will change the DeathSound of every emerald Object type's SOC 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.

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:

fornameinlist.iterate//contentsofloopend

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.

The metatable can also be applied on the same line the table is created in, as setmetatable then returns the table given (here {} is used as a dummy table):

localmyTable=setmetatable({},{__event=value})

getmetatable can be used to retrieve the metatable for a table or other variable type (if any is set):

localmetatable=getmetatable(myTable)

getmetatable can check the metatable of any type of value, rather than just tables, but will return nil if no metatable is set whatever the case.

After retrieving the metatable of the variable this way, modifying the metatable will directly affect the metatable for the variable itself!

Any metatables with the metatable event __metatable set will not be modifiable or retrievable – getmetatable will return the value of __metatable instead of the metatable, while setmetatable will print an error in the console!

List of events

For any examples below, "a" represents the variable the metatable events are applied to. "b" may be a second variable for use in binary operations such as addition or subtraction.

Name

Event

Type of value

Further details

Table indexes

__index

Accessing an index/value from a key/variable (a.key or a[key])

any function(a, key)or table

__index is highly useful for allowing a table to fallback on another table if a variable/key doesn't exist in the first, i.e., inheritance. This is used as a backbone for "object-oriented programming" in Lua (see this page for more info).

As a reminder, a.key is the same as a["key"]; i.e., key from a.key is a variable of type string in this situation.

If a fallback table is used, this table's own keys will be checked – if it doesn't exist there, the table's own __index will be triggered if it has one (this can lead to a long chain of tables falling back on each other).

__newindex

Assigning an index/value to a new key/variable(a[key] = value or a.key = value, old value is nil)

void function(a, key, value)or table

If a fallback table is used, this table's own keys will be checked – if it doesn't exist there, the table's own __newindex can be triggered if it has one; if it does already the table's own __usedindex will be triggered instead if it has one (this can lead to a long chain of tables falling back on each other).

The index/value is assigned to a fallback table's keys only if the fallback table has neither __newindex nor usedindex set.

__usedindex

Assigning an index/value to an existing key/variable(a[key] = value or a.key = value, old value is not nil)

void function(a, key, value)or table

This metamethod is unusable for non-tables

If a fallback table is used, this table's own keys will be checked – if it doesn't exist there, the table's own __newindex can be triggered if it has one; if it does already the table's own __usedindex will be triggered instead if it has one (this can lead to a long chain of tables falling back on each other).

The index/value is assigned to a fallback table's keys only if the fallback table has neither __newindex nor usedindex set.

Arithmetic

__add

Addition (a + b)

any function(a, b)

This operation can be done even when the other value doesn't share the same metamethod or doesn't have one.

if a doesn't have a metamethod, b's metamethod is used instead.

a is always the first argument in the function, and b the second, even if b's metamethod is the one called.

__sub

Subtraction (a - b)

any function(a, b)

This operation can be done even when the other value doesn't share the same metamethod or doesn't have one.

if a doesn't have a metamethod, b's metamethod is used instead.

a is always the first argument in the function, and b the second, even if b's metamethod is the one called.

__mul

Multiplication (a * b)

any function(a, b)

This operation can be done even when the other value doesn't share the same metamethod or doesn't have one.

if a doesn't have a metamethod, b's metamethod is used instead.

a is always the first argument in the function, and b the second, even if b's metamethod is the one called.

__div

Division (a / b)

any function(a, b)

This operation can be done even when the other value doesn't share the same metamethod or doesn't have one.

if a doesn't have a metamethod, b's metamethod is used instead.

a is always the first argument in the function, and b the second, even if b's metamethod is the one called.

__mod

Modulus (a % b)

any function(a, b)

This operation can be done even when the other value doesn't share the same metamethod or doesn't have one.

if a doesn't have a metamethod, b's metamethod is used instead.

a is always the first argument in the function, and b the second, even if b's metamethod is the one called.

__pow

Exponentation (a ^ b)

any function(a, b)

This operation can be done even when the other value doesn't share the same metamethod or doesn't have one.

if a doesn't have a metamethod, b's metamethod is used instead.

a is always the first argument in the function, and b the second, even if b's metamethod is the one called.

__unm

Unary negation ( -a )

any function(a)

__len

Length ( #a )

any function(a)

Comparison

__eq

Equals check (a == b)

any function(a, b)

Both tables MUST share the same metamethod for __eq for the function to be run.

__lt

Less-than (a < b)or Greater-than (b > a)

any function(a, b)

Greater-than operations are done by flipping the order of a and b so it is treated as a Less-than operation.

Both tables MUST share the same metamethod for __lt for the function to be run.

__lt may also be run for less than operations if a __le metamethod does not exist for both a and b (or they do not share the same __le metamethod).

In the above situation, a <= b is checked as b > a, and b >= a as a < b.

__le

Less-than-or-equals (a <= b)or Greater-than-or-equals (b >= a)

any function(a, b)

Greater-than-or-equals operations are done by flipping the order of a and b so it is treated as a Less-than-or-equals operation.

Both tables MUST share the same metamethod for __le for the function to be run.

Bitwise operations

__and

Bitwise AND (a & b)

any function(a, b)

This operation can be done even when the other value doesn't share the same metamethod or doesn't have one.

if a doesn't have a metamethod, b's metamethod is used instead.

a is always the first argument in the function, and b the second, even if b's metamethod is the one called.

__or

Bitwise OR (a | b)

any function(a, b)

This operation can be done even when the other value doesn't share the same metamethod or doesn't have one.

if a doesn't have a metamethod, b's metamethod is used instead.

a is always the first argument in the function, and b the second, even if b's metamethod is the one called.

__xor

Bitwise XOR (a ^^ b)

any function(a, b)

This operation can be done even when the other value doesn't share the same metamethod or doesn't have one.

if a doesn't have a metamethod, b's metamethod is used instead.

a is always the first argument in the function, and b the second, even if b's metamethod is the one called.

__shl

Left shift (a << b)

any function(a, b)

This operation can be done even when the other value doesn't share the same metamethod or doesn't have one.

if a doesn't have a metamethod, b's metamethod is used instead.

a is always the first argument in the function, and b the second, even if b's metamethod is the one called.

__shr

Right shift (a >> b)

any function(a, b)

This operation can be done even when the other value doesn't share the same metamethod or doesn't have one.

if a doesn't have a metamethod, b's metamethod is used instead.

a is always the first argument in the function, and b the second, even if b's metamethod is the one called.

__not

Bitwise NOT ( ~a )

any function(a)

Misc

__concat

Concatenation (a..b)

any function(a, b)

This operation can be done even when the other value doesn't share the same metamethod or doesn't have one.

if a doesn't have a metamethod, b's metamethod is used instead.

a is always the first argument in the function, and b the second, even if b's metamethod is the one called.