]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
target_voicescript beginnign
[divverent/nexuiz.git] / data / qcsrc / server / g_triggers.qc
1
2 void() SUB_UseTargets;
3
4 void DelayThink()
5 {
6         activator = self.enemy;
7         SUB_UseTargets ();
8         remove(self);
9 };
10
11 /*
12 ==============================
13 SUB_UseTargets
14
15 the global "activator" should be set to the entity that initiated the firing.
16
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.
19
20 Centerprints any self.message to the activator.
21
22 Removes all entities with a targetname that match self.killtarget,
23 and removes them, so some events can remove other triggers.
24
25 Search for (string)targetname in all entities that
26 match (string)self.target and call their .use function
27
28 ==============================
29 */
30 void SUB_UseTargets()
31 {
32         local entity t, stemp, otemp, act;
33
34 //
35 // check for a delay
36 //
37         if (self.delay)
38         {
39         // create a temp object to fire at a later time
40                 t = spawn();
41                 t.classname = "DelayedUse";
42                 t.nextthink = time + self.delay;
43                 t.think = DelayThink;
44                 t.enemy = activator;
45                 t.message = self.message;
46                 t.killtarget = self.killtarget;
47                 t.target = self.target;
48                 return;
49         }
50
51
52 //
53 // print the message
54 //
55         if (activator.classname == "player" && self.message != "")
56         {
57                 if(clienttype(activator) == CLIENTTYPE_REAL)
58                 {
59                         centerprint (activator, self.message);
60                         if (!self.noise)
61                                 play2(activator, "misc/talk.wav");
62                 }
63         }
64
65 //
66 // kill the killtagets
67 //
68         if (self.killtarget)
69         {
70                 t = world;
71                 do
72                 {
73                         t = find (t, targetname, self.killtarget);
74                         if (!t)
75                                 return;
76                         remove (t);
77                 } while ( 1 );
78         }
79
80 //
81 // fire targets
82 //
83         if (self.target)
84         {
85                 act = activator;
86                 t = world;
87                 do
88                 {
89                         t = find (t, targetname, self.target);
90                         if (!t)
91                         {
92                                 return;
93                         }
94                         stemp = self;
95                         otemp = other;
96                         self = t;
97                         other = stemp;
98                         if (self.use)
99                                 self.use ();
100                         self = stemp;
101                         other = otemp;
102                         activator = act;
103                 } while ( 1 );
104         }
105
106
107 };
108
109
110 //=============================================================================
111
112 float   SPAWNFLAG_NOMESSAGE = 1;
113 float   SPAWNFLAG_NOTOUCH = 1;
114
115 // the wait time has passed, so set back up for another activation
116 void multi_wait()
117 {
118         if (self.max_health)
119         {
120                 self.health = self.max_health;
121                 self.takedamage = DAMAGE_YES;
122                 self.solid = SOLID_BBOX;
123         }
124 };
125
126
127 // the trigger was just touched/killed/used
128 // self.enemy should be set to the activator so it can be held through a delay
129 // so wait for the delay time before firing
130 void multi_trigger()
131 {
132         if (self.nextthink > time)
133         {
134                 return;         // allready been triggered
135         }
136
137         if (self.classname == "trigger_secret")
138         {
139                 if (self.enemy.classname != "player")
140                         return;
141                 found_secrets = found_secrets + 1;
142                 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
143         }
144
145         if (self.noise)
146                 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
147
148 // don't trigger again until reset
149         self.takedamage = DAMAGE_NO;
150
151         activator = self.enemy;
152
153         SUB_UseTargets();
154
155         if (self.wait > 0)
156         {
157                 self.think = multi_wait;
158                 self.nextthink = time + self.wait;
159         }
160         else
161         {       // we can't just remove (self) here, because this is a touch function
162                 // called wheil C code is looping through area links...
163                 self.touch = SUB_Null;
164
165                 self.nextthink = time + 0.1;
166                 self.think = SUB_Remove;
167         }
168 };
169
170 void multi_use()
171 {
172         self.enemy = activator;
173         multi_trigger();
174 };
175
176 void multi_touch()
177 {
178         if not(self.spawnflags & 2)
179         {
180                 if not(other.iscreature)
181                         return;
182
183                 if(self.team)
184                 if(self.team == other.team)
185                         return;
186         }
187
188 // if the trigger has an angles field, check player's facing direction
189         if (self.movedir != '0 0 0')
190         {
191                 makevectors (other.angles);
192                 if (v_forward * self.movedir < 0)
193                         return;         // not facing the right way
194         }
195
196         EXACTTRIGGER_TOUCH;
197
198         self.enemy = other;
199         multi_trigger ();
200 };
201
202 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
203 {
204         if (!self.takedamage)
205                 return;
206         self.health = self.health - damage;
207         if (self.health <= 0)
208         {
209                 self.enemy = attacker;
210                 multi_trigger();
211         }
212 }
213
214 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
215 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.
216 If "delay" is set, the trigger waits some time after activating before firing.
217 "wait" : Seconds between triggerings. (.2 default)
218 If notouch is set, the trigger is only fired by other entities, not by touching.
219 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
220 sounds
221 1)      secret
222 2)      beep beep
223 3)      large switch
224 4)
225 set "message" to text string
226 */
227 void spawnfunc_trigger_multiple()
228 {
229         if (self.sounds == 1)
230         {
231                 precache_sound ("misc/secret.wav");
232                 self.noise = "misc/secret.wav";
233         }
234         else if (self.sounds == 2)
235         {
236                 precache_sound ("misc/talk.wav");
237                 self.noise = "misc/talk.wav";
238         }
239         else if (self.sounds == 3)
240         {
241                 precache_sound ("misc/trigger1.wav");
242                 self.noise = "misc/trigger1.wav";
243         }
244
245         if (!self.wait)
246                 self.wait = 0.2;
247         self.use = multi_use;
248
249         EXACTTRIGGER_INIT;
250
251         if (self.health)
252         {
253                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
254                         objerror ("health and notouch don't make sense\n");
255                 self.max_health = self.health;
256                 self.event_damage = multi_eventdamage;
257                 self.takedamage = DAMAGE_YES;
258                 self.solid = SOLID_BBOX;
259                 setorigin (self, self.origin);  // make sure it links into the world
260         }
261         else
262         {
263                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
264                 {
265                         self.touch = multi_touch;
266                         setorigin (self, self.origin);  // make sure it links into the world
267                 }
268         }
269 };
270
271
272 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
273 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
274 "targetname".  If "health" is set, the trigger must be killed to activate.
275 If notouch is set, the trigger is only fired by other entities, not by touching.
276 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
277 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.
278 sounds
279 1)      secret
280 2)      beep beep
281 3)      large switch
282 4)
283 set "message" to text string
284 */
285 void spawnfunc_trigger_once()
286 {
287         self.wait = -1;
288         spawnfunc_trigger_multiple();
289 };
290
291 //=============================================================================
292
293 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
294 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
295 */
296 void spawnfunc_trigger_relay()
297 {
298         self.use = SUB_UseTargets;
299 };
300
301 void delay_use()
302 {
303     self.think = SUB_UseTargets;
304     self.nextthink = self.wait;
305 }
306
307 void spawnfunc_trigger_delay()
308 {
309     if(!self.wait)
310         self.wait = 1;
311
312     self.use = delay_use;
313 }
314
315 //=============================================================================
316
317
318 void counter_use()
319 {
320         self.count = self.count - 1;
321         if (self.count < 0)
322                 return;
323
324         if (self.count != 0)
325         {
326                 if (activator.classname == "player"
327                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
328                 {
329                         if (self.count >= 4)
330                                 centerprint (activator, "There are more to go...");
331                         else if (self.count == 3)
332                                 centerprint (activator, "Only 3 more to go...");
333                         else if (self.count == 2)
334                                 centerprint (activator, "Only 2 more to go...");
335                         else
336                                 centerprint (activator, "Only 1 more to go...");
337                 }
338                 return;
339         }
340
341         if (activator.classname == "player"
342         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
343                 centerprint(activator, "Sequence completed!");
344         self.enemy = activator;
345         multi_trigger ();
346 };
347
348 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
349 Acts as an intermediary for an action that takes multiple inputs.
350
351 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
352
353 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
354 */
355 void spawnfunc_trigger_counter()
356 {
357         self.wait = -1;
358         if (!self.count)
359                 self.count = 2;
360
361         self.use = counter_use;
362 };
363
364 .float triggerhurttime;
365 void trigger_hurt_touch()
366 {
367         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
368         if (other.iscreature)
369         {
370                 if (other.takedamage)
371                 if (other.triggerhurttime < time)
372                 {
373                         EXACTTRIGGER_TOUCH;
374                         other.triggerhurttime = time + 1;
375                         Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
376                 }
377         }
378         else
379         {
380                 if (!other.owner)
381                 {
382                         if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
383                         {
384                                 EXACTTRIGGER_TOUCH;
385                                 other.pain_finished = min(other.pain_finished, time + 2);
386                         }
387                         else if (other.classname == "rune")                     // reset runes
388                         {
389                                 EXACTTRIGGER_TOUCH;
390                                 other.nextthink = min(other.nextthink, time + 1);
391                         }
392                 }
393         }
394
395         return;
396 };
397
398 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
399 Any object touching this will be hurt
400 set dmg to damage amount
401 defalt dmg = 5
402 */
403 .entity trigger_hurt_next;
404 entity trigger_hurt_last;
405 entity trigger_hurt_first;
406 void spawnfunc_trigger_hurt()
407 {
408         EXACTTRIGGER_INIT;
409         self.touch = trigger_hurt_touch;
410         if (!self.dmg)
411                 self.dmg = 1000;
412         if (!self.message)
413                 self.message = "was in the wrong place";
414         if (!self.message2)
415                 self.message2 = "was thrown into a world of hurt by";
416
417         if(!trigger_hurt_first)
418                 trigger_hurt_first = self;
419         if(trigger_hurt_last)
420                 trigger_hurt_last.trigger_hurt_next = self;
421         trigger_hurt_last = self;
422 };
423
424 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
425 {
426         entity th;
427
428         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
429                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
430                         return TRUE;
431
432         return FALSE;
433 }
434
435
436 // TODO add a way to do looped sounds with sound(); then complete this entity
437 .float volume, atten;
438 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
439
440 void spawnfunc_target_speaker()
441 {
442         if(self.noise)
443                 precache_sound (self.noise);
444         IFTARGETED
445         {
446                 if(!self.atten)
447                         self.atten = ATTN_NORM;
448                 else if(self.atten < 0)
449                         self.atten = 0;
450                 if(!self.volume)
451                         self.volume = 1;
452                 self.use = target_speaker_use;
453         }
454         else
455         {
456                 if(!self.atten)
457                         self.atten = ATTN_STATIC;
458                 else if(self.atten < 0)
459                         self.atten = 0;
460                 if(!self.volume)
461                         self.volume = 1;
462                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
463         }
464 };
465
466
467 void spawnfunc_func_stardust() {
468         self.effects = EF_STARDUST;
469 }
470
471 float pointparticles_SendEntity(entity to, float fl)
472 {
473         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
474         WriteByte(MSG_ENTITY, fl);
475         if(fl & 2)
476         {
477                 if(self.state)
478                         WriteCoord(MSG_ENTITY, self.impulse);
479                 else
480                         WriteCoord(MSG_ENTITY, 0); // off
481         }
482         if(fl & 4)
483         {
484                 WriteCoord(MSG_ENTITY, self.origin_x);
485                 WriteCoord(MSG_ENTITY, self.origin_y);
486                 WriteCoord(MSG_ENTITY, self.origin_z);
487         }
488         if(fl & 1)
489         {
490                 if(self.modelindex != 4.2)
491                 {
492                         WriteShort(MSG_ENTITY, self.modelindex);
493                         WriteCoord(MSG_ENTITY, self.mins_x);
494                         WriteCoord(MSG_ENTITY, self.mins_y);
495                         WriteCoord(MSG_ENTITY, self.mins_z);
496                         WriteCoord(MSG_ENTITY, self.maxs_x);
497                         WriteCoord(MSG_ENTITY, self.maxs_y);
498                         WriteCoord(MSG_ENTITY, self.maxs_z);
499                 }
500                 else
501                 {
502                         WriteShort(MSG_ENTITY, 0);
503                         WriteCoord(MSG_ENTITY, self.maxs_x);
504                         WriteCoord(MSG_ENTITY, self.maxs_y);
505                         WriteCoord(MSG_ENTITY, self.maxs_z);
506                 }
507                 WriteShort(MSG_ENTITY, self.cnt);
508                 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
509                 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
510                 WriteCoord(MSG_ENTITY, self.waterlevel);
511                 WriteCoord(MSG_ENTITY, self.count);
512                 WriteByte(MSG_ENTITY, self.glow_color);
513                 WriteString(MSG_ENTITY, self.noise);
514         }
515         return 1;
516 }
517
518 void pointparticles_use()
519 {
520         self.state = !self.state;
521         self.SendFlags |= 2;
522 }
523
524 void pointparticles_think()
525 {
526         if(self.origin != self.oldorigin)
527         {
528                 self.SendFlags |= 4;
529                 self.oldorigin = self.origin;
530         }
531         self.nextthink = time;
532 }
533
534 void spawnfunc_func_pointparticles()
535 {
536         if(self.model != "")
537                 setmodel(self, self.model);
538         if(self.noise != "")
539                 precache_sound (self.noise);
540
541         self.effects = EF_NODEPTHTEST;
542         self.SendEntity = pointparticles_SendEntity;
543         self.SendFlags = 7;
544         if(!self.modelindex)
545         {
546                 self.modelindex = 4.2;
547                 self.origin += self.mins;
548                 self.maxs = self.maxs - self.mins;
549         }
550         self.model = "net_entity";
551         if(!self.cnt)
552                 self.cnt = particleeffectnum(self.mdl);
553         IFTARGETED
554         {
555                 self.use = pointparticles_use;
556                 if(self.spawnflags & 1)
557                         self.state = 1;
558                 else
559                         self.state = 0;
560         }
561         else
562                 self.state = 1;
563         self.think = pointparticles_think;
564         self.nextthink = time;
565 }
566
567 void spawnfunc_func_sparks()
568 {
569         // self.cnt is the amount of sparks that one burst will spawn
570         if(self.cnt < 1) {
571                 self.cnt = 25.0; // nice default value
572         }
573
574         // self.wait is the probability that a sparkthink will spawn a spark shower
575         // range: 0 - 1, but 0 makes little sense, so...
576         if(self.wait < 0.05) {
577                 self.wait = 0.25; // nice default value
578         }
579
580         self.count = self.cnt;
581         self.mins = '0 0 0';
582         self.maxs = '0 0 0';
583         self.velocity = '0 0 -1';
584         self.mdl = "TE_SPARK";
585         self.impulse = 10 * self.wait; // by default 2.5/sec
586         self.wait = 0;
587         self.cnt = 0; // use mdl
588
589         spawnfunc_func_pointparticles();
590 }
591
592 float rainsnow_SendEntity(float to)
593 {
594         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
595         WriteByte(MSG_ENTITY, self.state);
596         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
597         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
598         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
599         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
600         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
601         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
602         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
603         WriteShort(MSG_ENTITY, self.count);
604         WriteByte(MSG_ENTITY, self.cnt);
605         return 1;
606 };
607
608 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
609 This is an invisible area like a trigger, which rain falls inside of.
610
611 Keys:
612 "velocity"
613  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
614 "cnt"
615  sets color of rain (default 12 - white)
616 "count"
617  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
618 */
619 void spawnfunc_func_rain()
620 {
621         self.dest = self.velocity;
622         self.velocity = '0 0 0';
623         if (!self.dest)
624                 self.dest = '0 0 -700';
625         self.angles = '0 0 0';
626         self.movetype = MOVETYPE_NONE;
627         self.solid = SOLID_NOT;
628         SetBrushEntityModel();
629         self.model = "";
630         if (!self.cnt)
631                 self.cnt = 12;
632         if (!self.count)
633                 self.count = 2000;
634         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
635         if (self.count < 1)
636                 self.count = 1;
637         if(self.count > 65535)
638                 self.count = 65535;
639
640         self.state = 1; // 1 is rain, 0 is snow
641         self.effects = EF_NODEPTHTEST;
642         self.SendEntity = rainsnow_SendEntity;
643         self.Version = 1;
644         self.modelindex = 1;
645         self.model = "net_entity";
646 };
647
648
649 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
650 This is an invisible area like a trigger, which snow falls inside of.
651
652 Keys:
653 "velocity"
654  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
655 "cnt"
656  sets color of rain (default 12 - white)
657 "count"
658  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
659 */
660 void spawnfunc_func_snow()
661 {
662         self.dest = self.velocity;
663         self.velocity = '0 0 0';
664         if (!self.dest)
665                 self.dest = '0 0 -300';
666         self.angles = '0 0 0';
667         self.movetype = MOVETYPE_NONE;
668         self.solid = SOLID_NOT;
669         SetBrushEntityModel();
670         self.model = "";
671         if (!self.cnt)
672                 self.cnt = 12;
673         if (!self.count)
674                 self.count = 2000;
675         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
676         if (self.count < 1)
677                 self.count = 1;
678         if(self.count > 65535)
679                 self.count = 65535;
680
681         self.state = 0; // 1 is rain, 0 is snow
682         self.effects = EF_NODEPTHTEST;
683         self.SendEntity = rainsnow_SendEntity;
684         self.Version = 1;
685         self.modelindex = 1;
686         self.model = "net_entity";
687 };
688
689
690 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
691
692 void misc_laser_aim()
693 {
694         vector a;
695         if(self.enemy)
696         {
697                 if(self.spawnflags & 2)
698                 {
699                         if(self.enemy.origin != self.mangle)
700                         {
701                                 self.mangle = self.enemy.origin;
702                                 self.SendFlags |= 2;
703                         }
704                 }
705                 else
706                 {
707                         a = vectoangles(self.enemy.origin - self.origin);
708                         a_x = -a_x;
709                         if(a != self.mangle)
710                         {
711                                 self.mangle = a;
712                                 self.SendFlags |= 2;
713                         }
714                 }
715         }
716         else
717         {
718                 if(self.angles != self.mangle)
719                 {
720                         self.mangle = self.angles;
721                         self.SendFlags |= 2;
722                 }
723         }
724         if(self.origin != self.oldorigin)
725         {
726                 self.SendFlags |= 1;
727                 self.oldorigin = self.origin;
728         }
729 }
730
731 void misc_laser_init()
732 {
733         if(self.target != "")
734                 self.enemy = find(world, targetname, self.target);
735 }
736
737 .entity pusher;
738 void misc_laser_think()
739 {
740         vector o;
741         entity oldself;
742
743         self.nextthink = time;
744
745         if(!self.state)
746                 return;
747
748         misc_laser_aim();
749
750         if(self.enemy)
751         {
752                 o = self.enemy.origin;
753                 if not(self.spawnflags & 2)
754                         o = self.origin + normalize(o - self.origin) * 32768;
755         }
756         else
757         {
758                 makevectors(self.mangle);
759                 o = self.origin + v_forward * 32768;
760         }
761
762         if(self.dmg)
763         {
764                 if(self.dmg < 0)
765                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
766                 else
767                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
768         }
769
770         if(self.enemy.target != "") // DETECTOR laser
771         {
772                 traceline(self.origin, o, MOVE_NORMAL, self);
773                 if(trace_ent.iscreature)
774                 {
775                         self.pusher = trace_ent;
776                         if(!self.count)
777                         {
778                                 self.count = 1;
779
780                                 oldself = self;
781                                 self = self.enemy;
782                                 activator = self.pusher;
783                                 SUB_UseTargets();
784                                 self = oldself;
785                         }
786                 }
787                 else
788                 {
789                         if(self.count)
790                         {
791                                 self.count = 0;
792
793                                 oldself = self;
794                                 self = self.enemy;
795                                 activator = self.pusher;
796                                 SUB_UseTargets();
797                                 self = oldself;
798                         }
799                 }
800         }
801 }
802
803 float laser_SendEntity(entity to, float fl)
804 {
805         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
806         fl = fl - (fl & 0xC0); // use that bit to indicate finite length laser
807         if(self.spawnflags & 2)
808                 fl |= 0x80;
809         if(self.alpha)
810                 fl |= 0x40;
811         WriteByte(MSG_ENTITY, fl);
812         if(fl & 1)
813         {
814                 WriteCoord(MSG_ENTITY, self.origin_x);
815                 WriteCoord(MSG_ENTITY, self.origin_y);
816                 WriteCoord(MSG_ENTITY, self.origin_z);
817         }
818         if(fl & 8)
819         {
820                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
821                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
822                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
823                 if(fl & 0x40)
824                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
825                 WriteShort(MSG_ENTITY, self.cnt + 1);
826         }
827         if(fl & 2)
828         {
829                 if(fl & 0x80)
830                 {
831                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
832                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
833                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
834                 }
835                 else
836                 {
837                         WriteCoord(MSG_ENTITY, self.mangle_x);
838                         WriteCoord(MSG_ENTITY, self.mangle_y);
839                 }
840         }
841         if(fl & 4)
842                 WriteByte(MSG_ENTITY, self.state);
843         return 1;
844 }
845
846 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
847 Any object touching the beam will be hurt
848 Keys:
849 "target"
850  spawnfunc_target_position where the laser ends
851 "mdl"
852  name of beam end effect to use
853 "colormod"
854  color of the beam (default: red)
855 "dmg"
856  damage per second (-1 for a laser that kills immediately)
857 */
858 void laser_use()
859 {
860         self.state = !self.state;
861         self.SendFlags |= 4;
862         misc_laser_aim();
863 }
864
865 void spawnfunc_misc_laser()
866 {
867         if(self.mdl)
868         {
869                 if(self.mdl == "none")
870                         self.cnt = -1;
871                 else
872                         self.cnt = particleeffectnum(self.mdl);
873         }
874         else if(!self.cnt)
875         {
876                 if(self.dmg)
877                         self.cnt = particleeffectnum("laser_deadly");
878                 else
879                         self.cnt = -1;
880         }
881
882         if(self.colormod == '0 0 0')
883                 if(!self.alpha)
884                         self.colormod = '1 0 0';
885         if(!self.message)
886                 self.message = "saw the light";
887         if (!self.message2)
888                 self.message2 = "was pushed into a laser by";
889         self.think = misc_laser_think;
890         self.nextthink = time;
891         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
892
893         self.effects = EF_NODEPTHTEST;
894         self.SendEntity = laser_SendEntity;
895         self.SendFlags = 15;
896         self.modelindex = 1;
897         self.model = "net_entity";
898         self.mangle = self.angles;
899
900         IFTARGETED
901         {
902                 self.use = laser_use;
903                 if(self.spawnflags & 1)
904                         self.state = 1;
905                 else
906                         self.state = 0;
907         }
908         else
909                 self.state = 1;
910 }
911
912 // tZorks trigger impulse / gravity
913 .float radius;
914 .float falloff;
915 .float strength;
916 .float lastpushtime;
917
918 // targeted (directional) mode
919 void trigger_impulse_touch1()
920 {
921         entity targ;
922     float pushdeltatime;
923     float str;
924
925         // FIXME: Better checking for what to push and not.
926         if not(other.iscreature)
927         if (other.classname != "corpse")
928         if (other.classname != "body")
929         if (other.classname != "gib")
930         if (other.classname != "missile")
931         if (other.classname != "rocket")
932         if (other.classname != "casing")
933         if (other.classname != "grenade")
934         if (other.classname != "plasma")
935         if (other.classname != "plasma_prim")
936         if (other.classname != "plasma_chain")
937         if (other.classname != "droppedweapon")
938                 return;
939
940         if (other.deadflag && other.iscreature)
941                 return;
942
943         EXACTTRIGGER_TOUCH;
944
945     targ = find(world, targetname, self.target);
946     if(!targ)
947     {
948         objerror("trigger_force without a (valid) .target!\n");
949         remove(self);
950         return;
951     }
952
953     if(self.falloff == 1)
954         str = (str / self.radius) * self.strength;
955     else if(self.falloff == 2)
956         str = (1 - (str / self.radius)) * self.strength;
957     else
958         str = self.strength;
959
960     pushdeltatime = time - other.lastpushtime;
961     if (pushdeltatime > 0.15) pushdeltatime = 0;
962     other.lastpushtime = time;
963     if(!pushdeltatime) return;
964
965     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
966 }
967
968 // Directionless (accelerator/decelerator) mode
969 void trigger_impulse_touch2()
970 {
971     float pushdeltatime;
972
973         // FIXME: Better checking for what to push and not.
974         if not(other.iscreature)
975         if (other.classname != "corpse")
976         if (other.classname != "body")
977         if (other.classname != "gib")
978         if (other.classname != "missile")
979         if (other.classname != "rocket")
980         if (other.classname != "casing")
981         if (other.classname != "grenade")
982         if (other.classname != "plasma")
983         if (other.classname != "plasma_prim")
984         if (other.classname != "plasma_chain")
985         if (other.classname != "droppedweapon")
986                 return;
987
988         if (other.deadflag && other.iscreature)
989                 return;
990
991         EXACTTRIGGER_TOUCH;
992
993     pushdeltatime = time - other.lastpushtime;
994     if (pushdeltatime > 0.15) pushdeltatime = 0;
995     other.lastpushtime = time;
996     if(!pushdeltatime) return;
997
998     //if(self.strength > 1)
999         other.velocity = other.velocity * (self.strength * pushdeltatime);
1000     //else
1001     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1002 }
1003
1004 // Spherical (gravity/repulsor) mode
1005 void trigger_impulse_touch3()
1006 {
1007     float pushdeltatime;
1008     float str;
1009
1010         // FIXME: Better checking for what to push and not.
1011         if not(other.iscreature)
1012         if (other.classname != "corpse")
1013         if (other.classname != "body")
1014         if (other.classname != "gib")
1015         if (other.classname != "missile")
1016         if (other.classname != "rocket")
1017         if (other.classname != "casing")
1018         if (other.classname != "grenade")
1019         if (other.classname != "plasma")
1020         if (other.classname != "plasma_prim")
1021         if (other.classname != "plasma_chain")
1022         if (other.classname != "droppedweapon")
1023                 return;
1024
1025         if (other.deadflag && other.iscreature)
1026                 return;
1027
1028         EXACTTRIGGER_TOUCH;
1029
1030     pushdeltatime = time - other.lastpushtime;
1031     if (pushdeltatime > 0.15) pushdeltatime = 0;
1032     other.lastpushtime = time;
1033     if(!pushdeltatime) return;
1034
1035     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1036
1037         str = min(self.radius, vlen(self.origin - other.origin));
1038
1039     if(self.falloff == 1)
1040         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1041     else if(self.falloff == 2)
1042         str = (str / self.radius) * self.strength; // 0 in the inside
1043     else
1044         str = self.strength;
1045
1046     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1047 }
1048
1049 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1050 -------- KEYS --------
1051 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1052          If not, this trigger acts like a damper/accelerator field.
1053
1054 strength : This is how mutch force to add in the direction of .target each second
1055            when .target is set. If not, this is hoe mutch to slow down/accelerate
1056            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1057
1058 radius   : If set, act as a spherical device rather then a liniar one.
1059
1060 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1061
1062 -------- NOTES --------
1063 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1064 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1065 */
1066
1067 void spawnfunc_trigger_impulse()
1068 {
1069         EXACTTRIGGER_INIT;
1070     if(self.radius)
1071     {
1072         if(!self.strength) self.strength = 2000;
1073         setorigin(self, self.origin);
1074         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1075         self.touch = trigger_impulse_touch3;
1076     }
1077     else
1078     {
1079         if(self.target)
1080         {
1081             if(!self.strength) self.strength = 950;
1082             self.touch = trigger_impulse_touch1;
1083         }
1084         else
1085         {
1086             if(!self.strength) self.strength = 0.9;
1087             self.touch = trigger_impulse_touch2;
1088         }
1089     }
1090 }
1091
1092 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1093 "Flip-flop" trigger gate... lets only every second trigger event through
1094 */
1095 void flipflop_use()
1096 {
1097         self.state = !self.state;
1098         if(self.state)
1099                 SUB_UseTargets();
1100 }
1101
1102 void spawnfunc_trigger_flipflop()
1103 {
1104         if(self.spawnflags & 1)
1105                 self.state = 1;
1106     self.use = flipflop_use;
1107 }
1108
1109 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1110 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1111 */
1112 void monoflop_use()
1113 {
1114         self.nextthink = time + self.wait;
1115         self.enemy = activator;
1116         if(self.state)
1117                 return;
1118         self.state = 1;
1119         SUB_UseTargets();
1120 }
1121 void monoflop_fixed_use()
1122 {
1123         if(self.state)
1124                 return;
1125         self.nextthink = time + self.wait;
1126         self.state = 1;
1127         self.enemy = activator;
1128         SUB_UseTargets();
1129 }
1130
1131 void monoflop_think()
1132 {
1133         self.state = 0;
1134         activator = self.enemy;
1135         SUB_UseTargets();
1136 }
1137
1138 void spawnfunc_trigger_monoflop()
1139 {
1140         if(!self.wait)
1141                 self.wait = 1;
1142         if(self.spawnflags & 1)
1143                 self.use = monoflop_fixed_use;
1144         else
1145                 self.use = monoflop_use;
1146         self.think = monoflop_think;
1147         self.state = 0;
1148 }
1149
1150 void multivibrator_send()
1151 {
1152         float newstate;
1153         float cyclestart;
1154
1155         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1156
1157         newstate = (time < cyclestart + self.wait);
1158
1159         activator = self;
1160         if(self.state != newstate)
1161                 SUB_UseTargets();
1162         self.state = newstate;
1163
1164         if(self.state)
1165                 self.nextthink = cyclestart + self.wait + 0.01;
1166         else
1167                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1168 }
1169
1170 void multivibrator_toggle()
1171 {
1172         if(self.nextthink == 0)
1173         {
1174                 multivibrator_send();
1175         }
1176         else
1177         {
1178                 if(self.state)
1179                 {
1180                         SUB_UseTargets();
1181                         self.state = 0;
1182                 }
1183                 self.nextthink = 0;
1184         }
1185 }
1186
1187 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1188 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1189 -------- KEYS --------
1190 target: trigger all entities with this targetname when it goes off
1191 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1192 phase: offset of the timing
1193 wait: "on" cycle time (default: 1)
1194 respawntime: "off" cycle time (default: same as wait)
1195 -------- SPAWNFLAGS --------
1196 START_ON: assume it is already turned on (when targeted)
1197 */
1198 void spawnfunc_trigger_multivibrator()
1199 {
1200         if(!self.wait)
1201                 self.wait = 1;
1202         if(!self.respawntime)
1203                 self.respawntime = self.wait;
1204
1205         self.state = 0;
1206         self.use = multivibrator_toggle;
1207         self.think = multivibrator_send;
1208         self.nextthink = time;
1209
1210         IFTARGETED
1211         {
1212                 if(!(self.spawnflags & 1))
1213                         self.nextthink = 0; // wait for a trigger event
1214         }
1215         else
1216                 self.nextthink = time;
1217 }
1218
1219
1220 void follow_init()
1221 {
1222         entity src, dst;
1223         src = find(world, targetname, self.killtarget);
1224         dst = find(world, targetname, self.target);
1225
1226         if(!src || !dst)
1227         {
1228                 objerror("follow: could not find target/killtarget");
1229                 return;
1230         }
1231
1232         dst.movetype = MOVETYPE_FOLLOW;
1233         dst.aiment = src;
1234         dst.punchangle = src.angles;
1235         dst.view_ofs = dst.origin - src.origin;
1236         dst.v_angle = dst.angles - src.angles;
1237
1238         remove(self);
1239 }
1240
1241 void spawnfunc_misc_follow()
1242 {
1243         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1244 }
1245
1246
1247
1248
1249 .entity voicescript; // attached voice script
1250 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1251 .float voicescript_nextthink; // time to play next voice
1252 .float voicescript_voiceend; // time when this voice ends
1253
1254 void target_voicescript_clear(entity pl)
1255 {
1256         pl.voicescript = world;
1257 }
1258
1259 void target_voicescript_use()
1260 {
1261         if(activator.voicescript != self)
1262         {
1263                 activator.voicescript = self;
1264                 activator.voicescript_index = 0;
1265                 activator.voicescript_nextthink = time;
1266         }
1267 }
1268
1269 void target_voicescript_next(entity pl)
1270 {
1271         entity vs;
1272         float i, n;
1273
1274         vs = pl.voicescript;
1275         if(!vs)
1276                 return;
1277         if(vs.message == "")
1278                 return;
1279         if(pl.classname != "player")
1280                 return;
1281         if(gameover)
1282                 return;
1283
1284         if(time >= pl.voicescript_voiceend)
1285         {
1286                 if(time >= pl.voicescript_nextthink)
1287                 {
1288                         // get the next voice...
1289                         n = tokenize_sane(vs.message);
1290
1291                         if(pl.voicescript_index < vs.cnt)
1292                                 i = pl.voicescript_index * 2;
1293                         else if(n > vs.cnt * 2)
1294                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1295                         else
1296                                 i = -1;
1297
1298                         if(i >= 0)
1299                         {
1300                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1301                                 pl.voicescript_voiceend = time + stof(argv(i + 1));
1302                         }
1303                         else
1304                                 pl.voicescript = world;
1305
1306                         pl.voicescript_index += 1;
1307                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1308                 }
1309         }
1310 }
1311
1312 void spawnfunc_target_voicescript()
1313 {
1314         // netname: directory of the sound files
1315         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1316         //          foo1 4.1 foo2 4.0 foo3 3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1317         // wait: average time between messages
1318         
1319         float i, n;
1320         self.use = target_voicescript_use;
1321
1322         n = tokenize_sane(self.message);
1323         self.cnt = n / 2;
1324         for(i = 0; i+1 < n; i += 2)
1325         {
1326                 if(argv(i) == "*")
1327                 {
1328                         self.cnt = i / 2;
1329                         ++i;
1330                 }
1331                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1332         }
1333 }