TRNG Scripting Tutorial Series - Tomb-Raider-Level-Editor/Tutorials GitHub Wiki
Written by MarlenaCrystal and Krystian
Krystian and I wanna introduce you to a little video tutorial series we were working on for some time by now. Instead of just having a wall of text, we thought it would be much more appealing to have the input of a text also in form of a video. A lot of people and possibly a lot of new people in the community still don’t dare to dive into the scripting of TRNG, which is a bit tricky sometimes, but if you understand it, it gets easier and easier and it’s fun to learn. TombEngine will do a complete different approach in the future by switching to a completely different script language, which will be useful even outside of Tomb Raider. This doesn’t mean, that TRNG scripting will die, though, as this is, for now, still a convenient way of building and scripting levels for many years by now.
The tutorials will be featured in this thread in text form, but also with a link to the video, explaining the same input, but with visuals and talking. The first tutorial will be the base for all the possible future tutorials since it will cover the main features of TRNG scripting and explaining what the different kind of commands do and how to research for them any further.
In the future we might add practical examples, like tutorials for actual features and how to set them up, how to script them to be implemented in the game
https://www.youtube.com/watch?v=eytOZlIX88k
Below is the accompanying text-form of the tutorial:
Customize
This command allows to make various small modifications to the game. These modifications can affect a specific feature or the game as a whole. This could be something that was already present in the original game (such as the number of secrets) or affect a new gameplay feature added with TRNG. Usually these commands work on their own, i.e. they don’t need any additional commands to be functional.
The syntax:
The Customize command’s syntax is always of the following form:
Customize= CUST_ABC, {arguments list}
CUST_ABC is one of several mnemonic constants, which you can check in the Reference panel of NG Center or TombIDE. Once you placed the first CUST_ mnemonic constant, the syntax of the rest of the command will change, giving an arguments list of various length, which will be hinted by both NG Center and TombIDE.
Placement:
Most Customize scripts can be placed in the [Level] section of the script and will be valid only for that level specifically. But a select few types of CUST_ can be placed in the [Title] level section, which will make them applicable to the Title level and/or the game as a whole. For example: CUST_SET_SECRET_NUMBER works differently if you place it in [Title] (sets global secrets count for the whole adventure, changing the original 70 figure of TR4) and if you place it in [Level] (setting the number of secrets to be found in that particular level).
Examples:
- CUST_SET_SECRET_NUMBER, already mentioned above, allows to change the number of secrets in a specific level (if placed in a [Level] section) or for the entire adventure (if placed in the [Title] section):
Customize= CUST_SET_SECRET_NUMBER, 3
This will set the secrets count, visible in the statistics screen, to 3 for the current level.
- CUST_FLARE is an example of a command with several arguments. It allows to modify some characteristics of flares, such as the color, duration of burning (lifetime), the intensity/radius of light given off, but also modify some graphics of the burning flare, e.g. add burning smoke or sparks to the flare, to mimic the flares of Tomb Raider 3. This is achieved through addition of FFL_ flags, which you can look up in the Reference section.
Customize= CUST_FLARE, Flare flags (FFL_ ), Lifetime in seconds (30 default), Red (0-255), Green (0-255), Blue (0-255), Intensity (16 default)
Parameters
This command is similar to the Customize command explained before. It has the PARAM_ mnemonic constant which will change the remaining syntax of the script with a variable number of arguments. The important distinction is that while there can only be a single CUST_ of a particular type under a [Level] section, you can have numerous PARAM_ of a given type, which are set apart by an ID number. It can host specific types of numbers that describe a certain feature or property of that feature. Generally, unlike Customize, they don’t work alone, but must be used in tandem with NG triggers, such as flipeffects, action triggers or conditions.
Syntax:
Parameters= PARAM_ABC, IdParameterList, {arguments list}
PARAM_ABC is one of several mnemonic constant for parameters, which you can look up in the Reference. The {arguments list} will again change, depending on the type of **PARAM_ **constant.
**IdParametersList **is a numerical identificator which is used to set apart different **PARAM_ **instances of the same type, when used in triggers. It can any positive number, such as 1, 2, 17 or 234.
Parameters script commands are placed only in [Level] sections and are relevant for that level (repeat the command in a different [Level] section to have it in another level).
Examples:
As mentioned above, Parameters can only be used together with particular triggers.
- **PARAM_PRINT_TEXT **can be used together with Flipeffect 203 or Flipeffect 210 (and some other flipeffects). It describes the formatting data for a line of text shown on screen, and in the case of the mentioned flipeffects, it will be one of the **ExtraNG **strings.
Parameters= PARAM_PRINT_TEXT, IdPrintText, Color(CL_ ), Font Type(FT_ ), Blink Time, Durate Time, X coordinate, Y coordinate
**IdPrintText **will allow to differentiate the **PARAM_PRINT_TEXT **in the drop down list of the Flipeffects’ (E)xtra field.
Color(CL_ ) allows to specify one of a few CL_ color constants for the color of the printed text (e.g. CL_WHITE or CL_BLUE). You can check the available CL_ flags in the Reference.
Font Type(FT_ ) allows to change some other characteristics of the printed text, such as the position on the screen (FT_BOTTOM_CENTER, FT_TOP_RIGHT), how big characters are printed (FT_NARROW_CHARS, FT_SIZE_HALF_HEIGHT), if they blink on/off (FT_BLINK_CHARS). Look up FT_ options in the Reference under Mnemonic Constants.
Blink Time is only valid if you have used **FT_BLINK_CHARS **in the previous field, and describes the interval of how rapidly the text will blink. It can be a power of 2 value, so 1, 2, 4, 8, 16, 32, 64 or 128.
Durate Time describes how long the text will be visible on screen, in seconds, after which it will disappear.
X coordinate, Y coordinate describe the position on the screen for the text, but more precisely than any of the FT_ flags for text position. It gives the ability to set text position on the screen as if on a 1024 x 768 display, giving units 0-1024 for the X coordinate (from left to right) and 0-768 for the Y coordinate (from top to bottom).
TriggerGroups
The **TriggerGroup **script command is a very common and useful command. It is used for storing exported script triggers. However, classic TRLE triggers cannot be exported. This is only possible with TRNG triggers, which are: flipeffects, action triggers and conditions. When you export such a trigger, you get something that looks like this:
$2000, 48, $6E (this is an exported flipeffect) $5000, 728, $E (this is an exported action trigger) $8000, 103, $1E (this is an exported condition)
Whatever type the trigger is, it’s always a set of 3 values separated by commas. The values will be different depending on the kind of trigger and the parameters or options chosen in the trigger. However, these exported script triggers need a “vessel” for carrying them, they can’t be used directly by themselves in the script. And this necessary “vessel” is the **TriggerGroup **command.
Syntax:
**TriggerGroups **are placed in a [Level] section of the script and are relevant to that level.
TriggerGroup= IDnum, {list of trigger triplets}
Below you have an example of a real TriggerGroup as you would see it in script:
TriggerGroup= 16, $2000, 375, $9, $2000, 306, $104, $2000, 127, $3
This is the generic syntax of TriggerGroup. **IDnum **is a positive number which you use to refer to a particular TriggerGroup (and thus the triggers that it hosts). The ID numbers are what set TriggerGroups apart. You specify this ID when using Flipeffects for activating a triggergroup, but also inside other script commands, as you’ll see later in this tutorial.
Then after the IDnum, what’s left is an array of the triple-valued script triggers. There can be just one single triplet or several triplets. For all practical intents, you will probably never need to worry about running into limits with how many exported triggers you have in a single trigger group. A good practice is giving short descriptions of what a particular triggergroup does, so to not get confused when looking at it later. You can add a comment/note after the main script command by using the ; (semicolon) character.
When a TriggerGroup is activated, it will read the trigger triplets from left from right and activate them consecutively. But importantly, in practical considerations, it’s done instantaneously, so there’s no noticeable delay between executing the first trigger and the last. You can use this when you need to perform a sequence of various triggers in a flash, as if they were all part of one single trigger. TriggerGroups can be reused infinitely many times in different parts of the script. There are however certain modifiers that can change how the triggergroup works or how the trigger order is read by TRNG, but this may be covered in another video.
Conditions
Conditions are not really a type of script command, but the concept of conditions is important in TRNG scripting, so it will also be covered. Conditions, in a basic sense, are what allow to introduce an element of decision into a script. For example, say you want to open a door, but only once a group of enemies have been killed. Or another example, display a certain string of text, but only if Lara’s health is sufficiently low (e.g. “I need to heal”). TRNG introduces the concept of such conditions to scripting.
Conditions are often achieved through exporting Condition type triggers to the script. As before, these exported conditions are added to TriggerGroups. The following logic is used for TriggerGroups containing conditions:
TriggerGroup= 27, $8000, 200, $21D, $8000, 103, $1E ; Lara’s HP below 200, Lara standing still
It starts by reading the first condition triplet. The engine checks if this condition is true (e.g. Lara’s health IS below 200 HP) or false (Lara’s health IS NOT below 200 HP). If it was true (Lara’s health was 150 HP, which is less than 200), the TriggerGroup moves to the next condition triplet and checks it the same way. Otherwise, if the condition turns out false (because Lara’s health was 400, thus above 200), it will no longer continue with reading the other condition trigger triplets and the result for the whole trigger group is considered to be false (even if the remaining condition trigger(s) could have been true).
The example is checking if Lara’s health is below 200 AND that she is currently doing the standing still animation (and not running, walking or jumping). The outcome of the above TriggerGroup can only be true if both conditions are satisfied at once, and not just one of them. This is useful to create more demanding and complex conditions out of simple ones.
The default behavior of conditions in triggergroups is the one described before (all conditions have to be true at once). It is possible, through special logical modifiers, to change this behavior so that meeting just one condition (or groups of conditions) out of a list of many is sufficient to satisfy the condition. But this may be covered in a later tutorial.
The TriggerGroup example illustrated before had only condition triggers in it, making it a pure conditional TriggerGroup. This doesn’t have to be the case, however. It is possible to have a mix of flipeffects or action triggers and also conditions (in fact, for some complex types of conditions this is necessary, but we won’t focus on this now). In such cases, it’s just good to remember that TriggerGroups are read from left to right, and both the regular triggers and conditions will be evaluated in such order.
Some commands can have implicit conditions built into them, occasionally making the need for condition triggers in TriggerGroups redundant. But you can also still supplement additional condition triggers in a TriggerGroup, to make the overall condition more demanding.
Global Triggers
Global Trigger is another very useful type of script command. The idea behind it is that it allows to create a type of trigger, that isn’t placed in the editor and doesn’t need to be activated by stepping onto a square in the level. Instead, it will be in the background, monitoring for a specific event in the game to happen, upon which it will activate. The activation of this triggers is global, rather than local (tied to a specific square in the editor), hence the name. This allows to introduce many interesting gameplay possibilities that would be either impossible or difficult to accomplish with the traditional placed triggers.
Syntax:
Global Triggers are placed in the [Level] section and valid for that particular level.
GlobalTrigger= IdNum, FGT_ Flags, GT_ constant, Parameter, IdCondition, IdTrigger, IdOnFalse
**IdNum **is an identifier to differentiate between many Global Triggers. This is a required field.
FGT_ flags can modify behavior characteristics of the Global Trigger, you can check them in the Reference. For instance, you can make the TriggerGroup not active immediately (FGT_DISABLED), so you can make it active only at a later point during the level. This is an optional field.
The GT_ constant is perhaps the most important, it describes what kind of game event the Global Trigger will be tracking in the background. There are very many GT_ types you can choose from for various kinds of events in the game. You can see the list and read about specific GT_ types in the Reference. This is a required field.
Parameter is a value used with some (but not all) types of GT_ constant, to give some extra information to the Global Trigger. This is an optional field.
IdCondition refers to the ID number of some TriggerGroup that holds the supplementary conditions for the global trigger. Note often these aren’t required and the GT_ constant on its own provides the necessary condition. This is an optional field.
IdTrigger refers to the ID number of the TriggerGroup that has the “things” you would like to trigger once the specified GT_ event occurs. In other words, it will be the consequences of the Global Trigger taking place. This is a required field.
IdOnFalse refers to the ID number of a TriggerGroup that will be executed if the global event is not occurring (i.e. the condition presented by the GT_ constant is false). This can be used to provide the global trigger with an alternative set of actions to do in case the outcome is false. This is an optional field.
Examples:
Some examples of Global Triggers, utilising the most common GT_ constants:
1) GT_ALWAYS
GT_ALWAYS isn’t a specific event, rather it indicates that something should be triggered all the time, at every game frame. As an example, you can use this to make Lara temporarily invincible, by restoring her health to the maximum HP (1000) via GT_ALWAYS. You can then disable this global trigger (Flipeffect 375) to again allow Lara to take damage. A useful combination is GT_ALWAYS with the FGT_ flag FGT_SINGLE_SHOT. It effectively allows you to activate something at the very first frame of the game (before the screen fades in from black), but then immediately disable it so it won’t get activated again.
GlobalTrigger= 4, FGT_SINGLE_SHOT, GT_ALWAYS, IGNORE, IGNORE, 12, IGNORE
TriggerGroup= 12, $2000, 169, $1BD ; trigger anim 445 for Lara
The above Global Trigger has TriggerGroup 12 used as the “triggered action”. This triggered action is a flipeffect for making Lara do animation #445, which can be a special animation with which you can start your level, before giving control of Lara over to the player (like a mini-cutscene, where Lara takes a glance around her surroundings).
2) GT_LARA_HP_LESS_THAN
The triggering event of this GT_ constant is Lara’s health being below a certain value (specified in the Parameter field of the Global Trigger). Lara’s health is always a value between 1000 and 0, where 1000 is full health and 0 is death. If you’d like to trigger something when Lara’s HP is below 200 (1/5th of the health bar), it would look like this:
GlobalTrigger= 7, IGNORE, GT_LARA_HP_LESS_THAN, 200, IGNORE, 31, IGNORE
TriggerGroup= 31, $2000, 72, $24 ;display NG string 2 “I should take a medipack”
What this Global Trigger and TriggerGroup combo will display the Extra NG string #2 (which is a line of Lara saying that she should take a medipack), whenever Lara’s health is below 200. It may not be a very ambitious idea, but it at least illustrates what you can do with Global Triggers (triggering something under specific game events, not associated with stepping onto a floor trigger).
Organizers
If there is ever a need to delay the triggering of something by a few seconds or have a sequence of triggering various events in short intervals, the classic TRLE solution would be the famous rolling ball trick. It often does this sufficiently well, but sometimes it’s not precise enough or somewhat limited. This is where Organizer commands come in.
An Organizer is a command dedicated to timing triggers at specific, settable intervals. Additionally, it’s possible to make this Organizer loop in an endless way, or make it operate at frame-length precision (the game engine runs at 30 frames per second, always, so there are 30 frames to a second).
Syntax:
Organizer= IdNum, FO_ flags, Parameter, {Time, TriggerGroupID} pairs
IdNum is the identifier of the organizer, needed to distinguish between different organizer commands.
FO_ flags affect the behavior of the Organizer command. Some examples are FO_ENABLED, which makes the Organizer active from the start of the level, without the need for further triggers (without this flag, they will be disabled by default). Another example is FO_TICK_TIME, which permits to measure the time not in seconds (which are default), but in game frames, where 30 frames are 1 second. Check out further FO_ flags in the Reference.
Parameter is used to store a value for use with certain FO_ flags. You can learn when it is used by examining the descriptions of FO_ flags, but in most cases it is simply not used and you can type IGNORE.
After this come pairings of Time and TriggerGroup IdNum. Time describes the amount of seconds from the previous time (and not since the triggering of the Organizer!) after which to trigger the corresponding TriggerGroup. To give an example:
Organizer= 5, IGNORE, IGNORE, 0, 9, 3, 13, 2, 15
Times have been highlighted in blue, TriggerGroup IDs in green. How do we interpret these times? The first pair: {0, 9} means that TriggerGroup 9 will be triggered after 0 seconds (so at the exact moment the Organizer is triggered). The next pair: {3, 13} means TriggerGroup 13 will be triggered 3 seconds after the previous time (0 + 3), so in 3 seconds. The pair after: {2, 15} means that TriggerGroup 15 will be executed after 2 seconds from the last time (0 + 3 + 2), so at 5 seconds since the Organizer was activated.
Organizers allow finetuned control over when events are triggered. A single time can be 65535 seconds, which works out to be 18 hours, 12 minutes and 15 seconds. In FO_TICK_TIME mode you can control time down to frames, permitting to time triggering something to particular frames of. e.g. Lara’s animation. Because of their commonplace usefulness and predictable outcomes, for most cases there is no need to use the rolling ball trick anymore in TRNG.
We hope that the above tutorial (in video and text form) will be useful and helpful in learning the ropes with TRNG scripting. We also anticipate that it will serve as a good basis for upcoming tutorials, making them easier to understand. Enjoy!