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;
260 setorigin (self, self.origin); // make sure it links into the world
266 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
267 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
268 "targetname". If "health" is set, the trigger must be killed to activate.
269 If notouch is set, the trigger is only fired by other entities, not by touching.
270 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
271 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.
277 set "message" to text string
279 void spawnfunc_trigger_once()
282 spawnfunc_trigger_multiple();
285 //=============================================================================
287 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
288 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
290 void spawnfunc_trigger_relay()
292 self.use = SUB_UseTargets;
297 self.think = SUB_UseTargets;
298 self.nextthink = self.wait;
301 void spawnfunc_trigger_delay()
306 self.use = delay_use;
309 //=============================================================================
314 self.count = self.count - 1;
320 if (activator.classname == "player"
321 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
324 centerprint (activator, "There are more to go...");
325 else if (self.count == 3)
326 centerprint (activator, "Only 3 more to go...");
327 else if (self.count == 2)
328 centerprint (activator, "Only 2 more to go...");
330 centerprint (activator, "Only 1 more to go...");
335 if (activator.classname == "player"
336 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
337 centerprint(activator, "Sequence completed!");
338 self.enemy = activator;
342 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
343 Acts as an intermediary for an action that takes multiple inputs.
345 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
347 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
349 void spawnfunc_trigger_counter()
355 self.use = counter_use;
358 .float triggerhurttime;
359 void trigger_hurt_touch()
361 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
364 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
367 other.pain_finished = min(other.pain_finished, time + 2);
369 else if (other.classname == "rune") // reset runes
372 other.nextthink = min(other.nextthink, time + 1);
376 if (other.takedamage)
377 if (other.triggerhurttime < time)
380 other.triggerhurttime = time + 1;
381 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
387 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
388 Any object touching this will be hurt
389 set dmg to damage amount
392 .entity trigger_hurt_next;
393 entity trigger_hurt_last;
394 entity trigger_hurt_first;
395 void spawnfunc_trigger_hurt()
398 self.touch = trigger_hurt_touch;
402 self.message = "was in the wrong place.";
404 if(!trigger_hurt_first)
405 trigger_hurt_first = self;
406 if(trigger_hurt_last)
407 trigger_hurt_last.trigger_hurt_next = self;
408 trigger_hurt_last = self;
411 float trace_hits_box_a0, trace_hits_box_a1;
413 float trace_hits_box_1d(float end, float thmi, float thma)
417 // just check if x is in range
425 // do the trace with respect to x
426 // 0 -> end has to stay in thmi -> thma
427 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
428 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
429 if(trace_hits_box_a0 > trace_hits_box_a1)
435 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
440 // now it is a trace from 0 to end
442 trace_hits_box_a0 = 0;
443 trace_hits_box_a1 = 1;
445 if(!trace_hits_box_1d(end_x, thmi_x, thma_x))
447 if(!trace_hits_box_1d(end_y, thmi_y, thma_y))
449 if(!trace_hits_box_1d(end_z, thmi_z, thma_z))
455 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
457 return trace_hits_box(start, end, thmi - ma, thma - mi);
460 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
464 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
465 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
472 // TODO add a way to do looped sounds with sound(); then complete this entity
473 .float volume, atten;
474 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
476 void spawnfunc_target_speaker()
479 precache_sound (self.noise);
483 self.atten = ATTN_NORM;
484 else if(self.atten < 0)
488 self.use = target_speaker_use;
493 self.atten = ATTN_STATIC;
494 else if(self.atten < 0)
498 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
503 void spawnfunc_func_stardust() {
504 self.effects = EF_STARDUST;
507 float pointparticles_SendEntity(entity to, float fl)
509 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
510 WriteByte(MSG_ENTITY, fl);
514 WriteCoord(MSG_ENTITY, self.impulse);
516 WriteCoord(MSG_ENTITY, 0); // off
520 WriteCoord(MSG_ENTITY, self.origin_x);
521 WriteCoord(MSG_ENTITY, self.origin_y);
522 WriteCoord(MSG_ENTITY, self.origin_z);
526 if(self.modelindex != 4.2)
528 WriteShort(MSG_ENTITY, self.modelindex);
529 WriteCoord(MSG_ENTITY, self.mins_x);
530 WriteCoord(MSG_ENTITY, self.mins_y);
531 WriteCoord(MSG_ENTITY, self.mins_z);
532 WriteCoord(MSG_ENTITY, self.maxs_x);
533 WriteCoord(MSG_ENTITY, self.maxs_y);
534 WriteCoord(MSG_ENTITY, self.maxs_z);
538 WriteShort(MSG_ENTITY, 0);
539 WriteCoord(MSG_ENTITY, self.maxs_x);
540 WriteCoord(MSG_ENTITY, self.maxs_y);
541 WriteCoord(MSG_ENTITY, self.maxs_z);
543 WriteShort(MSG_ENTITY, self.cnt);
544 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
545 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
546 WriteCoord(MSG_ENTITY, self.waterlevel);
547 WriteCoord(MSG_ENTITY, self.count);
548 WriteByte(MSG_ENTITY, self.glow_color);
549 WriteString(MSG_ENTITY, self.noise);
554 void pointparticles_use()
556 self.state = !self.state;
560 void pointparticles_think()
562 if(self.origin != self.oldorigin)
565 self.oldorigin = self.origin;
567 self.nextthink = time;
570 void spawnfunc_func_pointparticles()
573 setmodel(self, self.model);
575 precache_sound (self.noise);
577 self.effects = EF_NODEPTHTEST;
578 self.SendEntity = pointparticles_SendEntity;
582 self.modelindex = 4.2;
583 self.origin += self.mins;
584 self.maxs -= self.mins;
586 self.model = "net_entity";
588 self.cnt = particleeffectnum(self.mdl);
591 self.use = pointparticles_use;
592 if(self.spawnflags & 1)
599 self.think = pointparticles_think;
600 self.nextthink = time;
603 void spawnfunc_func_sparks()
605 // self.cnt is the amount of sparks that one burst will spawn
607 self.cnt = 25.0; // nice default value
610 // self.wait is the probability that a sparkthink will spawn a spark shower
611 // range: 0 - 1, but 0 makes little sense, so...
612 if(self.wait < 0.05) {
613 self.wait = 0.25; // nice default value
616 self.count = self.cnt;
619 self.velocity = '0 0 -1';
620 self.mdl = "TE_SPARK";
621 self.impulse = 0.1 / self.wait;
624 spawnfunc_func_pointparticles();
627 float rainsnow_SendEntity(float to)
629 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
630 WriteByte(MSG_ENTITY, self.state);
631 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
632 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
633 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
634 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
635 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
636 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
637 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
638 WriteShort(MSG_ENTITY, self.count);
639 WriteByte(MSG_ENTITY, self.cnt);
643 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
644 This is an invisible area like a trigger, which rain falls inside of.
648 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
650 sets color of rain (default 12 - white)
652 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
654 void spawnfunc_func_rain()
656 self.dest = self.velocity;
657 self.velocity = '0 0 0';
659 self.dest = '0 0 -700';
660 self.angles = '0 0 0';
661 self.movetype = MOVETYPE_NONE;
662 self.solid = SOLID_NOT;
663 SetBrushEntityModel();
669 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
672 if(self.count > 65535)
675 self.state = 1; // 1 is rain, 0 is snow
676 self.effects = EF_NODEPTHTEST;
677 self.SendEntity = rainsnow_SendEntity;
680 self.model = "net_entity";
684 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
685 This is an invisible area like a trigger, which snow falls inside of.
689 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
691 sets color of rain (default 12 - white)
693 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
695 void spawnfunc_func_snow()
697 self.dest = self.velocity;
698 self.velocity = '0 0 0';
700 self.dest = '0 0 -300';
701 self.angles = '0 0 0';
702 self.movetype = MOVETYPE_NONE;
703 self.solid = SOLID_NOT;
704 SetBrushEntityModel();
710 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
713 if(self.count > 65535)
716 self.state = 0; // 1 is rain, 0 is snow
717 self.effects = EF_NODEPTHTEST;
718 self.SendEntity = rainsnow_SendEntity;
721 self.model = "net_entity";
725 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
727 void misc_laser_aim()
732 if(self.spawnflags & 2)
734 if(self.enemy.origin != self.mangle)
736 self.mangle = self.enemy.origin;
742 a = vectoangles(self.enemy.origin - self.origin);
753 if(self.angles != self.mangle)
755 self.mangle = self.angles;
759 if(self.origin != self.oldorigin)
762 self.oldorigin = self.origin;
766 void misc_laser_init()
768 if(self.target != "")
769 self.enemy = find(world, targetname, self.target);
773 void misc_laser_think()
778 self.nextthink = time;
787 o = self.enemy.origin;
788 if not(self.spawnflags & 2)
789 o = self.origin + normalize(o - self.origin) * 32768;
793 makevectors(self.mangle);
794 o = self.origin + v_forward * 32768;
800 FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
802 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
805 if(self.enemy.target != "") // DETECTOR laser
807 traceline(self.origin, o, MOVE_NORMAL, self);
808 if(trace_ent.classname == "player")
810 self.pusher = trace_ent;
817 activator = self.pusher;
830 activator = self.pusher;
838 float laser_SendEntity(entity to, float fl)
840 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
841 fl = fl - (fl & 0xC0); // use that bit to indicate finite length laser
842 if(self.spawnflags & 2)
846 WriteByte(MSG_ENTITY, fl);
849 WriteCoord(MSG_ENTITY, self.origin_x);
850 WriteCoord(MSG_ENTITY, self.origin_y);
851 WriteCoord(MSG_ENTITY, self.origin_z);
855 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
856 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
857 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
859 WriteByte(MSG_ENTITY, self.alpha * 255.0);
860 WriteShort(MSG_ENTITY, self.cnt + 1);
866 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
867 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
868 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
872 WriteCoord(MSG_ENTITY, self.mangle_x);
873 WriteCoord(MSG_ENTITY, self.mangle_y);
877 WriteByte(MSG_ENTITY, self.state);
881 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
882 Any object touching the beam will be hurt
885 spawnfunc_target_position where the laser ends
887 name of beam end effect to use
889 color of the beam (default: red)
891 damage per second (-1 for a laser that kills immediately)
895 self.state = !self.state;
900 void spawnfunc_misc_laser()
904 if(self.mdl == "none")
907 self.cnt = particleeffectnum(self.mdl);
912 self.cnt = particleeffectnum("laser_deadly");
917 if(self.colormod == '0 0 0')
919 self.colormod = '1 0 0';
921 self.message = "saw the light";
922 self.think = misc_laser_think;
923 self.nextthink = time;
924 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
926 self.effects = EF_NODEPTHTEST;
927 self.SendEntity = laser_SendEntity;
930 self.model = "net_entity";
931 self.mangle = self.angles;
935 self.use = laser_use;
936 if(self.spawnflags & 1)
945 // tZorks trigger impulse / gravity
951 // targeted (directional) mode
952 void trigger_impulse_touch1()
958 // FIXME: Better checking for what to push and not.
959 if (other.classname != "player")
960 if (other.classname != "corpse")
961 if (other.classname != "body")
962 if (other.classname != "gib")
963 if (other.classname != "missile")
964 if (other.classname != "casing")
965 if (other.classname != "grenade")
966 if (other.classname != "plasma")
967 if (other.classname != "plasma_prim")
968 if (other.classname != "plasma_chain")
969 if (other.classname != "droppedweapon")
972 if (other.deadflag && other.classname == "player")
977 targ = find(world, targetname, self.target);
980 objerror("trigger_force without a (valid) .target!\n");
985 if(self.falloff == 1)
986 str = (str / self.radius) * self.strength;
987 else if(self.falloff == 2)
988 str = (1 - (str / self.radius)) * self.strength;
992 pushdeltatime = time - other.lastpushtime;
993 if (pushdeltatime > 0.15) pushdeltatime = 0;
994 other.lastpushtime = time;
995 if(!pushdeltatime) return;
997 other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1000 // Directionless (accelerator/decelerator) mode
1001 void trigger_impulse_touch2()
1003 float pushdeltatime;
1005 // FIXME: Better checking for what to push and not.
1006 if (other.classname != "player")
1007 if (other.classname != "corpse")
1008 if (other.classname != "body")
1009 if (other.classname != "gib")
1010 if (other.classname != "missile")
1011 if (other.classname != "casing")
1012 if (other.classname != "grenade")
1013 if (other.classname != "plasma")
1014 if (other.classname != "plasma_prim")
1015 if (other.classname != "plasma_chain")
1016 if (other.classname != "droppedweapon")
1019 if (other.deadflag && other.classname == "player")
1024 pushdeltatime = time - other.lastpushtime;
1025 if (pushdeltatime > 0.15) pushdeltatime = 0;
1026 other.lastpushtime = time;
1027 if(!pushdeltatime) return;
1029 //if(self.strength > 1)
1030 other.velocity = other.velocity * (self.strength * pushdeltatime);
1032 // other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1035 // Spherical (gravity/repulsor) mode
1036 void trigger_impulse_touch3()
1038 float pushdeltatime;
1041 // FIXME: Better checking for what to push and not.
1042 if (other.classname != "player")
1043 if (other.classname != "corpse")
1044 if (other.classname != "body")
1045 if (other.classname != "gib")
1046 if (other.classname != "missile")
1047 if (other.classname != "casing")
1048 if (other.classname != "grenade")
1049 if (other.classname != "plasma")
1050 if (other.classname != "plasma_prim")
1051 if (other.classname != "plasma_chain")
1052 if (other.classname != "droppedweapon")
1055 if (other.deadflag && other.classname == "player")
1060 pushdeltatime = time - other.lastpushtime;
1061 if (pushdeltatime > 0.15) pushdeltatime = 0;
1062 other.lastpushtime = time;
1063 if(!pushdeltatime) return;
1065 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1067 str = min(self.radius, vlen(self.origin - other.origin));
1069 if(self.falloff == 1)
1070 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1071 else if(self.falloff == 2)
1072 str = (str / self.radius) * self.strength; // 0 in the inside
1074 str = self.strength;
1076 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1079 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1080 -------- KEYS --------
1081 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1082 If not, this trigger acts like a damper/accelerator field.
1084 strength : This is how mutch force to add in the direction of .target each second
1085 when .target is set. If not, this is hoe mutch to slow down/accelerate
1086 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1088 radius : If set, act as a spherical device rather then a liniar one.
1090 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1092 -------- NOTES --------
1093 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1094 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1097 void spawnfunc_trigger_impulse()
1102 if(!self.strength) self.strength = 2000;
1103 setorigin(self, self.origin);
1104 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1105 self.touch = trigger_impulse_touch3;
1111 if(!self.strength) self.strength = 950;
1112 self.touch = trigger_impulse_touch1;
1116 if(!self.strength) self.strength = 0.9;
1117 self.touch = trigger_impulse_touch2;
1122 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1123 "Flip-flop" trigger gate... lets only every second trigger event through
1127 self.state = !self.state;
1132 void spawnfunc_trigger_flipflop()
1134 if(self.spawnflags & 1)
1136 self.use = flipflop_use;
1139 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1140 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1144 self.nextthink = time + self.wait;
1150 void monoflop_fixed_use()
1154 self.nextthink = time + self.wait;
1159 void monoflop_think()
1165 void spawnfunc_trigger_monoflop()
1169 if(self.spawnflags & 1)
1170 self.use = monoflop_fixed_use;
1172 self.use = monoflop_use;
1173 self.think = monoflop_think;
1177 void multivibrator_send()
1182 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1184 newstate = (time < cyclestart + self.wait);
1186 if(self.state != newstate)
1188 self.state = newstate;
1191 self.nextthink = cyclestart + self.wait + 0.01;
1193 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1196 void multivibrator_toggle()
1198 if(self.nextthink == 0)
1200 multivibrator_send();
1213 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1214 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1215 -------- KEYS --------
1216 target: trigger all entities with this targetname when it goes off
1217 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1218 phase: offset of the timing
1219 wait: "on" cycle time (default: 1)
1220 respawntime: "off" cycle time (default: same as wait)
1221 -------- SPAWNFLAGS --------
1222 START_ON: assume it is already turned on (when targeted)
1224 void spawnfunc_trigger_multivibrator()
1228 if(!self.respawntime)
1229 self.respawntime = self.wait;
1232 self.use = multivibrator_toggle;
1233 self.think = multivibrator_send;
1234 self.nextthink = time;
1238 if(!(self.spawnflags & 1))
1239 self.nextthink = 0; // wait for a trigger event
1242 self.nextthink = time;
1249 src = find(world, targetname, self.killtarget);
1250 dst = find(world, targetname, self.target);
1254 objerror("follow: could not find target/killtarget");
1258 dst.movetype = MOVETYPE_FOLLOW;
1260 dst.punchangle = src.angles;
1261 dst.view_ofs = dst.origin - src.origin;
1262 dst.v_angle = dst.angles - src.angles;
1267 void spawnfunc_misc_follow()
1269 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);