Tomb Raider Forums  

Go Back   Tomb Raider Forums > Tomb Raider Level Editor and Modding > Tomb Raider Level Editor > Tomb Engine

Reply
 
Thread Tools
Old 07-08-22, 16:14   #1
adngel
Member
 
adngel's Avatar
 
Joined: Jan 2004
Posts: 313
Post Lua Workshop

One of the TEN features is the implementation of Lua scripts, they require some programing knowledge, but definitelly will open the door to new frontiers.

Along this topic, I would like go sharing small tutorials here and there. I think that showing how to do things, can help people to use Lua in their levels, and eventually for them to do also their own scripts.

If you’ve got any doubt, suggestion or feedback, you are free to ask here too. We know Lua may looks a bit intimidating so I hope this topic can help to feel more confortable with this new workflow.


Index:
NEWS:
12 - 08 -2022: The Basics of Lua programming has been updated, you can check more in this post.
07 - 08 -2022: Lua for TEN documentation and tutorial Basics of Lua programming, have been published, check more information in this post.

Last edited by adngel; 21-08-22 at 13:58.
adngel is offline   Reply With Quote
Old 07-08-22, 16:15   #2
adngel
Member
 
adngel's Avatar
 
Joined: Jan 2004
Posts: 313
Default

Adding new levels to my game

The first we are going to do, is modify our gameflow script to can include our new level. (Or the many levels that we want).

What are these Lua files?

In the TombIDE program, you can find your lua files pressing the third button on the left panel:

In your Scripts folder, you will find these files, they are part of the Ten system. (You need them, you can edit them, but not delete them).

  • Settings: This contains some options about error logs, and some movements conditions, (in development).
  • Gameflow: This is like your main script file, here you will add your new levels, and their object data.
  • Strings: This is the place where you can set your new text lines.
  • Util: This is a code that contains some functions to make scripting easier.
  • Timer: This is a module with tools to make timed actions.
  • EventSequence: This module includes the EvenSequence structure to schedule a serie of actions. (in development).

Apart of these ones, then there are some extra Lua files dedicated to each level, for example, you have there Title.lua and TestLevel.lua.

So what do I need to add in these scripts to have a new level?
To add a new level, you must do three things.
  • Create a new level block in the Gameflow.lua.
  • Create a new string for your level name.
  • Create a new lua file for that level.

Creating the Level block in Gameflow

The script may look complex at first sight, but if you take a more general look, you can see that there are some 3 areas in this script.

  • Header, is the area that initialize system data, you usually don’t need to touch this.
  • Title level also need its block, and this is its place.
  • Level blocks, in this pic it’s shown 1 level block, but you may add as many level blocks as levels has your game.

So you can add your next level block at the end of the list.
The level block has this minimal structure:

Code:
Level1 = Level.new()

Level1.nameKey = "L0_Title"
Level1.scriptFile = "Scripts\\l_LaraGym.lua"
Level1.ambientTrack = "108"
Level1.levelFile = "Data\\LaraGym.ten"
Level1.loadScreenFile = "Screens\\rome.jpg"

Flow.AddLevel (level1)

Please not the word in blue, this must be unique for each level, I think an easy way to do that is just calling Level1 the first level, Level2 the second, etc..

Filling the level block:
- nameKey
Is the name of your level, you must add it to your strings file and put it here.
- scriptFile
Here is where you will put you Lua level file, we will cover it later.
- ambientTrack
This is the ambient music that has your level at the begginng, you can find the songs in the folder Engine/Audio
- levelFile
This is the file of your level, located in the Engine/Data folder.
- loadScreenFile
This is the image that will appear while your level is loading, you can find and add pictures at the folder Engine/Screens

Note: An usual cause of crash, is because builders forget to update the levelFile value.


Adding an string name

The strings goes in you String.lua file, so open it and go at the end of the file.

Before the bracket close, that's the place to inject your new strings. You can just copy the structure of the previous string and paste it below.

Example:

