This is an old revision of the document!
Table of Contents
FloorData
Overview
The FloorData is the key part of the level structure, which defines almost everything related to “physical” world — geometry, interaction and response of a level. While room geometry (see above) may be considered as a “face” of the level, FloorData is its “heart” and “brain”.
Distinctive feature of the FloorData is its serialized nature. While in room geometry you can easily jump through structures using data sizes and pointers to get the needed part, FloorData require sequential parsing, one unit by one.
The Concept
The FloorData defines special sector attributes such as individual floor and ceiling corner heights (slopes), collisional portals to other rooms, climbability of walls, and, most important of all, the various types of triggering. Each room sector (see [tr_room_sector] structure) points to the FloorData using FDIndex
variable. It is referenced as an array of 16-bit unsigned integers (uint16
s).
Therefore, the current [tr_room_sector] offset (not yet the FloorData pointer itself!) is calculated using this formula:
$S_{Offset} = \frac{X_{current} - X_{room}}{1024} \cdot n_{Zsectors} + \frac{Z_{current} - Z_{room}}{1024}$
…where $X_{current}$ and $Z_{current}$ are current player positions, $X_{room}$ and $Z_{room}$ are corresponding tr_room_info.x and tr_room_info.z fields, and $n_{Zsectors}$ is tr_room.NumZsectors value.
Then, the current FloorData pointer is derived from calculated [tr_room_sector] structure’s FDIndex
field. In other words, FDindex
is an offset into the FloorData[]
array.
As mentioned above, The FloorData consists of solely uint16_t
entries without general structure — the way engine treats specific entry depends on the sequence order and type of previously parsed entries. While it’s a bit difficult to understand it at first, you should get used to it. Main thing to remember is the FloorData should be read sequentially.
Understanding The Setup
First order of FloorData entries has a common “bitwise” structure, which we will call FDSetup
. The structure could be divided into three fields:
Hex value | 0x8000 | 0x7F00 | 0x001F |
|||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Field | EndData | SubFunction | Function |
Function
defines the type of action that must be done with current FloorData entry, and SubFunction
is usually used in that action’s conditions and case switches (if there are any). If there are no any special conditions for a given Function
, then SubFunction
is not used.
FDSetup
structure, but actually they belong to Function
value, but only in TR1 and TR2. When parsing FDSetup
for TR3+, you should use only the lower 5 bits (0..4) to find the Function
value, because some of TR3 triangulation functions use the upper 3 bits of the lower byte for other purpose. However, this will also work correctly in TR1 and TR2, since maximum possible function value is way below 5-bit limitation.
If EndData
is set, there should be no more similar FloorData entries (after the current one) in the FloorData[]
array — so further parsing must be stopped. Otherwise, the following uint16_t
should be interpreted after the current one in the same manner.
EndData
is set, it doesn’t specifically mean that there are no more uint16_t
following the current one at all. As we will see, some FloorData functions and subfunctions require to parse additional entries with their own rules. In programming terms, EndData
just indicates that parsing loop must be broken — however, there may be following code which reads additional entries.
FloorData
index 0 means the sector does not use floordata, there is still a “dummy” entry for index 0. This dummy entry doesn’t contain any useful information.
00
, 01
, 10
, and 11
, where the first digit is the corner’s X coordinate and the second digit is the corner’s Z coordinate, with both given as multiples of 1024.
FloorData Functions
Function 0x01 — Portal Sector
SubFunction is not used
The next FloorData
entry is the number of the room that this sector is a collisional portal to. An entity that arrives in a sector with this function present will gets its room membership changed to provided room number, without any change in position.
To understand what exactly happens when room membership is changed, you must understand how collisional portals work in Tomb Raider’s 4D space. When two rooms are connected with portal, it means that they also overlap within a distance of two sectors (because these sectors contain portal in each of the connected rooms). This way, when room is changed, it remains unnoticed by the player, cause portal sectors are interconnected:
Collisional portal layout. Blue sectors are walls around each room. Green sector is Room 2’s collisional portal to Room 1, and dark blue sector is Room 1’s collisional portal to Room 2 |
Function 0x02 — Floor Slant
SubFunction is not used
The next FloorData
entry contains two uint8_t
slant values for the floor of this sector. Slant values are specified in increments of 256 units (so-called clicks in TRLE terms). The high byte is the Z slope, while the low byte is the X slope. If the X slope is greater than zero, then its value is added to the floor heights of corners 00
and 01
. If it is less than zero, then its value is subtracted from the floor heights of corners 10
and 11
. If the Z slope is greater than zero, then its value is added to the floor heights of corners 00
and 10
. If it is less than zero, then its value is subtracted from the floor heights of corners 01
and 11
.
Function 0x03 — Ceiling Slant
SubFunction is not used
The next FloorData
entry contains two uint8_t
slant values for the ceiling of this sector. Slant values are specified in increments of 256 units. The high byte is the Z slope, while the low byte is the X slope. If the X slope is greater than zero, then its value is subtracted from the ceiling heights of corners 10
and 11
. If it is less than zero, then its value is added to the ceiling heights of corners 00
and 01
. If the Z slope is greater than zero, then its value is subtracted from the ceiling heights of corners 00
and 10
. If it is less than zero, then its value is added to the ceiling heights of corners 01
and 11
.
Function 0x04 — Trigger
The uint16_t
immediately following current entry is called TriggerSetup
, and contains general trigger properties stored in a “bitwise” manner:
Hex value | 0x3E00 | 0x0100 | 0x00FF |
|||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Field | Mask | Oneshot | Timer |
Timer
is a value generally used for making timed triggers of certain entities — for example, the door which opens only for a few seconds and then closes, or a fire which extinguishes and then burns again. In such case, engine copies timer value in corresponding field of each triggered entity. Then each entity’s timer begins to count time back, and when it reaches zero, entity deactivates.
However, it’s not the only purpose of Timer
field. As trigger may not specifically activate entities but do some other actions, Timer
field may be re-used as a general-purpose numerical field to specify particular trigger behaviour. We will mention it separately for such trigger actions.
Timer
field became signed, i.e. it may contain negative values. Effectively, it means that entities activated with such trigger won’t be immediately activated and then deactivated after given amount of time, but wait for a given time before being activated. Most prominent example is timed spike pit in the beginning of “Burial Chambers”.
Mask
: The five bits at 0x3E00
are the so-called Trigger Mask. The purpose of trigger mask is to create puzzle set-ups which require a combination of activated triggers to achieve certain result. A good example of trigger mask use is the multiple-switch room of “Palace Midas” in TR1.
XOR
operation (for switch trigger types — namely, switch and heavy switch — see further) or bitwise OR
operation on activation mask using trigger mask. Trigger action purposed for deactivation (namely, antitrigger, antipad and heavy antitrigger types) don’t take its trigger mask into consideration, and instead just reset target entity’s activation mask to zero.
Whenever entity’s activation mask is changed to anything but 0x1F (all bits set), entity is automatically deactivated, excluding the cases when OneShot
flag was previously set for a given entity — see further.
OneShot
flag is used only for activation of entities (it is also copied to entity’s own flag field with same name), and indicates that after activation, entity state is locked. It means that even if entity’s own activation mask is unset (as with switch trigger type — see further), entity will remain activated. However, it doesn’t mean that entity couldn’t be deactivated at all — because antitrigger trigger type (see further) ignores and resets this flag.
In these versions, any antitriggers for locked entity have no effect, and engine completely bypasses antitrigger processing for such entities. It means that once entity was activated by trigger with OneShot
flag, it couldn’t be deactivated at all.
There’s a bit different OneShot
flag behaviour for TR3 — entity state will be locked even if activation mask isn’t set yet. For example, if there are two complementary triggers for certain entity (let’s say, with activation masks 0x0F
and 0x10
), and each of them has OneShot
flag, effectively target entity won’t ever be activated, cause each trigger immediately locks entity state, bypassing further trigger processing.
Trigger types and trigger actions will be described separately right after listing all FloorData functions.
Function 0x05 — Kill Lara
SubFunction not used
Instantly kills Lara. Usually she is simply set on fire, however, there is one special case in TR3. If current level index is 7 (“Madubu Gorge”), then instead of catching fire, Lara will play drowning animation.
Function 0x06 — Climbable Walls
The SubFunction
indicates climbability of walls; its value is the bitwise OR
of the values associated with all the climbable-wall directions (0x01
= +Z, 0x02
= +X, 0x04
= -Z, 0x08
= -X), e.g. SubFunction 0x09
indicates that the walls on both the +Z and -X sides of this sector are climbable.
Functions 0x07 to 0x12 — Triangulation
Beginning with TR3, geometry layout of each sector was significantly changed. Engine introduced triangles as a minimal collisional unit, compared with rectangles only in TR1 and TR2. This advantage allowed to create much more organic and natural terrain (albeit still limited by minimal sector width and depth of 1024 units), but also complicated things a lot, introducing the whole new set of different FloorData collisional functions.
Similarly to slant functions, triangulation functions define the floor and ceiling corner heights, but besides, they also specify dividing up the floors and ceilings into triangles along either of the two diagonals. Also, one of the triangles may be a collisional portal to the room above (if in the ceiling) or to the room below (if in the floor).
Each triangulation function uint16_t
must be parsed differently, not like ordinary FDSetup
entry:
Hex value | 0x8000 | 0x7C00 | 0x03E0 | 0x001F |
||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Field | EndData | $H_{\triangle2}$ | $H_{\triangle1}$ | Function |
$H_{\triangle1}$ and $H_{\triangle2}$ are signed values, and replace FDSetup
's SubFunction
field.
Own triangulation function’s uint16_t
is followed by one extra uint16_t
to be parsed as follows:
Hex value | 0xF000 | 0x0F00 | 0x00F0 | 0x000F |
||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Field | $\Delta C_{11}$ | $\Delta C_{01}$ | $\Delta C_{00}$ | $\Delta C_{10}$ |
All four values here are unsigned.
The Triangulation Formula
The idea behind this set up is dividing each sector rectangle into two independent triangles, and adjust each triangle height by combination of corner and triangle heights. To get each triangle’s individual corner height, you should use this formula:
$H_{\angle} = H_{floor} + (\max(\Delta C_{10}, \Delta C_{00}, \Delta C_{01}, \Delta C_{11}) - \Delta C_{n} \cdot 1024 )$
…where $H_{\angle}$ is absolute floor height specified in [tr_room_sector]'s Floor
field, and $\Delta C_{n}$ is triangle’s individual corner height.
While four corner height values are shared by both triangles, triangle height values specify additional overall height of individual triangle. Therefore, sector corner heights may or may not be shared between two triangles:
Corner heights are not shared | Corner heights are shared |
The way engine interprets triangle height values $H_{\triangle1}$ and $H_{\triangle2}$ is not exactly known — however, meta2tr understands them and uses them to create so-called diagonal steps, example of which is pictured on the left side. There is no case of diagonal steps in original games, but they may exist in levels edited with meta2tr.
Overall, there are 12 different triangulation functions, which can be divided into two pairs of groups — one pair of groups is for floor, and another pair is for ceiling. Each pair is categorized by split direction, and each group is categorized if it’s floor or ceiling. In each group, there are three functions — first function denotes that both triangles in sector are solid, second and third functions denote that one of triangles is a collisional vertical portal. When function denotes a vertical portal, target room of a portal is taken from [tr_room_sector] structure — RoomBelow
for floor functions, and RoomAbove
for ceiling functions.
Here is an example illustration depicting sectors with all possible floor triangulation functions. Ceiling triangulation happens in similar manner.
Floor sector triangulation types. Black triangles depict vertical collisional portal to different room. |
X
axis in world coordinates also may be considered north for more simple reference (because you can always check compass direction in actual game engines, at least in TR1 and TR4).
Functions 0x07, 0x0B, 0x0C
These functions define floor triangles split in the northwest-southeast direction.
0x07
— Both triangles are solid.0x0B
— Triangle pointing its right angle to the southwest is a collisional portal.0x0C
— Triangle pointing its right angle to the northeast is a collisional portal.
Functions 0x08, 0x0D, 0x0E
These functions define floor triangles split in the northeast-southwest direction.
0x08
— Both triangles are solid.0x0D
— Triangle pointing its right angle to the southwest is a collisional portal.0x0E
— Triangle pointing its right angle to the northwest is a collisional portal.
Functions 0x09, 0x0F, 0x10
These functions define ceiling triangles split in the northwest direction.
0x09
— Both triangles are solid.0x0F
— Triangle pointing its right angle to the southwest is a collisional portal.0x10
— Triangle pointing its right angle to the northeast is a collisional portal.
Functions 0x0A, 0x11, 0x12
These functions define ceiling triangles split in the northeast direction.
0x0A
— Both triangles are solid.0x11
— Triangle pointing its right angle to the northwest is a collisional portal.0x12
— Triangle pointing its right angle to the southeast is a collisional portal.
Function 0x13 — Monkeyswing (only in TR3-5)
SubFunction is not used
Sets monkey-swingability of the ceiling in specified sector.
Function 0x14
This function has a different meaning in TR3 and TR4/5.
- In TR3, if Lara approaches sector with this FloorData function inside minecart vehicle, it will turn left 90 degrees, with a circle radius around 4 sectors (4096 units in world coordinates).
- In TR4 and TR5, this function is used together with special entity called Trigger Triggerer. The purpose of this entity is to perform deferred triggering. That is, if trigger FloorData function is placed in the same sector with function
0x14
, trigger won’t be activated until there’s an activated Trigger Triggerer object in the same sector. This allows to create setups where player can cross trigger sector without activating it, until some other event occurs later in level.
Function 0x15
This function has a different meaning in TR3 and TR4.
- In TR3, if Lara approaches sector with this FloorData function inside minecart vehicle, it will turn right 90 degrees, with a circle radius around 4 sectors (4096 units in world coordinates).
- In TR4, this function is used together with special entity called Mapper. If Mechanical Beetle is placed in sector with function
0x15
and inactive Mapper entity, it rotates in the same direction Mapper is pointing to, activates it, and then rolls forward, until next sector with function0x15
is reached. Then it waits until Lara picks it up.
Trigger Types
A trigger type specifies the condition of a given trigger function to be activated. Condition may be a type of activator (Lara or some other entity), a specific state of activator, specific trigger action (activate or deactivate), and so on.
Trigger type is placed in SubFunction
field of FDSetup
structure, so we will refer to trigger types as SubFunctions.
SubFunction 0x00 — Trigger
Activated by Lara whenever she enters a given sector — either steps, climbs, jumps over it, and so on.
SubFunction 0x01 — Pad
Activated by Lara only if she steps or lands on a given sector.
SubFunction 0x02 — Switch
This particular type of trigger takes first ActionList
entry’s Parameter
field as a reference to specific switch entity in level. It activates every time the switch state is changed. For Object trigger actions, activation means performing XOR
operation on these object’s (entities) activation masks. (See next section for description of Object trigger action and Parameter
field.)
Please note that this trigger type (as well as any other trigger types) always perform all trigger actions except Object in the same manner! Meaning, if there is a Camera or Flipeffect trigger action, it will be performed every time the switch is flipped on or off.
SubFunction 0x03 — Key
Similar to previous trigger type, it works only if there is a keyhole entity listed in the first ActionList
entry’s Parameter
field. It activates only if a key was inserted into that particular keyhole.
SubFunction 0x04 — Pickup
As above, this type of trigger works only if there is a pick-up entity listed in the first ActionList
entry’s Parameter
field. It activates only if this item was picked up by Lara.
SubFunction 0x05 — Heavytrigger
Activated by specific entity type (activator) wherever it enters a specified sector. Entity types which are able to activate heavytriggers are hardcoded, and usually include NPCs (enemies), rolling balls and pushable objects. Since TR4, heavytriggers may also be activated by destroying shatter static mesh which is placed in a given sector.
Note that heavytrigger does not perform deactivation action, if activator leaves trigger sector.
SubFunction 0x06 — Antipad
Same as Pad — activates only if Lara has landed or stepped onto a given sector. The difference is, Antipad performs deactivation for each case of Object trigger action. What deactivation specifically means is it resets entity activation mask to zero (trigger mask is ignored), thus flipping entity activation procedure.
As it was mentioned for Switch trigger type, any other trigger actions beside Object will perform exactly in the same manner as with normal trigger types. So you shouldn’t expect soundtrack to stop, if you have placed PlayTrack trigger action for antipad.
SubFunction 0x07 — Combat
Activated by Lara whenever she enters a given sector with her weapons drawn. This trigger type was (presumably) never used in original games.
SubFunction 0x08 — Dummy
This type doesn’t perform any trigger action listed for it except Object type — for these trigger actions, it applies standable collision for Lara on a given entities, if such entities are in this trigger sector. For particular entity types, it works even if entity is deactivated (e.g. collapsing floor), but for other types it works only if entity was activated (e.g. trapdoors). Selected behaviour is most likely hardcoded.
It’s worth noting that any trigger type will apply standable collision on such entity types, if they are in the same sector. It’s not a bug, rather a way TR engines process FloorData.
SubFunction 0x09 — Antitrigger
Same as Trigger, but performs deactivation for each case of Object trigger action.
Antitrigger type also copies its parent trigger’s OneShot
flag into any entities activated by Object trigger action, meaning that after any further entity activation it will be locked.
SubFunction 0x0A — Heavy switch
Don’t be fooled by the name of this trigger type. It is not literally a switch, as only similarity between it and switch type is XOR operation with activation mask. In fact, this trigger performs action when specific entity type (activator) enters a given trigger sector, but only if trigger mask is equal to activator’s activation mask.
The best example of heavy switch setup is Planetarium in “The Lost Library”. Trigger mask is only applied to raising block if pushable in trigger sector has a similar activation mask.
SubFunction 0x0B — Heavy antitrigger
Same as Antitrigger, but performs deactivation for each case of Object trigger action.
SubFunction 0x0C — Monkey
Activated by Lara whenever she enters a given sector in monkeyswing state. Best example is locust swarm attacking Lara when she monkeyswings across the street in “Trenches”.
SubFunction 0x0D — Skeleton
This trigger type temporarily replaces Lara model with a combination of models #25 (Lara skeleton), #26 (see-through body) and #27 (see-through joints). See-through body and joints are applied on top of the skeleton model with additive blending.
SubFunction 0x0E — Tightrope
Activated by Lara whenever she enters a given sector walking on a tightrope.
SubFunction 0x0F — Crawl
Activated by Lara whenever she enters a given sector crawling or crouching.
SubFunction 0x10 — Climb
Activated by Lara whenever she enters a given sector climbing on a wall.
This concludes the description of Trigger FloorData function trigger types.
Trigger Actions
Trigger function references an additional list of FloorData entries called ActionList
, which is a “chain” of entries that immediately follows TriggerSetup
entry. As you maybe already guessed, the ActionList
contains the list of actions to be performed for a specified trigger.
ActionList
entry format is:
Hex value | 0x8000 | 0x7C00 | 0x03FF |
|||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Field | ContBit | TrigAction | Parameter — used bytes may vary |
TrigAction
is a type of action to be performed. These will be listed seperately.
Parameter
is used with certain trigger actions which need a certain numerical argument provided to them.
ContBit
flag meaning is similar to EndData
flag described for FDSetup
structure. It indicates if there is another ActionList
entry after current one. If ContBit
is not set, it means we have reached the end of ActionList
, and there’s nothing more to do for a given trigger.
ActionList
's parent trigger type is either Switch or Key, first entry of ActionList
is used to get reference entity (switch or keyhole) index. Hence, it is ignored here, as by the time engine reaches ActionList
offset, its first entry is already parsed by preceding code.
ContBit
flag is not the same as EndData
flag! When writing a parser, do not overwrite one with another.
TrigAction 0x00 — Object
Activate or deactivate entity (object) with index specified in Parameter
.
TrigAction 0x01 — Camera
Switches to camera. Parameter
(bits 0..6 used) serves as index into Cameras[]
array.
uint16_t
entry after its own entry! Its format is:
Hex value | 0x8000 | 0x3E00 | 0x0100 | 0x0FF |
||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Field | ContBit | MoveTimer | Once | Timer |
Timer
is a number of seconds to wait before automatically switching back to the normal camera. If 0, it never switches back to normal camera, as long as trigger is active.
Once
: If set, only switch to camera once; otherwise, switch to camera every time trigger is active.
MoveTimer
: Specifies time which is used to smoothly move camera from Lara to desired camera viewpoint. Movement is done via spline function. The larger value is, the more time it takes to move camera away from Lara. Therefore, if MoveTimer
is zero, then camera immediately cuts to new viewpoint.
ContBit
flag overwrites the same flag from the preceding ActionList
entry.
TrigAction 0x02 — Underwater Current
Continuously moves Lara to specifed sink. Parameter
serves as index into Cameras[]
array. If sink is placed lower than current sector absolute floor height or upper than current sector absolute ceiling height, $Y$ coordinate will be ignored when dragging Lara to sink. Since TR3, sink also prevents Lara from surfacing the water.
Cameras[]
array was mentioned here by mistake, it is not. TR engines share the same structure for both cameras and sinks. The way engine treats it in either case will be discussed in corresponding section.
TrigAction 0x03: Flip Map
FlipMap is an internal engine array of uint8_t
s which is used to determine if alternate rooms should be turned on or off (in TRLE terms, flipped). It uses trigger mask in the same manner as for Object activation and deactivation, but in this case, alternate rooms are activated if given FlipMap
entry mask is set (0x1F
), and deactivated, if FlipMap
entry is not set (not 0x1F
).
This trigger action at first applies trigger mask to a given FlipMap
entry using OR
bitwise operation and then immediately checks if it’s already set or not. If FlipMap entry is set, then it immediately switches rooms to alternate mode.
Parameter
defines which FlipMap
entry engine should refer to decide should it switch alternate rooms on or off. The size of FlipMap
array is around 10 (judging by the number of unused FLIP_MAPn
flipeffect entries), but in original levels, number usually never tops 2 or 3.
FlipMap
array was merely used as a “hint table” to tell the engine if it should flip all rooms at once. That is, to check and apply another FlipMap entry, alternate rooms should have been reverted to previous state before — that’s the purpose of next two listed trigger actions. However, in TR4 algorithm was changed — each “flippable” room now bears additional parameter called “alternate group”, which strictly tells an engine to flip it only when room alternate group is equal to FlipMap Parameter
value. This change in algorithm made next two trigger actions unnecessary in TR4-5 (however, they are still available).
TrigAction 0x04 — Flip On
Tries to turn alternate rooms on, judging on current value of a given FlipMap
entry (entry index is specified by Parameter
). If corresponding FlipMap is not set (i.e. the value is not 0x1F
), rooms won’t be flipped. Parameter
defines a FlipMap
entry to work with.
TrigAction 0x05: — Flip Off
Tries to turn alternate rooms off, judging on current value of a given FlipMap
entry (entry index is specified by Parameter
). If corresponding FlipMap is not set (i.e. the value is not 0x1F
), rooms won’t be flipped. Parameter
defines a FlipMap
entry to work with.
TrigAction 0x06 — Look at Item
Specifies an entity which current camera should look at. If current camera is “ordinary” one following Lara, then it will also rotate Lara model in a target direction, creating an illusion of Lara looking at it. If current camera is changed to “triggered” one (by trigger action 0x01
— see above), then this camera’s orientation will be changed to a given entity. Note that if such camera change is desired, this action should come first, not the Camera one.
Parameter
specifies an entity index to look at.
TrigAction 0x07 — End Level
Immediately loads next level. In TR1-3 and TR5, Parameter
field is not used, i.e. engine just loads next level specified in script.
In TR4, so called “hub system” was implemented, which allows Lara to jump between levels back and forth. For this reason, Parameter
field must also explicitly specify level index to jump.
Also, since TR4 Lara can have multiple start positions for each level, so Timer
field in TriggerSetup
entry specifies an index of lara start posision AI object in AI objects array to warp Lara to. That is, if there’s an end level trigger with value 3
in Timer
field, it means that Lara will be warped to third start position AI object in AI objects array. For more info on AI objects, refer to this section.
TrigAction 0x08 — Play Soundtrack
Triggers a playback of a soundtrack specified in Parameter
field. Type of soundtrack (looped or one-shot) is hardcoded and assigned automatically.
This trigger action makes use of trigger mask and one-shot trigger flag to mark if this track was already played with a given trigger mask or not in a special internal soundtrack map. That is, if it is called with trigger mask set to, say, 0x01
, then all further calls from triggers with same trigger mask will be ignored. However, if same track playback is called with trigger mask value of 0x02
, it will play again, as it’s another byte in trigger mask. Effectively, it allows to play specified track six times (five bits of activation mask plus one bit of one-shot flag). Comparison is done via bitwise AND
operation, so if playback is called with trigger mask + one-shot value of (0x1F + 0x20 = 0x3F)
, then any other playback call to that track will be blocked.
To overcome this issue and enable complete soundtrack functionality, several patches were created by the community. However, PC version is missing soundtrack map structure, which potentially produces bugs when single track could be played independently by both triggers in the same level, although mostly this bug comes unnoticed, as majority of TR1 soundtracks are engaged only once in a level.
TrigAction 0x09 — Flipeffect
By the name of “flipeffect” comes any non-trivial or special trigger action which should be seperately defined. This workaround was implemented because TR engines lack any scripting language to program arbitrary trigger, so you can consider a flipeffect as a call to some “pre-compiled” scripted function.
For example, in TR2 “Lara’s Home”, there is a need to control assault course timer, like restarting it while reaching start point or stopping it when Lara is off the course. This task is accomplished via several different flipeffects.
TrigAction 0x0A — Secret Found
Plays “secret” soundtrack theme and marks a secret number specified in Parameter
field as found. For finding each secret, another Parameter
value must be specified, or else secret won’t be counted as found.
TrigAction 0x0B — Clear bodies
Removes dead bodies of enemies from a level to conserve memory usage. This action has effect only on entities which had clear body flag specified in their parameters (see further). Parameter
field is unused.
TrigAction 0x0C — Flyby
Engages a flyby camera sequence specified in Parameter
field. The feature was added in TR4 and enables to play cinematographic interludes with camera continuously “flying” from one point to another. Such sequences, their points, properties and order are defined in a level editor, and engine moves camera across them using spline function.
uint16_t
immediately following flyby’s own entry contains one-shot flag at 0x0100
. If this flag is not set, flyby will infinitely loop. As with Camera TrigAction, flag at 0x8000
is a continuation bit, which overrides previous entry’s continuation bit.
TrigAction 0x0D — Cutscene
Engages a cutscene pre-defined in script file. Parameter
is taken as cutscene ID. In turn, script file refers to CUTSEQ.PAK
(TR4) or CUTSEQ.BIN
(TR5) file to get all the data for a cutscene, such as actor positions and animations, camera movement, soundtrack and many more. There will be a special section describing particular CUTSEQ.PAK
file format.
This concludes the description of Trigger FloorData function action types.