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 play2(activator, "misc/talk.wav");
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.enemy, CHAN_AUTO, self.noise, VOL_BASE, 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_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);}
463 void spawnfunc_target_speaker()
466 precache_sound (self.noise);
468 self.use = target_speaker_use;
470 ambientsound (self.origin, self.noise, VOL_BASE, 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, VOL_BASE, 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;
547 SetBrushEntityModel();
553 self.count = 0.1 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
559 // convert from per second to per 0.1 sec,
560 self.count = ceil(self.count * 0.1);
561 self.think = rain_think;
562 self.nextthink = time + 0.5;
568 self.nextthink = time + 0.1 + random() * 0.05;
569 te_particlesnow(self.absmin, self.absmax, self.dest, self.count, self.cnt);
570 // WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
571 // WriteByte (MSG_BROADCAST, TE_PARTICLESNOW);
572 // WriteVec (MSG_BROADCAST, self.absmin);
573 // WriteVec (MSG_BROADCAST, self.absmax);
574 // WriteVec (MSG_BROADCAST, self.dest);
575 // WriteShort (MSG_BROADCAST, self.count);
576 // WriteByte (MSG_BROADCAST, self.cnt);
579 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
580 This is an invisible area like a trigger, which snow falls inside of.
584 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
586 sets color of rain (default 12 - white)
588 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
590 void spawnfunc_func_snow()
592 self.dest = self.velocity;
593 self.velocity = '0 0 0';
595 self.dest = '0 0 -300';
596 self.angles = '0 0 0';
597 self.movetype = MOVETYPE_NONE;
598 self.solid = SOLID_NOT;
599 SetBrushEntityModel();
605 self.count = 0.1 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
611 // convert from per second to per 0.1 sec,
612 self.count = ceil(self.count * 0.1);
613 self.think = snow_think;
614 self.nextthink = time + 0.5;
618 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
619 void misc_laser_think()
624 self.enemy = find(world, targetname, self.target);
629 o = self.enemy.origin;
633 makevectors(self.angles);
634 o = self.origin + v_forward * MAX_SHOT_DISTANCE;
640 FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
642 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
645 if(time > self.ltime)
647 traceline(self.origin, o, MOVE_WORLDONLY, self);
648 trailparticles(self, self.cnt, self.origin, trace_endpos);
649 pointparticles(self.lip, trace_endpos, trace_plane_normal, 256 * frametime);
650 self.ltime = time + self.wait;
652 self.nextthink = time;
654 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ?
655 Any object touching the beam will be hurt
658 spawnfunc_target_position where the laser ends
660 name of beam effect to use
662 damage per second (-1 for a laser that kills immediately)
664 delay between sending the particle effect
666 void spawnfunc_misc_laser()
670 self.cnt = particleeffectnum(self.mdl);
671 self.lip = particleeffectnum(strcat(self.mdl, "_end"));
675 self.cnt = particleeffectnum("misc_laser_beam");
676 self.lip = particleeffectnum("misc_laser_beam_end");
681 self.message = "saw the light";
682 self.think = misc_laser_think;
683 self.nextthink = time;
686 // tZorks trigger impulse / gravity
692 // targeted (directional) mode
693 void trigger_impulse_touch1()
699 // FIXME: Better checking for what to push and not.
700 if (other.classname != "player")
701 if (other.classname != "corpse")
702 if (other.classname != "body")
703 if (other.classname != "gib")
704 if (other.classname != "missile")
705 if (other.classname != "casing")
706 if (other.classname != "grenade")
707 if (other.classname != "plasma")
708 if (other.classname != "plasma_prim")
709 if (other.classname != "plasma_chain")
710 if (other.classname != "droppedweapon")
713 if (other.deadflag && other.classname == "player")
716 targ = find(world, targetname, self.target);
719 objerror("trigger_force without a (valid) .target!\n");
724 if(self.falloff == 1)
725 str = (str / self.radius) * self.strength;
726 else if(self.falloff == 2)
727 str = (1 - (str / self.radius)) * self.strength;
731 pushdeltatime = time - other.lastpushtime;
732 if (pushdeltatime > 0.15) pushdeltatime = 0;
733 other.lastpushtime = time;
734 if(!pushdeltatime) return;
736 other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
739 // Directionless (accelerator/decelerator) mode
740 void trigger_impulse_touch2()
744 // FIXME: Better checking for what to push and not.
745 if (other.classname != "player")
746 if (other.classname != "corpse")
747 if (other.classname != "body")
748 if (other.classname != "gib")
749 if (other.classname != "missile")
750 if (other.classname != "casing")
751 if (other.classname != "grenade")
752 if (other.classname != "plasma")
753 if (other.classname != "plasma_prim")
754 if (other.classname != "plasma_chain")
755 if (other.classname != "droppedweapon")
758 if (other.deadflag && other.classname == "player")
761 pushdeltatime = time - other.lastpushtime;
762 if (pushdeltatime > 0.15) pushdeltatime = 0;
763 other.lastpushtime = time;
764 if(!pushdeltatime) return;
766 //if(self.strength > 1)
767 other.velocity = other.velocity * (self.strength * pushdeltatime);
769 // other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
772 // Spherical (gravity/repulsor) mode
773 void trigger_impulse_touch3()
778 // FIXME: Better checking for what to push and not.
779 if (other.classname != "player")
780 if (other.classname != "corpse")
781 if (other.classname != "body")
782 if (other.classname != "gib")
783 if (other.classname != "missile")
784 if (other.classname != "casing")
785 if (other.classname != "grenade")
786 if (other.classname != "plasma")
787 if (other.classname != "plasma_prim")
788 if (other.classname != "plasma_chain")
789 if (other.classname != "droppedweapon")
792 if (other.deadflag && other.classname == "player")
795 pushdeltatime = time - other.lastpushtime;
796 if (pushdeltatime > 0.15) pushdeltatime = 0;
797 other.lastpushtime = time;
798 if(!pushdeltatime) return;
800 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
802 str = min(self.radius, vlen(self.origin - other.origin));
804 if(self.falloff == 1)
805 str = (1 - str / self.radius) * self.strength; // 1 in the inside
806 else if(self.falloff == 2)
807 str = (str / self.radius) * self.strength; // 0 in the inside
811 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
814 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
815 -------- KEYS --------
816 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
817 If not, this trigger acts like a damper/accelerator field.
819 strength : This is how mutch force to add in the direction of .target each second
820 when .target is set. If not, this is hoe mutch to slow down/accelerate
821 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
823 radius : If set, act as a spherical device rather then a liniar one.
825 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
827 -------- NOTES --------
828 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
829 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
832 void spawnfunc_trigger_impulse()
837 if(!self.strength) self.strength = 2000;
838 setorigin(self, self.origin);
839 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
840 self.touch = trigger_impulse_touch3;
846 if(!self.strength) self.strength = 950;
847 self.touch = trigger_impulse_touch1;
851 if(!self.strength) self.strength = 0.9;
852 self.touch = trigger_impulse_touch2;