]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_triggers.qc
Merge branch 'master' into terencehill/arena_and_ca_fixes
[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         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, CH_TRIGGER, 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, CH_TRIGGER, 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, CH_TRIGGER, 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, CH_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, CH_TRIGGER_SINGLE, 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, CH_TRIGGER_SINGLE, "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         if (!isPushable(other))
1381                 return;
1382
1383         EXACTTRIGGER_TOUCH;
1384
1385     targ = find(world, targetname, self.target);
1386     if(!targ)
1387     {
1388         objerror("trigger_force without a (valid) .target!\n");
1389         remove(self);
1390         return;
1391     }
1392
1393     if(self.falloff == 1)
1394         str = (str / self.radius) * self.strength;
1395     else if(self.falloff == 2)
1396         str = (1 - (str / self.radius)) * self.strength;
1397     else
1398         str = self.strength;
1399
1400     pushdeltatime = time - other.lastpushtime;
1401     if (pushdeltatime > 0.15) pushdeltatime = 0;
1402     other.lastpushtime = time;
1403     if(!pushdeltatime) return;
1404
1405     other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1406     other.flags &~= FL_ONGROUND;
1407     UpdateCSQCProjectile(other);
1408 }
1409
1410 // Directionless (accelerator/decelerator) mode
1411 void trigger_impulse_touch2()
1412 {
1413     float pushdeltatime;
1414
1415         if (self.active != ACTIVE_ACTIVE) 
1416                 return;
1417
1418         if (!isPushable(other))
1419                 return;
1420
1421         EXACTTRIGGER_TOUCH;
1422
1423     pushdeltatime = time - other.lastpushtime;
1424     if (pushdeltatime > 0.15) pushdeltatime = 0;
1425     other.lastpushtime = time;
1426     if(!pushdeltatime) return;
1427
1428     // div0: ticrate independent, 1 = identity (not 20)
1429     other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1430     UpdateCSQCProjectile(other);
1431 }
1432
1433 // Spherical (gravity/repulsor) mode
1434 void trigger_impulse_touch3()
1435 {
1436     float pushdeltatime;
1437     float str;
1438
1439         if (self.active != ACTIVE_ACTIVE) 
1440                 return;
1441
1442         if (!isPushable(other))
1443                 return;
1444
1445         EXACTTRIGGER_TOUCH;
1446
1447     pushdeltatime = time - other.lastpushtime;
1448     if (pushdeltatime > 0.15) pushdeltatime = 0;
1449     other.lastpushtime = time;
1450     if(!pushdeltatime) return;
1451
1452     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1453
1454         str = min(self.radius, vlen(self.origin - other.origin));
1455
1456     if(self.falloff == 1)
1457         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1458     else if(self.falloff == 2)
1459         str = (str / self.radius) * self.strength; // 0 in the inside
1460     else
1461         str = self.strength;
1462
1463     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1464     UpdateCSQCProjectile(other);
1465 }
1466
1467 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1468 -------- KEYS --------
1469 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1470          If not, this trigger acts like a damper/accelerator field.
1471
1472 strength : This is how mutch force to add in the direction of .target each second
1473            when .target is set. If not, this is hoe mutch to slow down/accelerate
1474            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1475
1476 radius   : If set, act as a spherical device rather then a liniar one.
1477
1478 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1479
1480 -------- NOTES --------
1481 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1482 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1483 */
1484
1485 void spawnfunc_trigger_impulse()
1486 {
1487         self.active = ACTIVE_ACTIVE;
1488
1489         EXACTTRIGGER_INIT;
1490     if(self.radius)
1491     {
1492         if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1493         setorigin(self, self.origin);
1494         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1495         self.touch = trigger_impulse_touch3;
1496     }
1497     else
1498     {
1499         if(self.target)
1500         {
1501             if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1502             self.touch = trigger_impulse_touch1;
1503         }
1504         else
1505         {
1506             if(!self.strength) self.strength = 0.9;
1507                         self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1508             self.touch = trigger_impulse_touch2;
1509         }
1510     }
1511 }
1512
1513 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1514 "Flip-flop" trigger gate... lets only every second trigger event through
1515 */
1516 void flipflop_use()
1517 {
1518         self.state = !self.state;
1519         if(self.state)
1520                 SUB_UseTargets();
1521 }
1522
1523 void spawnfunc_trigger_flipflop()
1524 {
1525         if(self.spawnflags & 1)
1526                 self.state = 1;
1527         self.use = flipflop_use;
1528         self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1529 }
1530
1531 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1532 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1533 */
1534 void monoflop_use()
1535 {
1536         self.nextthink = time + self.wait;
1537         self.enemy = activator;
1538         if(self.state)
1539                 return;
1540         self.state = 1;
1541         SUB_UseTargets();
1542 }
1543 void monoflop_fixed_use()
1544 {
1545         if(self.state)
1546                 return;
1547         self.nextthink = time + self.wait;
1548         self.state = 1;
1549         self.enemy = activator;
1550         SUB_UseTargets();
1551 }
1552
1553 void monoflop_think()
1554 {
1555         self.state = 0;
1556         activator = self.enemy;
1557         SUB_UseTargets();
1558 }
1559
1560 void monoflop_reset()
1561 {
1562         self.state = 0;
1563         self.nextthink = 0;
1564 }
1565
1566 void spawnfunc_trigger_monoflop()
1567 {
1568         if(!self.wait)
1569                 self.wait = 1;
1570         if(self.spawnflags & 1)
1571                 self.use = monoflop_fixed_use;
1572         else
1573                 self.use = monoflop_use;
1574         self.think = monoflop_think;
1575         self.state = 0;
1576         self.reset = monoflop_reset;
1577 }
1578
1579 void multivibrator_send()
1580 {
1581         float newstate;
1582         float cyclestart;
1583
1584         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1585
1586         newstate = (time < cyclestart + self.wait);
1587
1588         activator = self;
1589         if(self.state != newstate)
1590                 SUB_UseTargets();
1591         self.state = newstate;
1592
1593         if(self.state)
1594                 self.nextthink = cyclestart + self.wait + 0.01;
1595         else
1596                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1597 }
1598
1599 void multivibrator_toggle()
1600 {
1601         if(self.nextthink == 0)
1602         {
1603                 multivibrator_send();
1604         }
1605         else
1606         {
1607                 if(self.state)
1608                 {
1609                         SUB_UseTargets();
1610                         self.state = 0;
1611                 }
1612                 self.nextthink = 0;
1613         }
1614 }
1615
1616 void multivibrator_reset()
1617 {
1618         if(!(self.spawnflags & 1))
1619                 self.nextthink = 0; // wait for a trigger event
1620         else
1621                 self.nextthink = max(1, time);
1622 }
1623
1624 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1625 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1626 -------- KEYS --------
1627 target: trigger all entities with this targetname when it goes off
1628 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1629 phase: offset of the timing
1630 wait: "on" cycle time (default: 1)
1631 respawntime: "off" cycle time (default: same as wait)
1632 -------- SPAWNFLAGS --------
1633 START_ON: assume it is already turned on (when targeted)
1634 */
1635 void spawnfunc_trigger_multivibrator()
1636 {
1637         if(!self.wait)
1638                 self.wait = 1;
1639         if(!self.respawntime)
1640                 self.respawntime = self.wait;
1641
1642         self.state = 0;
1643         self.use = multivibrator_toggle;
1644         self.think = multivibrator_send;
1645         self.nextthink = max(1, time);
1646
1647         IFTARGETED
1648                 multivibrator_reset();
1649 }
1650
1651
1652 void follow_init()
1653 {
1654         entity src, dst;
1655         src = world;
1656         dst = world;
1657         if(self.killtarget != "")
1658                 src = find(world, targetname, self.killtarget);
1659         if(self.target != "")
1660                 dst = find(world, targetname, self.target);
1661
1662         if(!src && !dst)
1663         {
1664                 objerror("follow: could not find target/killtarget");
1665                 return;
1666         }
1667
1668         if(self.jointtype)
1669         {
1670                 // already done :P entity must stay
1671                 self.aiment = src;
1672                 self.enemy = dst;
1673         }
1674         else if(!src || !dst)
1675         {
1676                 objerror("follow: could not find target/killtarget");
1677                 return;
1678         }
1679         else if(self.spawnflags & 1)
1680         {
1681                 // attach
1682                 if(self.spawnflags & 2)
1683                 {
1684                         setattachment(dst, src, self.message);
1685                 }
1686                 else
1687                 {
1688                         attach_sameorigin(dst, src, self.message);
1689                 }
1690
1691                 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1692                 remove(self);
1693         }
1694         else
1695         {
1696                 if(self.spawnflags & 2)
1697                 {
1698                         dst.movetype = MOVETYPE_FOLLOW;
1699                         dst.aiment = src;
1700                         // dst.punchangle = '0 0 0'; // keep unchanged
1701                         dst.view_ofs = dst.origin;
1702                         dst.v_angle = dst.angles;
1703                 }
1704                 else
1705                 {
1706                         follow_sameorigin(dst, src);
1707                 }
1708
1709                 remove(self);
1710         }
1711 }
1712
1713 void spawnfunc_misc_follow()
1714 {
1715         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1716 }
1717
1718
1719
1720 void gamestart_use() {
1721         activator = self;
1722         SUB_UseTargets();
1723         remove(self);
1724 }
1725
1726 void spawnfunc_trigger_gamestart() {
1727         self.use = gamestart_use;
1728         self.reset2 = spawnfunc_trigger_gamestart;
1729
1730         if(self.wait)
1731         {
1732                 self.think = self.use;
1733                 self.nextthink = game_starttime + self.wait;
1734         }
1735         else
1736                 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1737 }
1738
1739
1740
1741
1742 .entity voicescript; // attached voice script
1743 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1744 .float voicescript_nextthink; // time to play next voice
1745 .float voicescript_voiceend; // time when this voice ends
1746
1747 void target_voicescript_clear(entity pl)
1748 {
1749         pl.voicescript = world;
1750 }
1751
1752 void target_voicescript_use()
1753 {
1754         if(activator.voicescript != self)
1755         {
1756                 activator.voicescript = self;
1757                 activator.voicescript_index = 0;
1758                 activator.voicescript_nextthink = time + self.delay;
1759         }
1760 }
1761
1762 void target_voicescript_next(entity pl)
1763 {
1764         entity vs;
1765         float i, n, dt;
1766
1767         vs = pl.voicescript;
1768         if(!vs)
1769                 return;
1770         if(vs.message == "")
1771                 return;
1772         if(pl.classname != "player")
1773                 return;
1774         if(gameover)
1775                 return;
1776
1777         if(time >= pl.voicescript_voiceend)
1778         {
1779                 if(time >= pl.voicescript_nextthink)
1780                 {
1781                         // get the next voice...
1782                         n = tokenize_console(vs.message);
1783
1784                         if(pl.voicescript_index < vs.cnt)
1785                                 i = pl.voicescript_index * 2;
1786                         else if(n > vs.cnt * 2)
1787                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1788                         else
1789                                 i = -1;
1790
1791                         if(i >= 0)
1792                         {
1793                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1794                                 dt = stof(argv(i + 1));
1795                                 if(dt >= 0)
1796                                 {
1797                                         pl.voicescript_voiceend = time + dt;
1798                                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1799                                 }
1800                                 else
1801                                 {
1802                                         pl.voicescript_voiceend = time - dt;
1803                                         pl.voicescript_nextthink = pl.voicescript_voiceend;
1804                                 }
1805
1806                                 pl.voicescript_index += 1;
1807                         }
1808                         else
1809                         {
1810                                 pl.voicescript = world; // stop trying then
1811                         }
1812                 }
1813         }
1814 }
1815
1816 void spawnfunc_target_voicescript()
1817 {
1818         // netname: directory of the sound files
1819         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1820         //          foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1821         //          Here, a - in front of the duration means that no delay is to be
1822         //          added after this message
1823         // wait: average time between messages
1824         // delay: initial delay before the first message
1825         
1826         float i, n;
1827         self.use = target_voicescript_use;
1828
1829         n = tokenize_console(self.message);
1830         self.cnt = n / 2;
1831         for(i = 0; i+1 < n; i += 2)
1832         {
1833                 if(argv(i) == "*")
1834                 {
1835                         self.cnt = i / 2;
1836                         ++i;
1837                 }
1838                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1839         }
1840 }
1841
1842
1843
1844 void trigger_relay_teamcheck_use()
1845 {
1846         if(activator.team)
1847         {
1848                 if(self.spawnflags & 2)
1849                 {
1850                         if(activator.team != self.team)
1851                                 SUB_UseTargets();
1852                 }
1853                 else
1854                 {
1855                         if(activator.team == self.team)
1856                                 SUB_UseTargets();
1857                 }
1858         }
1859         else
1860         {
1861                 if(self.spawnflags & 1)
1862                         SUB_UseTargets();
1863         }
1864 }
1865
1866 void trigger_relay_teamcheck_reset()
1867 {
1868         self.team = self.team_saved;
1869 }
1870
1871 void spawnfunc_trigger_relay_teamcheck()
1872 {
1873         self.team_saved = self.team;
1874         self.use = trigger_relay_teamcheck_use;
1875         self.reset = trigger_relay_teamcheck_reset;
1876 }
1877
1878
1879
1880 void trigger_disablerelay_use()
1881 {
1882         entity e;
1883
1884         float a, b;
1885         a = b = 0;
1886
1887         for(e = world; (e = find(e, targetname, self.target)); )
1888         {
1889                 if(e.use == SUB_UseTargets)
1890                 {
1891                         e.use = SUB_DontUseTargets;
1892                         ++a;
1893                 }
1894                 else if(e.use == SUB_DontUseTargets)
1895                 {
1896                         e.use = SUB_UseTargets;
1897                         ++b;
1898                 }
1899         }
1900
1901         if((!a) == (!b))
1902                 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1903 }
1904
1905 void spawnfunc_trigger_disablerelay()
1906 {
1907         self.use = trigger_disablerelay_use;
1908 }
1909
1910 float magicear_matched;
1911 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1912 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1913 {
1914         float domatch, dotrigger, matchstart, l;
1915         string s, msg;
1916         entity oldself;
1917         string savemessage;
1918
1919         magicear_matched = FALSE;
1920
1921         dotrigger = ((source.classname == "player") && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1922         domatch = ((ear.spawnflags & 32) || dotrigger);
1923
1924         if not(domatch)
1925                 return msgin;
1926
1927         if not(msgin)
1928         {
1929                 // we are in TUBA mode!
1930                 if not(ear.spawnflags & 256)
1931                         return msgin;
1932
1933                 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1934                         return msgin;
1935
1936                 magicear_matched = TRUE;
1937
1938                 if(dotrigger)
1939                 {
1940                         oldself = self;
1941                         activator = source;
1942                         self = ear;
1943                         savemessage = self.message;
1944                         self.message = string_null;
1945                         SUB_UseTargets();
1946                         self.message = savemessage;
1947                         self = oldself;
1948                 }
1949
1950                 if(ear.netname != "")
1951                         return ear.netname;
1952
1953                 return msgin;
1954         }
1955
1956         if(ear.spawnflags & 256) // ENOTUBA
1957                 return msgin;
1958
1959         if(privatesay)
1960         {
1961                 if(ear.spawnflags & 4)
1962                         return msgin;
1963         }
1964         else
1965         {
1966                 if(!teamsay)
1967                         if(ear.spawnflags & 1)
1968                                 return msgin;
1969                 if(teamsay > 0)
1970                         if(ear.spawnflags & 2)
1971                                 return msgin;
1972                 if(teamsay < 0)
1973                         if(ear.spawnflags & 8)
1974                                 return msgin;
1975         }
1976         
1977         matchstart = -1;
1978         l = strlen(ear.message);
1979
1980         if(ear.spawnflags & 128)
1981                 msg = msgin;
1982         else
1983                 msg = strdecolorize(msgin);
1984
1985         if(substring(ear.message, 0, 1) == "*")
1986         {
1987                 if(substring(ear.message, -1, 1) == "*")
1988                 {
1989                         // two wildcards
1990                         // as we need multi-replacement here...
1991                         s = substring(ear.message, 1, -2);
1992                         l -= 2;
1993                         if(strstrofs(msg, s, 0) >= 0)
1994                                 matchstart = -2; // we use strreplace on s
1995                 }
1996                 else
1997                 {
1998                         // match at start
1999                         s = substring(ear.message, 1, -1);
2000                         l -= 1;
2001                         if(substring(msg, -l, l) == s)
2002                                 matchstart = strlen(msg) - l;
2003                 }
2004         }
2005         else
2006         {
2007                 if(substring(ear.message, -1, 1) == "*")
2008                 {
2009                         // match at end
2010                         s = substring(ear.message, 0, -2);
2011                         l -= 1;
2012                         if(substring(msg, 0, l) == s)
2013                                 matchstart = 0;
2014                 }
2015                 else
2016                 {
2017                         // full match
2018                         s = ear.message;
2019                         if(msg == ear.message)
2020                                 matchstart = 0;
2021                 }
2022         }
2023
2024         if(matchstart == -1) // no match
2025                 return msgin;
2026
2027         magicear_matched = TRUE;
2028
2029         if(dotrigger)
2030         {
2031                 oldself = self;
2032                 activator = source;
2033                 self = ear;
2034                 savemessage = self.message;
2035                 self.message = string_null;
2036                 SUB_UseTargets();
2037                 self.message = savemessage;
2038                 self = oldself;
2039         }
2040
2041         if(ear.spawnflags & 16)
2042         {
2043                 return ear.netname;
2044         }
2045         else if(ear.netname != "")
2046         {
2047                 if(matchstart < 0)
2048                         return strreplace(s, ear.netname, msg);
2049                 else
2050                         return strcat(
2051                                 substring(msg, 0, matchstart),
2052                                 ear.netname,
2053                                 substring(msg, matchstart + l, -1)
2054                         );
2055         }
2056         else
2057                 return msgin;
2058 }
2059
2060 entity magicears;
2061 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2062 {
2063         entity ear;
2064         string msgout;
2065         for(ear = magicears; ear; ear = ear.enemy)
2066         {
2067                 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2068                 if not(ear.spawnflags & 64)
2069                         if(magicear_matched)
2070                                 return msgout;
2071                 msgin = msgout;
2072         }
2073         return msgin;
2074 }
2075
2076 void spawnfunc_trigger_magicear()
2077 {
2078         self.enemy = magicears;
2079         magicears = self;
2080
2081         // actually handled in "say" processing
2082         // spawnflags:
2083         //    1 = ignore say
2084         //    2 = ignore teamsay
2085         //    4 = ignore tell
2086         //    8 = ignore tell to unknown player
2087         //   16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2088         //   32 = perform the replacement even if outside the radius or dead
2089         //   64 = continue replacing/triggering even if this one matched
2090         //  128 = don't decolorize message before matching
2091         //  256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2092         //  512 = tuba notes must be exact right pitch, no transposing
2093         // message: either
2094         //   *pattern*
2095         // or
2096         //   *pattern
2097         // or
2098         //   pattern*
2099         // or
2100         //   pattern
2101         // netname:
2102         //   if set, replacement for the matched text
2103         // radius:
2104         //   "hearing distance"
2105         // target:
2106         //   what to trigger
2107         // movedir:
2108         //   for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2109
2110         self.movedir_x -= 1; // map to tuba instrument numbers
2111 }
2112
2113 void relay_activators_use()
2114 {
2115         entity trg, os;
2116         
2117         os = self;
2118         
2119         for(trg = world; (trg = find(trg, targetname, os.target)); )
2120         {
2121                 self = trg;
2122                 if (trg.setactive)
2123                         trg.setactive(os.cnt);
2124                 else
2125                 {
2126                         //bprint("Not using setactive\n");
2127                         if(os.cnt == ACTIVE_TOGGLE)
2128                                 if(trg.active == ACTIVE_ACTIVE)
2129                                         trg.active = ACTIVE_NOT;
2130                                 else    
2131                                         trg.active = ACTIVE_ACTIVE;
2132                         else
2133                                 trg.active = os.cnt;
2134                 }               
2135         }
2136         self = os;
2137 }
2138
2139 void spawnfunc_relay_activate()
2140 {
2141         self.cnt = ACTIVE_ACTIVE;
2142         self.use = relay_activators_use;
2143 }
2144
2145 void spawnfunc_relay_deactivate()
2146 {
2147         self.cnt = ACTIVE_NOT;
2148         self.use = relay_activators_use;        
2149 }
2150
2151 void spawnfunc_relay_activatetoggle()
2152 {
2153         self.cnt = ACTIVE_TOGGLE;
2154         self.use = relay_activators_use;        
2155 }
2156
2157 .string chmap, gametype;
2158 void spawnfunc_target_changelevel_use()
2159 {
2160         if(self.gametype != "")
2161                 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2162
2163         if (self.chmap == "")
2164                 localcmd("endmatch\n");
2165         else
2166                 localcmd(strcat("changelevel ", self.chmap, "\n"));
2167 }
2168
2169 void spawnfunc_target_changelevel()
2170 {
2171         self.use = spawnfunc_target_changelevel_use;
2172 }