Tomb Raider Forums  

Go Back   Tomb Raider Forums > Tomb Raider Level Editor and Modding > Tomb Raider Level Editor > Tutorials and Resources

Closed Thread
 
Thread Tools
Old 29-07-24, 23:12   #1
AkyV
Moderator
 
Joined: Dec 2011
Posts: 5,074
Default TEN - Script functions

Made using Tomb Editor 1.7.1. pack (including Tomb Engine 1.4.)
Last update to TE/TEN: 1.7.2./1.5.


In the tutorial about the script files I started a sequence about introducing the basics of the so-called Lua scripting: i.e. Lua scripting techniques.
Lua scripting is the method how the script is made for TEN.

Scripted events

In the tutorial which concentrates on the nodes I told that node events can be executable ("action") events or condition events for action events.
You could also know that TEN events can be defined by nodes or by scripting. Which means there can also be executable scripted events and condition scripted events.

Functions

Probably the main elements of TEN scripting are the so-called functions. Scripted events are organized in the form of functions.
So this is my second tutorial about the scripting techniques, concentrating on the basics of the functions this time.

CONTENTS:

1. Identifying the functions
2. The origin of the functions
3. Function contents
4. Forcing or query
5. Placing functions
6. Function name structure
7. Nodes and functions

----------

Notes:
  • As the title says, it is “only” a Tomb Engine (TEN) tutorial. Many aspects you need to know for this tutorial are specific Tomb Editor (TE), TombIDE (TIDE), WadTool (WT) etc. features.
    So non-TEN features are only referred now, if it is necessary. And as much as I found it necessary. You need to also meet these features in other tutorials, for further details about them.
  • Some related TEN features are only mentioned, but not discussed in this tutorial, because they belong better to (an)other tutorial(s).
    Find another info source or be patient till those other tutorials are made.
  • Some options or commands can be executed in several ways. I may not introduce all of the ways in the tutorial. (Using default keys are presumed.)
  • Explainig basics about Lua scripting is not the best decision in a precise way when you, the reader, is a beginner TEN builder. That is why I try to interpret Lua scripting mostly with my own words - while I also try to remain authentical, naturally. (Which is not too hard, anyway, because when I am making this tutorial sequence then I am not a Lua expert myself...)
  • Perhaps you understand the Lua basics better in a form of another tutorial.
    Help dropdown menu of Scripting Studio also leads you to this linked tutorial, anyway.

Last edited by AkyV; 04-11-24 at 20:02.
AkyV is online now  
Old 30-07-24, 23:15   #2
AkyV
Moderator
 
Joined: Dec 2011
Posts: 5,074
Default

1. Identifying the functions

If you first look at any script files (including the ones you should never edit in Engine subfolder), then you will be surely terrified:
"What is this? Is this scripting? Yes, you told me that it is different than TRNG scripting, but... but... how can I identify anything in this mess? How can I identify a function, for example?"

Be patient. Yes, you are right, it is not easy at first sight. But you start learning scripting in TEN now, and sooner or later you will know enough to understand everyhting in a TEN script.
So, yes, that question is a good start: how could you identify a function?

Well, here is a hint - very primitive, but it works:
If you see round brackets somewhere in the script, which are not parts of textual notes or not for a mathematical operation (like eg. (a+b)=c), then you have just found a function there. (As I told here, notes can be identified with starting -- sign.)
But those brackets also need to have some text before them. - For example:

Flow.AddLevel(title)

