{{indexmenu_n>3}}
====== 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 [[trs:room_geometry#tr_room_sector|[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 [[trs:room_geometry#tr_room_sector|[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 [[trs:room_geometry#tr_room_info|tr_room_info.x]] and [[trs:room_geometry#tr_room_info|tr_room_info.z]] fields, and $n_{Zsectors}$ is [[trs:room_geometry#tr_room|tr_room.NumZsectors]] value.
Then, the //current FloorData pointer// is derived from calculated [[trs:room_geometry#tr_room_sector|[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?nolink|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.
{{:icons:tr1.png?nolink|TR1 only}}{{:icons:tr2.png?nolink|TR2 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.
{{:icons:tr3.png?nolink|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 ====
{{:icons:tr2.png?nolink|TR2 only}}{{:icons:tr3.png?nolink|TR3 only}}{{:icons:tr4.png?nolink|TR4 only}}{{:icons:tr5.png?nolink|TR5 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 ====
{{:icons:tr3.png?nolink|TR3 only}}{{:icons:tr4.png?nolink|TR4 only}}{{:icons:tr5.png?nolink|TR5 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 [[trs:room_geometry#tr_room_sector|[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.png?nolink|illustrations/tri-notshared.png}}|{{:illustrations:tri-shared.png?nolink|illustrations/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 [[trs:room_geometry#tr_room_sector|[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?nolink|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.
* ''%%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//
{{:icons:tr3.png?nolink|TR3 only}}{{:icons:tr4.png?nolink|TR4 only}}{{:icons:tr5.png?nolink|TR5 only}} Sets monkey-swingability of the ceiling in specified sector.
==== Function 0x14 ====
{{:icons:tr3.png?nolink|TR3 only}}{{:icons:tr4.png?nolink|TR4 only}}{{:icons:tr5.png?nolink|TR5 only}} 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). If both this and ''0x15'' functions are set for a given sector, minecart will //stop// there.
* 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 ====
{{:icons:tr3.png?nolink|TR3 only}}{{:icons:tr4.png?nolink|TR4 only}} 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). If both this and ''0x14'' functions are set for a given sector, minecart will //stop// there.
* 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 function ''%%0x15%%'' is reached. Then it waits until Lara picks it up.
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.
{{:icons:tr3.png?nolink|TR3 only}}{{:icons:tr4.png?nolink|TR4 only}}{{:icons:tr5.png?nolink|TR5 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 ====
{{:icons:tr4.png?nolink|TR4 only}}{{:icons:tr5.png?nolink|TR5 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 ====
{{:icons:tr4.png?nolink|TR4 only}}{{:icons:tr5.png?nolink|TR5 only}} Same as //Antitrigger//, but performs //deactivation// for each case of //Object// trigger action.
==== SubFunction 0x0C — Monkey ====
{{:icons:tr4.png?nolink|TR4 only}}{{:icons:tr5.png?nolink|TR5 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 ====
{{:icons:tr5.png?nolink|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 ====
{{:icons:tr5.png?nolink|TR5 only}} Activated by Lara whenever she enters a given sector //walking on a tightrope//.
==== SubFunction 0x0F — Crawl ====
{{:icons:tr5.png?nolink|TR5 only}} Activated by Lara whenever she enters a given sector //crawling or crouching//.
==== SubFunction 0x10 — Climb ====
{{:icons:tr5.png?nolink|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//.
{{anchor:trigger_actions}}
===== 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.
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.
{{:icons:tr1.png?nolink|TR1 only}}{{:icons:tr2.png?nolink|TR2 only}}''%%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.
{{anchor:trigfunc_0x02}}
==== 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.
{{anchor:trigfunc_0x03}}
==== 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.
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.
{{:icons:tr4.png?nolink|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.
{{:icons:tr4.png?nolink|TR4 only}}{{:icons:tr5.png?nolink|TR5 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 [[trs:npc_behaviour#ai-objects|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.
{{anchor:trigaction-0x09-flipeffect}}
==== 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 [[trs:miscellany#flipeffects|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 ====
{{:icons:tr1.png?nolink|TR1 only}}{{:icons:tr2.png?nolink|TR2 only}}{{:icons:tr3.png?nolink|TR3 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 ====
{{:icons:tr4.png?nolink|TR4 only}}{{:icons:tr5.png?nolink|TR5 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 ====
{{:icons:tr4.png?nolink|TR4 only}}{{:icons:tr5.png?nolink|TR5 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//.