6 activator = self.enemy;
12 ==============================
15 the global "activator" should be set to the entity that initiated the firing.
17 If self.delay is set, a DelayedUse entity will be created that will actually
18 do the SUB_UseTargets after that many seconds have passed.
20 Centerprints any self.message to the activator.
22 Removes all entities with a targetname that match self.killtarget,
23 and removes them, so some events can remove other triggers.
25 Search for (string)targetname in all entities that
26 match (string)self.target and call their .use function
28 ==============================
32 local entity t, stemp, otemp, act;
39 // create a temp object to fire at a later time
41 t.classname = "DelayedUse";
42 t.nextthink = time + self.delay;
45 t.message = self.message;
46 t.killtarget = self.killtarget;
47 t.target = self.target;
55 if (activator.classname == "player" && self.message != "")
57 centerprint (activator, self.message);
59 sound (activator, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM);
63 // kill the killtagets
70 t = find (t, targetname, self.killtarget);
86 t = find (t, targetname, self.target);
107 //=============================================================================
109 float SPAWNFLAG_NOMESSAGE = 1;
110 float SPAWNFLAG_NOTOUCH = 1;
112 // the wait time has passed, so set back up for another activation
117 self.health = self.max_health;
118 self.takedamage = DAMAGE_YES;
119 self.solid = SOLID_BBOX;
124 // the trigger was just touched/killed/used
125 // self.enemy should be set to the activator so it can be held through a delay
126 // so wait for the delay time before firing
129 if (self.nextthink > time)
131 return; // allready been triggered
134 if (self.classname == "trigger_secret")
136 if (self.enemy.classname != "player")
138 found_secrets = found_secrets + 1;
139 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
143 sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
145 // don't trigger again until reset
146 self.takedamage = DAMAGE_NO;
148 activator = self.enemy;
154 self.think = multi_wait;
155 self.nextthink = time + self.wait;
158 { // we can't just remove (self) here, because this is a touch function
159 // called wheil C code is looping through area links...
160 self.touch = SUB_Null;
162 self.nextthink = time + 0.1;
163 self.think = SUB_Remove;
169 self.enemy = activator;
175 if (other.classname != "player")
179 if(self.team == other.team)
182 // if the trigger has an angles field, check player's facing direction
183 if (self.movedir != '0 0 0')
185 makevectors (other.angles);
186 if (v_forward * self.movedir < 0)
187 return; // not facing the right way
194 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
196 if (!self.takedamage)
198 self.health = self.health - damage;
199 if (self.health <= 0)
201 self.enemy = attacker;
206 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
207 Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time.
208 If "delay" is set, the trigger waits some time after activating before firing.
209 "wait" : Seconds between triggerings. (.2 default)
210 If notouch is set, the trigger is only fired by other entities, not by touching.
211 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
217 set "message" to text string
219 void spawnfunc_trigger_multiple()
221 if (self.sounds == 1)
223 precache_sound ("misc/secret.wav");
224 self.noise = "misc/secret.wav";
226 else if (self.sounds == 2)
228 precache_sound ("misc/talk.wav");
229 self.noise = "misc/talk.wav";
231 else if (self.sounds == 3)
233 precache_sound ("misc/trigger1.wav");
234 self.noise = "misc/trigger1.wav";
239 self.use = multi_use;
245 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
246 objerror ("health and notouch don't make sense\n");
247 self.max_health = self.health;
248 self.event_damage = multi_eventdamage;
249 self.takedamage = DAMAGE_YES;
250 self.solid = SOLID_BBOX;
251 setorigin (self, self.origin); // make sure it links into the world
255 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
257 self.touch = multi_touch;
263 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
264 Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching
265 "targetname". If "health" is set, the trigger must be killed to activate.
266 If notouch is set, the trigger is only fired by other entities, not by touching.
267 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
268 if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0.
274 set "message" to text string
276 void spawnfunc_trigger_once()
279 spawnfunc_trigger_multiple();
282 //=============================================================================
284 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
285 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
287 void spawnfunc_trigger_relay()
289 self.use = SUB_UseTargets;
294 self.think = SUB_UseTargets;
295 self.nextthink = self.wait;
298 void spawnfunc_trigger_delay()
303 self.use = delay_use;
306 //=============================================================================
311 self.count = self.count - 1;
317 if (activator.classname == "player"
318 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
321 centerprint (activator, "There are more to go...");
322 else if (self.count == 3)
323 centerprint (activator, "Only 3 more to go...");
324 else if (self.count == 2)
325 centerprint (activator, "Only 2 more to go...");
327 centerprint (activator, "Only 1 more to go...");
332 if (activator.classname == "player"
333 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
334 centerprint(activator, "Sequence completed!");
335 self.enemy = activator;
339 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
340 Acts as an intermediary for an action that takes multiple inputs.
342 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
344 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
346 void spawnfunc_trigger_counter()
352 self.use = counter_use;
355 .float triggerhurttime;
356 void trigger_hurt_touch()
360 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
361 other.pain_finished = min(other.pain_finished, time + 2);
362 else if (other.classname == "rune") // reset runes
363 other.nextthink = min(other.nextthink, time + 1);
366 if (other.takedamage)
367 if (other.triggerhurttime < time)
369 other.triggerhurttime = time + 1;
370 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
376 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
377 Any object touching this will be hurt
378 set dmg to damage amount
381 .entity trigger_hurt_next;
382 entity trigger_hurt_last;
383 entity trigger_hurt_first;
384 void spawnfunc_trigger_hurt()
387 self.touch = trigger_hurt_touch;
391 self.message = "was in the wrong place.";
393 if(!trigger_hurt_first)
394 trigger_hurt_first = self;
395 if(trigger_hurt_last)
396 trigger_hurt_last.trigger_hurt_next = self;
397 trigger_hurt_last = self;
400 float trace_hits_box_a0, trace_hits_box_a1;
402 float trace_hits_box_1d(float end, float thmi, float thma)
406 // just check if x is in range
414 // do the trace with respect to x
415 // 0 -> end has to stay in thmi -> thma
416 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
417 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
418 if(trace_hits_box_a0 > trace_hits_box_a1)
424 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
429 // now it is a trace from 0 to end
431 trace_hits_box_a0 = 0;
432 trace_hits_box_a1 = 1;
434 if(!trace_hits_box_1d(end_x, thmi_x, thma_x))
436 if(!trace_hits_box_1d(end_y, thmi_y, thma_y))
438 if(!trace_hits_box_1d(end_z, thmi_z, thma_z))
444 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
446 return trace_hits_box(start, end, thmi - ma, thma - mi);
449 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
453 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
454 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
461 void target_speaker_use() {sound(self, CHAN_VOICE, self.noise, 1, 1);}
463 void spawnfunc_target_speaker()
466 precache_sound (self.noise);
468 self.use = target_speaker_use;
470 ambientsound (self.origin, self.noise, 1, ATTN_STATIC);
474 void spawnfunc_func_stardust() {
475 self.effects = EF_STARDUST;
482 self.nextthink = time + 0.1;
484 if(random() < self.wait) {
485 te_spark(self.origin,'0 0 -1',self.cnt);
492 self.think = sparksthink;
493 self.nextthink = time + 0.2;
495 // self.cnt is the amount of sparks that one burst will spawn
497 self.cnt = 25.0; // nice default value
500 // self.wait is the probability that a sparkthink will spawn a spark shower
501 // range: 0 - 1, but 0 makes little sense, so...
502 if(self.wait < 0.05) {
503 self.wait = 0.25; // nice default value
508 precache_sound (self.noise);
509 ambientsound (self.origin, self.noise, 1, ATTN_STATIC);
515 self.nextthink = time + 0.1;
516 te_particlerain(self.absmin, self.absmax, self.dest, self.count, self.cnt);
517 // te_particlesnow(self.absmin, self.absmax, self.dest * 0.25, self.count, self.cnt);
518 // WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
519 // WriteByte (MSG_BROADCAST, TE_PARTICLERAIN);
520 // WriteVec (MSG_BROADCAST, self.absmin);
521 // WriteVec (MSG_BROADCAST, self.absmax);
522 // WriteVec (MSG_BROADCAST, self.dest);
523 // WriteShort (MSG_BROADCAST, self.count);
524 // WriteByte (MSG_BROADCAST, self.cnt);
527 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
528 This is an invisible area like a trigger, which rain falls inside of.
532 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
534 sets color of rain (default 12 - white)
536 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
538 void spawnfunc_func_rain()
540 self.dest = self.velocity;
541 self.velocity = '0 0 0';
543 self.dest = '0 0 -700';
544 self.angles = '0 0 0';
545 self.movetype = MOVETYPE_NONE;
546 self.solid = SOLID_NOT;
548 setmodel(self, self.model); // no precision needed
549 setorigin(self, self.origin);
550 setsize(self, self.mins, self.maxs);
556 self.count = 0.1 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
562 // convert from per second to per 0.1 sec,
563 self.count = ceil(self.count * 0.1);
564 self.think = rain_think;
565 self.nextthink = time + 0.5;
571 self.nextthink = time + 0.1 + random() * 0.05;
572 te_particlesnow(self.absmin, self.absmax, self.dest, self.count, self.cnt);
573 // WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
574 // WriteByte (MSG_BROADCAST, TE_PARTICLESNOW);
575 // WriteVec (MSG_BROADCAST, self.absmin);
576 // WriteVec (MSG_BROADCAST, self.absmax);
577 // WriteVec (MSG_BROADCAST, self.dest);
578 // WriteShort (MSG_BROADCAST, self.count);
579 // WriteByte (MSG_BROADCAST, self.cnt);
582 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
583 This is an invisible area like a trigger, which snow falls inside of.
587 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
589 sets color of rain (default 12 - white)
591 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
593 void spawnfunc_func_snow()
595 self.dest = self.velocity;
596 self.velocity = '0 0 0';
598 self.dest = '0 0 -300';
599 self.angles = '0 0 0';
600 self.movetype = MOVETYPE_NONE;
601 self.solid = SOLID_NOT;
603 setmodel(self, self.model); // no precision needed
604 setorigin(self, self.origin);
605 setsize(self, self.mins, self.maxs);
611 self.count = 0.1 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
617 // convert from per second to per 0.1 sec,
618 self.count = ceil(self.count * 0.1);
619 self.think = snow_think;
620 self.nextthink = time + 0.5;
624 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
625 void misc_laser_think()
630 self.enemy = find(world, targetname, self.target);
635 o = self.enemy.origin;
639 makevectors(self.angles);
640 o = self.origin + v_forward * MAX_SHOT_DISTANCE;
646 FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
648 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
651 if(time > self.ltime)
653 traceline(self.origin, o, MOVE_WORLDONLY, self);
654 trailparticles(self, self.cnt, self.origin, trace_endpos);
655 pointparticles(self.lip, trace_endpos, trace_plane_normal, 256 * frametime);
656 self.ltime = time + self.wait;
658 self.nextthink = time;
660 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ?
661 Any object touching the beam will be hurt
664 spawnfunc_target_position where the laser ends
666 name of beam effect to use
668 damage per second (-1 for a laser that kills immediately)
670 delay between sending the particle effect
672 void spawnfunc_misc_laser()
676 self.cnt = particleeffectnum(self.mdl);
677 self.lip = particleeffectnum(strcat(self.mdl, "_end"));
681 self.cnt = particleeffectnum("misc_laser_beam");
682 self.lip = particleeffectnum("misc_laser_beam_end");
687 self.message = "saw the light";
688 self.think = misc_laser_think;
689 self.nextthink = time;
692 // tZorks trigger impulse / gravity
698 // targeted (directional) mode
699 void trigger_impulse_touch1()
705 // FIXME: Better checking for what to push and not.
706 if (other.classname != "player")
707 if (other.classname != "corpse")
708 if (other.classname != "body")
709 if (other.classname != "gib")
710 if (other.classname != "missile")
711 if (other.classname != "casing")
712 if (other.classname != "grenade")
713 if (other.classname != "plasma")
714 if (other.classname != "plasma_prim")
715 if (other.classname != "plasma_chain")
716 if (other.classname != "droppedweapon")
719 if (other.deadflag && other.classname == "player")
722 targ = find(world, targetname, self.target);
725 objerror("trigger_force without a (valid) .target!\n");
730 if(self.falloff == 1)
731 str = (str / self.radius) * self.strength;
732 else if(self.falloff == 2)
733 str = (1 - (str / self.radius)) * self.strength;
737 pushdeltatime = time - other.lastpushtime;
738 if (pushdeltatime > 0.15) pushdeltatime = 0;
739 other.lastpushtime = time;
740 if(!pushdeltatime) return;
742 other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
745 // Directionless (accelerator/decelerator) mode
746 void trigger_impulse_touch2()
750 // FIXME: Better checking for what to push and not.
751 if (other.classname != "player")
752 if (other.classname != "corpse")
753 if (other.classname != "body")
754 if (other.classname != "gib")
755 if (other.classname != "missile")
756 if (other.classname != "casing")
757 if (other.classname != "grenade")
758 if (other.classname != "plasma")
759 if (other.classname != "plasma_prim")
760 if (other.classname != "plasma_chain")
761 if (other.classname != "droppedweapon")
764 if (other.deadflag && other.classname == "player")
767 pushdeltatime = time - other.lastpushtime;
768 if (pushdeltatime > 0.15) pushdeltatime = 0;
769 other.lastpushtime = time;
770 if(!pushdeltatime) return;
772 //if(self.strength > 1)
773 other.velocity = other.velocity * (self.strength * pushdeltatime);
775 // other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
778 // Spherical (gravity/repulsor) mode
779 void trigger_impulse_touch3()
784 // FIXME: Better checking for what to push and not.
785 if (other.classname != "player")
786 if (other.classname != "corpse")
787 if (other.classname != "body")
788 if (other.classname != "gib")
789 if (other.classname != "missile")
790 if (other.classname != "casing")
791 if (other.classname != "grenade")
792 if (other.classname != "plasma")
793 if (other.classname != "plasma_prim")
794 if (other.classname != "plasma_chain")
795 if (other.classname != "droppedweapon")
798 if (other.deadflag && other.classname == "player")
801 pushdeltatime = time - other.lastpushtime;
802 if (pushdeltatime > 0.15) pushdeltatime = 0;
803 other.lastpushtime = time;
804 if(!pushdeltatime) return;
806 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
808 str = min(self.radius, vlen(self.origin - other.origin));
810 if(self.falloff == 1)
811 str = (1 - str / self.radius) * self.strength; // 1 in the inside
812 else if(self.falloff == 2)
813 str = (str / self.radius) * self.strength; // 0 in the inside
817 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
820 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
821 -------- KEYS --------
822 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
823 If not, this trigger acts like a damper/accelerator field.
825 strength : This is how mutch force to add in the direction of .target each second
826 when .target is set. If not, this is hoe mutch to slow down/accelerate
827 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
829 radius : If set, act as a spherical device rather then a liniar one.
831 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
833 -------- NOTES --------
834 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
835 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
838 void spawnfunc_trigger_impulse()
843 if(!self.strength) self.strength = 2000;
844 setorigin(self, self.origin);
845 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
846 self.touch = trigger_impulse_touch3;
852 if(!self.strength) self.strength = 950;
853 self.touch = trigger_impulse_touch1;
857 if(!self.strength) self.strength = 0.9;
858 self.touch = trigger_impulse_touch2;