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