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 (uint16s).

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.

It may seem that bits 5..7 are unused in 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.

Even if 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.
While 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.
Several of the functions indicate adjustments to the sector’s corner heights. The corners will be denoted as 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.
Collisional floordata functions should always come first in sequence, with floor collision function strictly being first and ceiling collision function strictly being second. The reason is hardcoded floordata collision parser which always expects these two functions to be first in sequence.

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:

illustrations/doors.png
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.

This function is never combined with triangulation functions present in TR3 onwards (see further).

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.

This function is never combined with triangulation functions present in TR3 onwards (see further).

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.

Since TR4, 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.

Each entity in Tomb Raider has a similar field in its structure called activation mask. Activation of entity happens only when all bits of activation mask are set. Trigger action which activates an entity makes either bitwise 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.

TR1 onlyTR2 only 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.

TR3 only 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.

All other trigger actions, except activation of entities, are performed continuously. It’s not obvious, because engine uses various workarounds for specific trigger actions to prevent “repeated” execution, like playing same soundtracks over and over again. Such workarounds will be specifically mentioned.

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

TR2 onlyTR3 onlyTR4 onlyTR5 only 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

TR3 onlyTR4 onlyTR5 only 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.

Triangulation functions are never combined with slant functions present in TR1 and TR2. Each sector has either slant or triangulation function assigned to it, and never both of them. If there ever will be a paradoxical case of combination, most likely, only older function (i.e. slant) will be considered for collision calculation.

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:

illustrations/tri-notshared.pngillustrations/tri-shared.png
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.

illustrations/tri-types.png
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.

Functions 0x08, 0x0D, 0x0E

These functions define floor triangles split in the northeast-southwest direction.

Functions 0x09, 0x0F, 0x10

These functions define ceiling triangles split in the northwest direction.

Functions 0x0A, 0x11, 0x12

These functions define ceiling triangles split in the northeast direction.

Function 0x13 — Monkeyswing (only in TR3-5)

SubFunction is not used

TR3 onlyTR4 onlyTR5 only Sets monkey-swingability of the ceiling in specified sector.

Function 0x14

TR3 onlyTR4 onlyTR5 only This function has a different meaning in TR3 and TR4/5.

Function 0x15

TR3 onlyTR4 only This function has a different meaning in TR3 and TR4.

If Lara places beetle at the very same sector where beetle was already used, it will shake and explode. It happens because beetle checks if Mapper entity is active or not, and if it was already activated, it explodes instead of rolling.

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.

Trigger type names are directly borrowed from TRLE.

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.

TR3 onlyTR4 onlyTR5 only 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

TR4 onlyTR5 only 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

TR4 onlyTR5 only Same as Antitrigger, but performs deactivation for each case of Object trigger action.

SubFunction 0x0C — Monkey

TR4 onlyTR5 only 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

TR5 only 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

TR5 only Activated by Lara whenever she enters a given sector walking on a tightrope.

SubFunction 0x0F — Crawl

TR5 only Activated by Lara whenever she enters a given sector crawling or crouching.

SubFunction 0x10 — Climb

TR5 only 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 Parameterused 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.

If 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.

Camera trigger action uses one extra 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.

TR1 onlyTR2 onlyMoveTimer: 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.

While it may look like 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_ts 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.

From TR1 to TR3, 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.

TR4 only 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.

TR4 onlyTR5 only 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.

In TR1, soundtrack playback is more complicated. For some reason, in PC version programmers completely disabled playback for majority of soundtracks, leaving only five or six most significant ones to play (like title theme or cutscene audio). Looped soundtracks were also completely ignored — instead, background ambience is explicitly specified by script entry, rather than trigger action (that’s the reason why PC version has four different ambience types when compared to PSX version).

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.

Flipeffect does not mean “flip map effect” or so, and has no any direct relation to “flip maps”.

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.

The list of flipeffects differs across game versions. For precise info on each flipeffect for each game version, refer to this section.

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

TR1 onlyTR2 onlyTR3 only 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.

This trigger action caused significant confusion in TRLE community. In level editor, action is called BODYBAG, and makes no visible effect in game, so various speculations were made regarding action’s true purpose. Some people thought it is used to attach a backpack to Lara in “Angkor Wat” cutscene, another people thought it is used for lipsync or dragging SAS troop body in “City of the Dead”. All this speculation was proven wrong.

TrigAction 0x0C — Flyby

TR4 onlyTR5 only 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

TR4 onlyTR5 only 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.