Pay attention to the commas, only the last string don’t need comma at the end, but the rest yes need a comma after the bracket like: },
  • In the string, the string var name need to be unique, I’m using L0_Title, L1_Title, L2_Title, etc..
  • Inside, there are many gaps, it’s designed so each one will be used for different languages, at the moment we are going to fill only the first one that is used for English.
  • Once you’ve got it, save the strings.lua, and write in the nameKey of your level block, the string var name, between double commas.Iin my case, L0_title

Adding a new Lua script

In TombIDE, you can add a new file pressing the left button of the mouse, on the File Explorer, there you can find the create new file.


In the new window, just put the name of your new file, and choose the lua format. Then press Create.


When you create a new file, this file will be empty, to have a minimal workable level code, you need to add the next lines:

Code:
---- FILE: \Levels\l_laraGym.lua
 
LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnControlPhase = function() end
LevelFuncs.OnEnd = function() end

Put this code in the file you've just created and save it.

Finally you can update the scriptFile value in you level block with the address to your lua file. (In my case: "Scripts\\l_LaraGym.lua")


Completed

Once you've done, you should be able to find your level in the game menu and play it, (although first you must have compiled the Tomb Editor level!)

If it fails, the first check should be the log document. (you will find it in Engine\Logs\ ), look for a line that says [error], usually at the end of the document, these logs tends to provide useful information, like the line where your script has failed. (It can be for the lack of a comma, for a misspelled word, etc...)

