OpenTomb icon indicating copy to clipboard operation
OpenTomb copied to clipboard

Oneshot Trigger behaviour differs from original TR

Open T4Larson opened this issue 9 years ago • 12 comments

Tr1: Switch oneshot should lock the item only if the object is triggered on, in the original engine a oneshot-switch doesn't seem to lock an object if the os-switch only switches it off.

tr4: Tr4 has 3 separate oneshot-lock-bits for trigger,antitrigger and swtch types - that means activating a oneshot-trigger will still allow you to antitrigger and switch the objects. There's one special case (due to a fallthrough in the code), where a os-switch set both the switch-lock-bit and the trigger-lock-bit, when that oneshot-switch activates an object (if the os-switch deactivates an object, only the switch-lock-bit is set). Tr4 has also a "activation-state-negated" bit, which is used for pre-triggered objects: When pre-triggered objects are loaded (mask=11111), then the negation flag is set and the mask is cleared - so it must be triggered with all bits again to be deactivated. This bit is also cleared by the Tr4 antitrigger: Pre-triggered objects will be deactivated, and not be negated anymore from this point on.

Edit: I have no clue yet when these extra bits came in, tr2/tr3 needs checking... Edit2: Possibly related: #103 , #33

T4Larson avatar Aug 14 '15 19:08 T4Larson

  1. I got understanding of this setup through HUGE frustration with TR1 Gold one-shot secret triggers (thanks to amount of testing done by @vvs-). I understood it this way: if entity was not yet activated, then one-shot flag makes no difference (no idea if it's set by trigger which not yet activated it), and if entity is being activated, then one-shot flag takes action. In OpenTomb it's currently done via checking current entity activation mask - if it's fully set (11111), and one-shot flag is set as well, trigger processing is bypassed. You can learn the way it works in trigger_functions.lua at lines 64-68. You can also see that there's a condition for TR_III game version - the reason is, during my tests with TR3 I realized that any one-shot trigger locks entity - even if it doesn't fully set activation mask. For example, I have two triggers for some entity, one with mask 00001 and one-shot flag set, and another with mask 11110 and one-shot mask not set. If I step on first trigger, and then on second, entity won't activate! But if I step on second trigger, and then on first, entity will activate.
  2. So it's actually works in such complicated way? I thought that antitrigger just resets one-shot flag along with activation mask for a given entity. But now I did additional tests, and it's much more clear to me. However, this setup requires major changes in trigger layout for each entity... Current approach must be changed completely (as I used packed bitfield for keeping activation mask, oneshot flag and activation state flag in the same 8-bit field).
  3. Looks like "negation" flag was there since TR1 - classic example for this is Qualopec grated door, which is open by default, but closes on heavytrigger activated by the boulder. I call "negation" flag in trigger_functions.lua as state. When state is set, it means that entity was already activated, and when state is not set, it means entity needs activation. For pre-activated entity cases, activation callback is done on them, and then activation mask and state flag are both forced to be unset. In game it results in same "negation" of active/inactive entity states. As for antitrigger negation flag unsetting - but in TR1, if you won't go for main corridor in Tomb of Qualopec and won't activate a boulder, grated door won't be closed when Lara exits from Scion chamber. So it means that pre-activated entity negation flag behaviour (or, to be more clear, antitrigger behaviour) is also different between TR1 and TR4?

Lwmte avatar Aug 15 '15 00:08 Lwmte

In OpenTomb it's currently done via checking current entity activation mask

That's where it does this wrong. It should check the new mask instead.

vvs- avatar Aug 15 '15 13:08 vvs-

So it means that pre-activated entity negation flag behaviour (or, to be more clear, antitrigger behaviour) is also different between TR1 and TR4?

Yes, in Tr1 a pre-triggered object returns to its pre-triggered state (i.e. door open) when antitriggered, while in Tr4 it is deactivated (door closed) and then acts like a normal untriggered object.

T4Larson avatar Aug 15 '15 15:08 T4Larson

Seems like trigger behaviour was the most changed thing in TR code, as every single TR version has its own interpretations on what "trigger", "antitrigger" and "only once" means. Pity that there's no indication of such changes anywhere in header files or level editor, i.e. we're dealing with things that are called the same yet is completely different.

Lwmte avatar Aug 15 '15 17:08 Lwmte

I've made a testlevel (surprisingly got dxtre working without major problems): https://drive.google.com/open?id=0B0VG1ji-DF7rMjA3WVB4OFcwRFk (hope you can access it w/o google account). There seem to be more differences: Tr1 antitrigger only clears the bits from the trigger mask, not all bits in every case...

T4Larson avatar Aug 15 '15 20:08 T4Larson

Why not do a complete abstraction of triggers with several (nearly) independent states? I propose the following layout for "engine triggers":

  • Activation state: On or Off.
  • Activation type: None (activation state is not changed), single (activation state can only be set to "on") or repeatable (activation state toggles with each activation).
  • Movable state: Blocked (can't be moved at all), single (i.e. a switch can be pulled only once) or repeatable (e.g. a sensitive floor tile that opens a door every time Lara walks over it).
  • The trigger fires a "toggled" event every time the activation state gets changed.

The thing is that the activation type must be less or equal to the movable state; for example, it doesn't make sense to have a trigger that can only be moved once, but is able to toggle the activation state multiple times (although that would make sense for "timer triggers"...). Items that can be triggered (like doors) should carry their own internal trigger that can be triggered by in-game triggers; this way there could be multiple triggers in game that open a door, as the door itself only has a single-activation trigger internal that carries the state whether it's opened or not. And the "toggled" event of that internal trigger could be connected to playing an animation.

Maybe this was all nonsense, as the trigger logic is very confusing, but it could also be right.

stohrendorf avatar Aug 16 '15 01:08 stohrendorf

Biggest problem here is almost all trigger operations in originals were done via tricky bitwise operations, sometimes something got messed up (as noted by @T4Larson), some bugs were abused by level designers, so certain setups are dependent on particular trigger bug, etc.

Moreover, as I already told several times, triggers can't be single-shot, all triggers in TRs are executed every frame, and only particular trigger functions are checked for one-shot, so we really can't change much there.

Regarding activation type - if you dig into trigger script generator (in TranslateFloorData function), you will notice that each trigger function is placed either into single_events or cont_events block, which actually defines if that function must be called continously or only once.

Activation state is already implemented, and is flipped every time trigger or antitrigger is called.

However, I think I must go away from "bitwise" stuff I have introduced with trigger layout field, instead dividing activation mask, only once flags (there should be three of them) and activation state.

We may actually set activation mask more than 5 bits now, cause we're not limited... It may come in handy with our own custom file format, where we can define more than 5 activation bits for each trigger and entity.

Lwmte avatar Aug 16 '15 11:08 Lwmte

Yes, that's true. The trigger operations are very low level and the Core levels abused this very often. The same thing can be said about animations and collisions.

Making it more abstract should only make compatibility even more difficult.

Another problem is that to make such abstraction useful it should correspond to the actual behavior. And we don't even completely understand how they behaved in the original. Making incompatible abstraction and then trying to fit it into different framework won't help to achieve compatibility.

vvs- avatar Aug 16 '15 12:08 vvs-

Looks like it's related to #94.

vvs- avatar Jun 26 '16 16:06 vvs-

ANTIPAD trigger still doesn't work properly. This breaks boulder puzzle in room 34 of Atlantis.

vvs- avatar Dec 04 '16 19:12 vvs-

ANTITRIGGER presently can be activated by heavy activators. This seems wrong, though I didn't test it in Core's TR3 and it's actually used to be ignored in TR1.

vvs- avatar Dec 09 '16 17:12 vvs-

added condition for heavy triggers https://github.com/opentomb/OpenTomb/commit/1a498323590a15496e82cd657738964d6b5eab3d

TeslaRus avatar Aug 05 '17 17:08 TeslaRus