Yes, it is a function. (You can find it in the initial version of Gameflow.lua, anyway.) The text before the brackets is the name of the function. The (numerical, or - just like in this example - textual) value in the brackets is the argument of the function. The argument is an input data for the function - i.e. without the argument value, the function cannot work.
To understand it easily, consider it like a classic trigger action and its parameter. Eg. a trigger with "FlipMap" action chosen and value "2" typed in "Param" trigger window will turn on the flipmap which has ID2. So "Flow.AddLevel" is an "action" now, and "title" is the "parameter" for this action, and the game will read it this way: "I will do now what a Flow.AddLevel function should do, using title as the argument value". (But we won't discuss now what a Flow.AddLevel function should do.)

----------

Notes:
  • Don't forget: this Engine folder is inside Scripts folder, not the Engine folder inside directly in the project main folder.
  • Some functions don't need any input data. On the other hand, some functions need more input data values. (Just like eg. when some FinishLevel classic triggers have not one, but two parameter values: one refers to the level, and the other one refers to an OCB value of a LARA_START_POS nullmesh.)
    Which means that not all the functions have one argument. Some don't have any at all, and some have two, three or more arguments.
    If the function has no arguments, then the brackets are empty. If the function has two, three or more arguments, then there are two, three or more arguments typed in the brackets, separated by commas. - So:

    • Function without arguments:

      function_name()

    • Function with two arguments:

      function_name(argument1, argument2)

    • Function with three arguments:

      function_name(argument1, argument2, argument3)

  • In that FlipMap trigger I mentioned, the trigger identifies the value 2 of the parameter, because that value is typed in the proper way: in the Param window of the trigger.
    That is why arguments also need to be identified, using their names. I mean, eg. in the example above, "title" is only the value of that argument, but naturally there can be even other values for that argument. The argument name for Flow.AddLevel function is "level".
    In this example the arguments are named as A, B, C. Their values in the same order is 1, 2, 3 at the first occasion - but when the function is also executed somewhere else later, then the values are different: 4, 5, 6:

    FunctionX(A, B, C)
    FunctionX(1, 2, 3)
    FunctionX(4, 5, 6)

    So A is 1 or 4, B is 2 or 5, C is 3 or 6.
    Naturally you would like to know that when you can see numbers/texts in the brackets at a function, then those are the names or the values of the arguments.
    Well, mostly they are argument values - which means the function is just running there, with those argument values.
    They are argument names when the function is presented there not for running, but for something else:

    • When the syntax of the function is introduced in a tutorial.
    • When a brand new function is just being created in the script, so the argument(s) must be freshly named for it. (As you will see below, you will easily identify in the script that a function is just running in the script or just being created.)

  • Don't be sure that when you have just identified a function that is a scripted event. I mean, some other scripted tasks are also organized as functions. (Which means that many other scripted tasks are controlled in that "I will do now what a ... function should do, using ... as the argument value" way.)
    But don't worry - you will be easily able to distinguish event functions from other functions. Just keep reading this tutorial.

Last edited by AkyV; 21-08-24 at 10:02.
AkyV is online now  
Old 30-07-24, 23:24   #3
AkyV
Moderator
 
Joined: Dec 2011
Posts: 5,074
Default

2. The origin of the functions

The functions are coming from two different origins:
  • Many functions in TEN are preset, made by the developers of TEN. (Naturally all the initially existing functions in the script files are preset.)
    Don't worry - when you start building with TEN, you don't need to know all of the preset functions. I am sure you will get to know the purpose/usage of each preset function when you are just need that function the first time. - Just follow the corresponding tutorials - for examples the ones written by me, till now or later- where these functions are introduced.
  • You can also create your own custom functions - which functions will naturally do what you like. You will be the one who defines the purpose/usage of this function.
    Don't worry - when you will know enough about functions then you will know when you need to create a custom function yourself.
When you want to create a custom function, then you need to find out that how many arguments you want to define for that function.
After that find proper names for the function and the arguments. (Basically name them as you wish. However, I highly suggest to examine the function and argument names of the preset functions, and follow the naming methods used there - see for example here.)
After that you are ready to create a custom function, as a new script entry. The entry needs to use "function" tag at the brackets. There has to be an equals sign before the "function" tag. The function name you've just found out has to be typed before the equals sign. The argument names you've just found out have to be typed in the brackets.
So eg. when you want to create the function above with "FunctionX(A, B, C)" syntax, then use this rule:

function_name = "function"(argument1_name, argument2_name, argument3_name)

Which will look like this now:

FunctionX = function(A, B, C)

Or, alternatively, just simply type "function" tag before the function syntax:

function FunctionX(A, B, C)

FunctionX will be created when the game just reads this entry of the script. - However, naturally FunctionX cannot do anything for the time being, because you didn't tell it what to do.
Below in the tutorial we will discuss that how you can add some contents to the function, so it will know what to do.

----------

Note:
Not you are the only one who can create a custom function.
I mean, see eg. how many times functions are created in the script files of Engine subfolder. As I said, you should never touch those script files, only TEN developers are editing them.
Which means TEN developers are also creating custom functions.
You ask why developers didn't make them as preset functions. Well, later in the tutorial I will tell that why developer-made custom functions exist.

Last edited by AkyV; 21-08-24 at 10:10.
AkyV is online now  
Old 01-08-24, 16:56   #4
AkyV
Moderator
 
Joined: Dec 2011
Posts: 5,074
Default

3. Function contents

You will never know the contents of the preset functions, but it is it not important, anyway. I mean, it is enough if you know the syntax of the function (function and argument names), the purpose of the function, and the meaning of the arguments. The function will work as the developers coded it.
But that is not true for your custom functions where you are the one who needs to code the function contents.

After that entry, which is for creating a function, just start typing the function contents in the further lines of the script.
If it is done, then type one more latest line with the "end" tag - which means that the lines, which belong to that function, end here:

Code:
function_name = "function"(arguments)
     function_contents
end
or

Code:
"function" function_name(arguments)
     function_contents
end
(The tabulation and the empty spaces are only to look better, but it is not necessary.)
For example:

Code:
FunctionX = function(A, B, C)
     local result = (A+B)*C
end
or

Code:
function FunctionX(A, B, C)
     local result = (A+B)*C
end
So FunctionX is really simple: it adds the value of A argument to the value of B argument, then their sum will be multiplied by the value of C argument, and the result will be set in a local variable, which has "result" name. (So in the upper line with A, B and C, those are argument names, because the function is just being created there, and the argument needs to be named, as I said above. But in the lower line with A, B and C, arguments are already named, so in that case A, B and C are argument values.) - But what we will do with the result in result variable, that won't be discussed now...

Let's say you run this function, with these argument values:

FunctionX(3, 2, 4)

What will happen in the moment when the game just reads this script line?
Well, the value of "result" variable will be 20.
Why? Because A=3, B=2, C=4, and (3+2)*4=20.

Let's make this function is a bit more complex:

Code:
FunctionX = function(A, B, C)
     local result = (A+B)*C
     SetItemCount(ObjID.UZI_AMMO_ITEM, result)
end
This function uses "SetItemCount" preset function, which sets the required amount for an item in the inventory.
The function has two arguments. The name of the first argument is "objectID", the name of the second argument is "count". (The argument names can be identified in any tutorial, which is about the syntax of this function.)
In this example the value of objectID argument is "ObjID.UZI_AMMO_ITEM", the value of count argument is the value of the result variable. In the case of running "FunctionX(3, 2, 4)" now, the value of the result variable will be still 20, so this preset function sets 20 Uzi bullets in the inventory. - So this time we will do something with the result in result variable at last...
(Yes, the order of the lines is naturally important inside the function contents: first the first line will be read by the game, then the second one etc.)

As you can see, the contents of a custom function can be really diverse: variables, preset functions - and so on.
In further tutorials it will be discussed more.

----------

Note:
Any value can be substituted with anything else, which has the same value.
See eg. the previous example:
When you don't want to be bothered by the result variable, then you can ignore it, delete it. Which means that count argument won't call "(A+B)*C" operation indirectly, i.e. via result variable. Instead, count argument will call "(A+B)*C" operation directly:

Code:
FunctionX = function(A, B, C)
     SetItemCount(ObjID.UZI_AMMO_ITEM, (A+B)*C)
end

Last edited by AkyV; 24-08-24 at 18:16.
AkyV is online now  
Old 03-08-24, 10:32   #5
AkyV
Moderator
 
Joined: Dec 2011
Posts: 5,074
Default

4. Forcing or query

As I said that there are executable scripted events and condition scripted events. So does that mean that there are executable functions and there are condition functions?
Yes, it is true, but it is not always so easy to tell that which function is which type. - I mean, see for example this function:

Code:
function MakeTinyFlame(FlameSize)
     local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
     flame:SetOCB(FlameSize)
end
This custom function first uses a preset function to identify a FLAME_EMITTER2 nullmesh object, with having flame_emitter2_117 name in the map, storing this result in the local flame variable. (Yes, textual values, like names, need to be quoted.) Then it forces an OCB value (i.e. FlameSize argument value) for this object, using another preset function. (Yes, as you can see, a preset function name is not always constant. I.e. eg. in this preset function name the "flame:" part refers to the currently selected object, i.e. the value of flame variable.)

Or, when the same contents are only in one line, using the substitution I mentioned above:

Code:
function MakeTinyFlame(FlameSize)
     TEN.Objects.GetMoveableByName("flame_emitter2_117"):SetOCB(FlameSize)
end
Eg. you run the function with 1 argument value:

MakeTinyFlame(1)

OCB 1 value for FLAME_EMITTER2 objects will make the flames half-sized. So the size of flame_emitter2_117 flame will be decreased now.
Perhaps it is clearer this way (i.e. first we create the function, then we can run it):

Code:
function MakeTinyFlame(FlameSize)
     TEN.Objects.GetMoveableByName("flame_emitter2_117"):SetOCB(FlameSize)
end

MakeTinyFlame(1)
Or see for example this function:

Code:
function CheckTinyFlame()
     local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
     flame:GetOCB()
end
Seemingly the custom function does not need an argument this time, so you will run this custom function always this way:

CheckTinyFlame()

This custom function first identifies a FLAME_EMITTER2 nullmesh object with flame_emitter2_117 name, storing this result in the local flame variable. Then it asks its OCB value, using another preset function.
Perhaps it is clearer this way (i.e. first we create the function, then we can run it):

Code:
function CheckTinyFlame()
     local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
     flame:GetOCB()
end

CheckTinyFlame()
The first function (MakeTinyFlame) is apparently an executable function, because it is clear that it does something: it forces a value.
Does that mean that the second one (CheckTinyFlame) is a condition function? I mean, conditions always check something, like "if Lara is holding pistols in the hands" or "if the flipmap is on" etc., right? I mean, CheckTinyFlame also asks something: "which is the OCB value"?
No, that is not true. I mean, CheckTinyFlame is really a query, but a condition could be only a "yes or no" question, i.e. it should start with "if...". And that question starts with something else: "which...". So CheckTinyFlame is also an executable function: it does something, it gets a value. The difference is that it does not force a value, because it works in the opposite direction: it gets a value. So MakeTinyFlame does not have an output value, while CheckTinyFlame has one.
So if you want to turn that executable query into a condition query, then you need a "yes or no" question at the end of the function - for example:

Code:
function CheckTinyFlame()
     local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
     local OCB = flame:GetOCB()
     return OCB == 1
end
Now the output data of the function (i.e. the actual OCB of "flame_emitter2_117") is stored in a local variable, called "OCB".
When you type "return" command (officially called "statement", not command), there you create another output value for the function. This value is always an answer for a "yes or no" question, which is equal to another "yes or no" question about the whole function. I.e. when you ask (anywhere in the code, but out of this function) that "if CheckTinyFlame function is true?", then the answer for this is the answer at the return entry of this function.
I mean, the "==" sign you can see at the return entry always means there is a "yes or no" question there. So "OCB == 1" means that "if the value of OCB variable is 1?" The answer is naturally yes (true) if the OCB variable value is 1, or a no (false) if the OCB variable value is not 1.
So when you type this, out of the function:

if CheckTinyFlame() == true then

that is another "yes or no" question, which means "if CheckTinyFlame function is true?", and which also means the previous "yes or no" question, i.e. "if the value of OCB variable is 1?" (I.e. "if that flame is half-sized?")
So CheckTinyFlame is a condition function now, and if you'd like to check if this condition is true or false, then you need an "if CheckTinyFlame() == true then" entry where you want to check it. (But the method to use "if" situations - including how to exactly check a condition function output - will be discussed in another tutorial.)
Perhaps it is clearer this way (i.e. first we create the function, then we can check it - so we don't need to run it):

Code:
function CheckTinyFlame()
     local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
     local OCB = flame:GetOCB()
     return OCB == 1
end

if CheckTinyFlame() == true then
(...)
"(...)" means - as I said above - that we won't discuss in this tutorial that what should be typed here.

----------

Notes:
  • Output values called "output" not only because they are the result values of a function. They are also called "output" because they can be taken out of the function, to be used (even) somewhere else. - As you can see, this is what happened with the return "yes/no" output value above: i.e. we took it out of the function, where we checked it.
    And it could be true even for executable output values. I mean, see eg. the result of "flame:GetOCB()" preset function in the executable version of CheckTinyFlame custom function. Turn it also into a variable there, naming it eg. also "OCB" variable. The variable value now can be also used (even) somewhere else, out of the function - but important that in that case you can't call the variable "local":

    Code:
    function CheckTinyFlame()
         local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
         OCB = flame:GetOCB()
    end
    (Variables without calling them "local" are "global". In another tutorial I will tell what the difference is between local and global variables in TEN. Now only keep in mind that a variable needs to be global if you want to use its value out of its function.)
  • No one said that when you turn an executable query into a condition query, then that needs to lose its executable task.
    I mean, eg. in the condition version of CheckTinyFlame function, not only the "yes/no" result of the return entry could be an output value of the function, but even the OCB variable. So it is both an executable and a condition query at the same time. - But talking about the executable task of a condition function is meaningful only if the variable is global, because you want to use it (even) out of the function.
    However, keep in mind: if you want to use an output value out of the condition function, which value is not for the answer of a "yes or no" question, then it is not enough if you check the function, you also need to run it:

    Code:
    function CheckTinyFlame()
         local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
         OCB = flame:GetOCB()
         return OCB == 1
    end
    
    CheckTinyFlame()
    
    if CheckTinyFlame() == true then
    (...)
    So, after running the function, you have just got the global OCB value you can use somewhere else. (I.e. if you need to execute any executable part in a function, then you need to run the function everyway.)
    And, if you want to run the function not for that OCB value, but simply for that yes or no result, then you can also do it. - I mean, eg. this time the value of functionResult variable will be yes or no:

    Code:
    local functionResult = CheckTinyFlame()
    If you type the "return" in the line of OCB, instead of OCB variable, then naturally you cannot use the non-existing OCB variable out of the function - but the value of functionResult variable will be the current OCB value of the flame, instead of yes or no:

    Code:
    function CheckTinyFlame()
         local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
         return flame:GetOCB()
    end
    
    local functionResult = CheckTinyFlame()
  • A short version of CheckTinyFlame condition function, when we use substitution again, not putting the result of flame:GetOCB() into OCB variable:

    Code:
    function CheckTinyFlame()
         local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
         return flame:GetOCB() == 1
    end
    Or the shortest version, even without flame variable:

    Code:
    function CheckTinyFlame()
         return TEN.Objects.GetMoveableByName("flame_emitter2_117"):GetOCB() == 1
    end
    (So these versions of the function are pure conditions, not having any other output value.)
  • A "yes or no" result can be checked even inside the function. In that case you should store it in a variable, to check the variable value. If you want this result now as the return value of the function, then the return value could be even directly this variable value:

    Code:
    function CheckTinyFlame()
         local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
         OCB = flame:GetOCB()
         local X = OCB == 1
         if X == true then
         (...)
         return X
    end
    
    if CheckTinyFlame() == true then
    (...)
    So if OCB is 1 or not, that will be stored in variable X, and then you check if this variable is true (1) or false (not 1). Now X variable value should be the return value.
    Though, as you can see, if you also check it out of the function, then you won't check X, instead you still need to check the function itself. - That is why the X variable is local: you don't need to check it out of the function.
    (Now you don't need to run the function for the "inner check". I mean, when you do the "outer check", then the "inner check" will surely also happen. - But if it is a special condition, i.e. where you want only the inner check, not having a return value, then you need to run the function:

    Code:
    function CheckTinyFlame()
         local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
         OCB = flame:GetOCB()
         local X = OCB == 1
         if X == true then
         (...)
    end
    
    CheckTinyFlame()
    (...)
    )
    Or substitution is also working here naturally:

    Code:
    function CheckTinyFlame()
         local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
         if flame:GetOCB() == 1 == true then
         (...)
         return flame:GetOCB() == 1
    end
    
    if CheckTinyFlame() == true then
    (...)
  • MakeTiny Flame has an input value, but doesn't have an output value. On the other hand, CheckTiny Flame has an output value, but doesn't have an input value.
    But it is not a rule (i.e. to have only input or output values), it is only accidental. I mean, modify eg. this version" of CheckTinyFlame, to also have an input value:

    Code:
    function CheckTinyFlame(flameObject)
         local flame = TEN.Objects.GetMoveableByName(flameObject)
         OCB = flame:GetOCB()
         return OCB == 1
    end
    
    CheckTinyFlame("flame_emitter2_82")
    
    if CheckTinyFlame("flame_emitter2_82") == true then
    (...)
    In this example CheckTinyFlame is able to run/to be checked with any object, not only flame_emitter2_117, because the object comes into the function via an argument (input) value, which argument is named as "flameObject".
  • A return statement always quits the function, so you can't type any new line for the function contents after (i.e. below) the line of return. (The exceptions will be discussed in another tutorial.)
  • The examples in this chapter are only with zero or one argument. But naturally there could be examples here even for functions with more than one arguments - for example this checks if the OCB value of one flame is bigger than the OCB value of another flame:

    Code:
    function CheckTinyFlame(flameObject1, flameObject2)
         local flame1 = TEN.Objects.GetMoveableByName(flameObject1)
         local flame2 = TEN.Objects.GetMoveableByName(flameObject2)
         return flame1:GetOCB() > flame2:GetOCB()
    end
    
    if CheckTinyFlame("flame_emitter2_117", "flame_emitter2_82") == true then
    (...)
    (Yes, this time we typed > instead of == in the return value, because "yes or no" comes with a relation now. But, we will discuss it in another tutorial, with the "if" situations.)

Last edited by AkyV; 08-10-24 at 09:43.
AkyV is online now  
Old 03-08-24, 10:35   #6
AkyV
Moderator
 
Joined: Dec 2011
Posts: 5,074
Default

5. Placing functions

Technically you can make/use functions in any of the script files. However, you should use them this way:
  • If a function is a scripted event, then that should be typed only in level script files.
  • If a function is for something else:

    • If a function is some general setting, then it should be typed in Gameflow.lua.
    • Some functions are specific settings, mostly about Lara's animations, they will be typed in Settings.lua.
    • Strings.lua and SystemStrings.lua mostly have strings, but they also have some functions, to handle these scripts.

  • As I said, you don't need to care about functions of Engine subfolder files.
So we skip now using functions in the script of Engine subfolder. Besides, we also skip now how to handle functions in Gameflow.lua, Settings.lua, Strings.lua or SystemStrings.lua (they will be discussed in the tutorials concentrating on their functions).
So in this chapter we only discuss that how to place event functions in the level script files.

Initially all the level script files are almost totally empty. Now they only have a note to introduce the route of the current file, and six empty functions (i.e. six functions which seemingly have no contents):

Click image for larger version

Name:	94.jpg
Views:	38
Size:	24.3 KB
ID:	7124

Please note that you cannot find the sixth initial function (LevelFuncs.OnUseItem) in the screenshots or the examples of the tutorial, because those are made with a previous version of TE.

Reading the script

The game always reads the scripts from top to bottom: first the first script line, then the second script line, then the third script line etc. (I've mentioned it above yet, anyway.)
Which means if two things are connected to each other in the script, then the thing, which should happen first, must be in the upper position (so it will be read sooner), and the one that should happen after that, must be in the lower position (so it will be read later).
For example, when you want to run a function, then first you need to create the function. I.e. the block with the function contents must be typed in the upper position, and the line to run the function must be typed in the lower position.
Naturally any of the six initial functions will run only when that game phase is running. Eg. "OnStart" function will run only when the game starts. So if you place a "run function" line in "OnStart" initial function, as the contents of the initial function, then that "run function" line will happen only when the level starts. - That is why I do not suggest to run a function out of the initial functions, because the game will not know exactly that in which game phase it should read a "run function" line.

Code:
-- FILE: Levels\My_level.lua

function_name(arguments)

function_name = function(arguments)
     function_contents
end

LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
The example above is useless. Because you want to run the function before you created it.

Code:
-- FILE: Levels\My_level.lua

function_name = function(arguments)
     function_contents
end

LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end

function_name(arguments)

LevelFuncs.OnEnd = function() end
In the example above the function will run, because you run it after you created it.
However, the function is not running inside an initial function, so you can't be sure that when exactly it will be running.

Code:
-- FILE: Levels\My_level.lua

function_name = function(arguments)
     function_contents
end

LevelFuncs.OnLoad = function()
     function_name(arguments)
end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function()
     function_name(arguments)
end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
In the example above the function will run, because you run it after you created it.
Besides, you can exactly tell that when the function will be running: first when the level starts, then later each time when you load a savegame in that level.

Code:
-- FILE: Levels\My_level.lua

LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function()
     function_name = function(arguments)
          function_contents
     end

     function_name(arguments)
end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
In the example above the function will run only when the level starts - so it is enough now if you define the function only inside the "OnStart" initial function, if you wish. (I.e. it is not a must.)

CONCLUSION:
Any pure "run/call function" line you want to type in the script should be typed in an initial function. Everything typed out of initial functions must be only definitions: defining function contents, defining a variable value etc. - However, definitions also can go into initial functions.
(With "pure" I want to say that you type only that line there. I mean, naturally you can type "run/call function" lines even when you type not only that line there, but having the line in definitions instead.
See above eg. "SetItemCount(ObjID.UZI_AMMO_ITEM, result)" line which runs "only" as a part of a custom function contents.
Or see above eg. "local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117" )", which is "only" to set flame variable value - also in a custom function contents this time, anyway.)

Functions called directly in the script

As I told above, initial functions will run (automatically) when their phase is just running in the game. So you don't need to run (i.e. to call) a function in the script everyway. - This is what I call "indirect calling".
So, when a function runs in the script, then that should be called "direct calling". If a function event runs in the script, that will be always considered as a global event.
For example:

Code:
-- FILE: Levels\My_level.lua

function LevelFuncs.PrintHello()
    local write = "Hello, Lara!"
    local display = DisplayString(write, 250, 250, Color(255,0,0))
    ShowString(display, 10)
end

LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function()
     LevelFuncs.PrintHello()
end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
"LevelFuncs.PrintHello" custom function is used to print "Hello, Lara" text on the screen, when the level starts. (So "LevelFuncs.PrintHello()" line to run this function is a "pure running" this time.)
It first defines in "write" variable the text (quoted) which you want to print.
Then DisplayString preset function defines the coordinates and color for the value of write variable, i.e. for "Hello, Lara" text. The function result will be stored in display variable.
Then ShowsString preset function will print the text on the screen, for 10 seconds. It gets the values (text, coordinates, color) from display variable value.

This time:
  • Now you can use either the "function_name = function(arguments)" formula or the "function function_name(arguments)" formula to create a function.
  • You can call preset functions directly via the initial functions, you don't need to place them in your custom function. - For example, to set 20 Uzi ammo in the inventory, when the level starts:

    Code:
    -- FILE: Levels\My_level.lua
    
    LevelFuncs.OnLoad = function() end
    LevelFuncs.OnSave = function() end
    LevelFuncs.OnStart = function()
         SetItemCount(ObjID.UZI_AMMO_ITEM, 20)
    end
    LevelFuncs.OnLoop = function() end
    LevelFuncs.OnEnd = function() end
Functions called indirectly in event set editors

If a function is called via event set editors (I told here how), then that is naturally an indirect calling, because the function will not be called via the script. (These function events could be either local - i.e. "volume" - or global events.)
Which means you can create the function anywhere in the script, because you don't need to define the proper script spot below that creation, where this function will run in an initial function. - For example "LevelFuncs.PrintHello" will be called this time in a (any) event set editor:

Code:
-- FILE: Levels\My_level.lua

LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end

LevelFuncs.PrintHello = function()
    local write = "Hello, Lara!"
    local display = DisplayString(write, 250, 250, Color(255,0,0))
    ShowString(display, 10)
end
But this time:
  • You need to create the function with "function_name = function(arguments)" formula. I mean, the event set editors won't recognize the function if it is created in "function function_name(arguments)" formula.
  • You can't call preset functions directly via event set editors. Even if this preset function is the only contents of the custom function, you need to place it in your custom function, which will be called via the event set editors. - For example, to set 20 Uzi ammo in the inventory.

    Code:
    LevelFuncs.UziAmmo = function()
         SetItemCount(ObjID.UZI_AMMO_ITEM, 20)
    end
  • Creating a function in an initial function is not really useful. I mean, the different game phases in the global event set editor will show all the available functions even if you want to call it eg. only in "OnStart". Or, the local (volume) event set editor naturally cannot realize game phases, it will not know eg. what "OnStart" is.
    However, if you are sure that you want to use this function only as a global event, only eg. in "OnStart" phase, then you can create the function inside that initial function - but only as an in-script information, only to you:

    Code:
    -- FILE: Levels\My_level.lua
    
    LevelFuncs.OnLoad = function() end
    LevelFuncs.OnSave = function() end
    LevelFuncs.OnStart = function()
        LevelFuncs.PrintHello = function()
            local write = "Hello, Lara!"
            local display = DisplayString(write, 250, 250, Color(255,0,0))
            ShowString(display, 10)
        end
    end
    LevelFuncs.OnLoop = function() end
    LevelFuncs.OnEnd = function() end
Functions with a direct input

Let'see how the examples above will look, when the custom functions have some input values. - First let's see how you can use direct inputs.
I called an input "direct", when exactly the input value are typed in the brackets, when you run the function.
Modify the "called in the script" example above, to have an input value, which is called directly:

Code:
-- FILE: Levels\My_level.lua

function LevelFuncs.PrintHello(write)
    local display = DisplayString(write, 250, 250, Color(255,0,0))
    ShowString(display, 10)
end

LevelFuncs.OnLoad = function()
     LevelFuncs.PrintHello("Hello, you have just loaded a savegame!")
end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function()
     LevelFuncs.PrintHello("Hello, Lara!")
end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
This time the value of "write" argument can be two different textual values:
"Hello, Lara!" will be printed on the screen when the level starts.
But "Hello, you have just loaded a savegame!" will be printed on the screen each time when you have just loaded a savegame.
(Naturally you don't need quotation marks when the value is not textual, but numerical.)

Functions with an indirect input

I called an input "indirect", when not exactly the input value are typed in the brackets, when you run the function. I.e. the value is substituted for something else.
Modify the "called in the script, input called directly" example above, to have an input value, which is called indirectly:

Code:
-- FILE: Levels\My_level.lua

function LevelFuncs.PrintHello(write)
    local display = DisplayString(write, 250, 250, Color(255,0,0))
    ShowString(display, 10)
end

local hellotext = "Hello, Lara!"

LevelFuncs.OnLoad = function()
     hellotext = "Hello, you have just loaded a savegame!"
     LevelFuncs.PrintHello(hellotext)
end
LevelFuncs.OnSave = function()
     LevelFuncs.PrintHello(hellotext)
end
LevelFuncs.OnStart = function()
     LevelFuncs.PrintHello(hellotext)
end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
Let's see what happens in this example:
  1. First the script creates two definitions: first creates PrintHello function (with its contents), then it creates the textual value for hellotext variable.
    Two notes:

    • You can swap the order of the two definitions for each other: first the variable is typed (to have the textual value of "Hello, Lara!"), then the function. - I mean the order between the definitions is not important.
      What is important is the game should read definitions before it reads where they are used - just as I said above: the function will run only if you call it after creating this function. And the function will know its input value only if you define it before the running.
    • The hellotext variable could be local this time, because it is out of any function, so it can be used in the whole script of this level.

  2. The level starts, so the game will check the contents for "OnStart" phase of the script, to get to know that what things need to be called now.
    That is why the game will call "LevelFuncs.PrintHello(hellotext)" function. The only function argument is named "write", whose value is the value of hellotext variable this time: it is "Hello, Lara!" now. It will be printed on the screen now, for 10 seconds.
  3. You play the game, and then you make your first save.
    Now the game will check the contents for "OnSave" phase of the script, to get to know that what things need to be called now.
    That is why the game will call "LevelFuncs.PrintHello(hellotext)" function again. The hellotext variable value is still "Hello, Lara!", so that will be printed again on the screen, for 10 seconds.
  4. After some further playing, you decide that you load that first savegame.
    Now the game will check the contents for "OnLoad" phase of the script, to get to know that what things need to be called now.
    That is why the game first will set a new value for hellotext variable, which is "Hello, you have just loaded a savegame!" this time. Then it will call "LevelFuncs.PrintHello(hellotext)" function again. The hellotext variable value is already "Hello, you have just loaded a savegame!", so that will be printed on the screen this time, for 10 seconds.
  5. After some further playing, you decide that you make your second save.
    Now the game will check the contents for "OnSave" phase of the script again, to get to know that what things need to be called now.
    That is why the game will call "LevelFuncs.PrintHello(hellotext)" function again. The latest definition of hellotext variable was global, that is why the new variable value is valid for the whole level, not only for the function where the variable was defined the latest. (I.e. "Hello, you have just loaded a savegame!" value is not only for "OnLoad" function.) So "Hello, you have just loaded a savegame!" will be printed on the screen this time, for 10 seconds. (However, this text now is naturally dumb in this situation...)
Or here is a nicely handled version for the example, with more than one variables to serve that only one argument - plus, also using numerical arguments for text position values:

Code:
-- FILE: Levels\My_level.lua

function LevelFuncs.PrintHello(write, PosX, PosY)
    local display = DisplayString(write, PosX, PosY, Color(255,0,0))
    ShowString(display, 10)
end

local hellostart = "Hello, Lara!"
local helloload = "Hello, you have just loaded a savegame!"
local hellosave = "Hello, you have just saved a savegame!"
local A = 250
local B = A * 2

LevelFuncs.OnLoad = function()
     LevelFuncs.PrintHello(helloload, A, B)
end
LevelFuncs.OnSave = function()
     LevelFuncs.PrintHello(hellosave, A, B)
end
LevelFuncs.OnStart = function()
     LevelFuncs.PrintHello(hellostart, A, B)
end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
Input values for event set editor functions

In the case of functions in event set editors, you don't need to run functions in the script. So how can you set an input value?

Well, it is a bit tricky. - I mean, you need to type that input value in the little Argument window of the event set editor, on that page where the function is called. - For example:

Click image for larger version

Name:	95.jpg
Views:	52
Size:	26.1 KB
ID:	7126

But:
  • As you can see, the text (Hello, Lara!) in the Argument window isn't quoted this time.
  • Only textual input values can be set this way. I.e. if the function has a numerical argument, then you can't call the function via event set editors. (I.e. you should handle the numerical values only inside the function, as constant function contents, to be able to call that function here.)
  • Only one textual argument can be set this way. I.e. if the function has more than one textual arguments, then you can't call the function via event set editors. (I.e. you should handle the other textual values only inside the function, as constant function contents, to be able to call that function here.)
  • When a function is called via event set editors, then its first argument is engaged for the trigger activator. (Even if the event set is global, not having an activator.)
    This engaged argument isn't necessary to mention when you want to run a function via event set editors, but without using that only one textual argument. (I.e. when you create a custom function in the script for event set editors now, then just use empty brackets in the script line where the function is created, as if there weren't an argument at all - see eg. the "LevelFuncs.PrintHello = function()" function creation above.)
    But this engaged argument is necessary to mention when you want run a function via event set editors, with using that only one textual argument. As I said, the engaged argument is always the first one, so the only one textual argument will be the second argument. Name both arguments now as you wish.
    And now let's modify the "called in event set editor" example, too see how it looks:

    Code:
    -- FILE: Levels\My_level.lua
    
    LevelFuncs.OnLoad = function() end
    LevelFuncs.OnSave = function() end
    LevelFuncs.OnStart = function() end
    LevelFuncs.OnLoop = function() end
    LevelFuncs.OnEnd = function() end
    
    LevelFuncs.PrintHello = function(activator, write)
        local display = DisplayString(write, 250, 250, Color(255,0,0))
        ShowString(display, 10)
    end
    Now activator, the engaged first argument, will be automatically handled the way as it is coded by the developers, so you don't need to care about it. The second argument is called write, its value is coming from the Argument window of the event set editor. (Naturally when you call the function in another page of the event set editors, then you can type there even another text in the Argument window, to make the function print another text there.)
Using the outputs of executable functions

Let's see the example again when we created a global OCB variable in CheckTinyFlame function, to use the variable value out of the function.
So we have the actual OCB value of flame_emitter2_117 in OCB global variable. We create another function as well, to force this OCB variable value on flame_emitter2_118 object, to make it its OCB value, too, when the game has just been loaded. I.e. the input value of the new function is called "forcedValue", its actual value will be the value of that OCB variable.
What will happen is: when you load a savegame, then you can be sure that the OCB of the two objects will be surely the same, even if you changed the OCB of flame_emitter2_118 with some other method in the meantime:

Code:
-- FILE: Levels\My_level.lua

function CheckTinyFlame()
     local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
     OCB = flame:GetOCB()
end

function ForceOCB(forcedValue)
    TEN.Objects.GetMoveableByName("flame_emitter2_118"):SetOCB(forcedValue)
end

LevelFuncs.OnLoad = function()
     CheckTinyFlame()
     ForceOCB(OCB)
end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
Naturally you don't know the name of the output value of a preset function (or perhaps you did not define an output value in a variable in the contents of your custom function, defining the output as a "return", like in the case of "return flame:GetOCB()" above). In this case you can define the result of the preset/custom function in a variable, to use it as its output value:

Code:
-- FILE: Levels\My_level.lua

LevelFuncs.OnLoad = function()
     function ForceOCB(forcedValue)
         TEN.Objects.GetMoveableByName("flame_emitter2_118"):SetOCB(forcedValue)
     end

     local OCB = TEN.Objects.GetMoveableByName("flame_emitter2_117"):GetOCB()

     ForceOCB(OCB)
end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
But you don't need to place that result value in a variable - i.e. you can type even the whole function for an input value, so the game will use its result as that input value:

Code:
-- FILE: Levels\My_level.lua

LevelFuncs.OnLoad = function()
     function ForceOCB(forcedValue)
         TEN.Objects.GetMoveableByName("flame_emitter2_118"):SetOCB(forcedValue)
     end

     ForceOCB(TEN.Objects.GetMoveableByName("flame_emitter2_117"):GetOCB())
end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
(The executable output value of one function is seemingly an indirect input value for the other function. The method of indirect inputs, as I said above, won't give an available input for event set editor functions.)

Using the outputs of condition functions

As I said, you don't need to run the function, if it is a condition with a return value, it is enough if you check it out of the function.
But I didn't still say that this check must happen in another function, which is executable. Which is logical, because "if condition function event is true then executable condition event will happen". - For example:

Code:
-- FILE: Levels\My_level.lua

function CheckTinyFlame()
     local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
     local OCB = flame:GetOCB()
     return OCB == 1
end

function DoSomething()
     if CheckTinyFlame() == true then
     (...)
end

LevelFuncs.OnLoad = function()
     DoSomething()
end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
Or with a direct input value for "DoSomething" function:

Code:
-- FILE: Levels\My_level.lua

function CheckTinyFlame(Emitter)
     local flame = TEN.Objects.GetMoveableByName(Emitter)
     local OCB = flame:GetOCB()
     return OCB == 1
end

function DoSomething(Fire)
     if CheckTinyFlame(Fire) == true then
     (...)
end

LevelFuncs.OnLoad = function()
     DoSomething("flame_emitter2_117")
end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
Or with an indirect input value for "DoSomething" function:

Code:
-- FILE: Levels\My_level.lua

local Flame1 = "flame_emitter2_117"
local Flame2 = "flame_emitter2_118"

function CheckTinyFlame(Emitter)
     local flame = TEN.Objects.GetMoveableByName(Emitter)
     local OCB = flame:GetOCB()
     return OCB == 1
end

function DoSomething(Fire)
     if CheckTinyFlame(Fire) == true then
     (...)
end

LevelFuncs.OnLoad = function()
     DoSomething(Flame1)
end
LevelFuncs.OnSave = function()
     DoSomething(Flame2)
end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
Or a version when "DoSomething" function will always work on the object which is set for "CheckTinyFlame" function:

Code:
-- FILE: Levels\My_level.lua

local Fire = "flame_emitter2_117"

function CheckTinyFlame(Emitter)
     local flame = TEN.Objects.GetMoveableByName(Emitter)
     local OCB = flame:GetOCB()
     return OCB == 1
end

function DoSomething()
     if CheckTinyFlame(Fire) == true then
     (...)
end

LevelFuncs.OnLoad = function()
     DoSomething()
end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
Or a version, when "CheckTinyFlame" can be checked with different argument values, but "DoSomething" function will check it only as constant contents - however, the argument naturally is still necessary, because now "CheckTinyFlame" will be checked even with a different flame in "DoDifferentThing" function:

Code:
-- FILE: Levels\My_level.lua

function CheckTinyFlame(Emitter)
     local flame = TEN.Objects.GetMoveableByName(Emitter)
     local OCB = flame:GetOCB()
     return OCB == 1
end

function DoSomething()
     if CheckTinyFlame("flame_emitter2_117") == true then
     (...)
end

function DoDifferentThing()
     if CheckTinyFlame("flame_emitter2_118") == true then
     (...)
end

LevelFuncs.OnLoad = function()
     DoSomething()
end
LevelFuncs.OnSave = function()
     DoDifferentThing()
end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
Or a version for calling "DoSomething" function in event set editors (I added "LevelFuncs" tag to the function name with a good reason - soon you will understand):

Code:
-- FILE: Levels\My_level.lua

function CheckTinyFlame()
     local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117")
     local OCB = flame:GetOCB()
     return OCB == 1
end

LevelFuncs.DoSomething = function()
     if CheckTinyFlame() == true then
     (...)
end

LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
Or a version for calling "DoSomething" function in event set editors, but this time typing flame_emitter2_117 (or another input object name) in Argument window of the event set editor:

Code:
-- FILE: Levels\My_level.lua

function CheckTinyFlame(Emitter)
     local flame = TEN.Objects.GetMoveableByName(Emitter)
     local OCB = flame:GetOCB()
     return OCB == 1
end

LevelFuncs.DoSomething = function(activator, Emitter)
     if CheckTinyFlame(Emitter) == true then
     (...)
end

LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnLoop = function() end
LevelFuncs.OnEnd = function() end
----------

Notes:
  • The custom functions in this chapter are only with zero or one argument. But naturally there could be examples here even for functions with more than one arguments - for example:

    Code:
    -- FILE: Levels\My_level.lua
    
    function CheckTinyFlame(Emitter1, Emitter2)
         local flame1 = TEN.Objects.GetMoveableByName(Emitter1)
         local flame2 = TEN.Objects.GetMoveableByName(Emitter2)
         return flame1:GetOCB() > flame2:GetOCB()
    end
    
    function DoSomething(EmitterA, EmitterB)
         if CheckTinyFlame(EmitterA, EmitterB) == true then
         (...)
    end
    
    LevelFuncs.OnLoad = function()
         DoSomething("flame_emitter2_117", "flame_emitter2_82")
    end
    LevelFuncs.OnSave = function() end
    LevelFuncs.OnStart = function() end
    LevelFuncs.OnLoop = function() end
    LevelFuncs.OnEnd = function() end
  • However, you can check the function sometimes out of an executable function - if this check doesn't control an event in the game, but it controls "only" some thing in the script, like to set a variable value.
  • As you can see, you can check/call a custom function even inside another custom function (see eg. the example of LevelFuncs.DoSomething and CheckTinyFlame above.)
  • When you type all of these in the scripts, then you can see that different parts of what you type will have different character colors. Don't bother about them, in another tutorial we will discuss it.
  • You don't need to use strings LUA files for any text printed on the screen, which is typed in level script files.
  • So in the case of functions for event set editors we can type the function creations even after (below) the initial functions. Under some circumstances it works even if the function will be called in the script - but I don't suggest it, it could be an error source now.
    In fact, here or there you may encounter even some other scripted situations which will work when you try them, but I did not mention them, though. All of these ignorances are perhaps not accidental: those situations may fail many times.
    So always test your script carefully!
  • After this chapter, I think you can easily exactly understand the scripted examples I wrote here.
  • OnLoop initial function works a bit differently in the title. (Naturally, I mean the "loop" is only for the "real" levels, meaning the effective playing time of the level. Which obviously doesn't exist in title.) So the function is called "OnControlPhase" there - but we will discussed it not in this tutorial.

Last edited by AkyV; 13-11-24 at 07:26.
AkyV is online now  
Old 04-08-24, 11:47   #7
AkyV
Moderator
 
Joined: Dec 2011
Posts: 5,074
Default

6. Function name structure

Custom function names

As I said above, name the custom functions as you wish - except: probably you'd better follow the usual naming pattern of developers.

However, it is obviously a good question that why I thought it was important to add "LevelFuncs" tag to "DoSomething" function in the previous chapter, when I wanted to call the function in event set editors?
I said, event set editors are unable to realize preset functions, they will realize only custom functions - if the function is created with the "function_name = function(arguments)" formula. But I still did not say that a custom function can be realized in event set editors, only if its name has that "LevelFuncs" tag:

LevelFuncs.function_name = function(arguments)

A custom function created with the formula above will be realized by event set editors.
However, this custom function can be also called in the script.

function_name = function(arguments)
function function_name(arguments)
function LevelFuncs.function_name(arguments)

A custom function created with the formulas above will not be realized by event set editors.
These custom functions can be called only in the script.

However, as you can see, even if the event set editors realize a function, they never repeat the "LevelFuncs" tag in the editor: eg. LevelFuncs.DoSomething will be printed there simply only as DoSomething.

Preset function names

Preset functions are named differently. They are placed nicely in a "function name hierarchy":
  • There is a "main name group" ("master table", as the developes would call it), where all the preset functions belong to. This main group is called "TEN".
  • TEN main group has some "name groups" ("tables" or "modules", as the developes would call them), where preset functions are groupped.
  • Some of the name groups also have some "name subgroups" ("classes", as the developes would call them), where preset functions of that group are "subgroupped".
For example:
  • TEN.Objects: it is a module for things which can be identified in the map by some ID (Moveable or Static objects, rooms, cameras, sinks, sound sources, trigger volumes).
  • TEN.Objects.GetMoveableByName(name): this is a direct function of TEN.Objects module (i.e. you don't need to use the "subgroup" classes of the module to use this function), identifying a Moveable object, by the unique name it has in the map. - It also works with narrowed formulas, so you don't need to name the master table or the module, when you want to run their functions:

    • Objects.GetMoveableByName(name)
    • GetMoveableByName(name)

  • TEN.Objects.Moveable: this class is to run/check different functions on a specific Moveable object, identified previously eg. with the GetMoveableByName(name) function.
  • The class names are always there in the name of the functions, which are available directly in that class. But the master table and module names aren't mentioned now. - For example, for a Moveable class function:

    Moveable:GetOCB()

    But it is "only" the function syntax. When you write the script, then - as I said above, anyway - a specific thing needs to be named, if you see the class name in a function, followed by a ":" sign. - For example:

    local flame = TEN.Objects.GetMoveableByName("flame_emitter2_117" )
    flame:GetOCB()

    The first preset function (directly available via Objects module) identifies the Moveable object having "flame_emitter2_117" name, placing the identification in flame variable. Then the second preset function (directly available via Moveable class) will get the current OCB value of the object stored in flame variable. - So "class name with :" says: "type the thing here which is the subject of this function. This subject is defined in another function. This other function is available directly in the module of this class". And for a "Moveable" class name naturally a Moveable object needs to be defined, as a subject.

    But it is not always fully true. I mean, sometimes the subject of the second preset function isn't defined in its module. - For example:

    local print = TEN.Strings.DisplayString("Hello, Lara!", 500, 500, Color(255,0,0))
    print:SetColor(Color(0,255,0))
    TEN.Strings.ShowString(print, 10)


    The first function (DisplayString) looks as if it were placed directly in TEN.Strings module. But it is not true, in Strings module you can only find DisplayString class, and DisplayString class stores DisplayString function. (So if a function name looks like if it were placed directly in a module, but it is not, being directly in a class, then the function name is the same as the class name.) - For you, the builder it doesn't mean anything, anyway. Just imagine the function as if it were placed directly in TEN.Strings module. And that's all.
    Seeing the second function name, that also should be placed in DisplayString class, to set a new color for the text stored in print variable. - That is true, the function syntax looks this way:

    DisplayString:SetColor(color)

    I.e. this time not a Moveable object is defined for Moveable class, but a "string-to-be-displayed-on-the-screen" is defined for DisplayString class. (Naturally it is meaningless to use this function in this situation, because you have just defined its color in the first function. Instead, the good 0,255,0 color should be set in the first function, not typing the second function.)
    The third function will print the text stored in print variable, on the screen, for 10 seconds - using the color of the second function. (Because the game read the second function later, overwriting the color of the first function, which was read before that.) - As you can see with this third function: if a function is placed directly in a module, that function can exist not only to define a subject for a function directly in a class. Just like this third function, functions like that sometimes just simply do their own tasks.
But you can have some questions:
  • Q: When a custom function doesn't have LevelFuncs tag, and when someone uses a preset function without naming the master table and the module, then the functions looks the same way in a line which runs them. Eg. "DoSomething(Fire)" or "GetMoveableByName(Emitter1)". How can I distinguish them from each other?
    A: Well, when a fuction is custom, then you surely need to find its creation, its contents in the same script. Besides, when a function is preset, then it will be surely introduced in some tutorials.
  • Q: How will I know that when I see eg. "TEN.Objects.GetMoveableByName(name)" function or "TEN.Strings.DisplayString("Hello, Lara!", 500, 500, Color(255,0,0))" function, then which is placed directly in a module, and which is placed directly in a class?
    A: As I said, you don't need to know that if you want to use these functions, simply just use them as if they all were directly in a module. However, if you are curious, then different tutorials will explain the exact syntaxes.

----------

Notes:
  • The six initial level script file functions seemingly also have "LevelFuncs" tag. So should they be called technically also custom functions? It doesn't matter, we won't discuss it.
  • The name hierarchy is also available for custom functions, if you want - but you need to set "LevelFuncs" tag (optionally), and you can use only maximum one hierarchy level below LevelFuncs. (But these functions seem unavailable for event set editors.)
    What matters is the game needs to read the definition for this hierarchy before it reads the creations for functions of this hierarchy. - For example:

    Code:
    LevelFuncs.PrintAnything = {}
    
    function LevelFuncs.PrintAnything.PrintHello()
        function_contents
    end
    
    function LevelFuncs.ForceOCB(ObjectforForcing)
        function_contents
    end
    
    LevelFuncs.PrintAnything.PrintOCB = function(ObjectofOCB)
        function_contents
    end
    But you can't use "Engine" or "External" names for this level, the names are reserved by the developers.
    (Without "LevelFuncs" tag, the hierarchy and the function names look like this:

    PrintAnything = {}

    function PrintAnything.PrintHello()

    PrintAnything.PrintOCB = function(ObjectofOCB)


    )
  • In TEN.Objects.LaraObject class the function syntaxes will naturally always start with "LaraObject:", for example:

    LaraObject:GetAir()

    This time you do not need to define this function subject previously anywhere, the value of LaraObject will be naturally always simply just Lara, when you run/check the function:

    Lara:GetAir()

    However, the general Moveable object functions of TEN.Objects.Moveable class are naturally also available for Lara, who is also a Moveable object. In her case you don't need to identify Lara subject previously, simply use "Lara" instead the "Moveable" tag of the syntax. - For example:

    Moveable:GetState()

    This is the syntax of this function, useable for all the Moveable objects.

    Lara:GetState()

    And this is how you can get information about Lara's current state.

Last edited by AkyV; 04-11-24 at 20:09.
AkyV is online now  
Old 20-08-24, 11:48   #8
AkyV
Moderator
 
Joined: Dec 2011
Posts: 5,074
Default

7. Nodes and functions

There are two connections between nodes and functions.

Node events defined by functions

As I said above, not you are the only one who can create a custom function. Many of them are initially created - naturally by developers - in the LUA files of Engine subfolder, which you should never touch.
The script files we are interested in now, are in NodeCatalogs sub-subfolder here: (almost) each of them is for a group, which group you can find in the node menu when you right-click on the node editor surface, to add a node to an event set.
You can find eg. this in Moveable.lua there (which LUA is for "Moveable state" and "Moveable parameters" group):

Code:
-- !Name "If position of a moveable is within range..."
-- !Section "Moveable parameters"
-- !Description "Checks if moveable's current position is within specified range."
-- !Description "If single-dimension check is needed, set other dimensions to values well out of level bounds."
-- !Conditional "True"
-- !Arguments "NewLine, Moveables"
-- !Arguments "NewLine, Vector3, [ -1000000 | 1000000 ], Upper position bound" "NewLine, Vector3, [ -1000000 | 1000000 ], Lower position bound"

LevelFuncs.Engine.Node.TestMoveablePosition = function(moveableName, pos1, pos2)
	local pos = TEN.Objects.GetMoveableByName(moveableName):GetPosition()
	return (pos.x >= pos1.x and pos.x <= pos2.x and
		pos.y >= pos1.y and pos.y <= pos2.y and
		pos.z >= pos1.z and pos.z <= pos2.z)
end
What you can see here is:
  • This is for the condition node you can find in the node menu with "If position of a moveable is within range..." name.
  • The task of the node is defined via a custom function which name also has LevelFuncs tag, meaning that the function should be recognized by the event set editors. The function name is in a hierarchy, also having that reserved "Engine" level. The "Node" level in the name is referring that the function needs to be recognized by nodes.
  • These custom functions use preset functions and other operations to define the function contents.
So, as you can see, nodes and functions are not independent from each other, because the contents of a node is always defined via script lines (of Engine subfolder LUA files).
I.e. if you want, you can do exactly what a node event does, if you use the proper scripting instead of nodes.

Export nodes into the script

Just like when you exported a TRNG trigger into the script, you can export node contents into the script.
Just add this node in the event set editors to an event set (just temporarily, because you don't want to keep this node this time), then click on the "Pages" ("Export Lua script to clipboard") button at the bottom of the event set editor panel.

Click image for larger version

Name:	96.jpg
Views:	46
Size:	43.0 KB
ID:	7216

Now go into the level script file of this level, then hit CTRL+V. The contents of this node will be pasted there, as the contents of a new custom function (the first "end" belongs to the "if"):

Code:
LevelFuncs.ExportedNodeFunction = function(activator)
	if (LevelFuncs.Engine.Node.TestMoveablePosition("animating14_5", TEN.Vec3(1024,2048,4096), TEN.Vec3(2048,4096,8192))) then
	end
end
Notice that the pasted node contents were pasted in the form of checking the function which function was defined for this node event in Moveable.lua (LevelFuncs.Engine.Node.TestMoveablePosition).
(Naturally it will not check but run the function, if it is not a condition node event, but an executable one.)

What you can do here is:
  • The contents use the constant values set in the node previously. If you want to run the function with non-constant values, then you need to modify the contents, to use arguments instead of the constant values.
  • The pasted node function name can be shortened, but you need to use the lowest level of the hierarchy (Node) everyway:

    Engine.Node.TestMoveablePosition
    Node.TestMoveablePosition


    These name versions will also work.

    TestMoveablePosition

    This name version will not work.
  • The activator argument is named here. As I said above, you need to name this first argument only when you also use a textual second argument, and only if you want this function to be recognized by the event set editors. So feel free to make empty brackets here now ("(activator)" → "()").
  • The "ExportedNodeFunction" function name is only temporary. All the custom functions made from a node get this general name. So modify this name for any custom name which is specific for this custom function. (Naturally you don't need to keep "LevelFuncs" tag in the function name, if you don't want to use it in the event set editors, or if you don't want a name hierarchy for this function.)
  • Extend the function contents with further script lines, if you want. (Eg. this exported "if" block is naturally not enough, you obviously need to type in the "if" block that what this condition should be executed, when being true.)
  • If you exported only because you want to use the exported contents somewhere else, then copy/paste it there, and then delete all the exported lines.
  • If you are a good TEN scripter, then you don't need to export a node into the script: because node events can be all available in the script by directly, typing a function there.
But keep in mind that if an event set editor page (i.e. the event set at a specific game/trigger phase) has more than one nodes, then always all the nodes, i.e. the full contents of that editor page will be exported into the script, when you use the "Pages" button.
See for example, that how this complex example looks exported into the script:

Code:
LevelFuncs.ExportedNodeFunction = function(activator)
	if (LevelFuncs.Engine.Node.TestLaraWeaponType(1)) then
		if (LevelFuncs.Engine.Node.TestLaraHandStatus(4)) then
			LevelFuncs.Engine.Node.EnableMoveable("flame_emitter_a", 0)
		else
			if (LevelFuncs.Engine.Node.TestLaraHandStatus(0)) then
				LevelFuncs.Engine.Node.EnableMoveable("flame_emitter_b", 0)
			else
				LevelFuncs.Engine.Node.EnableMoveable("flame_emitter_c", 0)
			end
		end
	end
end
As you can see it is not simply pasting all the nodes of the page, i.e. the export also kept the exact connections between the nodes - so it authentically exported the whole event set there.

----------

Note:
As you can see - see TEN.Vec3(...) functions - some preset functions have different name hierarchy than I introduced them in the previous chapter. Never mind, there are always some exceptions - there will be tutorials for them, where we will discuss them.

Last edited by AkyV; 23-11-24 at 13:00.
AkyV is online now  
Closed Thread

Bookmarks

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 19:58.


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.