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()
358 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
361 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
364 other.pain_finished = min(other.pain_finished, time + 2);
366 else if (other.classname == "rune") // reset runes
369 other.nextthink = min(other.nextthink, time + 1);
373 if (other.takedamage)
374 if (other.triggerhurttime < time)
377 other.triggerhurttime = time + 1;
378 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
384 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
385 Any object touching this will be hurt
386 set dmg to damage amount
389 .entity trigger_hurt_next;
390 entity trigger_hurt_last;
391 entity trigger_hurt_first;
392 void spawnfunc_trigger_hurt()
395 self.touch = trigger_hurt_touch;
399 self.message = "was in the wrong place.";
401 if(!trigger_hurt_first)
402 trigger_hurt_first = self;
403 if(trigger_hurt_last)
404 trigger_hurt_last.trigger_hurt_next = self;
405 trigger_hurt_last = self;
408 float trace_hits_box_a0, trace_hits_box_a1;
410 float trace_hits_box_1d(float end, float thmi, float thma)
414 // just check if x is in range
422 // do the trace with respect to x
423 // 0 -> end has to stay in thmi -> thma
424 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
425 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
426 if(trace_hits_box_a0 > trace_hits_box_a1)
432 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
437 // now it is a trace from 0 to end
439 trace_hits_box_a0 = 0;
440 trace_hits_box_a1 = 1;
442 if(!trace_hits_box_1d(end_x, thmi_x, thma_x))
444 if(!trace_hits_box_1d(end_y, thmi_y, thma_y))
446 if(!trace_hits_box_1d(end_z, thmi_z, thma_z))
452 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
454 return trace_hits_box(start, end, thmi - ma, thma - mi);
457 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
461 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
462 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
469 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);}
471 void spawnfunc_target_speaker()
474 precache_sound (self.noise);
476 self.use = target_speaker_use;
478 ambientsound (self.origin, self.noise, VOL_BASE, ATTN_STATIC);
482 void spawnfunc_func_stardust() {
483 self.effects = EF_STARDUST;
490 self.nextthink = time + 0.1;
492 if(random() < self.wait) {
493 te_spark(self.origin,'0 0 -1',self.cnt);
500 self.think = sparksthink;
501 self.nextthink = time + 0.2;
503 // self.cnt is the amount of sparks that one burst will spawn
505 self.cnt = 25.0; // nice default value
508 // self.wait is the probability that a sparkthink will spawn a spark shower
509 // range: 0 - 1, but 0 makes little sense, so...
510 if(self.wait < 0.05) {
511 self.wait = 0.25; // nice default value
516 precache_sound (self.noise);
517 ambientsound (self.origin, self.noise, VOL_BASE, ATTN_STATIC);
523 self.nextthink = time + 0.1;
524 te_particlerain(self.absmin, self.absmax, self.dest, self.count, self.cnt);
525 // te_particlesnow(self.absmin, self.absmax, self.dest * 0.25, self.count, self.cnt);
526 // WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
527 // WriteByte (MSG_BROADCAST, TE_PARTICLERAIN);
528 // WriteVec (MSG_BROADCAST, self.absmin);
529 // WriteVec (MSG_BROADCAST, self.absmax);
530 // WriteVec (MSG_BROADCAST, self.dest);
531 // WriteShort (MSG_BROADCAST, self.count);
532 // WriteByte (MSG_BROADCAST, self.cnt);
535 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
536 This is an invisible area like a trigger, which rain falls inside of.
540 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
542 sets color of rain (default 12 - white)
544 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
546 void spawnfunc_func_rain()
548 self.dest = self.velocity;
549 self.velocity = '0 0 0';
551 self.dest = '0 0 -700';
552 self.angles = '0 0 0';
553 self.movetype = MOVETYPE_NONE;
554 self.solid = SOLID_NOT;
555 SetBrushEntityModel();
561 self.count = 0.1 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
567 // convert from per second to per 0.1 sec,
568 self.count = ceil(self.count * 0.1);
569 self.think = rain_think;
570 self.nextthink = time + 0.5;
576 self.nextthink = time + 0.1 + random() * 0.05;
577 te_particlesnow(self.absmin, self.absmax, self.dest, self.count, self.cnt);
578 // WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
579 // WriteByte (MSG_BROADCAST, TE_PARTICLESNOW);
580 // WriteVec (MSG_BROADCAST, self.absmin);
581 // WriteVec (MSG_BROADCAST, self.absmax);
582 // WriteVec (MSG_BROADCAST, self.dest);
583 // WriteShort (MSG_BROADCAST, self.count);
584 // WriteByte (MSG_BROADCAST, self.cnt);
587 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
588 This is an invisible area like a trigger, which snow falls inside of.
592 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
594 sets color of rain (default 12 - white)
596 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
598 void spawnfunc_func_snow()
600 self.dest = self.velocity;
601 self.velocity = '0 0 0';
603 self.dest = '0 0 -300';
604 self.angles = '0 0 0';
605 self.movetype = MOVETYPE_NONE;
606 self.solid = SOLID_NOT;
607 SetBrushEntityModel();
613 self.count = 0.1 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
619 // convert from per second to per 0.1 sec,
620 self.count = ceil(self.count * 0.1);
621 self.think = snow_think;
622 self.nextthink = time + 0.5;
626 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
627 void misc_laser_think()
632 self.enemy = find(world, targetname, self.target);
637 o = self.enemy.origin;
641 makevectors(self.angles);
642 o = self.origin + v_forward * MAX_SHOT_DISTANCE;
648 FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
650 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
653 if(time > self.ltime)
655 traceline(self.origin, o, MOVE_WORLDONLY, self);
656 trailparticles(self, self.cnt, self.origin, trace_endpos);
657 pointparticles(self.lip, trace_endpos, trace_plane_normal, 256 * frametime);
658 self.ltime = time + self.wait;
660 self.nextthink = time;
662 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ?
663 Any object touching the beam will be hurt
666 spawnfunc_target_position where the laser ends
668 name of beam effect to use
670 damage per second (-1 for a laser that kills immediately)
672 delay between sending the particle effect
674 void spawnfunc_misc_laser()
678 self.cnt = particleeffectnum(self.mdl);
679 self.lip = particleeffectnum(strcat(self.mdl, "_end"));
683 self.cnt = particleeffectnum("misc_laser_beam");
684 self.lip = particleeffectnum("misc_laser_beam_end");
689 self.message = "saw the light";
690 self.think = misc_laser_think;
691 self.nextthink = time;
694 // tZorks trigger impulse / gravity
700 // targeted (directional) mode
701 void trigger_impulse_touch1()
707 // FIXME: Better checking for what to push and not.
708 if (other.classname != "player")
709 if (other.classname != "corpse")
710 if (other.classname != "body")
711 if (other.classname != "gib")
712 if (other.classname != "missile")
713 if (other.classname != "casing")
714 if (other.classname != "grenade")
715 if (other.classname != "plasma")
716 if (other.classname != "plasma_prim")
717 if (other.classname != "plasma_chain")
718 if (other.classname != "droppedweapon")
721 if (other.deadflag && other.classname == "player")
726 targ = find(world, targetname, self.target);
729 objerror("trigger_force without a (valid) .target!\n");
734 if(self.falloff == 1)
735 str = (str / self.radius) * self.strength;
736 else if(self.falloff == 2)
737 str = (1 - (str / self.radius)) * self.strength;
741 pushdeltatime = time - other.lastpushtime;
742 if (pushdeltatime > 0.15) pushdeltatime = 0;
743 other.lastpushtime = time;
744 if(!pushdeltatime) return;
746 other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
749 // Directionless (accelerator/decelerator) mode
750 void trigger_impulse_touch2()
754 // FIXME: Better checking for what to push and not.
755 if (other.classname != "player")
756 if (other.classname != "corpse")
757 if (other.classname != "body")
758 if (other.classname != "gib")
759 if (other.classname != "missile")
760 if (other.classname != "casing")
761 if (other.classname != "grenade")
762 if (other.classname != "plasma")
763 if (other.classname != "plasma_prim")
764 if (other.classname != "plasma_chain")
765 if (other.classname != "droppedweapon")
768 if (other.deadflag && other.classname == "player")
773 pushdeltatime = time - other.lastpushtime;
774 if (pushdeltatime > 0.15) pushdeltatime = 0;
775 other.lastpushtime = time;
776 if(!pushdeltatime) return;
778 //if(self.strength > 1)
779 other.velocity = other.velocity * (self.strength * pushdeltatime);
781 // other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
784 // Spherical (gravity/repulsor) mode
785 void trigger_impulse_touch3()
790 // FIXME: Better checking for what to push and not.
791 if (other.classname != "player")
792 if (other.classname != "corpse")
793 if (other.classname != "body")
794 if (other.classname != "gib")
795 if (other.classname != "missile")
796 if (other.classname != "casing")
797 if (other.classname != "grenade")
798 if (other.classname != "plasma")
799 if (other.classname != "plasma_prim")
800 if (other.classname != "plasma_chain")
801 if (other.classname != "droppedweapon")
804 if (other.deadflag && other.classname == "player")
809 pushdeltatime = time - other.lastpushtime;
810 if (pushdeltatime > 0.15) pushdeltatime = 0;
811 other.lastpushtime = time;
812 if(!pushdeltatime) return;
814 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
816 str = min(self.radius, vlen(self.origin - other.origin));
818 if(self.falloff == 1)
819 str = (1 - str / self.radius) * self.strength; // 1 in the inside
820 else if(self.falloff == 2)
821 str = (str / self.radius) * self.strength; // 0 in the inside
825 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
828 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
829 -------- KEYS --------
830 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
831 If not, this trigger acts like a damper/accelerator field.
833 strength : This is how mutch force to add in the direction of .target each second
834 when .target is set. If not, this is hoe mutch to slow down/accelerate
835 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
837 radius : If set, act as a spherical device rather then a liniar one.
839 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
841 -------- NOTES --------
842 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
843 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
846 void spawnfunc_trigger_impulse()
851 if(!self.strength) self.strength = 2000;
852 setorigin(self, self.origin);
853 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
854 self.touch = trigger_impulse_touch3;
860 if(!self.strength) self.strength = 950;
861 self.touch = trigger_impulse_touch1;
865 if(!self.strength) self.strength = 0.9;
866 self.touch = trigger_impulse_touch2;