News Befunge Doom Inform Quake - Levels - Mods - Miscellaneous RISC OS Misc Links Contact |
Trigger me timbersOr something like thatI'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 all work 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. trig.zip (320,016 bytes) provides a sample implementation of these triggers, built around the QuakeC 1.01 source. There are also a couple of test maps, of which 'liftest' is likely to be the easiest to understand.
The New TriggersFirst 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_trainNow 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 does 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_pushNow 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_doorsHere'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 viewpointtrigger_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 :-)
|
Page last modified 03/03/2005 |