Trigger me timbers
Or something like that










I'll be telling you lot about a few improvements that every Quake mod should have. HL has quite a few of these, and so has much greater flexibility in its levels. The improvements are:
 

If you want to seperate out the triggers, then trigger_changetarget, trigger_random, trigger_explode, trigger_lots, trigger_sight, trigger_setspeed, trigger_message, will work all on their own. The improved path_corner needs to go with the improved func_train in order to be any use (Unless you're only using it for monsters). Trigger_hurt and trigger_push improvements should also work fine by themselves, as should the door changes. Trigger_toggle and trigger_if are tricky though because they rely on some of the other improvements; the bits that work fine by themselves are marked in the code given below.
 

The New Triggers

First off, get a copy of the QC source which you want to make the changes to. Then, open a new file and enter the following (Copy & paste would be a good idea):
 

float TCT_ONCE = 1;

void() chtarg_use =
{
 local entity e;
 e = find(world,targetname,self.target);
 while (e != world)
 {
  e.target = self.netname;
  e = find(e,targetname,self.target);
 }
 if (self.spawnflags & TCT_ONCE)
 {
  self.think = SUB_Remove;
  self.nextthink = time + 0.1;
  self.use = SUB_Null;
 }
};

void() trigger_changetarget =
{
 // Change target's targetname to netname
 self.use = chtarg_use;
};

float TRND_ONCE = 1;

void() rnd_use =
{
 local float p;
 p = random()*100;
 if (floor(p) < self.currentammo)
 {
  self.target = self.noise;
  self.killtarget = self.noise2;
 }
 else
 {
  self.target = self.noise1;
  self.killtarget = self.noise3;
 }
 SUB_UseTargets();
 if (self.spawnflags & TRND_ONCE)
 {
  self.think = SUB_Remove;
  self.nextthink = time + 0.1;
  self.use = SUB_Null;
 }
};

void() trigger_random =
{
 // noise and noise1 are the two targs
 // noise2 and noise3 are the kill targs
 // currentammo is the prob (0 to 100) of noise/noise2
 self.use = rnd_use;
};

float TEXPL_ONCE = 1;
float TEXPL_NOSPRITE = 2;

void() expl_use =
{
 local entity e;
 T_RadiusDamage(self,activator,self.dmg,world);
 if (!(self.spawnflags & TEXPL_NOSPRITE))
 {
  e = self;
  self = spawn();
  setorigin(self,e.origin);
  BecomeExplosion();
  self = e;
 }
 if (self.spawnflags & TEXPL_ONCE)
 {
  self.think = SUB_Remove;
  self.nextthink = time + 0.1;
  self.use = SUB_Null;
 }
};

void() trigger_explode =
{
 // Simply blow up with force of self.dmg when triggered
 self.use = expl_use;
};

float TLOTS_TRIGONCE = 1;

void() tlots_use =
{
 self.target = self.noise;
 SUB_UseTargets();
 self.target = self.noise1;
 SUB_UseTargets();
 self.target = self.noise2;
 SUB_UseTargets();
 self.target = self.noise3;
 SUB_UseTargets();
 self.target = self.noise4;
 SUB_UseTargets();
 self.target = self.netname;
 SUB_UseTargets();
 self.target = self.wad;
 SUB_UseTargets();
 self.target = self.map;
 SUB_UseTargets();
 self.target = self.deathtype;
 SUB_UseTargets();
 self.target = self.mdl;
 SUB_UseTargets();
 if (self.spawnflags & TLOTS_TRIGONCE)
 {
  self.think = SUB_Remove;
  self.nextthink = time + 0.1;
  self.use = SUB_Null;
 }
};

void() trigger_lots =
{
 // Triggers noise->noise3, netname, wad, map, deathtype, mdl and noise4
 // But for some reason Quake overwrites wad, so use target
 self.wad = self.target;
 self.use = tlots_use;
};

float TSIGHT_NOMONSTERS = 1;
float TSIGHT_NOPLAYERS = 2;
float TSIGHT_NOITEMS = 4;
float TSIGHT_TRIGGERONCE = 8; // Could set killtarget to the entity it's watching (e.g. if it's an info_notnull)
float TSIGHT_ONLYUSE = 16; // Don't check every 0.1s, only when used by something else

void() tsight_think =
{
 local entity e;
 local float t;
 t = FALSE;
 e = find(world,targetname,self.netname);
 if (e == world)
  t = TRUE;
 else
 {
  if (self.spawnflags & TSIGHT_NOMONSTERS)
   traceline(self.origin,e.origin + (e.mins * 0.5 + e.maxs * 0.5),TRUE,e);
  else
   traceline(self.origin,e.origin + (e.mins * 0.5 + e.maxs * 0.5),FALSE,e);
  if (trace_fraction != 1)
   if ((trace_ent.classname != "player") || !(self.spawnflags & TSIGHT_NOPLAYERS))
    if (!(trace_ent.flags & FL_ITEM) || !(self.spawnflags & TSIGHT_NOITEMS))
     t = TRUE;
 }
 if (t == TRUE)
 {
  if (!(self.spawnflags & TSIGHT_ONLYUSE))
   activator = trace_ent;
  SUB_UseTargets();
  if (self.spawnflags & TSIGHT_TRIGGERONCE)
  {
   self.think = SUB_Remove;
   if (self.spawnflags & TSIGHT_ONLYUSE)
    self.use = SUB_Null;
  }
 }
 if (!(self.spawnflags & TSIGHT_ONLYUSE) || ((t == TRUE) && (self.spawnflags & TSIGHT_TRIGGERONCE)))
  self.nextthink = time + 0.1;
};

void() trigger_sight =
{
 // Tracks its netname
 // Then when it loses sight, it activates its target
 // Netname could be a monster, a train, a info_notnull, etc.
 if (self.spawnflags & TSIGHT_ONLYUSE)
  self.use = tsight_think;
 else
 {
  self.think = tsight_think;
  self.nextthink = time + 0.5; // Wait for objects to spawn
 }
};

float TSPEED_ONCE = 1;

void() setspeed_use =
{
 local entity e;
 e = find(world,targetname,self.target);
 while (e != world)
 {
  e.speed = self.speed;
  e = find(e,targetname,self.target);
 }
 if (self.spawnflags & TSPEED_ONCE)
 {
  self.use = SUB_Null;
  self.think = SUB_Remove;
  self.nextthink = time + 0.1;
 }
};

void() trigger_setspeed =
{
 // Simply sets the speed of the target to the speed of itself
 self.use = setspeed_use;
};

float TMESS_ONCE = 1;

void() tmessage_use =
{
 local entity e;
 sound (self, CHAN_BODY, "misc/talk.wav", 1, ATTN_NONE);
 e = find(world,classname,"player");
 while (e != world)
 {
  centerprint(e,self.message);
  e = find(e,classname,"player");
 }
 if (self.spawnflags & TMESS_ONCE)
 {
  self.use = SUB_Null;
  self.think = SUB_Remove;
  self.nextthink = time + 0.1;
 }
};

void() trigger_message =
{
 // Send a message to everyone
 precache_sound("misc/talk.wav");
 self.use = tmessage_use;
};

float TOGGLE_ONCE = 1;
float TOGGLE_OFF = 2; // Turn objects off
float TOGGLE_TOGGLE = 4; // Toggle between turning objects on or off each time we are triggered

void() toggle_off;

void() toggle_on =
{
 // Turn on all recognized objects pointed to by target
 local entity oldself;
 oldself = self;
 self = find(world,targetname,oldself.target);
 while (self != world)
 {
  if (self.classname == "door") // ** Works fine on its own **
   if ((self.state != STATE_TOP) && (self.state != STATE_UP))
    door_go_up();
  if (self.use == light_use) // Quick hack of a light check, works fine on its own
   if (self.spawnflags & START_OFF) // Dark?
    light_use();
  if (self.classname == "func_train") // Needs the func_train improvements to work properly
   if (self.think == func_train_find) // Moving?
    train_next();
  if (self.classname == "trigger_hurt") // Needs the trigger_hurt improvements to work properly
   if (self.spawnflags & THURT_PAUSED)
    trigger_hurt_use();
  if (self.classname == "trigger_push") // Needs the trigger_push improvements to work properly
   if (self.spawnflags & PUSH_PAUSED)
    trigger_push_use();
  self = find(self,targetname,oldself.target);
 }
 self = oldself;
 if (self.spawnflags & TOGGLE_TOGGLE)
  self.use = toggle_off;
 if (self.spawnflags & TOGGLE_ONCE)
 {
  self.use = SUB_Null;
  self.think = SUB_Remove;
  self.nextthink = time + 0.1;
 }
};
// Requirements for the code in this function are the same as above
void() toggle_off =
{
 // Turn off all recognized objects pointed to by target
 local entity oldself;
 oldself = self;
 self = find(world,targetname,oldself.target);
 while (self != world)
 {
  if (self.classname == "door")
   if ((self.state != STATE_BOTTOM) && (self.state != STATE_DOWN))
    door_go_down();
  if (self.use == light_use) // Quick hack of a light check
   if (!(self.spawnflags & START_OFF)) // Light?
    light_use();
  if (self.classname == "func_train")
   if (self.think != func_train_find) // Moving?
   {
    if (self.netname != string_null)
     self.target = self.netname; // Bring back the old one
    if (self.count)
     self.speed = self.count; // Bring back the old speed too
    self.velocity = '0 0 0';
    self.think = func_train_find;
    self.nextthink = -1; // Stop it thinking
    sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
   }
  if (self.classname == "trigger_hurt")
   if (!(self.spawnflags & THURT_PAUSED))
    trigger_hurt_use();
  if (self.classname == "trigger_push")
   if (!(self.spawnflags & PUSH_PAUSED))
    trigger_push_use();
  self = find(self,targetname,oldself.target);
 }
 self = oldself;
 if (self.spawnflags & TOGGLE_TOGGLE)
  self.use = toggle_on;
 if (self.spawnflags & TOGGLE_ONCE)
 {
  self.use = SUB_Null;
  self.think = SUB_Remove;
  self.nextthink = time + 0.1;
 }
};

void() trigger_toggle =
{
 // Turns on/off a variety of triggers, funcs, etc.
 if (self.spawnflags & TOGGLE_OFF)
  self.use = toggle_off;
 else
  self.use = toggle_on;
};

float TRIGIF_ONCE = 1;
float TRIGIF_TOGGLE = 2;
// Same requirements as trigger_toggle
void() trigif_use =
{
 local entity e;
 e = find(world,targetname,self.noise3);
 self.target = self.noise2;
 if (e.classname == "door")
  if ((e.state != STATE_BOTTOM) && (e.state != STATE_DOWN))
   self.target = self.noise1;
 if (e.use == light_use)
  if (!(e.spawnflags & START_OFF))
   self.target = self.noise1;
 if (e.classname == "func_train")
  if (e.think != func_train_find)
   self.target = self.noise1;
 if (e.classname == "trigger_hurt")
  if (!(e.spawnflags & THURT_PAUSED))
   self.target = self.noise1;
 if (e.classname == "trigger_push")
  if (!(e.spawnflags & PUSH_PAUSED))
   self.target = self.noise1;
 SUB_UseTargets();
 if (self.spawnflags & TRIGIF_TOGGLE)
 {
  self.target = self.noise1;
  self.noise1 = self.noise2;
  self.noise2 = self.target;
 }
 if (self.spawnflags & TRIGIF_ONCE)
 {
  self.use = SUB_Null;
  self.think = SUB_Remove;
  self.nextthink = time + 0.1;
 }
};

void() trigger_if =
{
 // Activates 1 target (noise1) if the 1st ent it finds is 'on'
 // Activates 2nd target (noise2) if 1st ent is off
 // Looks for noise3
 self.use = trigif_use;
};
 

This is the changetarget, random, explode, lots, sight, setspeed, message, toggle and if code. They're simple point based triggers, which need something else to activate them. Hopefully you can see how they work. Make sure you include the QC file name in progs.h, below plats.qc (At the bottom should be fine). They all have spawnflags, even if it is as simple as 'trigger once'. Note that in the trigger once code, the triggers use code is set to SUB_Null to stop it getting re-triggered before it deletes itself.
 

Path_corner and func_train

Now we get on to the train improvements. This is in no particular order, so just do as I say and it will all come out in the wash.

Open plats.qc, and scroll down to train_use(). Replace:

 if (self.think != func_train_find)
  return;  // already activated

with:

 if ((self.think != func_train_find) && (self.classname != "misc_teleporttrain"))
 {
  if (self.netname != string_null)
   self.target = self.netname; // Bring back the old one
  if (self.count)
   self.speed = self.count; // Bring back the old speed too
  self.velocity = '0 0 0';
  self.think = func_train_find;
  self.nextthink = -1; // Stop it thinking
  sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
  return;  // already activated
 }

This bit of code is called when a train is triggered by a button, and if the train is running then it brings back its old target and speed (Just because of the way the rest of the plat code was written) to make sure it doesn't skip out any corners. It then sets its velocity to 0 to stop movement, sets self.think to func_train_find to make sure it knows its been stopped (Otherwise this code would get called again), and sets nextthink to -1 to stop func_train_find actually being called. The sound() line causes it to play its 'I've just reached a path_corner' sound, so that it doesn't continously play the movement sound. It also safeguards against Shub's teleporttrain pausing each time you teleport through it.

Just above train_wait(), insert the following function:

void(entity e) trig_pc =
{
 local entity oself;
 oself = self;
 self = e;
 self.noise = self.target; // Temp space
 self.target = self.netname; // One to trig
 SUB_UseTargets();
 self.target = self.noise;
 self = oself;
};

This just triggers a path_corners target for it. The function will be used in a minute, but basically all it does is swap the paths 'next path' entry with 'netname' (The entity we want to be triggered), calls the generic trigger code, then swaps them back to make sure the path works ok.

Now go into train_wait() itself, and add at the start:

 local entity targ;
 targ = find(world,targetname,self.netname);
 if (targ.netname)
  trig_pc(targ); // Trig stuff; trigger path_corner just reached to activate its target

Train_wait is called just as the train reaches a path_corner, which is when we want it to trigger the path_corner's target (Not when we leave the path_corner, or are just approaching it).

Now in train_next(), just underneath

 local entity targ;

add the lines

 self.netname = self.target;

This sets up the 'old path' which the pausing code uses.

 SUB_CalcMove (targ.origin - self.mins, self.speed, train_wait);

you need to add

  self.count = self.speed; // Old speed for when you pause the trains
  if (targ.speed)
   self.speed = targ.speed;
 if (self.velocity != '0 0 0')
 {
  self.worldtype = 1;
  sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
 }
 else if (self.worldtype)
 {
  self.worldtype = 0;
  sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
 }

to control the speed changes. The speed changes come after SUB_CalcMove because we want the speed to change when we reach this new targ waypoint, not when we approach it. The code also handles the sounds, so make sure you remove the other

 sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);

