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:
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:
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 :-)