]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
possibly allow untested welcome message disabling again
[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         string s;
34         float i;
35
36 //
37 // check for a delay
38 //
39         if (self.delay)
40         {
41         // create a temp object to fire at a later time
42                 t = spawn();
43                 t.classname = "DelayedUse";
44                 t.nextthink = time + self.delay;
45                 t.think = DelayThink;
46                 t.enemy = activator;
47                 t.message = self.message;
48                 t.killtarget = self.killtarget;
49                 t.target = self.target;
50                 return;
51         }
52
53
54 //
55 // print the message
56 //
57         if (activator.classname == "player" && self.message != "")
58         {
59                 if(clienttype(activator) == CLIENTTYPE_REAL)
60                 {
61                         centerprint (activator, self.message);
62                         if (!self.noise)
63                                 play2(activator, "misc/talk.wav");
64                 }
65         }
66
67 //
68 // kill the killtagets
69 //
70         s = self.killtarget;
71         if (s != "")
72         {
73                 for(t = world; (t = find(t, targetname, s)); )
74                         remove(t);
75         }
76
77 //
78 // fire targets
79 //
80         act = activator;
81         stemp = self;
82         otemp = other;
83
84         for(i = 0; i < 4; ++i)
85         {
86                 switch(i)
87                 {
88                         default:
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;
93                 }
94                 if (s != "")
95                 {
96                         for(t = world; (t = find(t, targetname, s)); )
97                         if(t.use)
98                         {
99                                 self = t;
100                                 other = stemp;
101                                 activator = act;
102                                 self.use();
103                         }
104                 }
105         }
106
107         activator = act;
108         self = stemp;
109         other = otemp;
110 };
111
112
113 //=============================================================================
114
115 float   SPAWNFLAG_NOMESSAGE = 1;
116 float   SPAWNFLAG_NOTOUCH = 1;
117
118 // the wait time has passed, so set back up for another activation
119 void multi_wait()
120 {
121         if (self.max_health)
122         {
123                 self.health = self.max_health;
124                 self.takedamage = DAMAGE_YES;
125                 self.solid = SOLID_BBOX;
126         }
127 };
128
129
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
133 void multi_trigger()
134 {
135         if (self.nextthink > time)
136         {
137                 return;         // allready been triggered
138         }
139
140         if (self.classname == "trigger_secret")
141         {
142                 if (self.enemy.classname != "player")
143                         return;
144                 found_secrets = found_secrets + 1;
145                 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
146         }
147
148         if (self.noise)
149                 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
150
151 // don't trigger again until reset
152         self.takedamage = DAMAGE_NO;
153
154         activator = self.enemy;
155
156         SUB_UseTargets();
157
158         if (self.wait > 0)
159         {
160                 self.think = multi_wait;
161                 self.nextthink = time + self.wait;
162         }
163         else
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;
167
168                 self.nextthink = time + 0.1;
169                 self.think = SUB_Remove;
170         }
171 };
172
173 void multi_use()
174 {
175         self.enemy = activator;
176         multi_trigger();
177 };
178
179 void multi_touch()
180 {
181         if not(self.spawnflags & 2)
182         {
183                 if not(other.iscreature)
184                         return;
185
186                 if(self.team)
187                 if(self.team == other.team)
188                         return;
189         }
190
191 // if the trigger has an angles field, check player's facing direction
192         if (self.movedir != '0 0 0')
193         {
194                 makevectors (other.angles);
195                 if (v_forward * self.movedir < 0)
196                         return;         // not facing the right way
197         }
198
199         EXACTTRIGGER_TOUCH;
200
201         self.enemy = other;
202         multi_trigger ();
203 };
204
205 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
206 {
207         if (!self.takedamage)
208                 return;
209         self.health = self.health - damage;
210         if (self.health <= 0)
211         {
212                 self.enemy = attacker;
213                 multi_trigger();
214         }
215 }
216
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!
223 sounds
224 1)      secret
225 2)      beep beep
226 3)      large switch
227 4)
228 set "message" to text string
229 */
230 void spawnfunc_trigger_multiple()
231 {
232         if (self.sounds == 1)
233         {
234                 precache_sound ("misc/secret.wav");
235                 self.noise = "misc/secret.wav";
236         }
237         else if (self.sounds == 2)
238         {
239                 precache_sound ("misc/talk.wav");
240                 self.noise = "misc/talk.wav";
241         }
242         else if (self.sounds == 3)
243         {
244                 precache_sound ("misc/trigger1.wav");
245                 self.noise = "misc/trigger1.wav";
246         }
247
248         if (!self.wait)
249                 self.wait = 0.2;
250         self.use = multi_use;
251
252         EXACTTRIGGER_INIT;
253
254         if (self.health)
255         {
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
263         }
264         else
265         {
266                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
267                 {
268                         self.touch = multi_touch;
269                         setorigin (self, self.origin);  // make sure it links into the world
270                 }
271         }
272 };
273
274
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.
281 sounds
282 1)      secret
283 2)      beep beep
284 3)      large switch
285 4)
286 set "message" to text string
287 */
288 void spawnfunc_trigger_once()
289 {
290         self.wait = -1;
291         spawnfunc_trigger_multiple();
292 };
293
294 //=============================================================================
295
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.
298 */
299 void spawnfunc_trigger_relay()
300 {
301         self.use = SUB_UseTargets;
302 };
303
304 void delay_use()
305 {
306     self.think = SUB_UseTargets;
307     self.nextthink = self.wait;
308 }
309
310 void spawnfunc_trigger_delay()
311 {
312     if(!self.wait)
313         self.wait = 1;
314
315     self.use = delay_use;
316 }
317
318 //=============================================================================
319
320
321 void counter_use()
322 {
323         self.count = self.count - 1;
324         if (self.count < 0)
325                 return;
326
327         if (self.count != 0)
328         {
329                 if (activator.classname == "player"
330                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
331                 {
332                         if (self.count >= 4)
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...");
338                         else
339                                 centerprint (activator, "Only 1 more to go...");
340                 }
341                 return;
342         }
343
344         if (activator.classname == "player"
345         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
346                 centerprint(activator, "Sequence completed!");
347         self.enemy = activator;
348         multi_trigger ();
349 };
350
351 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
352 Acts as an intermediary for an action that takes multiple inputs.
353
354 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
355
356 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
357 */
358 void spawnfunc_trigger_counter()
359 {
360         self.wait = -1;
361         if (!self.count)
362                 self.count = 2;
363
364         self.use = counter_use;
365 };
366
367 .float triggerhurttime;
368 void trigger_hurt_touch()
369 {
370         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
371         if (other.iscreature)
372         {
373                 if (other.takedamage)
374                 if (other.triggerhurttime < time)
375                 {
376                         EXACTTRIGGER_TOUCH;
377                         other.triggerhurttime = time + 1;
378                         Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
379                 }
380         }
381         else
382         {
383                 if (!other.owner)
384                 {
385                         if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
386                         {
387                                 EXACTTRIGGER_TOUCH;
388                                 other.pain_finished = min(other.pain_finished, time + 2);
389                         }
390                         else if (other.classname == "rune")                     // reset runes
391                         {
392                                 EXACTTRIGGER_TOUCH;
393                                 other.nextthink = min(other.nextthink, time + 1);
394                         }
395                 }
396         }
397
398         return;
399 };
400
401 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
402 Any object touching this will be hurt
403 set dmg to damage amount
404 defalt dmg = 5
405 */
406 .entity trigger_hurt_next;
407 entity trigger_hurt_last;
408 entity trigger_hurt_first;
409 void spawnfunc_trigger_hurt()
410 {
411         EXACTTRIGGER_INIT;
412         self.touch = trigger_hurt_touch;
413         if (!self.dmg)
414                 self.dmg = 1000;
415         if (!self.message)
416                 self.message = "was in the wrong place";
417         if (!self.message2)
418                 self.message2 = "was thrown into a world of hurt by";
419
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;
425 };
426
427 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
428 {
429         entity th;
430
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))
433                         return TRUE;
434
435         return FALSE;
436 }
437
438
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);}
442
443 void spawnfunc_target_speaker()
444 {
445         if(self.noise)
446                 precache_sound (self.noise);
447         IFTARGETED
448         {
449                 if(!self.atten)
450                         self.atten = ATTN_NORM;
451                 else if(self.atten < 0)
452                         self.atten = 0;
453                 if(!self.volume)
454                         self.volume = 1;
455                 self.use = target_speaker_use;
456         }
457         else
458         {
459                 if(!self.atten)
460                         self.atten = ATTN_STATIC;
461                 else if(self.atten < 0)
462                         self.atten = 0;
463                 if(!self.volume)
464                         self.volume = 1;
465                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
466         }
467 };
468
469
470 void spawnfunc_func_stardust() {
471         self.effects = EF_STARDUST;
472 }
473
474 float pointparticles_SendEntity(entity to, float fl)
475 {
476         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
477         WriteByte(MSG_ENTITY, fl);
478         if(fl & 2)
479         {
480                 if(self.state)
481                         WriteCoord(MSG_ENTITY, self.impulse);
482                 else
483                         WriteCoord(MSG_ENTITY, 0); // off
484         }
485         if(fl & 4)
486         {
487                 WriteCoord(MSG_ENTITY, self.origin_x);
488                 WriteCoord(MSG_ENTITY, self.origin_y);
489                 WriteCoord(MSG_ENTITY, self.origin_z);
490         }
491         if(fl & 1)
492         {
493                 if(self.modelindex != 4.2)
494                 {
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);
502                 }
503                 else
504                 {
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);
509                 }
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);
517         }
518         return 1;
519 }
520
521 void pointparticles_use()
522 {
523         self.state = !self.state;
524         self.SendFlags |= 2;
525 }
526
527 void pointparticles_think()
528 {
529         if(self.origin != self.oldorigin)
530         {
531                 self.SendFlags |= 4;
532                 self.oldorigin = self.origin;
533         }
534         self.nextthink = time;
535 }
536
537 void spawnfunc_func_pointparticles()
538 {
539         if(self.model != "")
540                 setmodel(self, self.model);
541         if(self.noise != "")
542                 precache_sound (self.noise);
543
544         self.effects = EF_NODEPTHTEST;
545         self.SendEntity = pointparticles_SendEntity;
546         self.SendFlags = 7;
547         if(!self.modelindex)
548         {
549                 self.modelindex = 4.2;
550                 self.origin += self.mins;
551                 self.maxs = self.maxs - self.mins;
552         }
553         self.model = "net_entity";
554         if(!self.cnt)
555                 self.cnt = particleeffectnum(self.mdl);
556         IFTARGETED
557         {
558                 self.use = pointparticles_use;
559                 if(self.spawnflags & 1)
560                         self.state = 1;
561                 else
562                         self.state = 0;
563         }
564         else
565                 self.state = 1;
566         self.think = pointparticles_think;
567         self.nextthink = time;
568 }
569
570 void spawnfunc_func_sparks()
571 {
572         // self.cnt is the amount of sparks that one burst will spawn
573         if(self.cnt < 1) {
574                 self.cnt = 25.0; // nice default value
575         }
576
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
581         }
582
583         self.count = self.cnt;
584         self.mins = '0 0 0';
585         self.maxs = '0 0 0';
586         self.velocity = '0 0 -1';
587         self.mdl = "TE_SPARK";
588         self.impulse = 10 * self.wait; // by default 2.5/sec
589         self.wait = 0;
590         self.cnt = 0; // use mdl
591
592         spawnfunc_func_pointparticles();
593 }
594
595 float rainsnow_SendEntity(float to)
596 {
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);
608         return 1;
609 };
610
611 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
612 This is an invisible area like a trigger, which rain falls inside of.
613
614 Keys:
615 "velocity"
616  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
617 "cnt"
618  sets color of rain (default 12 - white)
619 "count"
620  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
621 */
622 void spawnfunc_func_rain()
623 {
624         self.dest = self.velocity;
625         self.velocity = '0 0 0';
626         if (!self.dest)
627                 self.dest = '0 0 -700';
628         self.angles = '0 0 0';
629         self.movetype = MOVETYPE_NONE;
630         self.solid = SOLID_NOT;
631         SetBrushEntityModel();
632         self.model = "";
633         if (!self.cnt)
634                 self.cnt = 12;
635         if (!self.count)
636                 self.count = 2000;
637         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
638         if (self.count < 1)
639                 self.count = 1;
640         if(self.count > 65535)
641                 self.count = 65535;
642
643         self.state = 1; // 1 is rain, 0 is snow
644         self.effects = EF_NODEPTHTEST;
645         self.SendEntity = rainsnow_SendEntity;
646         self.Version = 1;
647         self.modelindex = 1;
648         self.model = "net_entity";
649 };
650
651
652 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
653 This is an invisible area like a trigger, which snow falls inside of.
654
655 Keys:
656 "velocity"
657  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
658 "cnt"
659  sets color of rain (default 12 - white)
660 "count"
661  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
662 */
663 void spawnfunc_func_snow()
664 {
665         self.dest = self.velocity;
666         self.velocity = '0 0 0';
667         if (!self.dest)
668                 self.dest = '0 0 -300';
669         self.angles = '0 0 0';
670         self.movetype = MOVETYPE_NONE;
671         self.solid = SOLID_NOT;
672         SetBrushEntityModel();
673         self.model = "";
674         if (!self.cnt)
675                 self.cnt = 12;
676         if (!self.count)
677                 self.count = 2000;
678         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
679         if (self.count < 1)
680                 self.count = 1;
681         if(self.count > 65535)
682                 self.count = 65535;
683
684         self.state = 0; // 1 is rain, 0 is snow
685         self.effects = EF_NODEPTHTEST;
686         self.SendEntity = rainsnow_SendEntity;
687         self.Version = 1;
688         self.modelindex = 1;
689         self.model = "net_entity";
690 };
691
692
693 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
694
695 void misc_laser_aim()
696 {
697         vector a;
698         if(self.enemy)
699         {
700                 if(self.spawnflags & 2)
701                 {
702                         if(self.enemy.origin != self.mangle)
703                         {
704                                 self.mangle = self.enemy.origin;
705                                 self.SendFlags |= 2;
706                         }
707                 }
708                 else
709                 {
710                         a = vectoangles(self.enemy.origin - self.origin);
711                         a_x = -a_x;
712                         if(a != self.mangle)
713                         {
714                                 self.mangle = a;
715                                 self.SendFlags |= 2;
716                         }
717                 }
718         }
719         else
720         {
721                 if(self.angles != self.mangle)
722                 {
723                         self.mangle = self.angles;
724                         self.SendFlags |= 2;
725                 }
726         }
727         if(self.origin != self.oldorigin)
728         {
729                 self.SendFlags |= 1;
730                 self.oldorigin = self.origin;
731         }
732 }
733
734 void misc_laser_init()
735 {
736         if(self.target != "")
737                 self.enemy = find(world, targetname, self.target);
738 }
739
740 .entity pusher;
741 void misc_laser_think()
742 {
743         vector o;
744         entity oldself;
745
746         self.nextthink = time;
747
748         if(!self.state)
749                 return;
750
751         misc_laser_aim();
752
753         if(self.enemy)
754         {
755                 o = self.enemy.origin;
756                 if not(self.spawnflags & 2)
757                         o = self.origin + normalize(o - self.origin) * 32768;
758         }
759         else
760         {
761                 makevectors(self.mangle);
762                 o = self.origin + v_forward * 32768;
763         }
764
765         if(self.dmg)
766         {
767                 if(self.dmg < 0)
768                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
769                 else
770                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
771         }
772
773         if(self.enemy.target != "") // DETECTOR laser
774         {
775                 traceline(self.origin, o, MOVE_NORMAL, self);
776                 if(trace_ent.iscreature)
777                 {
778                         self.pusher = trace_ent;
779                         if(!self.count)
780                         {
781                                 self.count = 1;
782
783                                 oldself = self;
784                                 self = self.enemy;
785                                 activator = self.pusher;
786                                 SUB_UseTargets();
787                                 self = oldself;
788                         }
789                 }
790                 else
791                 {
792                         if(self.count)
793                         {
794                                 self.count = 0;
795
796                                 oldself = self;
797                                 self = self.enemy;
798                                 activator = self.pusher;
799                                 SUB_UseTargets();
800                                 self = oldself;
801                         }
802                 }
803         }
804 }
805
806 float laser_SendEntity(entity to, float fl)
807 {
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)
811                 fl |= 0x80;
812         if(self.alpha)
813                 fl |= 0x40;
814         WriteByte(MSG_ENTITY, fl);
815         if(fl & 1)
816         {
817                 WriteCoord(MSG_ENTITY, self.origin_x);
818                 WriteCoord(MSG_ENTITY, self.origin_y);
819                 WriteCoord(MSG_ENTITY, self.origin_z);
820         }
821         if(fl & 8)
822         {
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);
826                 if(fl & 0x40)
827                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
828                 WriteShort(MSG_ENTITY, self.cnt + 1);
829         }
830         if(fl & 2)
831         {
832                 if(fl & 0x80)
833                 {
834                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
835                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
836                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
837                 }
838                 else
839                 {
840                         WriteCoord(MSG_ENTITY, self.mangle_x);
841                         WriteCoord(MSG_ENTITY, self.mangle_y);
842                 }
843         }
844         if(fl & 4)
845                 WriteByte(MSG_ENTITY, self.state);
846         return 1;
847 }
848
849 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
850 Any object touching the beam will be hurt
851 Keys:
852 "target"
853  spawnfunc_target_position where the laser ends
854 "mdl"
855  name of beam end effect to use
856 "colormod"
857  color of the beam (default: red)
858 "dmg"
859  damage per second (-1 for a laser that kills immediately)
860 */
861 void laser_use()
862 {
863         self.state = !self.state;
864         self.SendFlags |= 4;
865         misc_laser_aim();
866 }
867
868 void spawnfunc_misc_laser()
869 {
870         if(self.mdl)
871         {
872                 if(self.mdl == "none")
873                         self.cnt = -1;
874                 else
875                         self.cnt = particleeffectnum(self.mdl);
876         }
877         else if(!self.cnt)
878         {
879                 if(self.dmg)
880                         self.cnt = particleeffectnum("laser_deadly");
881                 else
882                         self.cnt = -1;
883         }
884
885         if(self.colormod == '0 0 0')
886                 if(!self.alpha)
887                         self.colormod = '1 0 0';
888         if(!self.message)
889                 self.message = "saw the light";
890         if (!self.message2)
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);
895
896         self.effects = EF_NODEPTHTEST;
897         self.SendEntity = laser_SendEntity;
898         self.SendFlags = 15;
899         self.modelindex = 1;
900         self.model = "net_entity";
901         self.mangle = self.angles;
902
903         IFTARGETED
904         {
905                 self.use = laser_use;
906                 if(self.spawnflags & 1)
907                         self.state = 1;
908                 else
909                         self.state = 0;
910         }
911         else
912                 self.state = 1;
913 }
914
915 // tZorks trigger impulse / gravity
916 .float radius;
917 .float falloff;
918 .float strength;
919 .float lastpushtime;
920
921 // targeted (directional) mode
922 void trigger_impulse_touch1()
923 {
924         entity targ;
925     float pushdeltatime;
926     float str;
927
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")
941                 return;
942
943         if (other.deadflag && other.iscreature)
944                 return;
945
946         EXACTTRIGGER_TOUCH;
947
948     targ = find(world, targetname, self.target);
949     if(!targ)
950     {
951         objerror("trigger_force without a (valid) .target!\n");
952         remove(self);
953         return;
954     }
955
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;
960     else
961         str = self.strength;
962
963     pushdeltatime = time - other.lastpushtime;
964     if (pushdeltatime > 0.15) pushdeltatime = 0;
965     other.lastpushtime = time;
966     if(!pushdeltatime) return;
967
968     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
969 }
970
971 // Directionless (accelerator/decelerator) mode
972 void trigger_impulse_touch2()
973 {
974     float pushdeltatime;
975
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")
989                 return;
990
991         if (other.deadflag && other.iscreature)
992                 return;
993
994         EXACTTRIGGER_TOUCH;
995
996     pushdeltatime = time - other.lastpushtime;
997     if (pushdeltatime > 0.15) pushdeltatime = 0;
998     other.lastpushtime = time;
999     if(!pushdeltatime) return;
1000
1001     //if(self.strength > 1)
1002         other.velocity = other.velocity * (self.strength * pushdeltatime);
1003     //else
1004     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1005 }
1006
1007 // Spherical (gravity/repulsor) mode
1008 void trigger_impulse_touch3()
1009 {
1010     float pushdeltatime;
1011     float str;
1012
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")
1026                 return;
1027
1028         if (other.deadflag && other.iscreature)
1029                 return;
1030
1031         EXACTTRIGGER_TOUCH;
1032
1033     pushdeltatime = time - other.lastpushtime;
1034     if (pushdeltatime > 0.15) pushdeltatime = 0;
1035     other.lastpushtime = time;
1036     if(!pushdeltatime) return;
1037
1038     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1039
1040         str = min(self.radius, vlen(self.origin - other.origin));
1041
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
1046     else
1047         str = self.strength;
1048
1049     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1050 }
1051
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.
1056
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)
1060
1061 radius   : If set, act as a spherical device rather then a liniar one.
1062
1063 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1064
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).
1068 */
1069
1070 void spawnfunc_trigger_impulse()
1071 {
1072         EXACTTRIGGER_INIT;
1073     if(self.radius)
1074     {
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;
1079     }
1080     else
1081     {
1082         if(self.target)
1083         {
1084             if(!self.strength) self.strength = 950;
1085             self.touch = trigger_impulse_touch1;
1086         }
1087         else
1088         {
1089             if(!self.strength) self.strength = 0.9;
1090             self.touch = trigger_impulse_touch2;
1091         }
1092     }
1093 }
1094
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
1097 */
1098 void flipflop_use()
1099 {
1100         self.state = !self.state;
1101         if(self.state)
1102                 SUB_UseTargets();
1103 }
1104
1105 void spawnfunc_trigger_flipflop()
1106 {
1107         if(self.spawnflags & 1)
1108                 self.state = 1;
1109     self.use = flipflop_use;
1110 }
1111
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"
1114 */
1115 void monoflop_use()
1116 {
1117         self.nextthink = time + self.wait;
1118         self.enemy = activator;
1119         if(self.state)
1120                 return;
1121         self.state = 1;
1122         SUB_UseTargets();
1123 }
1124 void monoflop_fixed_use()
1125 {
1126         if(self.state)
1127                 return;
1128         self.nextthink = time + self.wait;
1129         self.state = 1;
1130         self.enemy = activator;
1131         SUB_UseTargets();
1132 }
1133
1134 void monoflop_think()
1135 {
1136         self.state = 0;
1137         activator = self.enemy;
1138         SUB_UseTargets();
1139 }
1140
1141 void spawnfunc_trigger_monoflop()
1142 {
1143         if(!self.wait)
1144                 self.wait = 1;
1145         if(self.spawnflags & 1)
1146                 self.use = monoflop_fixed_use;
1147         else
1148                 self.use = monoflop_use;
1149         self.think = monoflop_think;
1150         self.state = 0;
1151 }
1152
1153 void multivibrator_send()
1154 {
1155         float newstate;
1156         float cyclestart;
1157
1158         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1159
1160         newstate = (time < cyclestart + self.wait);
1161
1162         activator = self;
1163         if(self.state != newstate)
1164                 SUB_UseTargets();
1165         self.state = newstate;
1166
1167         if(self.state)
1168                 self.nextthink = cyclestart + self.wait + 0.01;
1169         else
1170                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1171 }
1172
1173 void multivibrator_toggle()
1174 {
1175         if(self.nextthink == 0)
1176         {
1177                 multivibrator_send();
1178         }
1179         else
1180         {
1181                 if(self.state)
1182                 {
1183                         SUB_UseTargets();
1184                         self.state = 0;
1185                 }
1186                 self.nextthink = 0;
1187         }
1188 }
1189
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)
1200 */
1201 void spawnfunc_trigger_multivibrator()
1202 {
1203         if(!self.wait)
1204                 self.wait = 1;
1205         if(!self.respawntime)
1206                 self.respawntime = self.wait;
1207
1208         self.state = 0;
1209         self.use = multivibrator_toggle;
1210         self.think = multivibrator_send;
1211         self.nextthink = time;
1212
1213         IFTARGETED
1214         {
1215                 if(!(self.spawnflags & 1))
1216                         self.nextthink = 0; // wait for a trigger event
1217         }
1218         else
1219                 self.nextthink = time;
1220 }
1221
1222
1223 void follow_init()
1224 {
1225         entity src, dst;
1226         src = find(world, targetname, self.killtarget);
1227         dst = find(world, targetname, self.target);
1228
1229         if(!src || !dst)
1230         {
1231                 objerror("follow: could not find target/killtarget");
1232                 return;
1233         }
1234
1235         dst.movetype = MOVETYPE_FOLLOW;
1236         dst.aiment = src;
1237         dst.punchangle = src.angles;
1238         dst.view_ofs = dst.origin - src.origin;
1239         dst.v_angle = dst.angles - src.angles;
1240
1241         remove(self);
1242 }
1243
1244 void spawnfunc_misc_follow()
1245 {
1246         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1247 }
1248
1249
1250
1251
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
1256
1257 void target_voicescript_clear(entity pl)
1258 {
1259         pl.voicescript = world;
1260 }
1261
1262 void target_voicescript_use()
1263 {
1264         if(activator.voicescript != self)
1265         {
1266                 activator.voicescript = self;
1267                 activator.voicescript_index = 0;
1268                 activator.voicescript_nextthink = time + self.delay;
1269         }
1270 }
1271
1272 void target_voicescript_next(entity pl)
1273 {
1274         entity vs;
1275         float i, n;
1276
1277         vs = pl.voicescript;
1278         if(!vs)
1279                 return;
1280         if(vs.message == "")
1281                 return;
1282         if(pl.classname != "player")
1283                 return;
1284         if(gameover)
1285                 return;
1286
1287         if(time >= pl.voicescript_voiceend)
1288         {
1289                 if(time >= pl.voicescript_nextthink)
1290                 {
1291                         // get the next voice...
1292                         n = tokenize_sane(vs.message);
1293
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;
1298                         else
1299                                 i = -1;
1300
1301                         if(i >= 0)
1302                         {
1303                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1304                                 pl.voicescript_voiceend = time + stof(argv(i + 1));
1305                         }
1306                         else
1307                                 pl.voicescript = world;
1308
1309                         pl.voicescript_index += 1;
1310                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1311                 }
1312         }
1313 }
1314
1315 void spawnfunc_target_voicescript()
1316 {
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
1321         // delay: initial delay before the first message
1322         
1323         float i, n;
1324         self.use = target_voicescript_use;
1325
1326         n = tokenize_sane(self.message);
1327         self.cnt = n / 2;
1328         for(i = 0; i+1 < n; i += 2)
1329         {
1330                 if(argv(i) == "*")
1331                 {
1332                         self.cnt = i / 2;
1333                         ++i;
1334                 }
1335                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1336         }
1337 }