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 not(self.spawnflags & 2)
177 if (other.classname != "player")
181 if(self.team == other.team)
185 // if the trigger has an angles field, check player's facing direction
186 if (self.movedir != '0 0 0')
188 makevectors (other.angles);
189 if (v_forward * self.movedir < 0)
190 return; // not facing the right way
199 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
201 if (!self.takedamage)
203 self.health = self.health - damage;
204 if (self.health <= 0)
206 self.enemy = attacker;
211 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
212 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.
213 If "delay" is set, the trigger waits some time after activating before firing.
214 "wait" : Seconds between triggerings. (.2 default)
215 If notouch is set, the trigger is only fired by other entities, not by touching.
216 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
222 set "message" to text string
224 void spawnfunc_trigger_multiple()
226 if (self.sounds == 1)
228 precache_sound ("misc/secret.wav");
229 self.noise = "misc/secret.wav";
231 else if (self.sounds == 2)
233 precache_sound ("misc/talk.wav");
234 self.noise = "misc/talk.wav";
236 else if (self.sounds == 3)
238 precache_sound ("misc/trigger1.wav");
239 self.noise = "misc/trigger1.wav";
244 self.use = multi_use;
250 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
251 objerror ("health and notouch don't make sense\n");
252 self.max_health = self.health;
253 self.event_damage = multi_eventdamage;
254 self.takedamage = DAMAGE_YES;
255 self.solid = SOLID_BBOX;
256 setorigin (self, self.origin); // make sure it links into the world
260 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
262 self.touch = multi_touch;
263 setorigin (self, self.origin); // make sure it links into the world
269 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
270 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
271 "targetname". If "health" is set, the trigger must be killed to activate.
272 If notouch is set, the trigger is only fired by other entities, not by touching.
273 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
274 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.
280 set "message" to text string
282 void spawnfunc_trigger_once()
285 spawnfunc_trigger_multiple();
288 //=============================================================================
290 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
291 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
293 void spawnfunc_trigger_relay()
295 self.use = SUB_UseTargets;
300 self.think = SUB_UseTargets;
301 self.nextthink = self.wait;
304 void spawnfunc_trigger_delay()
309 self.use = delay_use;
312 //=============================================================================
317 self.count = self.count - 1;
323 if (activator.classname == "player"
324 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
327 centerprint (activator, "There are more to go...");
328 else if (self.count == 3)
329 centerprint (activator, "Only 3 more to go...");
330 else if (self.count == 2)
331 centerprint (activator, "Only 2 more to go...");
333 centerprint (activator, "Only 1 more to go...");
338 if (activator.classname == "player"
339 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
340 centerprint(activator, "Sequence completed!");
341 self.enemy = activator;
345 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
346 Acts as an intermediary for an action that takes multiple inputs.
348 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
350 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
352 void spawnfunc_trigger_counter()
358 self.use = counter_use;
361 .float triggerhurttime;
362 void trigger_hurt_touch()
364 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
367 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
370 other.pain_finished = min(other.pain_finished, time + 2);
372 else if (other.classname == "rune") // reset runes
375 other.nextthink = min(other.nextthink, time + 1);
379 if (other.takedamage)
380 if (other.triggerhurttime < time)
383 other.triggerhurttime = time + 1;
384 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
390 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
391 Any object touching this will be hurt
392 set dmg to damage amount
395 .entity trigger_hurt_next;
396 entity trigger_hurt_last;
397 entity trigger_hurt_first;
398 void spawnfunc_trigger_hurt()
401 self.touch = trigger_hurt_touch;
405 self.message = "was in the wrong place.";
407 if(!trigger_hurt_first)
408 trigger_hurt_first = self;
409 if(trigger_hurt_last)
410 trigger_hurt_last.trigger_hurt_next = self;
411 trigger_hurt_last = self;
414 float trace_hits_box_a0, trace_hits_box_a1;
416 float trace_hits_box_1d(float end, float thmi, float thma)
420 // just check if x is in range
428 // do the trace with respect to x
429 // 0 -> end has to stay in thmi -> thma
430 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
431 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
432 if(trace_hits_box_a0 > trace_hits_box_a1)
438 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
443 // now it is a trace from 0 to end
445 trace_hits_box_a0 = 0;
446 trace_hits_box_a1 = 1;
448 if(!trace_hits_box_1d(end_x, thmi_x, thma_x))
450 if(!trace_hits_box_1d(end_y, thmi_y, thma_y))
452 if(!trace_hits_box_1d(end_z, thmi_z, thma_z))
458 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
460 return trace_hits_box(start, end, thmi - ma, thma - mi);
463 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
467 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
468 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
475 // TODO add a way to do looped sounds with sound(); then complete this entity
476 .float volume, atten;
477 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
479 void spawnfunc_target_speaker()
482 precache_sound (self.noise);
486 self.atten = ATTN_NORM;
487 else if(self.atten < 0)
491 self.use = target_speaker_use;
496 self.atten = ATTN_STATIC;
497 else if(self.atten < 0)
501 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
506 void spawnfunc_func_stardust() {
507 self.effects = EF_STARDUST;
510 float pointparticles_SendEntity(entity to, float fl)
512 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
513 WriteByte(MSG_ENTITY, fl);
517 WriteCoord(MSG_ENTITY, self.impulse);
519 WriteCoord(MSG_ENTITY, 0); // off
523 WriteCoord(MSG_ENTITY, self.origin_x);
524 WriteCoord(MSG_ENTITY, self.origin_y);
525 WriteCoord(MSG_ENTITY, self.origin_z);
529 if(self.modelindex != 4.2)
531 WriteShort(MSG_ENTITY, self.modelindex);
532 WriteCoord(MSG_ENTITY, self.mins_x);
533 WriteCoord(MSG_ENTITY, self.mins_y);
534 WriteCoord(MSG_ENTITY, self.mins_z);
535 WriteCoord(MSG_ENTITY, self.maxs_x);
536 WriteCoord(MSG_ENTITY, self.maxs_y);
537 WriteCoord(MSG_ENTITY, self.maxs_z);
541 WriteShort(MSG_ENTITY, 0);
542 WriteCoord(MSG_ENTITY, self.maxs_x);
543 WriteCoord(MSG_ENTITY, self.maxs_y);
544 WriteCoord(MSG_ENTITY, self.maxs_z);
546 WriteShort(MSG_ENTITY, self.cnt);
547 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
548 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
549 WriteCoord(MSG_ENTITY, self.waterlevel);
550 WriteCoord(MSG_ENTITY, self.count);
551 WriteByte(MSG_ENTITY, self.glow_color);
552 WriteString(MSG_ENTITY, self.noise);
557 void pointparticles_use()
559 self.state = !self.state;
563 void pointparticles_think()
565 if(self.origin != self.oldorigin)
568 self.oldorigin = self.origin;
570 self.nextthink = time;
573 void spawnfunc_func_pointparticles()
576 setmodel(self, self.model);
578 precache_sound (self.noise);
580 self.effects = EF_NODEPTHTEST;
581 self.SendEntity = pointparticles_SendEntity;
585 self.modelindex = 4.2;
586 self.origin += self.mins;
587 self.maxs -= self.mins;
589 self.model = "net_entity";
591 self.cnt = particleeffectnum(self.mdl);
594 self.use = pointparticles_use;
595 if(self.spawnflags & 1)
602 self.think = pointparticles_think;
603 self.nextthink = time;
606 void spawnfunc_func_sparks()
608 // self.cnt is the amount of sparks that one burst will spawn
610 self.cnt = 25.0; // nice default value
613 // self.wait is the probability that a sparkthink will spawn a spark shower
614 // range: 0 - 1, but 0 makes little sense, so...
615 if(self.wait < 0.05) {
616 self.wait = 0.25; // nice default value
619 self.count = self.cnt;
622 self.velocity = '0 0 -1';
623 self.mdl = "TE_SPARK";
624 self.impulse = 0.1 / self.wait;
627 spawnfunc_func_pointparticles();
630 float rainsnow_SendEntity(float to)
632 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
633 WriteByte(MSG_ENTITY, self.state);
634 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
635 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
636 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
637 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
638 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
639 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
640 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
641 WriteShort(MSG_ENTITY, self.count);
642 WriteByte(MSG_ENTITY, self.cnt);
646 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
647 This is an invisible area like a trigger, which rain falls inside of.
651 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
653 sets color of rain (default 12 - white)
655 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
657 void spawnfunc_func_rain()
659 self.dest = self.velocity;
660 self.velocity = '0 0 0';
662 self.dest = '0 0 -700';
663 self.angles = '0 0 0';
664 self.movetype = MOVETYPE_NONE;
665 self.solid = SOLID_NOT;
666 SetBrushEntityModel();
672 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
675 if(self.count > 65535)
678 self.state = 1; // 1 is rain, 0 is snow
679 self.effects = EF_NODEPTHTEST;
680 self.SendEntity = rainsnow_SendEntity;
683 self.model = "net_entity";
687 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
688 This is an invisible area like a trigger, which snow falls inside of.
692 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
694 sets color of rain (default 12 - white)
696 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
698 void spawnfunc_func_snow()
700 self.dest = self.velocity;
701 self.velocity = '0 0 0';
703 self.dest = '0 0 -300';
704 self.angles = '0 0 0';
705 self.movetype = MOVETYPE_NONE;
706 self.solid = SOLID_NOT;
707 SetBrushEntityModel();
713 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
716 if(self.count > 65535)
719 self.state = 0; // 1 is rain, 0 is snow
720 self.effects = EF_NODEPTHTEST;
721 self.SendEntity = rainsnow_SendEntity;
724 self.model = "net_entity";
728 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
730 void misc_laser_aim()
735 if(self.spawnflags & 2)
737 if(self.enemy.origin != self.mangle)
739 self.mangle = self.enemy.origin;
745 a = vectoangles(self.enemy.origin - self.origin);
756 if(self.angles != self.mangle)
758 self.mangle = self.angles;
762 if(self.origin != self.oldorigin)
765 self.oldorigin = self.origin;
769 void misc_laser_init()
771 if(self.target != "")
772 self.enemy = find(world, targetname, self.target);
776 void misc_laser_think()
781 self.nextthink = time;
790 o = self.enemy.origin;
791 if not(self.spawnflags & 2)
792 o = self.origin + normalize(o - self.origin) * 32768;
796 makevectors(self.mangle);
797 o = self.origin + v_forward * 32768;
803 FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
805 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
808 if(self.enemy.target != "") // DETECTOR laser
810 traceline(self.origin, o, MOVE_NORMAL, self);
811 if(trace_ent.classname == "player")
813 self.pusher = trace_ent;
820 activator = self.pusher;
833 activator = self.pusher;
841 float laser_SendEntity(entity to, float fl)
843 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
844 fl = fl - (fl & 0xC0); // use that bit to indicate finite length laser
845 if(self.spawnflags & 2)
849 WriteByte(MSG_ENTITY, fl);
852 WriteCoord(MSG_ENTITY, self.origin_x);
853 WriteCoord(MSG_ENTITY, self.origin_y);
854 WriteCoord(MSG_ENTITY, self.origin_z);
858 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
859 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
860 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
862 WriteByte(MSG_ENTITY, self.alpha * 255.0);
863 WriteShort(MSG_ENTITY, self.cnt + 1);
869 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
870 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
871 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
875 WriteCoord(MSG_ENTITY, self.mangle_x);
876 WriteCoord(MSG_ENTITY, self.mangle_y);
880 WriteByte(MSG_ENTITY, self.state);
884 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
885 Any object touching the beam will be hurt
888 spawnfunc_target_position where the laser ends
890 name of beam end effect to use
892 color of the beam (default: red)
894 damage per second (-1 for a laser that kills immediately)
898 self.state = !self.state;
903 void spawnfunc_misc_laser()
907 if(self.mdl == "none")
910 self.cnt = particleeffectnum(self.mdl);
915 self.cnt = particleeffectnum("laser_deadly");
920 if(self.colormod == '0 0 0')
922 self.colormod = '1 0 0';
924 self.message = "saw the light";
925 self.think = misc_laser_think;
926 self.nextthink = time;
927 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
929 self.effects = EF_NODEPTHTEST;
930 self.SendEntity = laser_SendEntity;
933 self.model = "net_entity";
934 self.mangle = self.angles;
938 self.use = laser_use;
939 if(self.spawnflags & 1)
948 // tZorks trigger impulse / gravity
954 // targeted (directional) mode
955 void trigger_impulse_touch1()
961 // FIXME: Better checking for what to push and not.
962 if (other.classname != "player")
963 if (other.classname != "corpse")
964 if (other.classname != "body")
965 if (other.classname != "gib")
966 if (other.classname != "missile")
967 if (other.classname != "casing")
968 if (other.classname != "grenade")
969 if (other.classname != "plasma")
970 if (other.classname != "plasma_prim")
971 if (other.classname != "plasma_chain")
972 if (other.classname != "droppedweapon")
975 if (other.deadflag && other.classname == "player")
980 targ = find(world, targetname, self.target);
983 objerror("trigger_force without a (valid) .target!\n");
988 if(self.falloff == 1)
989 str = (str / self.radius) * self.strength;
990 else if(self.falloff == 2)
991 str = (1 - (str / self.radius)) * self.strength;
995 pushdeltatime = time - other.lastpushtime;
996 if (pushdeltatime > 0.15) pushdeltatime = 0;
997 other.lastpushtime = time;
998 if(!pushdeltatime) return;
1000 other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1003 // Directionless (accelerator/decelerator) mode
1004 void trigger_impulse_touch2()
1006 float pushdeltatime;
1008 // FIXME: Better checking for what to push and not.
1009 if (other.classname != "player")
1010 if (other.classname != "corpse")
1011 if (other.classname != "body")
1012 if (other.classname != "gib")
1013 if (other.classname != "missile")
1014 if (other.classname != "casing")
1015 if (other.classname != "grenade")
1016 if (other.classname != "plasma")
1017 if (other.classname != "plasma_prim")
1018 if (other.classname != "plasma_chain")
1019 if (other.classname != "droppedweapon")
1022 if (other.deadflag && other.classname == "player")
1027 pushdeltatime = time - other.lastpushtime;
1028 if (pushdeltatime > 0.15) pushdeltatime = 0;
1029 other.lastpushtime = time;
1030 if(!pushdeltatime) return;
1032 //if(self.strength > 1)
1033 other.velocity = other.velocity * (self.strength * pushdeltatime);
1035 // other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1038 // Spherical (gravity/repulsor) mode
1039 void trigger_impulse_touch3()
1041 float pushdeltatime;
1044 // FIXME: Better checking for what to push and not.
1045 if (other.classname != "player")
1046 if (other.classname != "corpse")
1047 if (other.classname != "body")
1048 if (other.classname != "gib")
1049 if (other.classname != "missile")
1050 if (other.classname != "casing")
1051 if (other.classname != "grenade")
1052 if (other.classname != "plasma")
1053 if (other.classname != "plasma_prim")
1054 if (other.classname != "plasma_chain")
1055 if (other.classname != "droppedweapon")
1058 if (other.deadflag && other.classname == "player")
1063 pushdeltatime = time - other.lastpushtime;
1064 if (pushdeltatime > 0.15) pushdeltatime = 0;
1065 other.lastpushtime = time;
1066 if(!pushdeltatime) return;
1068 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1070 str = min(self.radius, vlen(self.origin - other.origin));
1072 if(self.falloff == 1)
1073 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1074 else if(self.falloff == 2)
1075 str = (str / self.radius) * self.strength; // 0 in the inside
1077 str = self.strength;
1079 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1082 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1083 -------- KEYS --------
1084 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1085 If not, this trigger acts like a damper/accelerator field.
1087 strength : This is how mutch force to add in the direction of .target each second
1088 when .target is set. If not, this is hoe mutch to slow down/accelerate
1089 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1091 radius : If set, act as a spherical device rather then a liniar one.
1093 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1095 -------- NOTES --------
1096 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1097 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1100 void spawnfunc_trigger_impulse()
1105 if(!self.strength) self.strength = 2000;
1106 setorigin(self, self.origin);
1107 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1108 self.touch = trigger_impulse_touch3;
1114 if(!self.strength) self.strength = 950;
1115 self.touch = trigger_impulse_touch1;
1119 if(!self.strength) self.strength = 0.9;
1120 self.touch = trigger_impulse_touch2;
1125 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1126 "Flip-flop" trigger gate... lets only every second trigger event through
1130 self.state = !self.state;
1135 void spawnfunc_trigger_flipflop()
1137 if(self.spawnflags & 1)
1139 self.use = flipflop_use;
1142 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1143 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1147 self.nextthink = time + self.wait;
1153 void monoflop_fixed_use()
1157 self.nextthink = time + self.wait;
1162 void monoflop_think()
1168 void spawnfunc_trigger_monoflop()
1172 if(self.spawnflags & 1)
1173 self.use = monoflop_fixed_use;
1175 self.use = monoflop_use;
1176 self.think = monoflop_think;
1180 void multivibrator_send()
1185 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1187 newstate = (time < cyclestart + self.wait);
1189 if(self.state != newstate)
1191 self.state = newstate;
1194 self.nextthink = cyclestart + self.wait + 0.01;
1196 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1199 void multivibrator_toggle()
1201 if(self.nextthink == 0)
1203 multivibrator_send();
1216 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1217 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1218 -------- KEYS --------
1219 target: trigger all entities with this targetname when it goes off
1220 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1221 phase: offset of the timing
1222 wait: "on" cycle time (default: 1)
1223 respawntime: "off" cycle time (default: same as wait)
1224 -------- SPAWNFLAGS --------
1225 START_ON: assume it is already turned on (when targeted)
1227 void spawnfunc_trigger_multivibrator()
1231 if(!self.respawntime)
1232 self.respawntime = self.wait;
1235 self.use = multivibrator_toggle;
1236 self.think = multivibrator_send;
1237 self.nextthink = time;
1241 if(!(self.spawnflags & 1))
1242 self.nextthink = 0; // wait for a trigger event
1245 self.nextthink = time;
1252 src = find(world, targetname, self.killtarget);
1253 dst = find(world, targetname, self.target);
1257 objerror("follow: could not find target/killtarget");
1261 dst.movetype = MOVETYPE_FOLLOW;
1263 dst.punchangle = src.angles;
1264 dst.view_ofs = dst.origin - src.origin;
1265 dst.v_angle = dst.angles - src.angles;
1270 void spawnfunc_misc_follow()
1272 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);