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
196 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
198 if (!self.takedamage)
200 self.health = self.health - damage;
201 if (self.health <= 0)
203 self.enemy = attacker;
208 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
209 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.
210 If "delay" is set, the trigger waits some time after activating before firing.
211 "wait" : Seconds between triggerings. (.2 default)
212 If notouch is set, the trigger is only fired by other entities, not by touching.
213 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
219 set "message" to text string
221 void spawnfunc_trigger_multiple()
223 if (self.sounds == 1)
225 precache_sound ("misc/secret.wav");
226 self.noise = "misc/secret.wav";
228 else if (self.sounds == 2)
230 precache_sound ("misc/talk.wav");
231 self.noise = "misc/talk.wav";
233 else if (self.sounds == 3)
235 precache_sound ("misc/trigger1.wav");
236 self.noise = "misc/trigger1.wav";
241 self.use = multi_use;
247 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
248 objerror ("health and notouch don't make sense\n");
249 self.max_health = self.health;
250 self.event_damage = multi_eventdamage;
251 self.takedamage = DAMAGE_YES;
252 self.solid = SOLID_BBOX;
253 setorigin (self, self.origin); // make sure it links into the world
257 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
259 self.touch = multi_touch;
265 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
266 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
267 "targetname". If "health" is set, the trigger must be killed to activate.
268 If notouch is set, the trigger is only fired by other entities, not by touching.
269 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
270 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.
276 set "message" to text string
278 void spawnfunc_trigger_once()
281 spawnfunc_trigger_multiple();
284 //=============================================================================
286 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
287 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
289 void spawnfunc_trigger_relay()
291 self.use = SUB_UseTargets;
296 self.think = SUB_UseTargets;
297 self.nextthink = self.wait;
300 void spawnfunc_trigger_delay()
305 self.use = delay_use;
308 //=============================================================================
313 self.count = self.count - 1;
319 if (activator.classname == "player"
320 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
323 centerprint (activator, "There are more to go...");
324 else if (self.count == 3)
325 centerprint (activator, "Only 3 more to go...");
326 else if (self.count == 2)
327 centerprint (activator, "Only 2 more to go...");
329 centerprint (activator, "Only 1 more to go...");
334 if (activator.classname == "player"
335 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
336 centerprint(activator, "Sequence completed!");
337 self.enemy = activator;
341 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
342 Acts as an intermediary for an action that takes multiple inputs.
344 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
346 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
348 void spawnfunc_trigger_counter()
354 self.use = counter_use;
357 .float triggerhurttime;
358 void trigger_hurt_touch()
360 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
363 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
366 other.pain_finished = min(other.pain_finished, time + 2);
368 else if (other.classname == "rune") // reset runes
371 other.nextthink = min(other.nextthink, time + 1);
375 if (other.takedamage)
376 if (other.triggerhurttime < time)
379 other.triggerhurttime = time + 1;
380 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
386 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
387 Any object touching this will be hurt
388 set dmg to damage amount
391 .entity trigger_hurt_next;
392 entity trigger_hurt_last;
393 entity trigger_hurt_first;
394 void spawnfunc_trigger_hurt()
397 self.touch = trigger_hurt_touch;
401 self.message = "was in the wrong place.";
403 if(!trigger_hurt_first)
404 trigger_hurt_first = self;
405 if(trigger_hurt_last)
406 trigger_hurt_last.trigger_hurt_next = self;
407 trigger_hurt_last = self;
410 float trace_hits_box_a0, trace_hits_box_a1;
412 float trace_hits_box_1d(float end, float thmi, float thma)
416 // just check if x is in range
424 // do the trace with respect to x
425 // 0 -> end has to stay in thmi -> thma
426 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
427 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
428 if(trace_hits_box_a0 > trace_hits_box_a1)
434 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
439 // now it is a trace from 0 to end
441 trace_hits_box_a0 = 0;
442 trace_hits_box_a1 = 1;
444 if(!trace_hits_box_1d(end_x, thmi_x, thma_x))
446 if(!trace_hits_box_1d(end_y, thmi_y, thma_y))
448 if(!trace_hits_box_1d(end_z, thmi_z, thma_z))
454 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
456 return trace_hits_box(start, end, thmi - ma, thma - mi);
459 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
463 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
464 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
471 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);}
473 void spawnfunc_target_speaker()
476 precache_sound (self.noise);
478 self.use = target_speaker_use;
480 ambientsound (self.origin, self.noise, VOL_BASE, ATTN_STATIC);
484 void spawnfunc_func_stardust() {
485 self.effects = EF_STARDUST;
488 float pointparticles_SendEntity(entity to, float fl)
490 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
491 WriteByte(MSG_ENTITY, fl);
495 WriteCoord(MSG_ENTITY, self.impulse);
497 WriteCoord(MSG_ENTITY, 0); // off
501 WriteCoord(MSG_ENTITY, self.origin_x);
502 WriteCoord(MSG_ENTITY, self.origin_y);
503 WriteCoord(MSG_ENTITY, self.origin_z);
507 if(self.modelindex != 4.2)
509 WriteShort(MSG_ENTITY, self.modelindex);
510 WriteCoord(MSG_ENTITY, self.mins_x);
511 WriteCoord(MSG_ENTITY, self.mins_y);
512 WriteCoord(MSG_ENTITY, self.mins_z);
513 WriteCoord(MSG_ENTITY, self.maxs_x);
514 WriteCoord(MSG_ENTITY, self.maxs_y);
515 WriteCoord(MSG_ENTITY, self.maxs_z);
519 WriteShort(MSG_ENTITY, 0);
520 WriteCoord(MSG_ENTITY, self.maxs_x);
521 WriteCoord(MSG_ENTITY, self.maxs_y);
522 WriteCoord(MSG_ENTITY, self.maxs_z);
524 WriteShort(MSG_ENTITY, self.cnt);
525 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
526 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
527 WriteCoord(MSG_ENTITY, self.waterlevel);
528 WriteCoord(MSG_ENTITY, self.count);
529 WriteByte(MSG_ENTITY, self.glow_color);
530 WriteString(MSG_ENTITY, self.noise);
535 void pointparticles_use()
537 self.state = !self.state;
541 void pointparticles_think()
543 if(self.origin != self.oldorigin)
546 self.oldorigin = self.origin;
548 self.nextthink = time;
551 void spawnfunc_func_pointparticles()
554 setmodel(self, self.model);
556 precache_sound (self.noise);
558 self.effects = EF_NODEPTHTEST;
559 self.SendEntity = pointparticles_SendEntity;
563 self.modelindex = 4.2;
564 self.origin += self.mins;
565 self.maxs -= self.mins;
567 self.model = "net_entity";
569 self.cnt = particleeffectnum(self.mdl);
570 if(self.targetname != "")
572 self.use = pointparticles_use;
573 if(self.spawnflags & 1)
580 self.think = pointparticles_think;
581 self.nextthink = time;
584 void spawnfunc_func_sparks()
586 // self.cnt is the amount of sparks that one burst will spawn
588 self.cnt = 25.0; // nice default value
591 // self.wait is the probability that a sparkthink will spawn a spark shower
592 // range: 0 - 1, but 0 makes little sense, so...
593 if(self.wait < 0.05) {
594 self.wait = 0.25; // nice default value
597 self.count = self.cnt;
600 self.velocity = '0 0 -1';
601 self.mdl = "func_sparks";
602 self.impulse = 0.1 / self.wait;
605 spawnfunc_func_pointparticles();
608 float rainsnow_SendEntity(float to)
610 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
611 WriteByte(MSG_ENTITY, self.state);
612 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
613 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
614 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
615 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
616 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
617 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
618 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
619 WriteShort(MSG_ENTITY, self.count);
620 WriteByte(MSG_ENTITY, self.cnt);
624 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
625 This is an invisible area like a trigger, which rain falls inside of.
629 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
631 sets color of rain (default 12 - white)
633 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
635 void spawnfunc_func_rain()
637 self.dest = self.velocity;
638 self.velocity = '0 0 0';
640 self.dest = '0 0 -700';
641 self.angles = '0 0 0';
642 self.movetype = MOVETYPE_NONE;
643 self.solid = SOLID_NOT;
644 SetBrushEntityModel();
650 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
653 if(self.count > 65535)
656 self.state = 1; // 1 is rain, 0 is snow
657 self.effects = EF_NODEPTHTEST;
658 self.SendEntity = rainsnow_SendEntity;
661 self.model = "net_entity";
665 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
666 This is an invisible area like a trigger, which snow falls inside of.
670 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
672 sets color of rain (default 12 - white)
674 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
676 void spawnfunc_func_snow()
678 self.dest = self.velocity;
679 self.velocity = '0 0 0';
681 self.dest = '0 0 -300';
682 self.angles = '0 0 0';
683 self.movetype = MOVETYPE_NONE;
684 self.solid = SOLID_NOT;
685 SetBrushEntityModel();
691 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
694 if(self.count > 65535)
697 self.state = 0; // 1 is rain, 0 is snow
698 self.effects = EF_NODEPTHTEST;
699 self.SendEntity = rainsnow_SendEntity;
702 self.model = "net_entity";
706 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
708 void misc_laser_aim()
713 a = vectoangles(self.enemy.origin - self.origin);
721 if(self.origin != self.oldorigin)
724 self.oldorigin = self.origin;
728 void misc_laser_init()
730 self.enemy = find(world, targetname, self.target);
733 void misc_laser_think()
737 self.nextthink = time;
744 makevectors(self.mangle);
745 o = self.origin + 32768 * v_forward;
750 FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
752 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
756 float laser_SendEntity(entity to, float fl)
758 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
759 WriteByte(MSG_ENTITY, fl);
762 WriteCoord(MSG_ENTITY, self.origin_x);
763 WriteCoord(MSG_ENTITY, self.origin_y);
764 WriteCoord(MSG_ENTITY, self.origin_z);
768 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
769 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
770 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
771 WriteShort(MSG_ENTITY, self.cnt);
775 WriteCoord(MSG_ENTITY, self.mangle_x);
776 WriteCoord(MSG_ENTITY, self.mangle_y);
779 WriteByte(MSG_ENTITY, self.state);
783 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON
784 Any object touching the beam will be hurt
787 spawnfunc_target_position where the laser ends
789 name of beam end effect to use
791 color of the beam (default: red)
793 damage per second (-1 for a laser that kills immediately)
797 self.state = !self.state;
802 void spawnfunc_misc_laser()
806 self.cnt = particleeffectnum(strcat(self.mdl, "_end"));
808 self.cnt = particleeffectnum(self.mdl);
812 self.cnt = particleeffectnum("misc_laser_beam_end");
814 if(self.colormod == '0 0 0')
815 self.colormod = '1 0 0';
817 self.message = "saw the light";
818 self.think = misc_laser_think;
819 self.nextthink = time;
820 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
822 self.effects = EF_NODEPTHTEST;
823 self.SendEntity = laser_SendEntity;
826 self.model = "net_entity";
828 if(self.targetname != "")
830 self.use = laser_use;
831 if(self.spawnflags & 1)
840 // tZorks trigger impulse / gravity
846 // targeted (directional) mode
847 void trigger_impulse_touch1()
853 // FIXME: Better checking for what to push and not.
854 if (other.classname != "player")
855 if (other.classname != "corpse")
856 if (other.classname != "body")
857 if (other.classname != "gib")
858 if (other.classname != "missile")
859 if (other.classname != "casing")
860 if (other.classname != "grenade")
861 if (other.classname != "plasma")
862 if (other.classname != "plasma_prim")
863 if (other.classname != "plasma_chain")
864 if (other.classname != "droppedweapon")
867 if (other.deadflag && other.classname == "player")
872 targ = find(world, targetname, self.target);
875 objerror("trigger_force without a (valid) .target!\n");
880 if(self.falloff == 1)
881 str = (str / self.radius) * self.strength;
882 else if(self.falloff == 2)
883 str = (1 - (str / self.radius)) * self.strength;
887 pushdeltatime = time - other.lastpushtime;
888 if (pushdeltatime > 0.15) pushdeltatime = 0;
889 other.lastpushtime = time;
890 if(!pushdeltatime) return;
892 other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
895 // Directionless (accelerator/decelerator) mode
896 void trigger_impulse_touch2()
900 // FIXME: Better checking for what to push and not.
901 if (other.classname != "player")
902 if (other.classname != "corpse")
903 if (other.classname != "body")
904 if (other.classname != "gib")
905 if (other.classname != "missile")
906 if (other.classname != "casing")
907 if (other.classname != "grenade")
908 if (other.classname != "plasma")
909 if (other.classname != "plasma_prim")
910 if (other.classname != "plasma_chain")
911 if (other.classname != "droppedweapon")
914 if (other.deadflag && other.classname == "player")
919 pushdeltatime = time - other.lastpushtime;
920 if (pushdeltatime > 0.15) pushdeltatime = 0;
921 other.lastpushtime = time;
922 if(!pushdeltatime) return;
924 //if(self.strength > 1)
925 other.velocity = other.velocity * (self.strength * pushdeltatime);
927 // other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
930 // Spherical (gravity/repulsor) mode
931 void trigger_impulse_touch3()
936 // FIXME: Better checking for what to push and not.
937 if (other.classname != "player")
938 if (other.classname != "corpse")
939 if (other.classname != "body")
940 if (other.classname != "gib")
941 if (other.classname != "missile")
942 if (other.classname != "casing")
943 if (other.classname != "grenade")
944 if (other.classname != "plasma")
945 if (other.classname != "plasma_prim")
946 if (other.classname != "plasma_chain")
947 if (other.classname != "droppedweapon")
950 if (other.deadflag && other.classname == "player")
955 pushdeltatime = time - other.lastpushtime;
956 if (pushdeltatime > 0.15) pushdeltatime = 0;
957 other.lastpushtime = time;
958 if(!pushdeltatime) return;
960 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
962 str = min(self.radius, vlen(self.origin - other.origin));
964 if(self.falloff == 1)
965 str = (1 - str / self.radius) * self.strength; // 1 in the inside
966 else if(self.falloff == 2)
967 str = (str / self.radius) * self.strength; // 0 in the inside
971 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
974 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
975 -------- KEYS --------
976 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
977 If not, this trigger acts like a damper/accelerator field.
979 strength : This is how mutch force to add in the direction of .target each second
980 when .target is set. If not, this is hoe mutch to slow down/accelerate
981 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
983 radius : If set, act as a spherical device rather then a liniar one.
985 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
987 -------- NOTES --------
988 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
989 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
992 void spawnfunc_trigger_impulse()
997 if(!self.strength) self.strength = 2000;
998 setorigin(self, self.origin);
999 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1000 self.touch = trigger_impulse_touch3;
1006 if(!self.strength) self.strength = 950;
1007 self.touch = trigger_impulse_touch1;
1011 if(!self.strength) self.strength = 0.9;
1012 self.touch = trigger_impulse_touch2;
1017 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1018 "Flip-flop" trigger gate... lets only every second trigger event through
1022 self.state = !self.state;
1027 void spawnfunc_trigger_flipflop()
1029 if(self.spawnflags & 1)
1031 self.use = flipflop_use;
1034 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1035 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1039 self.nextthink = time + self.wait;
1045 void monoflop_fixed_use()
1049 self.nextthink = time + self.wait;
1054 void monoflop_think()
1060 void spawnfunc_trigger_monoflop()
1064 if(self.spawnflags & 1)
1065 self.use = monoflop_fixed_use;
1067 self.use = monoflop_use;
1068 self.think = monoflop_think;
1075 src = find(world, targetname, self.killtarget);
1076 dst = find(world, targetname, self.target);
1080 objerror("follow: could not find target/killtarget");
1084 dst.movetype = MOVETYPE_FOLLOW;
1086 dst.punchangle = src.angles;
1087 dst.view_ofs = dst.origin - src.origin;
1088 dst.v_angle = dst.angles - src.angles;
1093 void spawnfunc_misc_follow()
1095 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);