Respawnable trigger_once

The story so far…
In the early days we created a delayed spawn trigger_once. Our quest will not be over until we can create a trigger_multiple which is toggled on and off when triggered. Today we will get closer, read on…

Today we aim to create a trigger_once which can be “respawned”; although it initially fires just once when touched, we can use the trigger after it has fired to make it live again for one more touch. It will be possible to respawn it in this way an unlimited number of times. This hack builds on the ideas of the Build Your Own Trigger article, so I recommend you read that first, or revisit it as a reminder.

The hack begins with a “trigger_once” made using the method in the article above. Create an brush entity with the following fields

"classname" "InitTrigger"
"touch" "multi_touch"
"message" "test message"

If you touch this trigger, it will fire the message once then disappear (the entity is completely removed). As usual in this kind of hack, the slightly weird way of creating a standard entity lets us override any fields we like, in a way the standard spawn function would block. For testing purposes we’re just printing a message, but setting a target would work as usual.

Quick spot of theory. When a trigger_multiple fires, there is a waiting period (controlled by wait) before the trigger can be touched again. How does the QuakeC enforce this? There are several options I’d consider if I had to write it: setting the solid type to SOLID_NOT, clearing the collision model, moving the entity out of range. In fact standard Quake code does something else – it just checks if the entity’s nextthink time is in the future each time a collision occurs, and if so it doesn’t act on the collision.

It’s a bit strange that rather than turning off the collisions, it continues to let them happen but just disconnects the relay. One thing to realise is that all of the alternatives I suggested need a think function and a nextthink time to undo the change, so you may as well just detect that the nextthink is still pending and avoid having to destroy and recreate the setup of the entity. Knowing that this is what’s going on behind the scenes is key to today’s hack.

We only have to add one key to our trigger to turn it from trigger_once to trigger_multiplewait. Set this to 3 and the test message will loop while you stand inside the trigger. If you change it again to wait 999999999999, you seem to have got the worst of both worlds: your trigger will in effect never get around to resetting, so it has the same effect as a trigger_once would, but the entity is never removed either.

Add the following fields, and everything changes

"use" "hurt_on"
"targetname" "reboot"

hurt_on performs two functions, setting SOLID_TRIGGER and setting nextthink to -1. We’re using it for the latter effect here, since we know changing nextthink is the only thing needed to reactivate a trigger. Add a button with "target" "reboot", and you will have a trigger_once that reactivates on demand.

One enhancement that might be needed is for the trigger to start inactive, and only begin detecting collisions after being activated. This is relatively simple, we just need to find a function which will set a nextthink very far in the future, and have it run as soon as the entity spawns. door_go_up will fit the bill, so long as we are in STATE_UP in door terms. Luckily that is state 0, so we don’t need to set an extra key. Add the following to the trigger.

"think" "door_go_up"
"nextthink" "0.05"

One warning about this entity we’ve created, it’s a bit of a landmine! We have a function to deploy the mine, but once it’s deployed there’s no way to go near without setting it off. In less dramatic terms, there’s no way to restore the trigger, then change your mind before it is set off and disable it again for a while. The nuclear option of using killtarget to remove the entity completely will work of course, but that’s a permanent removal. I haven’t found a tweak that adds this feature yet, but if you can come up with one post a comment below.

I have more to say on this entity setup, but I like to keep the map hack posts shorter, so I’ll post a follow-up later. You can download the test map bleeptest to try out the hack (.map and .bsp included). There is a trigger in the pit at the end of the corridor which begins inactive. Hit the button behind you to activate it, then jump in to trigger the message, and repeat as many times as you like!


8 thoughts on “Respawnable trigger_once

  1. Ingenious stuff as usual, Preach! I applied your technique in making a door that was initially closed to both monsters and the player and then later turning it openable for both.

    I just had to change the touch key to multi_trigger and turn the trigger essentially into a trigger_multiple by adding an extra classic spikeshooter/func_door logic gate in between. (Once the func_door gate is opened by the first trigger, the spikeshooter will steadily hit a trigger which will keep resetting the door opening trigger. That way the door can be opened more than once.) I could have used the instantaneous logic gates from your other posts but for this application it wasn’t necessary.

    But I feel I could have executed this initially closed func_door in a lot simpler way. Maybe someone can come up with a better method in a reply to make me feel dumb. :)

      • That trick works only if the door needs to be opened just by the player. The actual trigger_multiple trigger can only be triggered by player collisions. I needed the custom “trigger_multiple” to be able to be triggered by monster collisions as well, so the intended func_door would work like a door without a targetname. (Doors without targetnames open to both the player and monsters).

        That seemingly trivial extra requirement of mine (monster collisions) required a surprising amount of work to be able to implement, it seems. :/

  2. Yeah, for monsters (not to mention anything else that hits the trigger) you’d need to combine the Build Your Own Trigger stuff with the Mid-map Spawning trick. Make the trigger like you describe above, then change the “classname” from “InitTrigger” to “info_notnull“, and set “use” “InitTrigger” instead. Then you can cut out the logic gate.

    • I just knew there HAD to be a way to do this without a logic gate, and you figured it out; thank you! I had tried fiddling with the info_notnull and “use” “InitTrigger” myself in different kinds of my own tests in vain, but just kept ending up with the map crashing with “null function” errors at startup. But now it works. Thanks again. :)

  3. A late extra thought on this one. The door_go_up trick earns style points for the way it sets a very high nextthink time, but the more direct way would be to just…directly set a very large nextthink time! It should have been doubly obvious because we actually set nextthink for that part of the hack to work. Ah well..

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.