The sound system just plays a sound if the train is moving, and plays the stop sound if it has just finished moving to stop it repeating endlessly.

Now in func_train_find, just after

 self.target = targ.target;

you need to add the line

  self.netname = self.target;

I'm not entirely sure that this line is needed, but it doesn't help to include it to make sure the train has an old wp to fall back on if it gets paused early on.

The trains now have controllable speed and a pause ability. Now we need to finish the path_corner trigger system, by adding some code to ai.qc to make sure that monsters trigger entities when they touch the paths. In t_movetarget, go down to the line

 if (other.enemy)
  return;  // fighting, not following a path

and add the following beneath it:

 if (self.netname)
 {
  self.noise = self.target; // Temp space
  self.target = self.netname; // One to trig
  SUB_UseTargets();
  self.target = self.noise;
 }

This, like trig_pc, calls the use target code. That's the basis of the system.
 

Trigger_hurt and trigger_push

Now on to trigger_hurt and trigger_push. Find the trigger_hurt section of triggers.qc, and add the lines

float THURT_PAUSABLE = 1;
float THURT_PAUSED = 2;

just above hurt_on(); Now go down below hurt_touch(), and add

void() trigger_hurt_use =
{
 if (self.spawnflags & THURT_PAUSED)
 {
  self.spawnflags = self.spawnflags - THURT_PAUSED;
  self.touch = hurt_touch;
 }
 else
 {
  self.spawnflags = self.spawnflags | THURT_PAUSED;
  self.touch = SUB_Null;
 }
};

