]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_triggers.qc
Merge branch 'master' into mirceakitsune/hud_postprocessing
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_triggers.qc
1 void SUB_DontUseTargets()
2 {
3 }
4
5
6 void() SUB_UseTargets;
7
8 void DelayThink()
9 {
10         activator = self.enemy;
11         SUB_UseTargets ();
12         remove(self);
13 };
14
15 /*
16 ==============================
17 SUB_UseTargets
18
19 the global "activator" should be set to the entity that initiated the firing.
20
21 If self.delay is set, a DelayedUse entity will be created that will actually
22 do the SUB_UseTargets after that many seconds have passed.
23
24 Centerprints any self.message to the activator.
25
26 Removes all entities with a targetname that match self.killtarget,
27 and removes them, so some events can remove other triggers.
28
29 Search for (string)targetname in all entities that
30 match (string)self.target and call their .use function
31
32 ==============================
33 */
34 void SUB_UseTargets()
35 {
36         local entity t, stemp, otemp, act;
37         string s;
38         float i;
39
40 //
41 // check for a delay
42 //
43         if (self.delay)
44         {
45         // create a temp object to fire at a later time
46                 t = spawn();
47                 t.classname = "DelayedUse";
48                 t.nextthink = time + self.delay;
49                 t.think = DelayThink;
50                 t.enemy = activator;
51                 t.message = self.message;
52                 t.killtarget = self.killtarget;
53                 t.target = self.target;
54                 t.target2 = self.target2;
55                 t.target3 = self.target3;
56                 t.target4 = self.target4;
57                 return;
58         }
59
60
61 //
62 // print the message
63 //
64         if (activator.classname == "player" && self.message != "")
65         {
66                 if(clienttype(activator) == CLIENTTYPE_REAL)
67                 {
68                         centerprint (activator, self.message);
69                         if (!self.noise)
70                                 play2(activator, "misc/talk.wav");
71                 }
72         }
73
74 //
75 // kill the killtagets
76 //
77         s = self.killtarget;
78         if (s != "")
79         {
80                 for(t = world; (t = find(t, targetname, s)); )
81                         remove(t);
82         }
83
84 //
85 // fire targets
86 //
87         act = activator;
88         stemp = self;
89         otemp = other;
90
91         if(stemp.target_random)
92                 RandomSelection_Init();
93
94         for(i = 0; i < 4; ++i)
95         {
96                 switch(i)
97                 {
98                         default:
99                         case 0: s = stemp.target; break;
100                         case 1: s = stemp.target2; break;
101                         case 2: s = stemp.target3; break;
102                         case 3: s = stemp.target4; break;
103                 }
104                 if (s != "")
105                 {
106                         for(t = world; (t = find(t, targetname, s)); )
107                         if(t.use)
108                         {
109                                 if(stemp.target_random)
110                                 {
111                                         RandomSelection_Add(t, 0, string_null, 1, 0);
112                                 }
113                                 else
114                                 {
115                                         self = t;
116                                         other = stemp;
117                                         activator = act;
118                                         self.use();
119                                 }
120                         }
121                 }
122         }
123
124         if(stemp.target_random && RandomSelection_chosen_ent)
125         {
126                 self = RandomSelection_chosen_ent;
127                 other = stemp;
128                 activator = act;
129                 self.use();
130         }
131
132         activator = act;
133         self = stemp;
134         other = otemp;
135 };
136
137
138 //=============================================================================
139
140 float   SPAWNFLAG_NOMESSAGE = 1;
141 float   SPAWNFLAG_NOTOUCH = 1;
142
143 // the wait time has passed, so set back up for another activation
144 void multi_wait()
145 {
146         if (self.max_health)
147         {
148                 self.health = self.max_health;
149                 self.takedamage = DAMAGE_YES;
150                 self.solid = SOLID_BBOX;
151         }
152 };
153
154
155 // the trigger was just touched/killed/used
156 // self.enemy should be set to the activator so it can be held through a delay
157 // so wait for the delay time before firing
158 void multi_trigger()
159 {
160         if (self.nextthink > time)
161         {
162                 return;         // allready been triggered
163         }
164
165         if (self.classname == "trigger_secret")
166         {
167                 if (self.enemy.classname != "player")
168                         return;
169                 found_secrets = found_secrets + 1;
170                 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
171         }
172
173         if (self.noise)
174                 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
175
176 // don't trigger again until reset
177         self.takedamage = DAMAGE_NO;
178
179         activator = self.enemy;
180         other = self.goalentity;
181         SUB_UseTargets();
182
183         if (self.wait > 0)
184         {
185                 self.think = multi_wait;
186                 self.nextthink = time + self.wait;
187         }
188         else if (self.wait == 0)
189         {
190                 multi_wait(); // waiting finished
191         }
192         else
193         {       // we can't just remove (self) here, because this is a touch function
194                 // called wheil C code is looping through area links...
195                 self.touch = SUB_Null;
196         }
197 };
198
199 void multi_use()
200 {
201         self.goalentity = other;
202         self.enemy = activator;
203         multi_trigger();
204 };
205
206 void multi_touch()
207 {
208         if not(self.spawnflags & 2)
209                 if not(other.iscreature)
210                         return;
211
212         if(self.team)
213                 if((self.spawnflags & 4 == 0) == (self.team != other.team))
214                         return;
215
216 // if the trigger has an angles field, check player's facing direction
217         if (self.movedir != '0 0 0')
218         {
219                 makevectors (other.angles);
220                 if (v_forward * self.movedir < 0)
221                         return;         // not facing the right way
222         }
223
224         EXACTTRIGGER_TOUCH;
225
226         self.enemy = other;
227         self.goalentity = other;
228         multi_trigger ();
229 };
230
231 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
232 {
233         if (!self.takedamage)
234                 return;
235         if(self.spawnflags & DOOR_NOSPLASH)
236                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
237                         return;
238         self.health = self.health - damage;
239         if (self.health <= 0)
240         {
241                 self.enemy = attacker;
242                 self.goalentity = inflictor;
243                 multi_trigger();
244         }
245 }
246
247 void multi_reset()
248 {
249         if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
250                 self.touch = multi_touch;
251         if (self.max_health)
252         {
253                 self.health = self.max_health;
254                 self.takedamage = DAMAGE_YES;
255                 self.solid = SOLID_BBOX;
256         }
257         self.think = SUB_Null;
258         self.team = self.team_saved;
259 }
260
261 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
262 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.
263 If "delay" is set, the trigger waits some time after activating before firing.
264 "wait" : Seconds between triggerings. (.2 default)
265 If notouch is set, the trigger is only fired by other entities, not by touching.
266 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
267 sounds
268 1)      secret
269 2)      beep beep
270 3)      large switch
271 4)
272 set "message" to text string
273 */
274 void spawnfunc_trigger_multiple()
275 {
276         self.reset = multi_reset;
277         if (self.sounds == 1)
278         {
279                 precache_sound ("misc/secret.wav");
280                 self.noise = "misc/secret.wav";
281         }
282         else if (self.sounds == 2)
283         {
284                 precache_sound ("misc/talk.wav");
285                 self.noise = "misc/talk.wav";
286         }
287         else if (self.sounds == 3)
288         {
289                 precache_sound ("misc/trigger1.wav");
290                 self.noise = "misc/trigger1.wav";
291         }
292
293         if (!self.wait)
294                 self.wait = 0.2;
295         else if(self.wait < -1)
296                 self.wait = 0;
297         self.use = multi_use;
298
299         EXACTTRIGGER_INIT;
300
301         self.team_saved = self.team;
302
303         if (self.health)
304         {
305                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
306                         objerror ("health and notouch don't make sense\n");
307                 self.max_health = self.health;
308                 self.event_damage = multi_eventdamage;
309                 self.takedamage = DAMAGE_YES;
310                 self.solid = SOLID_BBOX;
311                 setorigin (self, self.origin);  // make sure it links into the world
312         }
313         else
314         {
315                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
316                 {
317                         self.touch = multi_touch;
318                         setorigin (self, self.origin);  // make sure it links into the world
319                 }
320         }
321 };
322
323
324 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
325 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
326 "targetname".  If "health" is set, the trigger must be killed to activate.
327 If notouch is set, the trigger is only fired by other entities, not by touching.
328 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
329 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.
330 sounds
331 1)      secret
332 2)      beep beep
333 3)      large switch
334 4)
335 set "message" to text string
336 */
337 void spawnfunc_trigger_once()
338 {
339         self.wait = -1;
340         spawnfunc_trigger_multiple();
341 };
342
343 //=============================================================================
344
345 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
346 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
347 */
348 void spawnfunc_trigger_relay()
349 {
350         self.use = SUB_UseTargets;
351         self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
352 };
353
354 void delay_use()
355 {
356     self.think = SUB_UseTargets;
357     self.nextthink = self.wait;
358 }
359
360 void delay_reset()
361 {
362         self.think = SUB_Null;
363 }
364
365 void spawnfunc_trigger_delay()
366 {
367     if(!self.wait)
368         self.wait = 1;
369
370     self.use = delay_use;
371     self.reset = delay_reset;
372 }
373
374 //=============================================================================
375
376
377 void counter_use()
378 {
379         self.count = self.count - 1;
380         if (self.count < 0)
381                 return;
382
383         if (self.count != 0)
384         {
385                 if (activator.classname == "player"
386                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
387                 {
388                         if (self.count >= 4)
389                                 centerprint (activator, "There are more to go...");
390                         else if (self.count == 3)
391                                 centerprint (activator, "Only 3 more to go...");
392                         else if (self.count == 2)
393                                 centerprint (activator, "Only 2 more to go...");
394                         else
395                                 centerprint (activator, "Only 1 more to go...");
396                 }
397                 return;
398         }
399
400         if (activator.classname == "player"
401         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
402                 centerprint(activator, "Sequence completed!");
403         self.enemy = activator;
404         multi_trigger ();
405 };
406
407 void counter_reset()
408 {
409         self.count = self.cnt;
410         multi_reset();
411 }
412
413 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
414 Acts as an intermediary for an action that takes multiple inputs.
415
416 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
417
418 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
419 */
420 void spawnfunc_trigger_counter()
421 {
422         self.wait = -1;
423         if (!self.count)
424                 self.count = 2;
425         self.cnt = self.count;
426
427         self.use = counter_use;
428         self.reset = counter_reset;
429 };
430
431 void trigger_hurt_use()
432 {
433         if(activator.classname == "player")
434                 self.enemy = activator;
435         else
436                 self.enemy = world; // let's just destroy it, if taking over is too much work
437 }
438
439 .float triggerhurttime;
440 void trigger_hurt_touch()
441 {
442         if (self.active != ACTIVE_ACTIVE) 
443                 return;
444
445         if(self.team)
446                 if((self.spawnflags & 4 == 0) == (self.team != other.team))
447                         return;
448
449         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
450         if (other.iscreature)
451         {
452                 if (other.takedamage)
453                 if (other.triggerhurttime < time)
454                 {
455                         EXACTTRIGGER_TOUCH;
456                         other.triggerhurttime = time + 1;
457
458                         entity own;
459                         own = self.enemy;
460                         if(own.classname != "player")
461                         {
462                                 own = self;
463                                 self.enemy = world; // I still hate you all
464                         }
465
466                         Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
467                 }
468         }
469         else
470         {
471                 if (!other.owner)
472                 {
473                         if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
474                         {
475                                 EXACTTRIGGER_TOUCH;
476                                 other.pain_finished = min(other.pain_finished, time + 2);
477                         }
478                         else if (other.classname == "rune")                     // reset runes
479                         {
480                                 EXACTTRIGGER_TOUCH;
481                                 other.nextthink = min(other.nextthink, time + 1);
482                         }
483                 }
484         }
485
486         return;
487 };
488
489 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
490 Any object touching this will be hurt
491 set dmg to damage amount
492 defalt dmg = 5
493 */
494 .entity trigger_hurt_next;
495 entity trigger_hurt_last;
496 entity trigger_hurt_first;
497 void spawnfunc_trigger_hurt()
498 {
499         EXACTTRIGGER_INIT;
500         self.active = ACTIVE_ACTIVE;
501         self.touch = trigger_hurt_touch;
502         self.use = trigger_hurt_use;
503         self.enemy = world; // I hate you all
504         if (!self.dmg)
505                 self.dmg = 1000;
506         if (!self.message)
507                 self.message = "was in the wrong place";
508         if (!self.message2)
509                 self.message2 = "was thrown into a world of hurt by";
510         // self.message = "someone like %s always gets wrongplaced";
511
512         if(!trigger_hurt_first)
513                 trigger_hurt_first = self;
514         if(trigger_hurt_last)
515                 trigger_hurt_last.trigger_hurt_next = self;
516         trigger_hurt_last = self;
517 };
518
519 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
520 {
521         entity th;
522
523         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
524                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
525                         return TRUE;
526
527         return FALSE;
528 }
529
530 //////////////////////////////////////////////////////////////
531 //
532 //
533 //
534 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
535 //
536 //////////////////////////////////////////////////////////////
537
538 .float triggerhealtime;
539 void trigger_heal_touch()
540 {
541         if (self.active != ACTIVE_ACTIVE) 
542                 return;
543         
544         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
545         if (other.iscreature)
546         {
547                 if (other.takedamage)
548                 if (other.triggerhealtime < time)
549                 {
550                         EXACTTRIGGER_TOUCH;
551                         other.triggerhealtime = time + 1;
552                         
553                         if (other.health < self.max_health)
554                         {
555                                 other.health = min(other.health + self.health, self.max_health);
556                                 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
557                                 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
558                         }
559                 }
560         }
561 };
562
563 void spawnfunc_trigger_heal()
564 {
565         self.active = ACTIVE_ACTIVE;
566         
567         EXACTTRIGGER_INIT;
568         self.touch = trigger_heal_touch;
569         if (!self.health)
570                 self.health = 10;
571         if (!self.max_health)
572                 self.max_health = 200; //Max health topoff for field
573         if(self.noise == "")
574                 self.noise = "misc/mediumhealth.wav";
575         precache_sound(self.noise);
576 };
577
578
579 //////////////////////////////////////////////////////////////
580 //
581 //
582 //
583 //End trigger_heal
584 //
585 //////////////////////////////////////////////////////////////
586
587 .entity trigger_gravity_check;
588 void trigger_gravity_remove(entity own)
589 {
590         if(own.trigger_gravity_check.owner == own)
591         {
592                 UpdateCSQCProjectile(own);
593                 own.gravity = own.trigger_gravity_check.gravity;
594                 remove(own.trigger_gravity_check);
595         }
596         else
597                 backtrace("Removing a trigger_gravity_check with no valid owner");
598         own.trigger_gravity_check = world;
599 }
600 void trigger_gravity_check_think()
601 {
602         // This spawns when a player enters the gravity zone and checks if he left.
603         // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
604         // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
605         if(self.count <= 0)
606         {
607                 if(self.owner.trigger_gravity_check == self)
608                         trigger_gravity_remove(self.owner);
609                 else
610                         remove(self);
611                 return;
612         }
613         else
614         {
615                 self.count -= 1;
616                 self.nextthink = time;
617         }
618 };
619
620 void trigger_gravity_use()
621 {
622         self.state = !self.state;
623 };
624
625 void trigger_gravity_touch()
626 {
627         float g;
628
629         if(self.state != TRUE)
630                 return;
631
632         EXACTTRIGGER_TOUCH;
633
634         g = self.gravity;
635
636         if not(self.spawnflags & 1)
637         {
638                 if(other.trigger_gravity_check)
639                 {
640                         if(self == other.trigger_gravity_check.enemy)
641                         {
642                                 // same?
643                                 other.trigger_gravity_check.count = 2; // gravity one more frame...
644                                 return;
645                         }
646
647                         // compare prio
648                         if(self.cnt > other.trigger_gravity_check.enemy.cnt)
649                                 trigger_gravity_remove(other);
650                         else
651                                 return;
652                 }
653                 other.trigger_gravity_check = spawn();
654                 other.trigger_gravity_check.enemy = self;
655                 other.trigger_gravity_check.owner = other;
656                 other.trigger_gravity_check.gravity = other.gravity;
657                 other.trigger_gravity_check.think = trigger_gravity_check_think;
658                 other.trigger_gravity_check.nextthink = time;
659                 other.trigger_gravity_check.count = 2;
660                 if(other.gravity)
661                         g *= other.gravity;
662         }
663
664         if (other.gravity != g)
665         {
666                 other.gravity = g;
667                 if(self.noise != "")
668                         sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
669                 UpdateCSQCProjectile(self.owner);
670         }
671 };
672
673 void spawnfunc_trigger_gravity()
674 {
675         if(self.gravity == 1)
676                 return;
677
678         EXACTTRIGGER_INIT;
679         self.touch = trigger_gravity_touch;
680         if(self.noise != "")
681                 precache_sound(self.noise);
682
683         self.state = TRUE;
684         IFTARGETED
685         {
686                 self.use = trigger_gravity_use;
687                 if(self.spawnflags & 2)
688                         self.state = FALSE;
689         }
690 };
691
692 //=============================================================================
693
694 // TODO add a way to do looped sounds with sound(); then complete this entity
695 .float volume, atten;
696 void target_speaker_use_off();
697 void target_speaker_use_activator()
698 {
699         if(clienttype(activator) != CLIENTTYPE_REAL)
700                 return;
701         string snd;
702         if(substring(self.noise, 0, 1) == "*")
703         {
704                 var .string sample;
705                 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
706                 if(GetPlayerSoundSampleField_notFound)
707                         snd = "misc/null.wav";
708                 else if(activator.sample == "")
709                         snd = "misc/null.wav";
710                 else
711                 {
712                         tokenize_console(activator.sample);
713                         float n;
714                         n = stof(argv(1));
715                         if(n > 0)
716                                 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
717                         else
718                                 snd = strcat(argv(0), ".wav"); // randomization
719                 }
720         }
721         else
722                 snd = self.noise;
723         msg_entity = activator;
724         soundto(MSG_ONE, self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
725 }
726 void target_speaker_use_on()
727 {
728         string snd;
729         if(substring(self.noise, 0, 1) == "*")
730         {
731                 var .string sample;
732                 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
733                 if(GetPlayerSoundSampleField_notFound)
734                         snd = "misc/null.wav";
735                 else if(activator.sample == "")
736                         snd = "misc/null.wav";
737                 else
738                 {
739                         tokenize_console(activator.sample);
740                         float n;
741                         n = stof(argv(1));
742                         if(n > 0)
743                                 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
744                         else
745                                 snd = strcat(argv(0), ".wav"); // randomization
746                 }
747         }
748         else
749                 snd = self.noise;
750         sound(self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
751         if(self.spawnflags & 3)
752                 self.use = target_speaker_use_off;
753 }
754 void target_speaker_use_off()
755 {
756         sound(self, CHAN_TRIGGER, "misc/null.wav", VOL_BASE * self.volume, self.atten);
757         self.use = target_speaker_use_on;
758 }
759 void target_speaker_reset()
760 {
761         if(self.spawnflags & 1) // LOOPED_ON
762         {
763                 if(self.use == target_speaker_use_on)
764                         target_speaker_use_on();
765         }
766         else if(self.spawnflags & 2)
767         {
768                 if(self.use == target_speaker_use_off)
769                         target_speaker_use_off();
770         }
771 }
772
773 void spawnfunc_target_speaker()
774 {
775         // TODO: "*" prefix to sound file name
776         // TODO: wait and random (just, HOW? random is not a field)
777         if(self.noise)
778                 precache_sound (self.noise);
779
780         if(!self.atten && !(self.spawnflags & 4))
781         {
782                 IFTARGETED
783                         self.atten = ATTN_NORM;
784                 else
785                         self.atten = ATTN_STATIC;
786         }
787         else if(self.atten < 0)
788                 self.atten = 0;
789
790         if(!self.volume)
791                 self.volume = 1;
792
793         IFTARGETED
794         {
795                 if(self.spawnflags & 8) // ACTIVATOR
796                         self.use = target_speaker_use_activator;
797                 else if(self.spawnflags & 1) // LOOPED_ON
798                 {
799                         target_speaker_use_on();
800                         self.reset = target_speaker_reset;
801                 }
802                 else if(self.spawnflags & 2) // LOOPED_OFF
803                 {
804                         self.use = target_speaker_use_on;
805                         self.reset = target_speaker_reset;
806                 }
807                 else
808                         self.use = target_speaker_use_on;
809         }
810         else if(self.spawnflags & 1) // LOOPED_ON
811         {
812                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
813                 remove(self);
814         }
815         else if(self.spawnflags & 2) // LOOPED_OFF
816         {
817                 objerror("This sound entity can never be activated");
818         }
819         else
820         {
821                 // Quake/Nexuiz fallback
822                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
823                 remove(self);
824         }
825 };
826
827
828 void spawnfunc_func_stardust() {
829         self.effects = EF_STARDUST;
830 }
831
832 .string bgmscript;
833 .float bgmscriptattack;
834 .float bgmscriptdecay;
835 .float bgmscriptsustain;
836 .float bgmscriptrelease;
837 float pointparticles_SendEntity(entity to, float fl)
838 {
839         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
840
841         // optional features to save space
842         fl = fl & 0x0F;
843         if(self.spawnflags & 2)
844                 fl |= 0x10; // absolute count on toggle-on
845         if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
846                 fl |= 0x20; // 4 bytes - saves CPU
847         if(self.waterlevel || self.count != 1)
848                 fl |= 0x40; // 4 bytes - obscure features almost never used
849         if(self.mins != '0 0 0' || self.maxs != '0 0 0')
850                 fl |= 0x80; // 14 bytes - saves lots of space
851
852         WriteByte(MSG_ENTITY, fl);
853         if(fl & 2)
854         {
855                 if(self.state)
856                         WriteCoord(MSG_ENTITY, self.impulse);
857                 else
858                         WriteCoord(MSG_ENTITY, 0); // off
859         }
860         if(fl & 4)
861         {
862                 WriteCoord(MSG_ENTITY, self.origin_x);
863                 WriteCoord(MSG_ENTITY, self.origin_y);
864                 WriteCoord(MSG_ENTITY, self.origin_z);
865         }
866         if(fl & 1)
867         {
868                 if(self.model != "null")
869                 {
870                         WriteShort(MSG_ENTITY, self.modelindex);
871                         if(fl & 0x80)
872                         {
873                                 WriteCoord(MSG_ENTITY, self.mins_x);
874                                 WriteCoord(MSG_ENTITY, self.mins_y);
875                                 WriteCoord(MSG_ENTITY, self.mins_z);
876                                 WriteCoord(MSG_ENTITY, self.maxs_x);
877                                 WriteCoord(MSG_ENTITY, self.maxs_y);
878                                 WriteCoord(MSG_ENTITY, self.maxs_z);
879                         }
880                 }
881                 else
882                 {
883                         WriteShort(MSG_ENTITY, 0);
884                         if(fl & 0x80)
885                         {
886                                 WriteCoord(MSG_ENTITY, self.maxs_x);
887                                 WriteCoord(MSG_ENTITY, self.maxs_y);
888                                 WriteCoord(MSG_ENTITY, self.maxs_z);
889                         }
890                 }
891                 WriteShort(MSG_ENTITY, self.cnt);
892                 if(fl & 0x20)
893                 {
894                         WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
895                         WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
896                 }
897                 if(fl & 0x40)
898                 {
899                         WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
900                         WriteByte(MSG_ENTITY, self.count * 16.0);
901                 }
902                 WriteString(MSG_ENTITY, self.noise);
903                 if(self.noise != "")
904                 {
905                         WriteByte(MSG_ENTITY, floor(self.atten * 64));
906                         WriteByte(MSG_ENTITY, floor(self.volume * 255));
907                 }
908                 WriteString(MSG_ENTITY, self.bgmscript);
909                 if(self.bgmscript != "")
910                 {
911                         WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
912                         WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
913                         WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
914                         WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
915                 }
916         }
917         return 1;
918 }
919
920 void pointparticles_use()
921 {
922         self.state = !self.state;
923         self.SendFlags |= 2;
924 }
925
926 void pointparticles_think()
927 {
928         if(self.origin != self.oldorigin)
929         {
930                 self.SendFlags |= 4;
931                 self.oldorigin = self.origin;
932         }
933         self.nextthink = time;
934 }
935
936 void pointparticles_reset()
937 {
938         if(self.spawnflags & 1)
939                 self.state = 1;
940         else
941                 self.state = 0;
942 }
943
944 void spawnfunc_func_pointparticles()
945 {
946         if(self.model != "")
947                 setmodel(self, self.model);
948         if(self.noise != "")
949                 precache_sound (self.noise);
950         
951         if(!self.bgmscriptsustain)
952                 self.bgmscriptsustain = 1;
953         else if(self.bgmscriptsustain < 0)
954                 self.bgmscriptsustain = 0;
955
956         if(!self.atten)
957                 self.atten = ATTN_NORM;
958         else if(self.atten < 0)
959                 self.atten = 0;
960         if(!self.volume)
961                 self.volume = 1;
962         if(!self.count)
963                 self.count = 1;
964         if(!self.impulse)
965                 self.impulse = 1;
966
967         if(!self.modelindex)
968         {
969                 setorigin(self, self.origin + self.mins);
970                 setsize(self, '0 0 0', self.maxs - self.mins);
971         }
972         if(!self.cnt)
973                 self.cnt = particleeffectnum(self.mdl);
974
975         Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
976
977         IFTARGETED
978         {
979                 self.use = pointparticles_use;
980                 self.reset = pointparticles_reset;
981                 self.reset();
982         }
983         else
984                 self.state = 1;
985         self.think = pointparticles_think;
986         self.nextthink = time;
987 }
988
989 void spawnfunc_func_sparks()
990 {
991         // self.cnt is the amount of sparks that one burst will spawn
992         if(self.cnt < 1) {
993                 self.cnt = 25.0; // nice default value
994         }
995
996         // self.wait is the probability that a sparkthink will spawn a spark shower
997         // range: 0 - 1, but 0 makes little sense, so...
998         if(self.wait < 0.05) {
999                 self.wait = 0.25; // nice default value
1000         }
1001
1002         self.count = self.cnt;
1003         self.mins = '0 0 0';
1004         self.maxs = '0 0 0';
1005         self.velocity = '0 0 -1';
1006         self.mdl = "TE_SPARK";
1007         self.impulse = 10 * self.wait; // by default 2.5/sec
1008         self.wait = 0;
1009         self.cnt = 0; // use mdl
1010
1011         spawnfunc_func_pointparticles();
1012 }
1013
1014 float rainsnow_SendEntity(entity to, float sf)
1015 {
1016         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1017         WriteByte(MSG_ENTITY, self.state);
1018         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1019         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1020         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1021         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1022         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1023         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1024         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1025         WriteShort(MSG_ENTITY, self.count);
1026         WriteByte(MSG_ENTITY, self.cnt);
1027         return 1;
1028 };
1029
1030 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1031 This is an invisible area like a trigger, which rain falls inside of.
1032
1033 Keys:
1034 "velocity"
1035  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1036 "cnt"
1037  sets color of rain (default 12 - white)
1038 "count"
1039  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1040 */
1041 void spawnfunc_func_rain()
1042 {
1043         self.dest = self.velocity;
1044         self.velocity = '0 0 0';
1045         if (!self.dest)
1046                 self.dest = '0 0 -700';
1047         self.angles = '0 0 0';
1048         self.movetype = MOVETYPE_NONE;
1049         self.solid = SOLID_NOT;
1050         SetBrushEntityModel();
1051         if (!self.cnt)
1052                 self.cnt = 12;
1053         if (!self.count)
1054                 self.count = 2000;
1055         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1056         if (self.count < 1)
1057                 self.count = 1;
1058         if(self.count > 65535)
1059                 self.count = 65535;
1060
1061         self.state = 1; // 1 is rain, 0 is snow
1062         self.Version = 1;
1063
1064         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1065 };
1066
1067
1068 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1069 This is an invisible area like a trigger, which snow falls inside of.
1070
1071 Keys:
1072 "velocity"
1073  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1074 "cnt"
1075  sets color of rain (default 12 - white)
1076 "count"
1077  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1078 */
1079 void spawnfunc_func_snow()
1080 {
1081         self.dest = self.velocity;
1082         self.velocity = '0 0 0';
1083         if (!self.dest)
1084                 self.dest = '0 0 -300';
1085         self.angles = '0 0 0';
1086         self.movetype = MOVETYPE_NONE;
1087         self.solid = SOLID_NOT;
1088         SetBrushEntityModel();
1089         if (!self.cnt)
1090                 self.cnt = 12;
1091         if (!self.count)
1092                 self.count = 2000;
1093         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1094         if (self.count < 1)
1095                 self.count = 1;
1096         if(self.count > 65535)
1097                 self.count = 65535;
1098
1099         self.state = 0; // 1 is rain, 0 is snow
1100         self.Version = 1;
1101
1102         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1103 };
1104
1105
1106 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1107
1108 .float modelscale;
1109 void misc_laser_aim()
1110 {
1111         vector a;
1112         if(self.enemy)
1113         {
1114                 if(self.spawnflags & 2)
1115                 {
1116                         if(self.enemy.origin != self.mangle)
1117                         {
1118                                 self.mangle = self.enemy.origin;
1119                                 self.SendFlags |= 2;
1120                         }
1121                 }
1122                 else
1123                 {
1124                         a = vectoangles(self.enemy.origin - self.origin);
1125                         a_x = -a_x;
1126                         if(a != self.mangle)
1127                         {
1128                                 self.mangle = a;
1129                                 self.SendFlags |= 2;
1130                         }
1131                 }
1132         }
1133         else
1134         {
1135                 if(self.angles != self.mangle)
1136                 {
1137                         self.mangle = self.angles;
1138                         self.SendFlags |= 2;
1139                 }
1140         }
1141         if(self.origin != self.oldorigin)
1142         {
1143                 self.SendFlags |= 1;
1144                 self.oldorigin = self.origin;
1145         }
1146 }
1147
1148 void misc_laser_init()
1149 {
1150         if(self.target != "")
1151                 self.enemy = find(world, targetname, self.target);
1152 }
1153
1154 .entity pusher;
1155 void misc_laser_think()
1156 {
1157         vector o;
1158         entity oldself;
1159         entity hitent;
1160         vector hitloc;
1161
1162         self.nextthink = time;
1163
1164         if(!self.state)
1165                 return;
1166
1167         misc_laser_aim();
1168
1169         if(self.enemy)
1170         {
1171                 o = self.enemy.origin;
1172                 if not(self.spawnflags & 2)
1173                         o = self.origin + normalize(o - self.origin) * 32768;
1174         }
1175         else
1176         {
1177                 makevectors(self.mangle);
1178                 o = self.origin + v_forward * 32768;
1179         }
1180
1181         if(self.dmg || self.enemy.target != "")
1182         {
1183                 traceline(self.origin, o, MOVE_NORMAL, self);
1184         }
1185         hitent = trace_ent;
1186         hitloc = trace_endpos;
1187
1188         if(self.enemy.target != "") // DETECTOR laser
1189         {
1190                 if(trace_ent.iscreature)
1191                 {
1192                         self.pusher = hitent;
1193                         if(!self.count)
1194                         {
1195                                 self.count = 1;
1196
1197                                 oldself = self;
1198                                 self = self.enemy;
1199                                 activator = self.pusher;
1200                                 SUB_UseTargets();
1201                                 self = oldself;
1202                         }
1203                 }
1204                 else
1205                 {
1206                         if(self.count)
1207                         {
1208                                 self.count = 0;
1209
1210                                 oldself = self;
1211                                 self = self.enemy;
1212                                 activator = self.pusher;
1213                                 SUB_UseTargets();
1214                                 self = oldself;
1215                         }
1216                 }
1217         }
1218
1219         if(self.dmg)
1220         {
1221                 if(self.team)
1222                         if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1223                                 return;
1224                 if(hitent.takedamage)
1225                         Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1226         }
1227 }
1228
1229 float laser_SendEntity(entity to, float fl)
1230 {
1231         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1232         fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1233         if(self.spawnflags & 2)
1234                 fl |= 0x80;
1235         if(self.alpha)
1236                 fl |= 0x40;
1237         if(self.scale != 1 || self.modelscale != 1)
1238                 fl |= 0x20;
1239         if(self.spawnflags & 4)
1240                 fl |= 0x10;
1241         WriteByte(MSG_ENTITY, fl);
1242         if(fl & 1)
1243         {
1244                 WriteCoord(MSG_ENTITY, self.origin_x);
1245                 WriteCoord(MSG_ENTITY, self.origin_y);
1246                 WriteCoord(MSG_ENTITY, self.origin_z);
1247         }
1248         if(fl & 8)
1249         {
1250                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1251                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1252                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1253                 if(fl & 0x40)
1254                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
1255                 if(fl & 0x20)
1256                 {
1257                         WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1258                         WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1259                 }
1260                 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1261                         WriteShort(MSG_ENTITY, self.cnt + 1);
1262         }
1263         if(fl & 2)
1264         {
1265                 if(fl & 0x80)
1266                 {
1267                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1268                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1269                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1270                 }
1271                 else
1272                 {
1273                         WriteAngle(MSG_ENTITY, self.mangle_x);
1274                         WriteAngle(MSG_ENTITY, self.mangle_y);
1275                 }
1276         }
1277         if(fl & 4)
1278                 WriteByte(MSG_ENTITY, self.state);
1279         return 1;
1280 }
1281
1282 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1283 Any object touching the beam will be hurt
1284 Keys:
1285 "target"
1286  spawnfunc_target_position where the laser ends
1287 "mdl"
1288  name of beam end effect to use
1289 "colormod"
1290  color of the beam (default: red)
1291 "dmg"
1292  damage per second (-1 for a laser that kills immediately)
1293 */
1294 void laser_use()
1295 {
1296         self.state = !self.state;
1297         self.SendFlags |= 4;
1298         misc_laser_aim();
1299 }
1300
1301 void laser_reset()
1302 {
1303         if(self.spawnflags & 1)
1304                 self.state = 1;
1305         else
1306                 self.state = 0;
1307 }
1308
1309 void spawnfunc_misc_laser()
1310 {
1311         if(self.mdl)
1312         {
1313                 if(self.mdl == "none")
1314                         self.cnt = -1;
1315                 else
1316                 {
1317                         self.cnt = particleeffectnum(self.mdl);
1318                         if(self.cnt < 0)
1319                                 if(self.dmg)
1320                                         self.cnt = particleeffectnum("laser_deadly");
1321                 }
1322         }
1323         else if(!self.cnt)
1324         {
1325                 if(self.dmg)
1326                         self.cnt = particleeffectnum("laser_deadly");
1327                 else
1328                         self.cnt = -1;
1329         }
1330         if(self.cnt < 0)
1331                 self.cnt = -1;
1332
1333         if(self.colormod == '0 0 0')
1334                 if(!self.alpha)
1335                         self.colormod = '1 0 0';
1336         if(!self.message)
1337                 self.message = "saw the light";
1338         if (!self.message2)
1339                 self.message2 = "was pushed into a laser by";
1340         if(!self.scale)
1341                 self.scale = 1;
1342         if(!self.modelscale)
1343                 self.modelscale = 1;
1344         else if(self.modelscale < 0)
1345                 self.modelscale = 0;
1346         self.think = misc_laser_think;
1347         self.nextthink = time;
1348         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1349
1350         self.mangle = self.angles;
1351
1352         Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1353
1354         IFTARGETED
1355         {
1356                 self.reset = laser_reset;
1357                 laser_reset();
1358                 self.use = laser_use;
1359         }
1360         else
1361                 self.state = 1;
1362 }
1363
1364 // tZorks trigger impulse / gravity
1365 .float radius;
1366 .float falloff;
1367 .float strength;
1368 .float lastpushtime;
1369
1370 // targeted (directional) mode
1371 void trigger_impulse_touch1()
1372 {
1373         entity targ;
1374     float pushdeltatime;
1375     float str;
1376
1377         if (self.active != ACTIVE_ACTIVE) 
1378                 return;
1379
1380         // FIXME: Better checking for what to push and not.
1381         if not(other.iscreature)
1382         if (other.classname != "corpse")
1383         if (other.classname != "body")
1384         if (other.classname != "gib")
1385         if (other.classname != "missile")
1386         if (other.classname != "rocket")
1387         if (other.classname != "casing")
1388         if (other.classname != "grenade")
1389         if (other.classname != "plasma")
1390         if (other.classname != "plasma_prim")
1391         if (other.classname != "plasma_chain")
1392         if (other.classname != "droppedweapon")
1393         if (other.classname != "nexball_basketball")
1394         if (other.classname != "nexball_football")
1395                 return;
1396
1397         if (other.deadflag && other.iscreature)
1398                 return;
1399
1400         EXACTTRIGGER_TOUCH;
1401
1402     targ = find(world, targetname, self.target);
1403     if(!targ)
1404     {
1405         objerror("trigger_force without a (valid) .target!\n");
1406         remove(self);
1407         return;
1408     }
1409
1410     if(self.falloff == 1)
1411         str = (str / self.radius) * self.strength;
1412     else if(self.falloff == 2)
1413         str = (1 - (str / self.radius)) * self.strength;
1414     else
1415         str = self.strength;
1416
1417     pushdeltatime = time - other.lastpushtime;
1418     if (pushdeltatime > 0.15) pushdeltatime = 0;
1419     other.lastpushtime = time;
1420     if(!pushdeltatime) return;
1421
1422     other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1423     other.flags &~= FL_ONGROUND;
1424     UpdateCSQCProjectile(other);
1425 }
1426
1427 // Directionless (accelerator/decelerator) mode
1428 void trigger_impulse_touch2()
1429 {
1430     float pushdeltatime;
1431
1432         if (self.active != ACTIVE_ACTIVE) 
1433                 return;
1434
1435         // FIXME: Better checking for what to push and not.
1436         if not(other.iscreature)
1437         if (other.classname != "corpse")
1438         if (other.classname != "body")
1439         if (other.classname != "gib")
1440         if (other.classname != "missile")
1441         if (other.classname != "rocket")
1442         if (other.classname != "casing")
1443         if (other.classname != "grenade")
1444         if (other.classname != "plasma")
1445         if (other.classname != "plasma_prim")
1446         if (other.classname != "plasma_chain")
1447         if (other.classname != "droppedweapon")
1448         if (other.classname != "nexball_basketball")
1449         if (other.classname != "nexball_football")
1450                 return;
1451
1452         if (other.deadflag && other.iscreature)
1453                 return;
1454
1455         EXACTTRIGGER_TOUCH;
1456
1457     pushdeltatime = time - other.lastpushtime;
1458     if (pushdeltatime > 0.15) pushdeltatime = 0;
1459     other.lastpushtime = time;
1460     if(!pushdeltatime) return;
1461
1462     // div0: ticrate independent, 1 = identity (not 20)
1463     other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1464     UpdateCSQCProjectile(other);
1465 }
1466
1467 // Spherical (gravity/repulsor) mode
1468 void trigger_impulse_touch3()
1469 {
1470     float pushdeltatime;
1471     float str;
1472
1473         if (self.active != ACTIVE_ACTIVE) 
1474                 return;
1475
1476         // FIXME: Better checking for what to push and not.
1477         if not(other.iscreature)
1478         if (other.classname != "corpse")
1479         if (other.classname != "body")
1480         if (other.classname != "gib")
1481         if (other.classname != "missile")
1482         if (other.classname != "rocket")
1483         if (other.classname != "casing")
1484         if (other.classname != "grenade")
1485         if (other.classname != "plasma")
1486         if (other.classname != "plasma_prim")
1487         if (other.classname != "plasma_chain")
1488         if (other.classname != "droppedweapon")
1489         if (other.classname != "nexball_basketball")
1490         if (other.classname != "nexball_football")
1491                 return;
1492
1493         if (other.deadflag && other.iscreature)
1494                 return;
1495
1496         EXACTTRIGGER_TOUCH;
1497
1498     pushdeltatime = time - other.lastpushtime;
1499     if (pushdeltatime > 0.15) pushdeltatime = 0;
1500     other.lastpushtime = time;
1501     if(!pushdeltatime) return;
1502
1503     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1504
1505         str = min(self.radius, vlen(self.origin - other.origin));
1506
1507     if(self.falloff == 1)
1508         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1509     else if(self.falloff == 2)
1510         str = (str / self.radius) * self.strength; // 0 in the inside
1511     else
1512         str = self.strength;
1513
1514     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1515     UpdateCSQCProjectile(other);
1516 }
1517
1518 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1519 -------- KEYS --------
1520 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1521          If not, this trigger acts like a damper/accelerator field.
1522
1523 strength : This is how mutch force to add in the direction of .target each second
1524            when .target is set. If not, this is hoe mutch to slow down/accelerate
1525            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1526
1527 radius   : If set, act as a spherical device rather then a liniar one.
1528
1529 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1530
1531 -------- NOTES --------
1532 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1533 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1534 */
1535
1536 void spawnfunc_trigger_impulse()
1537 {
1538         self.active = ACTIVE_ACTIVE;
1539
1540         EXACTTRIGGER_INIT;
1541     if(self.radius)
1542     {
1543         if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1544         setorigin(self, self.origin);
1545         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1546         self.touch = trigger_impulse_touch3;
1547     }
1548     else
1549     {
1550         if(self.target)
1551         {
1552             if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1553             self.touch = trigger_impulse_touch1;
1554         }
1555         else
1556         {
1557             if(!self.strength) self.strength = 0.9;
1558                         self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1559             self.touch = trigger_impulse_touch2;
1560         }
1561     }
1562 }
1563
1564 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1565 "Flip-flop" trigger gate... lets only every second trigger event through
1566 */
1567 void flipflop_use()
1568 {
1569         self.state = !self.state;
1570         if(self.state)
1571                 SUB_UseTargets();
1572 }
1573
1574 void spawnfunc_trigger_flipflop()
1575 {
1576         if(self.spawnflags & 1)
1577                 self.state = 1;
1578         self.use = flipflop_use;
1579         self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1580 }
1581
1582 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1583 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1584 */
1585 void monoflop_use()
1586 {
1587         self.nextthink = time + self.wait;
1588         self.enemy = activator;
1589         if(self.state)
1590                 return;
1591         self.state = 1;
1592         SUB_UseTargets();
1593 }
1594 void monoflop_fixed_use()
1595 {
1596         if(self.state)
1597                 return;
1598         self.nextthink = time + self.wait;
1599         self.state = 1;
1600         self.enemy = activator;
1601         SUB_UseTargets();
1602 }
1603
1604 void monoflop_think()
1605 {
1606         self.state = 0;
1607         activator = self.enemy;
1608         SUB_UseTargets();
1609 }
1610
1611 void monoflop_reset()
1612 {
1613         self.state = 0;
1614         self.nextthink = 0;
1615 }
1616
1617 void spawnfunc_trigger_monoflop()
1618 {
1619         if(!self.wait)
1620                 self.wait = 1;
1621         if(self.spawnflags & 1)
1622                 self.use = monoflop_fixed_use;
1623         else
1624                 self.use = monoflop_use;
1625         self.think = monoflop_think;
1626         self.state = 0;
1627         self.reset = monoflop_reset;
1628 }
1629
1630 void multivibrator_send()
1631 {
1632         float newstate;
1633         float cyclestart;
1634
1635         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1636
1637         newstate = (time < cyclestart + self.wait);
1638
1639         activator = self;
1640         if(self.state != newstate)
1641                 SUB_UseTargets();
1642         self.state = newstate;
1643
1644         if(self.state)
1645                 self.nextthink = cyclestart + self.wait + 0.01;
1646         else
1647                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1648 }
1649
1650 void multivibrator_toggle()
1651 {
1652         if(self.nextthink == 0)
1653         {
1654                 multivibrator_send();
1655         }
1656         else
1657         {
1658                 if(self.state)
1659                 {
1660                         SUB_UseTargets();
1661                         self.state = 0;
1662                 }
1663                 self.nextthink = 0;
1664         }
1665 }
1666
1667 void multivibrator_reset()
1668 {
1669         if(!(self.spawnflags & 1))
1670                 self.nextthink = 0; // wait for a trigger event
1671         else
1672                 self.nextthink = max(1, time);
1673 }
1674
1675 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1676 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1677 -------- KEYS --------
1678 target: trigger all entities with this targetname when it goes off
1679 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1680 phase: offset of the timing
1681 wait: "on" cycle time (default: 1)
1682 respawntime: "off" cycle time (default: same as wait)
1683 -------- SPAWNFLAGS --------
1684 START_ON: assume it is already turned on (when targeted)
1685 */
1686 void spawnfunc_trigger_multivibrator()
1687 {
1688         if(!self.wait)
1689                 self.wait = 1;
1690         if(!self.respawntime)
1691                 self.respawntime = self.wait;
1692
1693         self.state = 0;
1694         self.use = multivibrator_toggle;
1695         self.think = multivibrator_send;
1696         self.nextthink = time;
1697
1698         IFTARGETED
1699                 multivibrator_reset();
1700 }
1701
1702
1703 void follow_init()
1704 {
1705         entity src, dst;
1706         src = world;
1707         dst = world;
1708         if(self.killtarget != "")
1709                 src = find(world, targetname, self.killtarget);
1710         if(self.target != "")
1711                 dst = find(world, targetname, self.target);
1712
1713         if(!src && !dst)
1714         {
1715                 objerror("follow: could not find target/killtarget");
1716                 return;
1717         }
1718
1719         if(self.jointtype)
1720         {
1721                 // already done :P entity must stay
1722                 self.aiment = src;
1723                 self.enemy = dst;
1724         }
1725         else if(!src || !dst)
1726         {
1727                 objerror("follow: could not find target/killtarget");
1728                 return;
1729         }
1730         else if(self.spawnflags & 1)
1731         {
1732                 // attach
1733                 if(self.spawnflags & 2)
1734                 {
1735                         setattachment(dst, src, self.message);
1736                 }
1737                 else
1738                 {
1739                         attach_sameorigin(dst, src, self.message);
1740                 }
1741
1742                 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1743                 remove(self);
1744         }
1745         else
1746         {
1747                 if(self.spawnflags & 2)
1748                 {
1749                         dst.movetype = MOVETYPE_FOLLOW;
1750                         dst.aiment = src;
1751                         // dst.punchangle = '0 0 0'; // keep unchanged
1752                         dst.view_ofs = dst.origin;
1753                         dst.v_angle = dst.angles;
1754                 }
1755                 else
1756                 {
1757                         follow_sameorigin(dst, src);
1758                 }
1759
1760                 remove(self);
1761         }
1762 }
1763
1764 void spawnfunc_misc_follow()
1765 {
1766         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1767 }
1768
1769
1770
1771 void gamestart_use() {
1772         activator = self;
1773         SUB_UseTargets();
1774         remove(self);
1775 }
1776
1777 void spawnfunc_trigger_gamestart() {
1778         self.use = gamestart_use;
1779         self.reset2 = spawnfunc_trigger_gamestart;
1780
1781         if(self.wait)
1782         {
1783                 self.think = self.use;
1784                 self.nextthink = game_starttime + self.wait;
1785         }
1786         else
1787                 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1788 }
1789
1790
1791
1792
1793 .entity voicescript; // attached voice script
1794 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1795 .float voicescript_nextthink; // time to play next voice
1796 .float voicescript_voiceend; // time when this voice ends
1797
1798 void target_voicescript_clear(entity pl)
1799 {
1800         pl.voicescript = world;
1801 }
1802
1803 void target_voicescript_use()
1804 {
1805         if(activator.voicescript != self)
1806         {
1807                 activator.voicescript = self;
1808                 activator.voicescript_index = 0;
1809                 activator.voicescript_nextthink = time + self.delay;
1810         }
1811 }
1812
1813 void target_voicescript_next(entity pl)
1814 {
1815         entity vs;
1816         float i, n, dt;
1817
1818         vs = pl.voicescript;
1819         if(!vs)
1820                 return;
1821         if(vs.message == "")
1822                 return;
1823         if(pl.classname != "player")
1824                 return;
1825         if(gameover)
1826                 return;
1827
1828         if(time >= pl.voicescript_voiceend)
1829         {
1830                 if(time >= pl.voicescript_nextthink)
1831                 {
1832                         // get the next voice...
1833                         n = tokenize_console(vs.message);
1834
1835                         if(pl.voicescript_index < vs.cnt)
1836                                 i = pl.voicescript_index * 2;
1837                         else if(n > vs.cnt * 2)
1838                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1839                         else
1840                                 i = -1;
1841
1842                         if(i >= 0)
1843                         {
1844                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1845                                 dt = stof(argv(i + 1));
1846                                 if(dt >= 0)
1847                                 {
1848                                         pl.voicescript_voiceend = time + dt;
1849                                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1850                                 }
1851                                 else
1852                                 {
1853                                         pl.voicescript_voiceend = time - dt;
1854                                         pl.voicescript_nextthink = pl.voicescript_voiceend;
1855                                 }
1856
1857                                 pl.voicescript_index += 1;
1858                         }
1859                         else
1860                         {
1861                                 pl.voicescript = world; // stop trying then
1862                         }
1863                 }
1864         }
1865 }
1866
1867 void spawnfunc_target_voicescript()
1868 {
1869         // netname: directory of the sound files
1870         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1871         //          foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1872         //          Here, a - in front of the duration means that no delay is to be
1873         //          added after this message
1874         // wait: average time between messages
1875         // delay: initial delay before the first message
1876         
1877         float i, n;
1878         self.use = target_voicescript_use;
1879
1880         n = tokenize_console(self.message);
1881         self.cnt = n / 2;
1882         for(i = 0; i+1 < n; i += 2)
1883         {
1884                 if(argv(i) == "*")
1885                 {
1886                         self.cnt = i / 2;
1887                         ++i;
1888                 }
1889                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1890         }
1891 }
1892
1893
1894
1895 void trigger_relay_teamcheck_use()
1896 {
1897         if(activator.team)
1898         {
1899                 if(self.spawnflags & 2)
1900                 {
1901                         if(activator.team != self.team)
1902                                 SUB_UseTargets();
1903                 }
1904                 else
1905                 {
1906                         if(activator.team == self.team)
1907                                 SUB_UseTargets();
1908                 }
1909         }
1910         else
1911         {
1912                 if(self.spawnflags & 1)
1913                         SUB_UseTargets();
1914         }
1915 }
1916
1917 void trigger_relay_teamcheck_reset()
1918 {
1919         self.team = self.team_saved;
1920 }
1921
1922 void spawnfunc_trigger_relay_teamcheck()
1923 {
1924         self.team_saved = self.team;
1925         self.use = trigger_relay_teamcheck_use;
1926         self.reset = trigger_relay_teamcheck_reset;
1927 }
1928
1929
1930
1931 void trigger_disablerelay_use()
1932 {
1933         entity e;
1934
1935         float a, b;
1936         a = b = 0;
1937
1938         for(e = world; (e = find(e, targetname, self.target)); )
1939         {
1940                 if(e.use == SUB_UseTargets)
1941                 {
1942                         e.use = SUB_DontUseTargets;
1943                         ++a;
1944                 }
1945                 else if(e.use == SUB_DontUseTargets)
1946                 {
1947                         e.use = SUB_UseTargets;
1948                         ++b;
1949                 }
1950         }
1951
1952         if((!a) == (!b))
1953                 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1954 }
1955
1956 void spawnfunc_trigger_disablerelay()
1957 {
1958         self.use = trigger_disablerelay_use;
1959 }
1960
1961 float magicear_matched;
1962 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1963 {
1964         float domatch, dotrigger, matchstart, l;
1965         string s, msg;
1966         entity oldself;
1967
1968         magicear_matched = FALSE;
1969
1970         dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1971         domatch = ((ear.spawnflags & 32) || dotrigger);
1972         if not(domatch)
1973                 return msgin;
1974
1975         if(privatesay)
1976         {
1977                 if(ear.spawnflags & 4)
1978                         return msgin;
1979         }
1980         else
1981         {
1982                 if(!teamsay)
1983                         if(ear.spawnflags & 1)
1984                                 return msgin;
1985                 if(teamsay > 0)
1986                         if(ear.spawnflags & 2)
1987                                 return msgin;
1988                 if(teamsay < 0)
1989                         if(ear.spawnflags & 8)
1990                                 return msgin;
1991         }
1992         
1993         matchstart = -1;
1994         l = strlen(ear.message);
1995
1996         if(self.spawnflags & 128)
1997                 msg = msgin;
1998         else
1999                 msg = strdecolorize(msgin);
2000
2001         if(substring(ear.message, 0, 1) == "*")
2002         {
2003                 if(substring(ear.message, -1, 1) == "*")
2004                 {
2005                         // two wildcards
2006                         // as we need multi-replacement here...
2007                         s = substring(ear.message, 1, -2);
2008                         l -= 2;
2009                         if(strstrofs(msg, s, 0) >= 0)
2010                                 matchstart = -2; // we use strreplace on s
2011                 }
2012                 else
2013                 {
2014                         // match at start
2015                         s = substring(ear.message, 1, -1);
2016                         l -= 1;
2017                         if(substring(msg, -l, l) == s)
2018                                 matchstart = strlen(msg) - l;
2019                 }
2020         }
2021         else
2022         {
2023                 if(substring(ear.message, -1, 1) == "*")
2024                 {
2025                         // match at end
2026                         s = substring(ear.message, 0, -2);
2027                         l -= 1;
2028                         if(substring(msg, 0, l) == s)
2029                                 matchstart = 0;
2030                 }
2031                 else
2032                 {
2033                         // full match
2034                         s = ear.message;
2035                         if(msg == ear.message)
2036                                 matchstart = 0;
2037                 }
2038         }
2039
2040         if(matchstart == -1) // no match
2041                 return msgin;
2042
2043         magicear_matched = TRUE;
2044
2045         if(dotrigger)
2046         {
2047                 oldself = activator = self;
2048                 self = ear;
2049                 SUB_UseTargets();
2050                 self = oldself;
2051         }
2052
2053         if(ear.spawnflags & 16)
2054         {
2055                 return ear.netname;
2056         }
2057         else if(ear.netname != "")
2058         {
2059                 if(matchstart < 0)
2060                         return strreplace(s, ear.netname, msg);
2061                 else
2062                         return strcat(
2063                                 substring(msg, 0, matchstart),
2064                                 ear.netname,
2065                                 substring(msg, matchstart + l, -1)
2066                         );
2067         }
2068         else
2069                 return msgin;
2070 }
2071
2072 entity magicears;
2073 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2074 {
2075         entity ear;
2076         string msgout;
2077         for(ear = magicears; ear; ear = ear.enemy)
2078         {
2079                 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2080                 if not(ear.spawnflags & 64)
2081                         if(magicear_matched)
2082                                 return msgout;
2083                 msgin = msgout;
2084         }
2085         return msgin;
2086 }
2087
2088 void spawnfunc_trigger_magicear()
2089 {
2090         self.enemy = magicears;
2091         magicears = self;
2092
2093         // actually handled in "say" processing
2094         // spawnflags:
2095         //   1 = ignore say
2096         //   2 = ignore teamsay
2097         //   4 = ignore tell
2098         //   8 = ignore tell to unknown player
2099         //   16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2100         //   32 = perform the replacement even if outside the radius or dead
2101         //   64 = continue replacing/triggering even if this one matched
2102         // message: either
2103         //   *pattern*
2104         // or
2105         //   *pattern
2106         // or
2107         //   pattern*
2108         // or
2109         //   pattern
2110         // netname:
2111         //   if set, replacement for the matched text
2112         // radius:
2113         //   "hearing distance"
2114         // target:
2115         //   what to trigger
2116 }
2117
2118 void relay_activators_use()
2119 {
2120         entity trg, os;
2121         
2122         os = self;
2123         
2124         for(trg = world; (trg = find(trg, targetname, os.target)); )
2125         {
2126                 self = trg;
2127                 if (trg.setactive)
2128                         trg.setactive(os.cnt);
2129                 else
2130                 {
2131                         //bprint("Not using setactive\n");
2132                         if(os.cnt == ACTIVE_TOGGLE)
2133                                 if(trg.active == ACTIVE_ACTIVE)
2134                                         trg.active = ACTIVE_NOT;
2135                                 else    
2136                                         trg.active = ACTIVE_ACTIVE;
2137                         else
2138                                 trg.active = os.cnt;
2139                 }               
2140         }
2141         self = os;
2142 }
2143
2144 void spawnfunc_relay_activate()
2145 {
2146         self.cnt = ACTIVE_ACTIVE;
2147         self.use = relay_activators_use;
2148 }
2149
2150 void spawnfunc_relay_deactivate()
2151 {
2152         self.cnt = ACTIVE_NOT;
2153         self.use = relay_activators_use;        
2154 }
2155
2156 void spawnfunc_relay_activatetoggle()
2157 {
2158         self.cnt = ACTIVE_TOGGLE;
2159         self.use = relay_activators_use;        
2160 }
2161
2162 .string chmap, gametype;
2163 void spawnfunc_target_changelevel_use()
2164 {
2165         if(self.gametype != "")
2166                 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2167
2168         if (self.chmap == "")
2169                 localcmd("endmatch\n");
2170         else
2171                 localcmd(strcat("changelevel ", self.chmap, "\n"));
2172 };
2173
2174 void spawnfunc_target_changelevel()
2175 {
2176         self.use = spawnfunc_target_changelevel_use;
2177 };