Extra features
That provided code, was quite minimal, there are more elements that you can add to your level block, like horizon, sky layer, fog, weather, etc... You can find all the features in the documentation. (You can download from this git hub: https://github.com/Stranger1992/Tomb-Engine-Demo-Levels )

For example:
Code:
Level0 = Level.new()

Level0.nameKey = "l0_Title"
Level0.scriptFile = "Scripts\\Levels\\l_LaraGym.lua"
Level0.ambientTrack = "108"
Level0.levelFile = "Data\\LaraMoves.ten"
Level0.loadScreenFile = "Screens\\rome.jpg"

Level0.horizon = true
Level0.weather = 2
Level0.weatherStrength = 1

Level0.layer1 = Flow.SkyLayer.new(Color.new(72, 80, 96), 5)
Level0.fog = Flow.Fog.new(Color.new(32, 56, 64), 5, 20)

Flow.AddLevel(Level0)

Last edited by adngel; 12-08-22 at 09:26.
adngel is offline   Reply With Quote
Old 07-08-22, 22:54   #3
Kubsy
Member
 
Kubsy's Avatar
 
Joined: Nov 2019
Posts: 908
Default

In addition to that, in that repo, you will find my Lua basics tutorial which will teach you Lua from basics. I encourage you to read it.

Both my tutorial and Squidshire's documentation will be regularly updated so check often!

I am aware that my Lua tutorial has information that does not reflect some stuff (for example volume triggers) I will update accordingly this week to reflect those changes

Stay tuned for more and happy learning. I am extremely curious of what you will guys create with Lua
Kubsy is offline   Reply With Quote
Old 07-08-22, 23:19   #4
adngel
Member
 
adngel's Avatar
 
Joined: Jan 2004
Posts: 313
Default

I've put the link to the repo in the first message.

To can see the documentation, you have to download it (press the green button named "code" and select Download ZIP).

One inside the folder, you can find the TRLE projects for Lua and a folder named Lua tutorial / docs.

Open the file index.html (in any internet browser) and there you can find several information about using Lua for TEN.

Last edited by adngel; 07-08-22 at 23:28.
adngel is offline   Reply With Quote
Old 08-08-22, 13:21   #5
adngel
Member
 
adngel's Avatar
 
Joined: Jan 2004
Posts: 313
Default

Making functions for showing text

Today I want to explain how to create functions and how to link them with the level of Tomb Editor, for that, we will use simple functions that only will print a line of text.

Learning about Functions:

Once you’ve got your level, you also will have a lua file dedicated to that level, that document is where we write the functions.


As we remember, our level code starts like this:

Code:
LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnControlPhase = function() end
LevelFuncs.OnEnd = function() end
These lines are functions, the instructions you put inside (between the brakets (), and the end), only will be activated when the function is called, (like a trigger) so they are very useful to control a program.
  • OnLoad - Is triggered when you enter the level due to a save game load.
  • OnSave - Is triggered when you do a save in your level.
  • OnStart - Is triggered when you start your level from the beggining.
  • OnControlPhase - Is triggered on every cycle. If your PC is rendering the game 30 frames per second, then this function is being called 30 times per second. Think on this like an OnUpdate.
  • OnEnd - Is triggered when you close your level. (To go the main menu, or to the next level).

These are the default level functions, but you can create more. I’ll add two here:

Code:
LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnControlPhase = function() end
LevelFuncs.OnEnd = function() end

m_Util = require("Util")
m_Util.ShortenTENCalls()
 
LevelFuncs.PrintTextVolume = function()
    local text = "Function Text Volume Activated"
    local string = DisplayString(text, 100, 100, Color.new(250,250,250))
    ShowString(string, 5)
end
 
function PrintTextFunc ()
    local text = "Function Text Function Activated"
    local string = DisplayString(text, 100, 200, Color.new(250,250,250))
    ShowString(string, 5)
end
Here you can see we have two structures for functions.
Code:
LevelFuncs.FunctionName = function()
.
.
.
end
And
Code:
function FunctionName ()
.
.
.
end
The difference is that the LevelFuncs version, can be detected by Tomb Editor, and you can use call it from a volume trigger. The second one, is more traditional, but only can be called from Lua code.

About the content, you can see is mostly similar in both cases.
  • local text = "Function Text Function Activated"This is the text we are going to show. Check the local word at the beggining, that means that when this function ends, the game will forget about this variable. (So same name can be used in the other function).
  • local string, this variable contain information about the text line we want to print, (the actual text, the x y position and the letters color)
  • ShowString (string, 5) what it das is show the text line we have stored in string, during 5 seconds.

In order to can use the TEN specifict functions, (like DisplayString and ShowString), is necessary we call also a module, that what these two lines are doing:

m_Util = require("Util")
m_Util.ShortenTENCalls()


But, we will talk about modules another day.

For now, copy these functions in your level and save the file.

Linking the level lua file to the Tomb Editor:

Open your level in Tomb Editor.

The first step we must do to can call your Levelfuncs in the level, is link the level lua file with your level Tomb Editor project. So we have to open the Level Settings (in the Tools Tab of the top).


Once in the level settings, go to the Misc area, and fill the Lua slot with your level lua file. Then save your project to avoid this step in the future.


Creating a Trigger Volume:

After linking the file, Tomb Editor has access to the functions you write in the Lua file. But to activates the level, you need a trigger volume.

To create a trigger volume, select a group of tiles, and press the key V. (You can press Ok in the window for now).


That pink cube, is a new version of trigger, you can move, rotate and scale. If you select it and press the key O, you will open the edition window.


In this window we create the eventriggers that will link to our code functions.
  • To start, press the + button in the top left corner. That will create an event. (An event is like a trigger, but you can connect different volumes to this same event).
  • When you creates a new event, you have to put the event name (something that helps you to identify and know what it does).
  • Under the name, you see 3 tabs, When Enter, When inside and When Leave, these are the moments when the function will triggered. We will keep working with When Enter.
  • Then, you should be able to see a list of all your LevelFuncs from you lua level file. (If you don’t, review that your file is linked correctly in the Level Settings). You have to make click in the function you want to activate with this volume.
  • After it, there is a box called Arguments, it’s used to send values to the function, like the timer box of the classic triggers.
  • Also there is a call counter, If you leave 0, the trigger will activate every time, But if you put another value, like 3, then volume only will activate the first 3 times. This is the new version of the One Shot feature.
  • At the bottom, there are the Activators, this is who can activate the volume, by default is Lara, but you can tick other boxes if that’s what you need.
For our test we don’t need anything else. We can compile and test our new volume.


If everything has gone well, we will be able to see our text line every time we step in the volume.


Calling a functions from another code:

That was the way to activate the Levelfunc function, but how do we call the traditional function? As we told, it has to be done from Lua, for example, we are going to add the call in the function Start() so our traditional function print its text at the beggining of the level.

Code:
LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function()
    PrintTextFunc ()
    LevelFuncs.PrintTextVolume ()
end
LevelFuncs.OnControlPhase = function() end
LevelFuncs.OnEnd = function() end

m_Util = require("Util")
m_Util.ShortenTENCalls()
 
LevelFuncs.PrintTextVolume = function()
    local text = "Function Text Volume Activated"
    local string = DisplayString(text, 100, 100, Color.new(250,250,250))
    ShowString(string, 5)
end
 
function PrintTextFunc ()
    local text = "Function Text Function Activated"
    local string = DisplayString(text, 100, 200, Color.new(250,250,250))
    ShowString(string, 5)
end
That’s all what you need. To call a traditional function, you must put its name, followed by the brackets, in another function.

You can see that I also put the function LevelFuncs.PrintTextVolume in there, that's to show you can call LevelFuncs also from Lua too.

Save the file, and run your level, (you don’t need to recompile your level to see your the Lua changes). If everything is ok, we should be able to see both lines at the beggining of the level.


Calling a functions with arguments:

To end this day lesson, I’ll show about sending arguments to the function.

Sometimes we may want to use the same function, but with a little small change, (a different timer, a different object, a different password, etc...) to do that, we use arguments. We are going to read some arguments and print them on the screen.

In the Tomb Editor, we’ve already seen that there is a box to type arguments for our functions, we are going to put something there, it can be a number, or a word. I’ll type chocolate.


Although we must do some changes in our code to can read that. Take a look in the next version of the code.

Code:
LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function()
    PrintTextFunc ("Vainilla")
end
LevelFuncs.OnControlPhase = function() end
LevelFuncs.OnEnd = function() end

m_Util = require("Util")
m_Util.ShortenTENCalls()
 
LevelFuncs.PrintTextVolume = function( triggerer, arg )
    local text = "Function Text Volume Activated likes " .. arg
    local string = DisplayString(text, 100, 100, Color.new(250,250,250))
    ShowString(string, 5)
end
 
function PrintTextFunc ( arg  )
    local text = "Function Text Function Activated likes " .. arg 
    local string = DisplayString(text, 100, 200, Color.new(250,250,250))
    ShowString(string, 5)
end
In our LevelFuncs, in the brackets (), we have put two arguments.
  • triggerer Is a reference to the moveable that activated the volume
  • arg: That is the argument we put in the volume

Although we are not using the triggerer in this function, we have to put it becaues the order is important, the arguments in LevelFuncs functions, are the second input value. That’s why we have to put triggerer first.

Once we’ve got the argument variable in the parameters (inside the bracket), we can use it inside that function, for example, the text I’m sending, is a combination of the written line "Function Text Function Activated likes " and whatever is in the argument arg. (Check that I useed the double points .. to combine both words).


But we don’t always need the volumes to set arguments, we can sent arguments directly to our traditional functions too, we just need to put the value, in the brackets when we call the function.

In this same code, look at the call in OnStart function, We keep calling our traditional function but we are sending the word "vainilla", (We put the " to make the program knows that is a text, not a variable).

The traditional functions do not have triggerers, so we don’t need to put it on the parameter, that’s why I put directly arg, and used it in a similar way I did on LevelFuncs.

Now, if we save the code and run the level, we should be able to see ours texts using the words we send.


Using arguments with the functions is also a very useful feature which definitely we will use in our codes.

/---------------------------------------/


I hope you find this lesson useful, please if you’ve got any doubt or issue, feel free to ask.

Last edited by adngel; 08-08-22 at 13:51.
adngel is offline   Reply With Quote
Old 12-08-22, 10:15   #6
Kubsy
Member
 
Kubsy's Avatar
 
Joined: Nov 2019
Posts: 908
Default

Hi!

Lua Tutorial has been updated! Here is the changelog:

1.1
- Improve grammar and elaborate some stuff (Thanks Sezz).
- Reflected Volume triggers section.

1.2
- Added a new section called: Small example scripts which provides some basic example stuff.


You can download the lua tutorial using Adngel's google drive (if you want to see the tutorial online), view it in github repo: https://github.com/Stranger1992/Tomb...a-Basics-Guide, or download from dropbox: https://www.dropbox.com/sh/7rsxm45u4...Qc8f426da?dl=0 (to have a local copy for yourself)

Last edited by Kubsy; 21-08-22 at 13:42.
Kubsy is offline   Reply With Quote
Old 24-08-22, 19:00   #7
adngel
Member
 
adngel's Avatar
 
Joined: Jan 2004
Posts: 313
Default

My third pack about the modules is having more changes so it will take a few more delay.

But I wanted to shared these code that covers a couples of things I was asked:

- How to rotate a static object
- How to send several arguments with the volume trigger.


A level lua code.
Code:
-----------------------
-- Level Global vars
-----------------------
DeltaTime = 0.0

-----------------------
-- Main Functions
-----------------------

LevelFuncs.OnStart = function() 
    InitializeModules ()
end

LevelFuncs.OnControlPhase = function(dt) 
	DeltaTime = dt
end

LevelFuncs.OnEnd = function() end

LevelFuncs.OnLoad = function() 
    InitializeModules ()
end

LevelFuncs.OnSave = function() 
end

function InitializeModules ()
    --Utils
    m_Util = require("Util")
    m_Util.ShortenTENCalls()
end

-----------------------
-- Level Functions
-----------------------

--Rotating Object

LevelFuncs.RotateMyObjectUpdate = function (Trigerer, Argument)
	local ParameterValues = SplitString (Argument)
	
	local objectName = ParameterValues[1]
	local rotSpeed = ParameterValues[2] * DeltaTime
	myObject = GetStaticByName(objectName)

	local newRotation = Rot1_myObject:GetRotation().y + rotSpeed
	myObject:SetRotation(Rotation.new(0, newRotation, 0))
end

function SplitString (inputstr, sep)
	if sep == nil then  --If there is not a separator, then use spaces as separator.
		sep = "%s"
	end
	local t={}
	for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
		table.insert(t, str)
	end
	return t
end
So in Tomb Editor, you place a static object that has the lua name: "RotatingStatue", knowing that, then you create a volume, while Lara is inside, the object will be rotating, so we select the RotateMyObjectUpdate in the "When Inside" tab. And, in the arguments, instead to put a number or a word, we write the next:

Argument: RotatingStatue 30

Two words separated by an space, so later, every word will be a different value. You'll see, jumping back to our code, the function RotateMyObjectUpdate also takes the usual triggerer and args, but then, we use the function SplitString to separate the words. And later, put all those words in a new table.

What are these table? think on them like arrays, a chain of data that you can access later calling them with its right bracket.

So If the string is "RotatingStatue 30"
After passing through the function, our ParameterValues table will have
ParameterValues [1] = "RotatingStatue"
ParameterValues [2] = "30"

We can use them, but I like to translate that data into other variables with better name. objectName and rotSpeed.

In that way, I could bring multiples values from the VolumeTrigger onto my function.


About the object rotation, I hope the rest get easier to understand. Because I'm rotating for several frames, I'm calling this function in onUpdate or WhenInside the volume trigger.

Then it reads the current rotation of the object, and adds a speed. (the number of degrees that should move in a 1 second).

P.S. Don't ignore the DeltaTime, it's important. (A story for another day), just check this is defined at beggining of the code, and updated in the OnControlPhase function.

Last edited by adngel; 24-08-22 at 19:25.
adngel is offline   Reply With Quote
Old 25-11-22, 19:42   #8
Kubsy
Member
 
Kubsy's Avatar
 
Joined: Nov 2019
Posts: 908
Default

Creating an ammo counter in Lua

(Based on tutorial from l.m. at trlevel.de https://www.trlevel.de/lexicon/index...-ammo-counter/ picture and code credits go to him)

Hi guys today we shall learn how to create an ammo counter in TEN's Lua programming language as shown below:



you need:
- a basic understanding of how variables works
- a basic understanding of how decision-making works (if else statement work)
- a basic understanding of logical operators (and, or, not but this code uses
not only)
- TEN 1.0.3 due to new lua commands introduced in this version.

If you don't know what are variables and/or decision-making then I recommend reading this tutorial: https://github.com/Stranger1992/Tomb...a-Basics-Guide

note: Unfortunately this is not possible with node editors, due to the fact that they are designed for room-based events and this tutorial uses OnControlPhase() which is necessary if you want the counter to appear throughout the game.

ok let's dive in.

The script to make the ammo counter is as follows:

Code:
LevelFuncs.OnControlPhase = function()
    ShowAmmoCounter()
end

function ShowAmmoCounter()
    local holdWeapon = Lara:GetHandStatus()
    local ammoMessage = DisplayString("Ammo: " .. Lara:GetAmmoCount(), 100, 300, Color(0,255,128), false)

    if (Lara:GetAmmoCount() == -1) then
        unlimited = true
    else
        unlimited = false
    end

    if not unlimited then
        if holdWeapon == 4 then
            ammoMessage:SetKey('Ammo: ' .. Lara:GetAmmoCount())
            ShowString(ammoMessage)
        else
            HideString(ammoMessage)
        end
    end
end
I shall go through line by line.

Code:
LevelFuncs.OnControlPhase = function()
    ShowAmmoCounter()
end
this piece of code above will be activated every game frame, the OnControlPhase works by calling the code every single game frame.

the ShowAmmoCounter() is to call the function.

Fyi: of course you could copy paste the entire code to the OnControlPhase function however I wouldn't recommend this because you will probably have lots of things being checked every game frame and so it's easier to split those pieces of codes into subprograms (functions)

Code:
    local holdWeapon = Lara:GetHandStatus()
    local ammoMessage = DisplayString("Ammo: " .. Lara:GetAmmoCount(), 100, 300, Color(0,255,128))
the code above declares and initializes local variables the first variable will hold lara's hand status named holdWeapon (will go over it a little bit later)

The second variables stores the the message itself into a ammoMessage variable where:
- The operator ".." is used to concatenate (join) the strings together
- 100 = x (horizontal) position of the text
- 300 = y (vertical) position of the text
- Color(0,255,128) = creates a colour of the text with the r = 0, g = 255,
b = 128.

Note: A disadvantage using raw values (100, 300) is that the text may not be shown or misaligned depending on the player's resolution. you should therefore try to use a command PercentToScreen(x,y) which takes in a percentage (say 50 for x and 50 for y) and returns a pixel coordinate of the screen which will align nicely depending on the resolution. More on this and an example here: https://lwmte.github.io/1%20modules/...ercentToScreen

Code:
  
 if (Lara:GetAmmoCount() == -1) then
        unlimited = true
    else
        unlimited = false
    end
this code will now evaluate if the ammo count is unlimited (1) (note the -1) if yes then puts true value to unlimited variable which means the text will be simply "unlimited" if no then puts false value.

Code:
if not unlimited then
        if holdWeapon == 4 then
            ShowString(ammoMessage)
        else
            HideString(ammoMessage)
        end
    end
end
this is another if statement, this time it will evaluate whether the weapon lara has equipped is unlimited or not.

In this case for every weapon except pistols (unless you write a code to make shotgun have unlimited ammo for example) this if statement will be true (note the not which is a logical operator). So because the ammo for pistols is unlimited, then the if statement is skipped entirely and string will not be shown/hidden.

holdWeapon == 4
this checks if Lara's hand status is 4. According to the API document (2) the status is as follows:

Code:
1=Busy(climbing,etc), 2=WeaponDraw, 3=WeaponUndraw, 4=WeaponInHand.
we are using 4 which is WeaponInHand to check that Lara is holding a weapon.

if Lara is holding the weapon, then the variable ammoMessage will be print onto the screen. If not then the message will be hidden so that it is not shown constantly.

Note: you can of course make your code more readable, for example instead of having holdWeapon == 4 you could add a new variable with a string in it and then check for equality. For example

Code:
local isHoldingWeapon = 4
holdWeapon == isHoldingWeapon
and so forth.

This is the end of the tutorial, now you will be able to draw a message onto the screen to show the current ammo count for each weapon .

references:
1) https://lwmte.github.io/2%20classes/...t:GetAmmoCount
2) https://lwmte.github.io/2%20classes/...:GetHandStatus

https://www.trlevel.de/lexicon/index...-ammo-counter/

Last edited by Kubsy; 08-03-23 at 16:07. Reason: editing on the fly
Kubsy is offline   Reply With Quote
Old 27-11-22, 12:58   #9
Kubsy
Member
 
Kubsy's Avatar
 
Joined: Nov 2019
Posts: 908
Default

Turning on Soft Collision for static objects and vice versa

(Based on l.m.'s tutorial from trlevel.de https://www.trlevel.de/lexicon/index...tatics-setzen/ code credit goes to him)

https://streamable.com/8vlk49

Tomb Engine has a default hard collision on for all statics. However, there may be cases where you just want to have some statics have soft collision. Well there are 2 ways to achieve this:

- Setting soft collision using static slots
- Setting soft collision using static's lua name set in Tomb Editor

I shall go through the 2 methods.

you will need:
- basic understanding of array (in Lua they are called tables)
- basic understanding of loops (especially nested loop, more on that later)
- basic understanding of pairs() function.
- Static:SetSolid() command (https://lwmte.github.io/2%20classes/...tatic:SetSolid)

again if you don't know anything about the arrays, loops etc then I recommend you to read the tutorial: https://github.com/Stranger1992/Tomb...a-Basics-Guide


Setting soft collision using static slots

the whole code is as follows:

Code:
LevelFuncs.OnStart = function() 
    SetStaticsSoftCollision()
end

function SetStaticsSoftCollision()
    local staticSlots = {
        0,2,4,5,6,7,11,12
    }

    for _, staticSlot in pairs(staticSlots) do
        local statics = GetStaticsBySlot(staticSlot)
        for _, static in pairs(statics) do
            static:SetSolid(false)
        end
    end
end
I shall go through it step by step.

Code:
LevelFuncs.OnStart = function() 
    SetStaticsSoftCollision()
end
This block will call the SetStaticsSoftCollision() when the level starts (OnStart)

Code:
    local staticSlots = {
        0,2,4,5,6,7,11,12
    }
here. We store values in a table, these values represent static slots to be iterated later via loop (you can of course modify this to your case)

Code:
  
for _, staticSlot in pairs(staticSlots) do
        local statics = GetStaticsBySlot(staticSlot)
        for _, static in pairs(statics) do
            static:SetSolid(false)
        end
    end
end
Now here we do a nested loop (nested loops are loops that are inside a loop)

the outer loop
Code:
for _, staticSlot in pairs(staticSlots) do
        local statics = GetStaticsBySlot(staticSlot)
traverses through the table and lets the engine know that these are static slots via GetStaticsBySlot

in the pairs() brackets you are passing staticSlots table variable for the loop to iterate through, the staticSlot is used to access the numbers in the table. You don't need to worry about index variable as it just accesses the index of the number in the table so you don't need to worry about it (index of digit 0 is 1, digit 2 is 2 and so forth).

note: if the index is never used/referenced then instead of index you can use an underscore _

hence from:

Code:
for index, staticSlot in pairs(staticSlots) do
        local statics = GetStaticsBySlot(staticSlot)
will become:

Code:
for _, staticSlot in pairs(staticSlots) do
        local statics = GetStaticsBySlot(staticSlot)
end of note

Code:
for _, static in pairs(statics) do
            static:SetSolid(false)
this inner loop will now loop through the statics variable to make them soft.

pairs(statics) loops through the table statics when we assigned them to GetStaticBySlot. we then reference each with static and setting them to soft collision static:SetSolid(false)

Setting soft collision using static's lua name

The previous method is great because we can make every static in the slot to have soft collision. However, now you may ask: "But what if I DON'T want every static in the same slot to have soft collision? I want some of them to have hard collision."

Well this is also doable:

Code:
LevelFuncs.OnStart = function() 
    SetStaticsSoftCollision()
end

function SetStaticsSoftCollision()

    local SoftCollisionStatics = {
        "static_mesh_433", 
        "static_mesh_399", 
        "static_mesh_398"
    }

    for _, value in pairs(SoftCollisionStatics) do
        local static = GetStaticByName(value)
        static:SetSolid(false)
    end
end
This code is very similar to our previous code for slot. However one difference is that instead of static slots numbers (2,4,50 etc) we are now using static's lua name set in Tomb Editor and put them into the table and we only need one loop.

note: the strings must be comma separated as you see in the above example

(to change object's lua name: right-click on the object -> rename object -> type the lua name you want the object to have for example: "static_mesh_5" to "wooden pedestal")

Thanks for reading the tutorial

references:
- https://lwmte.github.io/2%20classes/...tatic:SetSolid
- https://www.trlevel.de/lexicon/index...tatics-setzen/

Last edited by Kubsy; 27-11-22 at 13:11.
Kubsy is offline   Reply With Quote
Old 27-12-22, 19:29   #10
Kubsy
Member
 
Kubsy's Avatar
 
Joined: Nov 2019
Posts: 908
Default

Infinite Boulder

In this tutorial, I will show you how to make an infinite boulder which will be activated infinitely.

Code:
spawner = GetMoveableByName("Boulder_spawner")
boulder = Moveable(TEN.Objects.ObjID.ROLLINGBALL, "boulder", spawner:GetPosition())

LevelFuncs.OnStart = function()
    boulder:MakeInvisible()
end

LevelFuncs.BoulderSpawn = function()
    boulder:Enable()
end

LevelFuncs.resetBoulder = function()
    boulder:SetPosition(spawner:GetPosition())
end
----

Code:
spawner = GetMoveableByName("Boulder_spawner")
boulder = Moveable(TEN.Objects.ObjID.ROLLINGBALL, "boulder", spawner:GetPosition())
this block declares and initializes 2 variables:
- spawner variable which gets the moveable by its lua name set in Tomb Editor. In this case it's "Boulder_spawner" and a nulllmesh (doesn't matter which slot)
- Boulder variable which creates a new boulder via the Moveable() function. To learn more about this function, read here: https://lwmte.github.io/2%20classes/....html#Moveable

----

Code:
LevelFuncs.OnStart = function()
    boulder:MakeInvisible()
end

LevelFuncs.BoulderSpawn = function()
    boulder:Enable()
end

LevelFuncs.resetBoulder = function()
    boulder:SetPosition(spawner:GetPosition())
end
The OnStart command will make the boulder invisible since the engine will make the boulder visible so I made it invisible, however you don't have to do this if you don't want to.

Boulderspawn function will simply trigger the boulder.

ResetBoulder will reset to its initial position using the nullmesh.

---

Setup in TE

To set this up you need:
- nullmesh from any slot
- 2 volumes

in my test level this looks like this:



the nullmesh at the top acts as a boulder "spawner", the volume to the right makes the boulder trigger (activated by lara), the volume at the bottom resets the boulder position (activated by Other objects - we do not want Lara to reset the position of boulder.).




Let's see how this looks in game!

clicky

Notes:
- You don't have to use the moveable() function to create the boulder, you can just place it in TE, however there are scenarios that moveable() can be very useful and used in an interesting way. One of the example is making an infinite random enemy spawner: https://github.com/Stranger1992/Tomb...Lua-Scripts#12

That is the end of the tutorial, let me know if you have any suggestions
Kubsy is offline   Reply With Quote
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off



All times are GMT. The time now is 21:23.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.
Tomb Raider Forums is not owned or operated by CDE Entertainment Ltd.
Lara Croft and Tomb Raider are trademarks of CDE Entertainment Ltd.