Now change trigger_hurt itself to:

void() trigger_hurt =
{
 InitTrigger ();
 if (self.spawnflags & THURT_PAUSABLE)  //
  self.use = trigger_hurt_use;     // *** Trig stuff
 if (!(self.spawnflags & THURT_PAUSED)) //
  self.touch = hurt_touch;
 if (!self.dmg)
  self.dmg = 5;
};

That's trigger_hurt done. A spawnflag controls whether it can be paused, and another spawnflag controls whether it is paused at the moment.

The changes are virtually the same for trigger_push. Underneath

float PUSH_ONCE = 1;

add the lines

float PUSH_PAUSABLE = 2;
float PUSH_PAUSED = 4;

Then under trigger_push_touch add the function

void() trigger_push_use =
{
 if (self.spawnflags & PUSH_PAUSED)
 {
  self.spawnflags = self.spawnflags - PUSH_PAUSED;
  self.touch = trigger_push_touch;
 }
 else
 {
  self.spawnflags = self.spawnflags | PUSH_PAUSED;
  self.touch = SUB_Null;
 }
};

And then change trigger_push to:

void() trigger_push =
{
 InitTrigger ();
 precache_sound ("ambience/windfly.wav");
 if (self.spawnflags & PUSH_PAUSABLE)
  self.use = trigger_push_use;
 if (!(self.spawnflags & PUSH_PAUSED))
  self.touch = trigger_push_touch;
 if (!self.speed)
  self.speed = 1000;
};

That's that one done as well. You can now have lots of fun making spaceships which you can depressurise!
 

Func_doors

Here's one of the shorter ones - making doors trigger things when they open and close. Open doors.qc, and at the top of door_hit_top place:

 if (self.netname)
 {
  self.deathtype = self.target;
  self.target = self.netname;
  SUB_UseTargets();
  self.target = self.deathtype;
 }

And at the top of door_hit_bottom:

 if (self.map)
 {
  self.deathtype = self.target;
  self.target = self.map;
  SUB_UseTargets();
  self.target = self.deathtype;
 }

This should now work with all doors!
 

The changes from the mappers viewpoint

trigger_changetarget:

trigger_random: trigger_explode: trigger_lots: trigger_sight: trigger_setspeed: trigger_message: path_corner: func_train: trigger_hurt: trigger_push: func_door (And other doors): trigger_toggle: trigger_if:


A few ways to use these features


Improvements


Disclaimer

Don't expect any of this to work under all conditions and with all mods/versions of the QC source. In fact, it's best if you don't expect it to work at all. Also I'm not taking any responsibility for any of it. Except that I want my name in the credits of any mod that uses these :-)