Note: if you notice any errors (grammar, code mistakes etc) or you have any suggestions to improve the tutorial, let me know on TombRaiderForums: HERE and I will amend the tutorial when I have time. 🙂
Hi, and welcome to the basics of Lua! This page will launch straight to the point teaching you basic programming theory to get you started making Lua scripts for TombEngine.
If you feel familiar enough with the material and would like to learn more complex and in-depth stuff about Lua, check out the official documentation: https://www.lua.org/pil/contents.html
Variables are an essential feature of every scripting language. They serve as containers that store specified information, be it text, numbers, or userdata, and allow you to manipulate their contents in many scenarios.
In Lua, you can define local and global variables:
Local variables exist only within the code block in which they are defined.
Global variables exist script-wide and can be accessed from anywhere.
(Quick aside: the following is an example of a code block. Instructions belonging to the block are typically inset with a tab for ease of readability. A key detail to remember: in Lua, blocks are always closed with the keywordendat the bottom!)
i = 0
while (i < 10) do
local name = "TombEngine"
print(name)
i = i + 1
end
In the above example, the local variable name, defining the string of text "TombEngine", exists only within the block of this while loop (explained in section 5). This means it cannot be used anywhere except inside of it. Every local variable must have thelocalkeyword before the declaration of its name.
Meanwhile, the global variable i, defining the integer 0, exists across the script. This means it can be used anywhere within it, inside and outside any block. A variable without a keyword before the declaration of its name is global by default.
Data Types
In Lua, there are seven types of data you may use in your scripts:
1. Strings
Strings are collections of characters storing text, and are particularly useful if you want to store words, phrases, or sentences for dialogue in a variable.
To declare and define a string, initialize the variable with whatever characters you wish, encased within double-quotes (“”) or single-quotes (”):
local laraCuriousString = "Huh, what is this?"
local lernerAngryString = 'Lara, what are you doing?!'
2. Integers
Integers are whole numbers which can be positive or negative. They are useful for storing things such as health values and timers, and are ideal for dealing with dreaded magic numbers (more on them later).
To declare and define an integer, simply initialize the variable with a whole number:
local entityHealth = 100
local negativeNumber = -6
3. Floating-Point Number (Decimals)
Floating-point numbers are decimal point numbers, which, just like integers, can be positive or negative. They are useful for storing things that require more precision than what an integer provides.
To declare a and define floating-point number, initialise the numeric variable with a decimal place:
local orientationX = 23.9
local negativeRotationY = -55.5
4. Booleans
Booleans can have only two possible values: true or false. Boolean variables are useful for checking event conditions. Example:
isLaraKilled = false
if isLaraKilled == true
print("What have you done?!!")
end
5. Tables
Tables are best understood as a collection, which may be represented by an array (a list of values) or a hash table (a look-up dictionary). They are quite complex and may not be needed for simple setups, but are still worth learning about. Tables will be discussed further in section 6 (Tables (Arrays)).
6. Userdata
Userdata is a very essential data type and will be very common during TombEngine Lua scripting. Userdata are anything that is not built-in lua type hence in TombEngine, you will have functions and methods which are: GetMoveableByName or Color.new (explanation on these are in the Docs)
Userdata can be used like this: local raptor = TEN.Objects.GetMoveableByName("raptor1") or Color(255, 255, 255)
7. Functions
Functions are not strictly data types, but it may help to conceptualize them as such! They are required to set up volume triggers in Tomb Editor.
Functions primarily serve to provide a clean, intuitive way to create utilitarian blocks of code which may be reused as many times as you like, avoiding the need for repetition.
Common uses for functions include:
Modifying objects in the game or in your script; for example, incrementing an ammo counter.
Making a query; for example, to check whether Lara’s health is below half capacity.
Returning a value; for example, to perform a very specific mathematical calculation.
easy way to store a boolean in a variable from a condition (optional)
you can easily store the result of a condition in a variable, which stores a boolean (true/false) of the result:local unlimited = Lara:GetAmmoCount() == -1
This statement means: “Is the ammo count unlimited?” (note the -1) if yes it will store is as true if not then false.
general formula is:local variableName = condition
where:
condition – something to be evaluated which then returns and stores true/false
2) Mathematical Operations
In programming, you can do arithmetic operators to Integers and decimal-point numbers in order to do some calculations. In Lua, there are 7 types of Arithmetic. Before explaining these Operators, let’s define some variables:
local a = 10
local b = 5
now we can use these 2 variables to perform arithmetic:
1. Addition
You can add variables as long as they have numbers:local c = a + b print(c) Output: 15
Explanation: since we declared and initialised 2 variables already (a = 10 and b = 5) we declared another variable (c) which will add these 2 variables together.
2. Subtraction
You can subtract variables as long as they have numbers:local c = a – b print(c) Output: 5
Explanation: This will subtract 2 variables to give you c = 5
3. Multiplication
You can multiply variables as long as they have numbers:
note: in programming, in order to do multiplication, you have to put an asterisk * not like in normal maths you put “x”local c = a * b print(c) Output: 50
Explanation This will multiply 2 variables to give you c = 50
4. Division
You can divide variables as long as they have numbers:
Note: in programming, you need to put / to divide.local c = a / b print(c) Output: 2
Explanation: Here we have divided 2 variables to give you c = 2
5. Modulus
A modulus (or a modulo) is a special operator which acts like division but it will return the remainder.
A modulus is performed with a % signlocal c = a % b print(c) Output: 0
Explanation: since 10 divided by 5 is 2 then the remainder is 0 because it doesn’t give you a “leftover” number and 10 / 5 can be exactly divided which won’t give you a fractional/decimal number
if you were to put:local a = 10 local b = 6 local c = a % b print(c) Output: 4
then the remainder is 4 as 10 / 6 cannot be exactly divided and it’s a decimal number.
6. Exponent (power operator)
An exponent is an operator which will multiply the number by itself
An exponent is performed by: a^b where a = number and b = number of times to multiply itself bylocal c = a^2 print(c) Output: 100
Explanation: since a = 10 then 10 to the power of 2 is 100 (because 10 x 10 = 100)
7. Unary (negation)
Unary is a special operator which will convert the positive number to a negative
Unary is performed by putting - to a variablelocal c = -a print(c) Output: -10
explanation: we negated the a variable so now it became -10 (think of it as -1 x 10 or -(10) = -10)
All of these operators come in handy if you want to perform some sort of calculations in-game for example you can perform a tricky calculation to move a static or rotate it or even to check distances precisely and etc.
3) Decision-Making
In programming, Decision-making implies making choices and evaluating if one condition is true or false, if either one of them is true then it will take that route however if it is false then it will take an alternative route.
in Lua there are 3 decision-making statements:
if statements
an if statement will check if the condition is true and if it is then it executes the code
local a = 2 local b = 1 local c = a + b if c == 3 then print(“c is 3”) end OR if c == 3 then print(“c is 3”) end
In the above example, the if statement determine if c = 3 if it is then it prints that c contains 3 value. as you can see, you can make an inline if or a block if statement.
elseif statement
elseif statement will trigger if the if statement appears to be false, you can add as many elseifs as you like
local a = 2 local b = 2 local c = a + b if c == 3 then print(“c is 3”) elseif c == 4 then print(“c is 4”) end
else statement
else statement is used if neither if and elseif statement is true
local a = 1 local b = 1 local c = a + b if c == 3 then print(“c is 3”) elseif c == 4 then print(“c is 4”) else print(“c is neither a 3 or a 4”) end
Ternary Operator
Ternary Operator is an alternative if statement which can be embed to the variable
It is useful for Boolean checks
local result = (Lara:GetHP() <= 0) and true or false if LaraDead == true then DeathMSG = “GAME OVER” LaraDeathMSG = DisplayString(DisplayString(DeathMSG, 500, 500, Color(255,0,0)) ShowString(LaraDeathMSG, 10) end
The formula for Ternary operator is local variableName = condition() and trueResult or falseResult
4) Relational and Logical Operators
Relational and Logical operators are the types of operators which checks for comparison for both variables given the operator. They are commonly used within loops and if statements so let’s go have a look at them:
Relational operators
In Lua (and in any other programming language) you have 6 relational operators:
1. Equal ==
The equal sign will check if 2 variables are equal to each other
local a = 10
local b = 10
if a == b then print("a is equal to b") end
2. does not equal to ~=
The does not equal to if the 2 variables don’t equal each other
local a = 23
local b = 9
if a ~= b then
print("a does not equal to b")
else
print("a is equal to b")
end
3. greater than >
greater than > will check if the left-hand side is equal to right-hand side.
local a = 23
local b = 9
if a > b then print("a is greater than b") end
4. less than <
less than < is the same as greater than > but checks if left-hand side is less than the right-hand side.
local a = 23
local b = 9
if b < a then print("b is less than a") end
5. greater than or equal to >=
greater than or equal to >= checks if the left-hand side is greater or equal to left-hand side.
local a = 23
local b = 22
if a >= b then print("a is either greater than or equal to b") end
6. less than or equal to <=
less than or equal to <= checks if the left-hand side is less or equal to left-hand side.
local a = 23
local b = 22
if b <= a then print("b is either less than or equal to a") end
logical operators
Again, in Lua (and in any other programming language) you have 3 types of logical operators:
1. AND operator
AND operator will check if both or more expressions are true
local score = 60
local health = 40
if score >= 60 and health >= 50 then
print("score and health both are above minimum, well done!")
else
print("Score or health is not above the minimum, game over!")
2. OR operator
OR operator will evaluate if either one of the expressions (if there are 2 or more expressions) is true
local healthLeft = 25
local game_over = false
if healthLeft <= 25 or game_over == true then
print("Game over!")
else
print("Game is not over yet!")
end
3. NOT operator
NOT operator will evaluate if the expression is not true
local isLaraDead = true
if not isLaraDead then
print("Lara is still alive")
else
print("She dead!")
end
5) Loops
Loops are another important programming concept used by all programming languages. Loops execute statements over and over until a certain condition has been reached or it has been set to true. Loops are useful to repeat the same stuff over again for example loop the script until Lara has picked up the artefact or killed an enemy.
In Lua there are 4 types of loops which may be used:
1. For loops
For loops are types of loops which will loop the statements until something has been reached or something is completed.
The syntax for For loops is:for init,max/min value, n do statement(s) end
Where:
init = Variable initialisation
max/min value = minimum or maximum value to loop
n = increments the loop by n number
Example:for i = 0, 5, 1 do if i == 5 then print(“This is the final step!”) else print(“We are at step ” .. i) end end output: – We are at step 1 – We are at step 2 – We are at step 3 – We are at step 4 – This is the final step!
2. While loop
A while loop is another type of loop which will loop endlessly while a condition is true. The while loop checks for the condition at the top of the block
the syntax for a while loop is:while(condition) do statement(s) end
Where the condition is the condition you specify for the while loop to test if it is true or not.
With a while loop, you can do an infinite loop but be careful as doing an infinite loop not correctly may perform some unintended behaviour (for example infinitely spawn enemies) and the engine may not handle this and the game will crash.while(true) do print(“Aaaaaaaa”) end
3. Repeat … until loop
Repeat … until the loop is another loop which is similar to a while loop however, the repeat loop will check if the condition is true at the bottom of the loop
this means that in a while loop if the condition is true initially, then the while loop will not execute. However, if the repeat until the loop is true initially, it will still run the statements but then it will stop.
the syntax for a repeat until the loop is:repeat statement(s) until( condition )
there is a keyword that may also be useful which is a break statement which will terminate the loop.
break statement can be used to break the loop if the loop has reached halfway to executing, for example, if you set a variable to i = 0 and put an if statement to check if the number has reached 5, then it will execute the break statement to terminate the loop.
4. Pairs and Ipairs
Pairs() and ipairs() are functions which are mostly used for loops, they can be used for stuff that don’t have an ending point and will print out key and values for both of them. This is similar to enumerate() in python
ipairs() is used to iterate through an array:local namesArray = {“Lara”, “VonCroy”, “Seth”} for index, value in ipairs(namesArray) do print(“name ” .. value .. ” has value ” .. index) end output: index Lara has value 1 index VonCroy has value 2 index Seth has value 3
pairs() are similar to ipairs() but they are used with the dictionary element’s key and value:local inventory = { [“small medipack”] = 4, [“Big Medipack”] = 2, [“HK ammo”] = 100, } print(“You have:”) for itemName, itemValue in pairs(inventory) do print(itemValue, itemName) end Output: You have: 2 Big Medipack 4 small medipack 100 HK ammo
6) Tables (Arrays)
In Programming, arrays are a list of values which contain multiple values with the same data type. In Lua, they are called tables. An index in an array is the position where the value is in for example:local names = {“Lara”, “Zip”, “Winston”} print(names[2]) Output: Zip
in the above example, we have initialised a table (array) with 3 values, they must be enclosed with curly brace {}. To get a value from the table, you have to put the number in the square brackets with the table name (so in the above example, it is names[2])
In Lua, the index will always start at 1. In most of the other popular programming languages, the index would start at 0.
to retrieve the length of the table, you can put a # operator in front of the table name print(#names) -- 3
Tables could be a very useful feature to store multiple enemies in the same table in order to manipulate them at the same time such as: destroying them, modifying their health for each of them etc simultaneously.
There are very essential methods which you can use for table manipulation which can be found here: https://www.tutorialspoint.com/lua/lua_tables.htm however these 2 will be very essential for table manipulation:
table.insert (table, pos, value)
this will insert the value given the table name, position value (for example inserting the value at position 7) and the value
table.insert(names, 4, "Natla") – this will insert the name “Natla” at position 4 in names table
Or for inserting, you can use a # operatormyTab[#names + 1] = “Larson” print(#names, names[4]) output: Larson
Be careful with this operator though as leaving a gap will give you incorrect length of the array:local myTab = {“Lara”, “Zip”, “Winston”} myTab[5] = “Pierre” print(#myTab) output: 3
table.remove(table, pos)
This simply removes the value given its position
table.remove(names, 2) – this removes the value “Zip” from namestable
7) Lua’s Mathematics Library
In Lua, you have a mathematics library which lets you do even more complex mathematics, similar to Scientific or Engineering mathematics. There is a full list of mathematical methods are explained in here: https://www.tutorialspoint.com/lua/lua_math_library.htm
note:
1 radian = 57,3 degrees approx, 2π radians = 360 degrees, π radians = 180 degrees and so on…
general formula: to convert radians to degrees: Radians x (180/π) and from degrees to radians: Degrees × (π/180)
radians should be rounded to 3 significant figures
0.364621 radians = 0.365 radians
Here’s a list of some useful math methods you might use:
math.pi value of pi (π) to be used in math functions
math.rad(x) value of x converted to radians given in degrees
math.rad(90) – 1.57 rad (π/2 radians)
math.sin(x) returns the sine of x (x in radians)
math.sin(math.pi) output: 0 (this is because the sine graph cuts the x-axis at π hence 0
math.tan(math.pi/2) output: undefined (the tangent graph is undefined at math.pi/2 (90 degrees)
math.tan(math.pi/4) output: 1
math.tan(70) output: 1.22 (3 significant figures)
math.random (m, n) randomises the number given the m and n where m = minimum value and n – maximum value
math.random(1,7) output: returns a random number which is between 1 and 7
Again, there are many functions in the maths library such as: math.log(x) – natural logarithm of x math.asin(x) – arc sine of x or math.atan (x) the arc tangent of x however these are very difficult to explain and I will leave it to you to do some research about them.
8) Creating Functions for Volume Triggers
In the earlier section (Variables and data types) we learned that functions are blocks of code that can be used as often as you like and in many volume triggers.
This section will show you have to create functions for volume triggers.
Creating Functions
In order to make functions, we need to make use of a method called LevelFuncs which will allow you to put the function in the volume trigger so make sure to have it or it will not appear! We can then call our function then make it equal to a function() and then you can make statements to execute when the function is called:
LevelFuncs.EnemySpawner = function() local baddy = TEN.Objects.GetMoveableByName(“SwordBaddy”) baddy:Enable() end
Congrats, you now have an enemyspawner function and now you can insert it to your volume trigger
Volume Triggers and How to Use Them
Volume triggers are a new special trigger in TEN which allow you to put Lua functions inside, you have 2 kinds of triggers: Sphere and Cube Both can be resized and moved freely and they are not tied to the floor, this is great!
To insert a volume trigger, you need to have 2 buttons on your top bar shown:
if you do not then you can right-click on your toolbar -> customize -> move the sphere and cube triggers to the right-hand side of the window and press apply, as shown in the pictures:
Great you now can place your volume triggers on the map which will look like this:
If you double-click on them, you will have a little window shown:
The window shows how to trigger stuff from lua script. On the left hand side, you have event sets which you can rename, delete and copy, they are used to apply to different volume triggers if you want the same stuff to happen in different places (like alarm or killing Lara)
the volume triggers can be activated by anything, not just Lara, so you can have these triggers triggered by an enemy, a camera, moveable or by even a static! On the right-hand side is where you place your function accordingly. You have 3 states:
OnEnter – this will trigger the volume once Lara or another object enters the volume trigger
OnInside – Triggers the volume when Lara or other objects are inside the volume trigger and it will trigger per game frame
OnLeave – Triggers the volume when Lara or another object leaves the volume trigger
You also have call counts and arguments:
Call counts are how many times should the function be triggered, think of it as advanced “One-shot” but you can choose how many times you can activate
Arguments are used to pass anything to the function as a parameter. Consider this example:LevelFuncs.PrintTextVolume = function( triggerer, arg ) local text = “Function Text Volume Activated likes ” .. arg local string = DisplayString(text, 100, 100, Color(250,250,250)) ShowString(string, 5) end (credits to Adngel)
In this example, if you put in the argument “Lara” the code will print: “Function Text Volume Activated likes Lara”
note:
Triggerer: Moveable that activates the volume
arg: Arguments passed in from the volume window
They don’t have to be named exactly (example: Triggerer can be named as MoveableTrigger) but their order must match!!
So as you can see with volume triggers, you can do a lot of new and exciting stuff! I will be looking forward to what you create with these 🙂
Special TombEngine Fields
While scripting you will have 5 fields available at your disposal:
OnStart() – Calls the function when the game starts
OnLoad() – Calls the function when the game is loaded via save
OnSave() – Calls the function when the game is saved
OnExit() – Calls the function when the player leaves the game (includes: finishing a level, exiting to title menu and loading a save in different level)
OnControlPhase(delta) – a special field which will call the function per game frame (you can also pass in a delta time as argument, more on that in the next section)
What Is Delta Time and How Do I Use It?
Imagine you have a 10×10 room and there’s a static on the other end of the room and Lara on the another. You decide to create a function which will move the static across the room with a velocity of 2 clicks to the right in OnControlPhase() per frame. Everything goes right and it’s all good however what if you have a potato pc and your game decides to lag? Well here’s the problem the static will teleport instead of moving at a constant velocity whereas on another pc – powerful with constant 30 fps, will act normally. This is the problem and you have to solve it. The way to solve it is by using delta time (dt) for your OnControlPhase()
Delta time is the time difference between the last frame and the current time (i.e time difference to render the frame)
9) Creating Functions Inside the Script
You can also create functions (also called as subprograms) while scripting, remember a function is a block of code which can be executed as many times as you like.
the syntax for creating functions is as follows:optional function scope function function name( argument1, argument2, argument3……..,argumentn) function_body return parameter_result (separated by comma) end
where:
Optional function scope – scope of the function, local or global.
function name – the name of your function
arguments – Arguments to be passed in to process the value (This is optional – you can leave it blank, it depends on what your function will do.)
function body – statements which will be done once the function is called and if any arguments have been passed in
return – returns the value from the process inside the function separated by commas meaning you can return multiple values (optional)
function InitSentence()
local laraChatting = "Lara: Get me that code, OR ELSE!"
local text = DisplayString(laraChatting, 500, 500, Color(255,0,0))
ShowString(text, 6)
end
LevelFuncs.OnStart = function()
InitSentence()
end
Explanation:
The LevelFuncs.Onstart = function() will be executed once you start the game, it will call the function InitSentence()
Inside the InitSentence() function, you have:
a variable laraChatting to make a string variable where Lara says something
another variable text which uses DisplayString() to customise the string, the text is 500 pixels both along the x and y-axis and colour of red.
ShowString() is used to actually display the string on screen for 6 seconds.
This results in:
10) Script Readability
Programming can be a fun task sometimes, you write code and bam it works as expected. However, there’s one thing you need to consider especially if you want to share the code with others, and that is readability. You do not want a really messy code in your script, that is neither good for you nor for others when you share because:
Will make debugging difficult
Here are some good techniques to improve readability in your code:
Magic Numbers
Consider this script command:
Color(245,134,100)
What can you notice about this command? Sure you know that you’re defining a new colour which will have those RGB values, ok perfect! Now let’s take a look at another example.
Now this is an extreme problem because you are now dealing with random numbers which you do not know what they mean, for example: what does – {0,0,0,0,0,0} mean? or a bunch of 0s or a 10? Now you’re in panic mode.
These numbers are called Magic numbers. Magic numbers is when you have numbers in your code but they do not give you a clear explanation of what they do, meaning they will hold you back because you need to fully understand what they mean and traverse either through code or the docs to find out if necessary and that will be time-consuming, and it’s a bad practice in general.
One way to fix this is to define variables which will help deal with those pesky magic numbers:local raptorPosition = Vec3(25674, 34, 12456) local raptorRotation = Rotation(0,0,0) local raptorRoomLocation = 100 local raptorAnimNumber = 0 local raptorFrameNumber = 0 local raptorHealth = 10 local raptorOcb = 0 local raptorAINumber = {0,0,0,0,0,0} local newRaptor = Moveable(ObjID.RAPTOR, “raptor”, raptorPosition, raptorRotation, raptorRoomLocation, raptorAnimNumber, raptorFrameNumber, raptorHealth, raptorOcb, raptorAINumber)
Now as you can see this is extremely nice and better, you now eliminated magic numbers and you know what each field do and make readability and debugging much easier for you and for other. Congrats 🙂
Comments
You can also comment your code or comment the code out completely to debug a certain issue, it will be ignored by the compiler (or TEN itself).
To make a single-line comment, you should use a double dash -- symbol
local number = 12 -- This will comment on the variable line
Or if you can multiple lines commented out, you should use --[[ --]] symbol
–[[ local hi = “yo” local company = “Copro Dengise” local name = “Del” local holyObject = “Del’s calculator” –]]
As you can see, comments are useful for commenting on what is going on in the code and debugging easily.
Naming Conventions
Imagine you are reading a person’s example script. let’s say the example is about moving the static. LevelFuncs.!@MOvESTatIC = function() local KiTCHenUTENSils = GetStaticByName(Utensils) local tOTHEriGHTSEctor = 1024 KiTCHenUTENSils:SetPosition(Vec3(0,tOTHErIGHTSEctor,0))
What can you notice?
Unfortunately, the variable naming and function naming is not great – you have lowercase and uppercase characters everywhere and also the special characters at the beginning of the function name. This is not great as this decreases readability a lot, especially in a larger script and most importantly: retains consistency.
To combat this, in programming, we have a term called Naming Conventions. Naming Conventions are naming rules for variables, functions, and classes that you, your friend, your corporation, or anyone has set.
There are several naming conventions but 3 most popular are:
PascalCase
PascalCase is when the variable or function name has uppercase character in every connected word.
from above example: LevelFuncs.MoveStatic = function()
I use PascalCase for function naming.
CamelCase
CamelCase is similar to PascalCase but this time, the starting word character has a lower-case character, whilst other words have upper-case character.
From above example: local kitchenUtensils = GetStaticByName(KitchenUtensils)
I use CamelCase for variable naming.
snake_case
snake_case is also a popular name convention. Snake_case refers to names instead of being connected together, they are separated with an underscore (_) between them.
LevelFuncs.move_static = function()
I don’t use it, but you can use it for functions and variables.
Naming conventions aren’t tied to each thing for example you don’t have to strictly use PascalCase for functions just because someone has that as well.
10) Small example scripts
Section not finished
Now that you’ve learned a few stuff from the previous sections, we can put a few examples into practice.
Adding enemies to a table and setting an event
This example shows how we can add enemies to a table using a loop, this is useful if you want to enable multiple enemies from the script and modify their values simultaneously.
function TriggerBeetles()
LevelVars.Beetles = {}
for i = 1, 4 do
LevelVars.Beetles[i] = GetMoveableByName("big_beetle_"..i)
LevelVars.Beetles[i]:SetHP(10)
LevelVars.Beetles[i]:Enable()
end
end
LevelFuncs.SpawnBeetles = function()
TriggerBeetles()
end
Explanation:
LevelVars.Beetles – A table to put enemies there to later do something with them.
for i = 1, 4 do
LevelVars.Beetles[i] = GetMoveableByName("big_beetle_"..i)
LevelVars.Beetles[i]:SetHP(10)
LevelVars.Beetles[i]:Enable()
LevelVars.Beetles[i]:SetOnKilled("OnBeetleDead")
end
Here, we have a for loop which goes from 1 to 4 (depends on how many enemies you placed)
Then for each value of i (in this case a beetle):
we are getting the moveable name set in Tomb Editor for each beetle and put them into the table respectively.
the .. operator is concatenation, it joins the strings together, so here we are connecting i value to “big_beetle_” which becomes “big_beetle_1” and so on.
then we set their HP to 10 and enabling them.
LevelVars.Beetles[i]:SetOnKilled("OnBeetleDead") this will call the OnBeetleDead function once the beetles are dead (explained a little bit later)
LevelFuncs.SpawnBeetles = function()
TriggerBeetles()
end
This code simply calls the function when lara steps to a volume trigger.
We can further extend the code to add a condition if all beetles are dead, in this case make Lara say something and open a door:
LevelFuncs.OnBeetlesDead = function()
local countingAlives = 0
for i = 1, 4 do
if LevelVars.Beetles[i]:GetHP() > 0 then
countingAlives = countingAlives + 1
end
end
if countingAlives <= 0 then
local beetleDead = "Wow you killed all of the beetles, nice"
local showMessage1 = DisplayString(beetleDead, 100, 800, Color(203,105,63))
ShowString(showMessage1, 4)
local door = GetMoveableByName("Door")
door:Enable()
end
end
Explanation:
local countingAlives = 0
for i = 1, 4 do
if LevelVars.Beetles[i]:GetHP() > 0 then
countingAlives = countingAlives + 1
end
end
We start off by initializing a variable countingAlives which holds a value of 0
We then make a for loop which will get each of beetle’s health and if their health is above 0 (i.e. alive) then we add it to the countingAlives variable
Note: The variable will decrease each time the beetle is killed.
if countingAlives <= 0 then
local beetleDead = "Wow you killed all of the beetles, nice"
local showMessage1 = DisplayString(beetleDead, 100, 800, Color(203,105,63))
ShowString(showMessage1, 4)
local door = GetMoveableByName("Door")
door:Enable()
end
This if statement will check if there are no enemies alive (i.e. the variable is less than or equal to 0) if there’s no beetles alive then:
We set up a string variable beetleDead which holds the sentence to print.
We set up another variable to define what colour and position it should have
The sentence will have 100 pixels in x direction and 800 pixels in y direction (remember +ve = the text will go down)
The sentence will have rgb values of 203, 105 and 63 which is roughly an orange colour.
Print the message onto screen using ShowString() which prints out the message with customized position and colour and for 4 seconds
if you put ShowString(showMessage1) without specifying time a 2nd argument, the function will show the message infinitely until a player hits a volume trigger with HideString() function
We then initialize another variable which contains a door moveable (named in Tomb Editor) then enable it.
Note:
You need ‘LevelVars’ to be able to save the table, if you don’t do that you will get errors and bugs.
Doing basic stuff
Open a door
With Lua you can open a door by defining and initializing a variable and using Enable() function which is described in TEN API:
LevelFuncs.OpenDoor = function()
local door = GetMoveableByName("Door_1")
door:Enable()
end
Explanation
Define and initialize a door variable which holds the door named in your Tomb Editor project (right click on the object to rename the Lua name)
door:Enable() activates the moveable (simply just opening the door).
Trigger an enemy
We can also use the opening door method to trigger some goons:
LevelFuncs.SpawnGoons = function()
local shotgunGoon = GetMoveableByName("Henchman_2")
local machinegunGoon = GetMoveableByName("Henchman_1")
shotgunGoon:Enable()
machinegunGoon:Enable()
end
Making something happen when killed an enemy
Using Moveable:SetOnKilled() we can set what will happen when an entity is killed, let’s use the above example with Goons
LevelFuncs.SpawnShotgunGoon = function()
local shotgunGoon = GetMoveableByName("Henchman_2")
shotgunGoon:Enable()
shotgunGoon:SetOnKilled("SpawnMachinegunGoon")
end
LevelFuncs.SpawnMachinegunGoon = function()
local machinegunGoon = GetMoveableByName("Henchman_1")
machinegunGoon:Enable()
shotgunGoon:SetOnKilled("SpawnMachinegunGoon") will call the SpawnMachinegunGoon function once the shotgunGoon is killed
Setting x amount of items
In TEN Lua, we can also set amount of ammo, medi kits or any other items for inventory:
LevelFuncs.Removepistols = function()
SetItemCount(InvID.REVOLVER_AMMO_ITEM, 16)
end
Explanation:
SetItemCount() Will set the amount of item you want to give or take away from the player
In this example I am giving 16 bullets of Revolver ammo to the player
If you want to give an item to player, you must have InvID. before the actual item or the game will crash
LaraHP = Lara:GetHP() We assign a variable to the integer that stands for Lara’s HP
LaraHPToShow = tostring(LaraHP) We create a string that holds the number, by transforming from int to string.
ShowLaraHP = DisplayString(LaraHPToShow, 500, 500, Color(255,0,0)) We create a string that can be displayed using “LaraHPToShow”
It is translated 500 to the right and 500 down, and a colour of red (because rgb is 255,0,0)
ShowString(ShowLaraHP) We tell the engine to show the string, and point it to what it should show, in this case that string being
Very important note:
If you put the code to the OnControlPhase function, there will be a nasty error coming up in your TEN logs:
[2022-Aug-24 14:54:39] [error] The infinite string 272190104 (key "Lara's Health: 1000") went out of scope without being hidden.
This error means it is infinitely showing up without a way to hide it, it didn’t cause any problems however it should be fixed. One way to fix it is you can do this:
I didn’t put this into a function because it can be used everywhere, for example you can put the code in OnControlPhase() to display Lara’s HP but it also will get updated each frame (explained above). Or you can put it in a normal levelfunc or normal function to display the health once for various events.
In Squidshire’s docs, the tostring() is described as __tostring() you should use tostring()
Those were just a few examples of how to use Lua to trigger basic stuff. Of course TEN also features Legacy triggers which are basically triggers that you put on floor just like in TRNG or any other engine, but to do advanced and complicated stuff you have to use Lua.
Conclusion
Congratulations! You have reached the end of this tutorial. I hope it has helped you understand Lua and gain more confidence about scripting for TombEngine. We look forward to seeing the creative puzzles, contraptions, and effects you make. 🙂