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;
41 // create a temp object to fire at a later time
43 t.classname = "DelayedUse";
44 t.nextthink = time + self.delay;
47 t.message = self.message;
48 t.killtarget = self.killtarget;
49 t.target = self.target;
57 if (activator.classname == "player" && self.message != "")
59 if(clienttype(activator) == CLIENTTYPE_REAL)
61 centerprint (activator, self.message);
63 play2(activator, "misc/talk.wav");
68 // kill the killtagets
73 for(t = world; (t = find(t, targetname, s)); )
84 for(i = 0; i < 4; ++i)
89 case 0: s = self.target; break;
90 case 1: s = self.target2; break;
91 case 2: s = self.target3; break;
92 case 3: s = self.target4; break;
96 for(t = world; (t = find(t, targetname, s)); )
113 //=============================================================================
115 float SPAWNFLAG_NOMESSAGE = 1;
116 float SPAWNFLAG_NOTOUCH = 1;
118 // the wait time has passed, so set back up for another activation
123 self.health = self.max_health;
124 self.takedamage = DAMAGE_YES;
125 self.solid = SOLID_BBOX;
130 // the trigger was just touched/killed/used
131 // self.enemy should be set to the activator so it can be held through a delay
132 // so wait for the delay time before firing
135 if (self.nextthink > time)
137 return; // allready been triggered
140 if (self.classname == "trigger_secret")
142 if (self.enemy.classname != "player")
144 found_secrets = found_secrets + 1;
145 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
149 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
151 // don't trigger again until reset
152 self.takedamage = DAMAGE_NO;
154 activator = self.enemy;
160 self.think = multi_wait;
161 self.nextthink = time + self.wait;
164 { // we can't just remove (self) here, because this is a touch function
165 // called wheil C code is looping through area links...
166 self.touch = SUB_Null;
168 self.nextthink = time + 0.1;
169 self.think = SUB_Remove;
175 self.enemy = activator;
181 if not(self.spawnflags & 2)
183 if not(other.iscreature)
187 if(self.team == other.team)
191 // if the trigger has an angles field, check player's facing direction
192 if (self.movedir != '0 0 0')
194 makevectors (other.angles);
195 if (v_forward * self.movedir < 0)
196 return; // not facing the right way
205 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
207 if (!self.takedamage)
209 self.health = self.health - damage;
210 if (self.health <= 0)
212 self.enemy = attacker;
217 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
218 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.
219 If "delay" is set, the trigger waits some time after activating before firing.
220 "wait" : Seconds between triggerings. (.2 default)
221 If notouch is set, the trigger is only fired by other entities, not by touching.
222 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
228 set "message" to text string
230 void spawnfunc_trigger_multiple()
232 if (self.sounds == 1)
234 precache_sound ("misc/secret.wav");
235 self.noise = "misc/secret.wav";
237 else if (self.sounds == 2)
239 precache_sound ("misc/talk.wav");
240 self.noise = "misc/talk.wav";
242 else if (self.sounds == 3)
244 precache_sound ("misc/trigger1.wav");
245 self.noise = "misc/trigger1.wav";
250 self.use = multi_use;
256 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
257 objerror ("health and notouch don't make sense\n");
258 self.max_health = self.health;
259 self.event_damage = multi_eventdamage;
260 self.takedamage = DAMAGE_YES;
261 self.solid = SOLID_BBOX;
262 setorigin (self, self.origin); // make sure it links into the world
266 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
268 self.touch = multi_touch;
269 setorigin (self, self.origin); // make sure it links into the world
275 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
276 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
277 "targetname". If "health" is set, the trigger must be killed to activate.
278 If notouch is set, the trigger is only fired by other entities, not by touching.
279 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
280 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.
286 set "message" to text string
288 void spawnfunc_trigger_once()
291 spawnfunc_trigger_multiple();
294 //=============================================================================
296 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
297 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
299 void spawnfunc_trigger_relay()
301 self.use = SUB_UseTargets;
306 self.think = SUB_UseTargets;
307 self.nextthink = self.wait;
310 void spawnfunc_trigger_delay()
315 self.use = delay_use;
318 //=============================================================================
323 self.count = self.count - 1;
329 if (activator.classname == "player"
330 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
333 centerprint (activator, "There are more to go...");
334 else if (self.count == 3)
335 centerprint (activator, "Only 3 more to go...");
336 else if (self.count == 2)
337 centerprint (activator, "Only 2 more to go...");
339 centerprint (activator, "Only 1 more to go...");
344 if (activator.classname == "player"
345 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
346 centerprint(activator, "Sequence completed!");
347 self.enemy = activator;
351 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
352 Acts as an intermediary for an action that takes multiple inputs.
354 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
356 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
358 void spawnfunc_trigger_counter()
364 self.use = counter_use;
367 .float triggerhurttime;
368 void trigger_hurt_touch()
370 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
371 if (other.iscreature)
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');
385 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
388 other.pain_finished = min(other.pain_finished, time + 2);
390 else if (other.classname == "rune") // reset runes
393 other.nextthink = min(other.nextthink, time + 1);
401 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
402 Any object touching this will be hurt
403 set dmg to damage amount
406 .entity trigger_hurt_next;
407 entity trigger_hurt_last;
408 entity trigger_hurt_first;
409 void spawnfunc_trigger_hurt()
412 self.touch = trigger_hurt_touch;
416 self.message = "was in the wrong place";
418 self.message2 = "was thrown into a world of hurt by";
420 if(!trigger_hurt_first)
421 trigger_hurt_first = self;
422 if(trigger_hurt_last)
423 trigger_hurt_last.trigger_hurt_next = self;
424 trigger_hurt_last = self;
427 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
431 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
432 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
439 // TODO add a way to do looped sounds with sound(); then complete this entity
440 .float volume, atten;
441 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
443 void spawnfunc_target_speaker()
446 precache_sound (self.noise);
450 self.atten = ATTN_NORM;
451 else if(self.atten < 0)
455 self.use = target_speaker_use;
460 self.atten = ATTN_STATIC;
461 else if(self.atten < 0)
465 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
470 void spawnfunc_func_stardust() {
471 self.effects = EF_STARDUST;
474 float pointparticles_SendEntity(entity to, float fl)
476 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
477 WriteByte(MSG_ENTITY, fl);
481 WriteCoord(MSG_ENTITY, self.impulse);
483 WriteCoord(MSG_ENTITY, 0); // off
487 WriteCoord(MSG_ENTITY, self.origin_x);
488 WriteCoord(MSG_ENTITY, self.origin_y);
489 WriteCoord(MSG_ENTITY, self.origin_z);
493 if(self.modelindex != 4.2)
495 WriteShort(MSG_ENTITY, self.modelindex);
496 WriteCoord(MSG_ENTITY, self.mins_x);
497 WriteCoord(MSG_ENTITY, self.mins_y);
498 WriteCoord(MSG_ENTITY, self.mins_z);
499 WriteCoord(MSG_ENTITY, self.maxs_x);
500 WriteCoord(MSG_ENTITY, self.maxs_y);
501 WriteCoord(MSG_ENTITY, self.maxs_z);
505 WriteShort(MSG_ENTITY, 0);
506 WriteCoord(MSG_ENTITY, self.maxs_x);
507 WriteCoord(MSG_ENTITY, self.maxs_y);
508 WriteCoord(MSG_ENTITY, self.maxs_z);
510 WriteShort(MSG_ENTITY, self.cnt);
511 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
512 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
513 WriteCoord(MSG_ENTITY, self.waterlevel);
514 WriteCoord(MSG_ENTITY, self.count);
515 WriteByte(MSG_ENTITY, self.glow_color);
516 WriteString(MSG_ENTITY, self.noise);
521 void pointparticles_use()
523 self.state = !self.state;
527 void pointparticles_think()
529 if(self.origin != self.oldorigin)
532 self.oldorigin = self.origin;
534 self.nextthink = time;
537 void spawnfunc_func_pointparticles()
540 setmodel(self, self.model);
542 precache_sound (self.noise);
544 self.effects = EF_NODEPTHTEST;
545 self.SendEntity = pointparticles_SendEntity;
549 self.modelindex = 4.2;
550 self.origin += self.mins;
551 self.maxs = self.maxs - self.mins;
553 self.model = "net_entity";
555 self.cnt = particleeffectnum(self.mdl);
558 self.use = pointparticles_use;
559 if(self.spawnflags & 1)
566 self.think = pointparticles_think;
567 self.nextthink = time;
570 void spawnfunc_func_sparks()
572 // self.cnt is the amount of sparks that one burst will spawn
574 self.cnt = 25.0; // nice default value
577 // self.wait is the probability that a sparkthink will spawn a spark shower
578 // range: 0 - 1, but 0 makes little sense, so...
579 if(self.wait < 0.05) {
580 self.wait = 0.25; // nice default value
583 self.count = self.cnt;
586 self.velocity = '0 0 -1';
587 self.mdl = "TE_SPARK";
588 self.impulse = 10 * self.wait; // by default 2.5/sec
590 self.cnt = 0; // use mdl
592 spawnfunc_func_pointparticles();
595 float rainsnow_SendEntity(float to)
597 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
598 WriteByte(MSG_ENTITY, self.state);
599 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
600 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
601 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
602 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
603 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
604 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
605 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
606 WriteShort(MSG_ENTITY, self.count);
607 WriteByte(MSG_ENTITY, self.cnt);
611 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
612 This is an invisible area like a trigger, which rain falls inside of.
616 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
618 sets color of rain (default 12 - white)
620 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
622 void spawnfunc_func_rain()
624 self.dest = self.velocity;
625 self.velocity = '0 0 0';
627 self.dest = '0 0 -700';
628 self.angles = '0 0 0';
629 self.movetype = MOVETYPE_NONE;
630 self.solid = SOLID_NOT;
631 SetBrushEntityModel();
637 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
640 if(self.count > 65535)
643 self.state = 1; // 1 is rain, 0 is snow
644 self.effects = EF_NODEPTHTEST;
645 self.SendEntity = rainsnow_SendEntity;
648 self.model = "net_entity";
652 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
653 This is an invisible area like a trigger, which snow falls inside of.
657 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
659 sets color of rain (default 12 - white)
661 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
663 void spawnfunc_func_snow()
665 self.dest = self.velocity;
666 self.velocity = '0 0 0';
668 self.dest = '0 0 -300';
669 self.angles = '0 0 0';
670 self.movetype = MOVETYPE_NONE;
671 self.solid = SOLID_NOT;
672 SetBrushEntityModel();
678 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
681 if(self.count > 65535)
684 self.state = 0; // 1 is rain, 0 is snow
685 self.effects = EF_NODEPTHTEST;
686 self.SendEntity = rainsnow_SendEntity;
689 self.model = "net_entity";
693 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
695 void misc_laser_aim()
700 if(self.spawnflags & 2)
702 if(self.enemy.origin != self.mangle)
704 self.mangle = self.enemy.origin;
710 a = vectoangles(self.enemy.origin - self.origin);
721 if(self.angles != self.mangle)
723 self.mangle = self.angles;
727 if(self.origin != self.oldorigin)
730 self.oldorigin = self.origin;
734 void misc_laser_init()
736 if(self.target != "")
737 self.enemy = find(world, targetname, self.target);
741 void misc_laser_think()
746 self.nextthink = time;
755 o = self.enemy.origin;
756 if not(self.spawnflags & 2)
757 o = self.origin + normalize(o - self.origin) * 32768;
761 makevectors(self.mangle);
762 o = self.origin + v_forward * 32768;
768 FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
770 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
773 if(self.enemy.target != "") // DETECTOR laser
775 traceline(self.origin, o, MOVE_NORMAL, self);
776 if(trace_ent.iscreature)
778 self.pusher = trace_ent;
785 activator = self.pusher;
798 activator = self.pusher;
806 float laser_SendEntity(entity to, float fl)
808 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
809 fl = fl - (fl & 0xC0); // use that bit to indicate finite length laser
810 if(self.spawnflags & 2)
814 WriteByte(MSG_ENTITY, fl);
817 WriteCoord(MSG_ENTITY, self.origin_x);
818 WriteCoord(MSG_ENTITY, self.origin_y);
819 WriteCoord(MSG_ENTITY, self.origin_z);
823 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
824 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
825 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
827 WriteByte(MSG_ENTITY, self.alpha * 255.0);
828 WriteShort(MSG_ENTITY, self.cnt + 1);
834 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
835 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
836 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
840 WriteCoord(MSG_ENTITY, self.mangle_x);
841 WriteCoord(MSG_ENTITY, self.mangle_y);
845 WriteByte(MSG_ENTITY, self.state);
849 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
850 Any object touching the beam will be hurt
853 spawnfunc_target_position where the laser ends
855 name of beam end effect to use
857 color of the beam (default: red)
859 damage per second (-1 for a laser that kills immediately)
863 self.state = !self.state;
868 void spawnfunc_misc_laser()
872 if(self.mdl == "none")
875 self.cnt = particleeffectnum(self.mdl);
880 self.cnt = particleeffectnum("laser_deadly");
885 if(self.colormod == '0 0 0')
887 self.colormod = '1 0 0';
889 self.message = "saw the light";
891 self.message2 = "was pushed into a laser by";
892 self.think = misc_laser_think;
893 self.nextthink = time;
894 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
896 self.effects = EF_NODEPTHTEST;
897 self.SendEntity = laser_SendEntity;
900 self.model = "net_entity";
901 self.mangle = self.angles;
905 self.use = laser_use;
906 if(self.spawnflags & 1)
915 // tZorks trigger impulse / gravity
921 // targeted (directional) mode
922 void trigger_impulse_touch1()
928 // FIXME: Better checking for what to push and not.
929 if not(other.iscreature)
930 if (other.classname != "corpse")
931 if (other.classname != "body")
932 if (other.classname != "gib")
933 if (other.classname != "missile")
934 if (other.classname != "rocket")
935 if (other.classname != "casing")
936 if (other.classname != "grenade")
937 if (other.classname != "plasma")
938 if (other.classname != "plasma_prim")
939 if (other.classname != "plasma_chain")
940 if (other.classname != "droppedweapon")
943 if (other.deadflag && other.iscreature)
948 targ = find(world, targetname, self.target);
951 objerror("trigger_force without a (valid) .target!\n");
956 if(self.falloff == 1)
957 str = (str / self.radius) * self.strength;
958 else if(self.falloff == 2)
959 str = (1 - (str / self.radius)) * self.strength;
963 pushdeltatime = time - other.lastpushtime;
964 if (pushdeltatime > 0.15) pushdeltatime = 0;
965 other.lastpushtime = time;
966 if(!pushdeltatime) return;
968 other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
971 // Directionless (accelerator/decelerator) mode
972 void trigger_impulse_touch2()
976 // FIXME: Better checking for what to push and not.
977 if not(other.iscreature)
978 if (other.classname != "corpse")
979 if (other.classname != "body")
980 if (other.classname != "gib")
981 if (other.classname != "missile")
982 if (other.classname != "rocket")
983 if (other.classname != "casing")
984 if (other.classname != "grenade")
985 if (other.classname != "plasma")
986 if (other.classname != "plasma_prim")
987 if (other.classname != "plasma_chain")
988 if (other.classname != "droppedweapon")
991 if (other.deadflag && other.iscreature)
996 pushdeltatime = time - other.lastpushtime;
997 if (pushdeltatime > 0.15) pushdeltatime = 0;
998 other.lastpushtime = time;
999 if(!pushdeltatime) return;
1001 //if(self.strength > 1)
1002 other.velocity = other.velocity * (self.strength * pushdeltatime);
1004 // other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1007 // Spherical (gravity/repulsor) mode
1008 void trigger_impulse_touch3()
1010 float pushdeltatime;
1013 // FIXME: Better checking for what to push and not.
1014 if not(other.iscreature)
1015 if (other.classname != "corpse")
1016 if (other.classname != "body")
1017 if (other.classname != "gib")
1018 if (other.classname != "missile")
1019 if (other.classname != "rocket")
1020 if (other.classname != "casing")
1021 if (other.classname != "grenade")
1022 if (other.classname != "plasma")
1023 if (other.classname != "plasma_prim")
1024 if (other.classname != "plasma_chain")
1025 if (other.classname != "droppedweapon")
1028 if (other.deadflag && other.iscreature)
1033 pushdeltatime = time - other.lastpushtime;
1034 if (pushdeltatime > 0.15) pushdeltatime = 0;
1035 other.lastpushtime = time;
1036 if(!pushdeltatime) return;
1038 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1040 str = min(self.radius, vlen(self.origin - other.origin));
1042 if(self.falloff == 1)
1043 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1044 else if(self.falloff == 2)
1045 str = (str / self.radius) * self.strength; // 0 in the inside
1047 str = self.strength;
1049 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1052 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1053 -------- KEYS --------
1054 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1055 If not, this trigger acts like a damper/accelerator field.
1057 strength : This is how mutch force to add in the direction of .target each second
1058 when .target is set. If not, this is hoe mutch to slow down/accelerate
1059 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1061 radius : If set, act as a spherical device rather then a liniar one.
1063 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1065 -------- NOTES --------
1066 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1067 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1070 void spawnfunc_trigger_impulse()
1075 if(!self.strength) self.strength = 2000;
1076 setorigin(self, self.origin);
1077 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1078 self.touch = trigger_impulse_touch3;
1084 if(!self.strength) self.strength = 950;
1085 self.touch = trigger_impulse_touch1;
1089 if(!self.strength) self.strength = 0.9;
1090 self.touch = trigger_impulse_touch2;
1095 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1096 "Flip-flop" trigger gate... lets only every second trigger event through
1100 self.state = !self.state;
1105 void spawnfunc_trigger_flipflop()
1107 if(self.spawnflags & 1)
1109 self.use = flipflop_use;
1112 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1113 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1117 self.nextthink = time + self.wait;
1118 self.enemy = activator;
1124 void monoflop_fixed_use()
1128 self.nextthink = time + self.wait;
1130 self.enemy = activator;
1134 void monoflop_think()
1137 activator = self.enemy;
1141 void spawnfunc_trigger_monoflop()
1145 if(self.spawnflags & 1)
1146 self.use = monoflop_fixed_use;
1148 self.use = monoflop_use;
1149 self.think = monoflop_think;
1153 void multivibrator_send()
1158 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1160 newstate = (time < cyclestart + self.wait);
1163 if(self.state != newstate)
1165 self.state = newstate;
1168 self.nextthink = cyclestart + self.wait + 0.01;
1170 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1173 void multivibrator_toggle()
1175 if(self.nextthink == 0)
1177 multivibrator_send();
1190 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1191 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1192 -------- KEYS --------
1193 target: trigger all entities with this targetname when it goes off
1194 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1195 phase: offset of the timing
1196 wait: "on" cycle time (default: 1)
1197 respawntime: "off" cycle time (default: same as wait)
1198 -------- SPAWNFLAGS --------
1199 START_ON: assume it is already turned on (when targeted)
1201 void spawnfunc_trigger_multivibrator()
1205 if(!self.respawntime)
1206 self.respawntime = self.wait;
1209 self.use = multivibrator_toggle;
1210 self.think = multivibrator_send;
1211 self.nextthink = time;
1215 if(!(self.spawnflags & 1))
1216 self.nextthink = 0; // wait for a trigger event
1219 self.nextthink = time;
1226 src = find(world, targetname, self.killtarget);
1227 dst = find(world, targetname, self.target);
1231 objerror("follow: could not find target/killtarget");
1235 dst.movetype = MOVETYPE_FOLLOW;
1237 dst.punchangle = src.angles;
1238 dst.view_ofs = dst.origin - src.origin;
1239 dst.v_angle = dst.angles - src.angles;
1244 void spawnfunc_misc_follow()
1246 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1252 .entity voicescript; // attached voice script
1253 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1254 .float voicescript_nextthink; // time to play next voice
1255 .float voicescript_voiceend; // time when this voice ends
1257 void target_voicescript_clear(entity pl)
1259 pl.voicescript = world;
1262 void target_voicescript_use()
1264 if(activator.voicescript != self)
1266 activator.voicescript = self;
1267 activator.voicescript_index = 0;
1268 activator.voicescript_nextthink = time;
1272 void target_voicescript_next(entity pl)
1277 vs = pl.voicescript;
1280 if(vs.message == "")
1282 if(pl.classname != "player")
1287 if(time >= pl.voicescript_voiceend)
1289 if(time >= pl.voicescript_nextthink)
1291 // get the next voice...
1292 n = tokenize_sane(vs.message);
1294 if(pl.voicescript_index < vs.cnt)
1295 i = pl.voicescript_index * 2;
1296 else if(n > vs.cnt * 2)
1297 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1303 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1304 pl.voicescript_voiceend = time + stof(argv(i + 1));
1307 pl.voicescript = world;
1309 pl.voicescript_index += 1;
1310 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1315 void spawnfunc_target_voicescript()
1317 // netname: directory of the sound files
1318 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1319 // foo1 4.1 foo2 4.0 foo3 3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1320 // wait: average time between messages
1323 self.use = target_voicescript_use;
1325 n = tokenize_sane(self.message);
1327 for(i = 0; i+1 < n; i += 2)
1334 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));